Search Unity

Passive Skills and Ability Modifiers

Discussion in 'Scripting' started by KnightsHouseGames, Oct 12, 2019.

  1. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    This is basically a continuation of this thread:

    https://forum.unity.com/threads/rpg-ability-crafting-system.732659/

    A summary:

    I'm building a Turn Based RPG in the style of the late 90s/early 2000s, and right now I'm working out the ability system. The core idea of the system is rather than the character just straight up learning abilities, they learn Concepts, which they mix together in order to create different abilities.This is the system we worked out in the previous thread. since that time I've been putting together some visual assets to make representing the information to the player a little easier, by giving all the currently existing concepts icons.



    Right now the player can only really make active skills that could be used in battle (though, this is mostly untested since my battle system is currently totally separate and uses premade skills from a CSV file, which the ultimate hope is that this system replaces that, once it works). In addition to those active abilities, I'd like to be able to make passive skills, things that activate on their own in specific circumstances.

    An example would be the knight being able to learn counter attacks, based on the damage type he is being attacked with, or the white wizard being able to cast a shield on themselves if they take an attack that puts their health below a certain level. or even things like stat or damage modifiers, but only under certain circumstances. Really, I think about it like the items in roguelikes like Risk of Rain, or card effects in Yu Gi Oh, where they can drastically tweak the rules of the game to give the player some unique advantage.

    In addition to this, I'd like to add concepts to the game that act like modifiers to the active abilities, allowing you to make minor tweaks to an ability to make it suit your needs better. Like maybe a modification that allows an ability to consume more stamina in exchange for more damage, or the reverse of that, reducing stamina cost in exchange for less damage. Or even modifiers that change the behavior of an ability entirely, like allowing that ability to hit all enemies in exchange for greatly reduced damage per hit. The most obvious analog to a system like this might be Support Materia from Final Fantasy VII

    I feel like both of these pieces rely on something common; the ability to create some sort of exceptions to the regular rules of the game.

    I just have no idea of how one would do this elegantly. I'm sure with enough time and energy I could slap together some sort of junky, hard coded solution that would pretty much require me to make every ability in the entire game a custom class, but that just feels super dumb. And I imagine, since this sort of mechanic has become pretty commonplace these days, especially in roguelikes and such which can have hundreds of items and possible combinations, that there must be some elegant way of doing this sort of thing.

    Before making this thread, I tried researching this and found most of the solutions deal solely with stat modification, something I already know at least partially how to do, not the more advanced features I'm trying to accomplish. If anyone has an idea for how I might approach something like this, that would be greatly appreciated.
     
  2. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    Try thinking about your skills as collections of components.

    A fire ball skill is not just one class with knowledge of how to target, animate, and deal damage, it is three separate components: a single target system, a basic missile animation system that happens to use a fiery looking ball, and a damage payload, which interacts with the targets HP system.

    The more you can break each skill down into components, the more variety you will be able to achieve.

    As an example, consider our simple skill definition above: a target system, an animation system, and a payload system.

    Let's imagine some target systems:
    - single target, user specified
    - double target, two adjacent enemies
    - splash damage, all adjacent enemies
    - all enemies
    - single ally target

    And some animation systems:
    - homing missile, object flys from source to target
    - simple missile, object flys from source in straight line until it hits something
    - instant, plays impact animation on target same time the source is playing casting animation

    And payload systems:
    - apply straight damage
    - add status effect
    - move the target

    Just with this simple start, you've already got:
    5*3*3=45 different skills. I'll also suggest that payloads make sense to be combinable too. A skill might want to deal damage, add a status and move the target all together, so just these simple ingredients add up to:
    5*3*(3*2*1)=90.

    The simple example I gave uses a skill with three "component slots". Increasing the number of slots is the best way to increase the total number of skills but can also be the most difficult. You need to break the skills up into their constituent systems. The second way to increase the number of combinations is to provide more variations in each slot.

    A recently released game has been boasting about its "one billion" guns, but thought of as components, it's nothing more than 10 guns, each with 9 slots that accept one of 10 variations to modify the output of the gun. That's something like 100 classes, which is much more reasonable than actually coding 1000000000.

    There's a lot more to consider, but I think this will help you get started.
     
    WalnutBat likes this.
  3. hedgeh0g

    hedgeh0g

    Joined:
    Jul 18, 2014
    Posts:
    102
    I'm currently working on exactly the same project (even though I have no time :< ). I'm planning to build a talent tree system like in WoW and Borderlands, thus, I need to handle different modifiers to a range of stats (attack, defense, etc.) the approach I'm planning consists in giving to each passive skill a special tag through an enum, such as:

    Code (CSharp):
    1. enum modifyerType
    2. {
    3. preattack, // applied before attacking , such as % stat increase
    4. postattack, // bonus damage, such as % increase on a specific attack
    5. andsoon...
    6. }; m_type;
    All of these passives must be stored somewhere, possibly an ordererd data structure in order to be used anywhere.

    Now, my BattleManager knows that it is my Rogue's turn. The Rogue wants to attack, so he calculates every possible bonus like this:

    Code (CSharp):
    1. int CalculateDamage()
    2. {
    3. int m_preattackbonus = GetPreAttackBonuses(//* any argument*/);
    4. // Insert damage formula
    5. int m_postattackbonus = GetPostAttackBonus(/* arguments*/);
    6. // add to the formula
    7. }
    Finally, the Rogue will pass to BattleManager his attack value (not necessary as an integer, you might wanna use another data structure to identify other stuff such as weapon used or element), the BattleManager will pass the value to the enemy targetted unit who will also make its defensive calculation.

    I'm open to criticism on this approach, this my very rough idea.
     
  4. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    I feel like perhaps I should detail a little more what I have in place, so it's a little easier to understand

    My abilities are all derived from an abstract class "ScriptableAbility" which is a scriptable object, with the different versions being customized for different basic purposes, broken down generally into Dexterity and Magic based skills. Most of the exposed parameters are the same for dex and magic, they just use a different formula for damage calculation. Here is an example.

    Screenshot_2019-10-14_16-48-48.png

    These abilities are in what is essentially a recipe book on the character, which are then added to their ability slots based on combining concepts on the ability screen. Here is a video of the ability screen in action.



    The idea is that in addition to those core concepts that exist now, there would be modifier concepts that would have effects when applied to a combination that is compatible with that modifier. The recipe would use the scriptable object as a blueprint, and instantiate a version of the ability into the slot, with the modifications applied.

    For reference, here is the current state of the Ability Slot class

    Code (CSharp):
    1. [System.Serializable]
    2. public class AbilitySlot {
    3.  
    4.     Concept[] currentConcepts = new Concept[5];
    5.  
    6.     public ScriptableAbility slot { get; private set;}
    7.  
    8.     public List<string> GetCurrentIngrediants()
    9.     {
    10.         List<string> ingrediantsList = new List<string> ();
    11.         for (int i = 0; i < currentConcepts.Length; i++)
    12.         {
    13.             if(currentConcepts[i] != null)
    14.                 ingrediantsList.Add (currentConcepts [i].name);
    15.         }
    16.         return ingrediantsList;
    17.     }
    18.  
    19.     public void ConfirmSlot()
    20.     {
    21.         slot = ScriptableObject.CreateInstance("ScriptableGimmickSkill") as ScriptableAbility;
    22.         slot.name = "Empty";
    23.     }
    24.  
    25.     public void AddConcept(Concept newConcept, int location)
    26.     {
    27.         currentConcepts[location] = newConcept;
    28.     }
    29.  
    30.     public void RemoveConcept(int ConceptNumber)
    31.     {
    32.         currentConcepts[ConceptNumber] = null;
    33.     }
    34.  
    35.     public Concept GetConcept(int ConceptNumber)
    36.     {
    37.         return currentConcepts [ConceptNumber];
    38.     }
    39.  
    40.     public void SetAbility(ScriptableAbility newAbility)
    41.     {
    42.         slot = newAbility;
    43.     }
    44.  
    45.     public void RemoveAbility()
    46.     {
    47.         slot = null;
    48.     }
    49.  
    50.     public void ClearSlot()
    51.     {
    52.         RemoveAbility ();
    53.         for (int i = 0; i < currentConcepts.Length; i++) {
    54.             RemoveConcept (i);
    55.         }
    56.     }
    57.  
    58. }
    The modifier concepts can't go in the same slots as the regular core concepts, because that would mess up the recipe check. I was thinking of maybe adding a second array which would hold the modifiers, and the placement UI would check what kind of concept was being placed, and place the concept in the correct array, and from the player's perspective they wouldn't know the difference? Then once the mix check happens, the recipe class would look at that second array and make the modifications.

    But thats where my idea for that runs into a roadblock, as far as how to make the recipe class know which concepts it would be compatible with (not all modifiers are gonna make sense with every ability, obviously, like making a passive skill that is meant to target only the caster have the "All Targets" modifier makes no sense) and maybe some of the modifiers have effects that are different depending on the ability they are modifying if they are compatible.

    On the topic of Passive skills, this sorta has a similar issue. I figure I would probably make an inherited member like I have for the other types of abilities for passive skills, perhaps a few based on the way they act, but the thing that really throws me off is telling it when to do the thing.

    An example of something like this would be an effect like the Crowbar from Risk of Rain. It applies a 50% increase to damage * the number of crowbars you have, but ONLY against enemies with 90% health or higher. So it not only has to know how to do the thing, but it also has to know under what conditions to do the thing.

    Another would be the classic case of counter attacking. In most RPGs, theres a distinction between Physical and Magical damage, and quite often, a character with a passive counter ability might only counter physical attacks, and not magical attacks. So the ability would have to know when the player is under attack in battle, what kind of attack is coming in(ie if the attack is one that is valid for countering), and the character has to basically insert this action into the actions queue, ahead of anything else, and execute it, if they survive the attack they've just taken.

    Those two examples are wildly different, but have the common denominator of having conditions to their execution, and doing something when those conditions are met, that can all be wildly different and game altering in ways that make classifying them incredibly difficult.

    There seems to be this magic box on both of these examples, where there needs to be some sort of "This is the part where you do the thing, and that thing can be anything, dependent on the situation", with the modifiers it's the case of having the recipe modify the action, with the passive skills it's...well...pretty much the entire thing.
     
  5. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    For conditions, i have a ConditionType base class with
    Code (CSharp):
    1.  
    2.     public virtual float CheckCondition(EntityRefs targetCondition){
    3.         return 1f;
    4.     }
    It returns float because if the condition is true, by how much? (Not a yes/no thing)

    Here's for target's direction check (it's a 2D game)

    Code (CSharp):
    1.  
    2.  
    3.     public float bonus = 1f;
    4.     public enum DirectionCondition{Up, Down, Front, Back};
    5.     public DirectionCondition directionCondition = DirectionCondition.Front;
    6.  
    7.     public override float CheckCondition(EntityRefs target){
    8.         bool check = false;
    9.         switch(directionCondition){
    10.         case DirectionCondition.Front:
    11.             if (target.playerSkill.facingRight != skill.myR.playerSkill.facingRight) {
    12.                 check = true;
    13.             }
    14.             break;
    15.         case DirectionCondition.Back:
    16.             if (target.playerSkill.facingRight == skill.myR.playerSkill.facingRight) {
    17.                 check = true;
    18.             }
    19.             break;
    20.         case DirectionCondition.Up:
    21.         case DirectionCondition.Down:
    22. // etc
    23.         }
    24.  
    25.         if (check) {
    26.             return bonus;
    27.         }
    28.         else {
    29.             if (type == StatModify.Multiply) {
    30.                 if (hitOrMiss)
    31.                     return 0f;
    32.                 else
    33.                     return 1f;
    34.             }
    35.             else
    36.                 return 0f;
    37.         }
    38.     }
    And related to how the total "effect" of a thing (passive, buff, equipment), they're all the same. A Calculate() takes the List of the effect in number and processes it from top to bottom
    So, in my DamageOut struct it uses this Calculate too, and the context is damage (a pretty special case)
    Whereas equipment, passive bonus etc, all still use this Calculate with its own context of where the final number is added to. So i can make a "+5 def per every Hp percent you have" etc
    My inspiration is Dota, so the complete system can make most Dota skills.
    https://forum.unity.com/threads/skill-casting-method-and-secondary-effects.279190/
    This system is from an old project tho, and i'll soon be remaking some parts of it. But for the damage calculation part, i'm pretty satisfied and might not change much


    Code (CSharp):
    1.  
    2.     public static float Calculate(this List<EffectParam> paramList, EntityRefs pRef, ItemEquipment weapon)
    3.     {
    4.         float num = 0f;
    5.  
    6.         foreach(EffectParam dmg in paramList)
    7.         {
    8.             switch(dmg.stat)
    9.             {
    10.             case StatType.WeaponAtk:
    11.                 if(!weapon) // Unarmed                  
    12.                     continue;
    13.  
    14.                 if(dmg.statModify == StatModify.Multiply)
    15.                     num *= weapon.weaponDamage.num;
    16.                 else
    17.                     num += weapon.weaponDamage.num;
    18.                 break;
    19.  
    20.             case StatType.Num:
    21.                 if(dmg.statModify == StatModify.Multiply)
    22.                     num *= dmg.num;
    23.                 else
    24.                     num += dmg.num;
    25.                 break;
    26.  
    27.             case StatType.Atk:
    28.                 if(!pRef)
    29.                     continue;
    30.                 if(dmg.statModify == StatModify.Multiply)
    31.                     num *= pRef.entity.myStat.Atk * dmg.num;
    32.                 else
    33.                     num += pRef.entity.myStat.Atk * dmg.num;
    34.                 break;
    35.  
    36.             case StatType.MAtk:
    37.                 if(!pRef)
    38.                     continue;
    39.                 if(dmg.statModify == StatModify.Multiply)
    40.                     num *= pRef.entity.myStat.MAtk * dmg.num;
    41.                 else
    42.                     num += pRef.entity.myStat.MAtk * dmg.num;
    43.                 break;
    44.  
    45.             case StatType.RAtk:
    46.                 if(!pRef)
    47.                     continue;
    48.                 if(dmg.statModify == StatModify.Multiply)
    49.                     num *= pRef.entity.myStat.RAtk * dmg.num;
    50.                 else
    51.                     num += pRef.entity.myStat.RAtk * dmg.num;
    52.                 break;
    53.  
    54.             case StatType.Def:
    55.                 if(!pRef)
    56.                     continue;
    57.                 if(dmg.statModify == StatModify.Multiply)
    58.                     num *= pRef.entity.myStat.Def * dmg.num;
    59.                 else
    60.                     num += pRef.entity.myStat.Def * dmg.num;
    61.                 break;
    62.  
    63.             case StatType.MDef:
    64.                 if(!pRef)
    65.                     continue;
    66.                 if(dmg.statModify == StatModify.Multiply)
    67.                     num *= pRef.entity.myStat.MDef * dmg.num;
    68.                 else
    69.                     num += pRef.entity.myStat.MDef * dmg.num;
    70.                 break;
    71.  
    72.             case StatType.Str:
    73.                 if(!pRef)
    74.                     continue;
    75.                 if(dmg.statModify == StatModify.Multiply)
    76.                     num *= pRef.entity.myStat.Str * dmg.num;
    77.                 else
    78.                     num += pRef.entity.myStat.Str * dmg.num;
    79.                 break;
    80.  
    81.             case StatType.Agi:
    82.                 if(!pRef)
    83.                     continue;
    84.                 if(dmg.statModify == StatModify.Multiply)
    85.                     num *= pRef.entity.myStat.Agi * dmg.num;
    86.                 else
    87.                     num += pRef.entity.myStat.Agi * dmg.num;
    88.                 break;
    89.  
    90.             case StatType.Dex:
    91.                 if(!pRef)
    92.                     continue;
    93.                 if(dmg.statModify == StatModify.Multiply)
    94.                     num *= pRef.entity.myStat.Dex * dmg.num;
    95.                 else
    96.                     num += pRef.entity.myStat.Dex * dmg.num;
    97.                 break;
    98.  
    99.             case StatType.Hp:
    100.                 if(!pRef)
    101.                     continue;
    102.                 if(dmg.statModify == StatModify.Multiply)
    103.                     num *= pRef.entity.curHp * dmg.num;
    104.                 else
    105.                     num += pRef.entity.curHp * dmg.num;
    106.                 break;
    107.  
    108.             case StatType.MaxHp:
    109.                 if(!pRef)
    110.                     continue;
    111.                 if(dmg.statModify == StatModify.Multiply)
    112.                     num *= pRef.entity.MaxHp * dmg.num;
    113.                 else
    114.                     num += pRef.entity.MaxHp * dmg.num;
    115.                 break;
    116.  
    117.             }
    118.         }
    119.  
    120.         return num;
    121.     }
    edit:
    Oh yeah, i checked your other thread about recipes and since it's still a bit related here that you want some effects to only affect recipes with certain "ingredient".

    My magic skill variation uses a bitmask
    Code (CSharp):
    1.  
    2. ++ spell combo from elements bitmask
    3. 0001 fire
    4. 0010 air
    5. 0011 fire air =
    6. 0100 water
    7. 0101 water fire = steam
    8. 0110 water air =
    9. 0111 water air fire =
    10. 1000 earth
    11. 1001 earth fire = meteor
    12. 1010 earth air =
    13. 1011 earth air fire =
    14. 1100 earth water =
    15. 1101 earth water fire =
    16. 1110 earth water air =
    17. 1111 earth water air fire =
    18. and dark light nature ether etc
    This doesn't support Fire Fire Fire out of the box, but you can add a "weight" for each entry too and that works too (i dont have that in mine).
    In bitmask, you can simply check ingredientMask.HasFlag(Element.Fire) as a condition and your passive will affect all Fire spells, for example
     
  6. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    So since this is sort of 2 problems, it seems like addressing the modifiers seems like the easier of the 2 to address first, since I at least have a starting point for approaching this.

    I've been thinking about this problem in the background for the last day or so, as I was busy with other things yesterday. it seems like the approaches detailed by @hedgeh0g and @Crouching-Tuna involving making some sort of enum sorta make sense, but the individual descriptions confuse me a little.

    I spent a little time thinking about how I could possibly break down both of these problems into enums, and the modifiers made a bit more sense for this approach, since all of the modifiers I could think of basically fell into 3 catagories, all of which modified already existing aspects of the ability, while the passive effects are a bit more varied and I feel like make a little less sense for this approach. My Modifiers basically break down like this.

    - Parameter modifiers, which would alter things like the ability's power, force, timing, or cost
    - All Targets, which is pretty much just an All Materia, which just sets the "all targets" bool to true, pretty simple
    - Add or Remove Status, which would allow an ability that doesn't add or remove a status effect to add or remove a status effect.

    I know I want the recipe to apply the changes to the instantiated version of the ability once it detects the modifier concept, so I guess I could just figure out what modifier concepts I want to be compatible with with each recipe and program them into the recipe somehow?

    I'm gonna have to take a look at my concepts and recipes and see if I can somehow make the recipes smart enough to know how to apply custom modifiers, and add something to the concepts so that theres a distinction between ones used in mixing and ones used in modification
     
  7. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    I've made some progress, but I've hit a very annoying wall.

    I added a bool onto my concepts called isModifer. I also overhauled my Ability Slot to have 2 arrays, one for the mix ingredients, one for the modifiers. The methods I use to load up the different concept slots are now smart, so they detect the state of the isModifer bool, and place the concept into the corresponding list, which actually worked out super nice, so now adding a concept marked as a modifer won't effect the mixing of regular concepts.

    I created a modifier class

    Code (CSharp):
    1.     public string name;
    2.  
    3.     public int Lv { get; private set;}
    4.  
    5.     public ModifierType modifierType;
    6.  
    7.     public modification[] Modifications;
    8.  
    9.     public void SetLv(int newLv)
    10.     {
    11.         Lv = newLv;
    12.     }
    13.  
    14.     [System.Serializable]
    15.     public struct modification{
    16.         [Range(-100, 100)]
    17.         public int power;
    18.         [Range(-100, 100)]
    19.         public int force;
    20.         [Range(-100, 100)]
    21.         public int prep;
    22.         [Range(-100, 100)]
    23.         public int cast;
    24.         [Range(-100, 100)]
    25.         public int cooldown;
    26.         [Range(-100, 100)]
    27.         public int cost;
    28.  
    29.         public Effect status;
    30.  
    31.         public bool addStatus;
    32.         public bool removeStatus;
    33.         public bool allTargets;
    34.     }
    35.  
    36. }
    37. public enum ModifierType {Parameter, Status, AllTargets};
    I added an array of these to the Recipe Class, called CompatibleModifiers, which I can then load with all the modifiers I might want to have on an ability, and what effects those modifiers will have.

    It is at this point that I am realizing my understanding of Polymorphism isn't as good as I would like it to be.

    I needed a way to apply the modifiers themselves to the abilities. So I made an abstract class on each type of ability for applying the modifers. A little tedious, but the theory of it works

    The tough part is tying it all together on the recipes class, when the ability itself gets made.

    Code (CSharp):
    1. [CreateAssetMenu (menuName = "Abilities/Recipe")]
    2. public class Recipe : ScriptableObject {
    3.  
    4.     [SerializeField]
    5.     List<string> requiredConcepts = new List<string>();
    6.  
    7.     [SerializeField]
    8.     List<Modifier> compatibleModifiers = new List<Modifier>();
    9.  
    10.     [Header("Resulting Abilities"), SerializeField]
    11.     ScriptableAbility abilityLv1;
    12.     [SerializeField]
    13.     ScriptableAbility abilityLv2;
    14.     [SerializeField]
    15.     ScriptableAbility abilityLv3;
    16.  
    17.     public bool IngrediantCheck(List<string> potentialConcepts)
    18.     {
    19.         var craftAttempt = new HashSet<string> (potentialConcepts);
    20.         var ingrediantList = new HashSet<string> (requiredConcepts);
    21.         return craftAttempt.SetEquals(ingrediantList);
    22.     }
    23.  
    24.     public ScriptableAbility deliverResult(List<Concept> _modifiers)
    25.     {
    26.         ScriptableDexAttack newAbility = ScriptableObject.CreateInstance<ScriptableDexAttack> ();
    27.         newAbility.Initialize (abilityLv1 as ScriptableDexAttack);
    28.         List<Modifier> ApplicableModifiers = new List<Modifier> ();
    29.  
    30.         //run through the attached modifiers and apply them
    31.         if (_modifiers.Count > 0) {
    32.             for (int j = 0; j < _modifiers.Count; j++) {
    33.                 for (int k = 0; k < compatibleModifiers.Count; k++) {
    34.                     if (_modifiers [j].name == compatibleModifiers [k].name) {
    35.                         ApplicableModifiers.Add (compatibleModifiers [k]);
    36.                         ApplicableModifiers [ApplicableModifiers.Count - 1].SetLv (_modifiers [j].currentLv - 1);
    37.                     }
    38.                 }
    39.             }
    40.             newAbility.ApplyModifers (ApplicableModifiers);
    41.         }
    42.  
    43.         return newAbility;
    44.     }
    45.  
    46. }
    I have a ScriptableAbility Abstract class that I derive all of my other abilities from, with each ability having the pieces it needs to do what it does, with the hope that they would behave nicely anywhere I would put just "scriptableAbility". Of course, this hasn't been the case.

    The reason I am using scriptable objects for this is I'd like to be able to have fine control over the base abilities, being able to tweak them nicely in the inspector, making designing all the various types of abilities much easier, as before I was using this huge, clunky CSV file, and that really sucked for...pretty much everything. I want to use the ScriptableAbilities as sort of a blueprint, where it spits out an instantiated version of the base skill, which can be tweaked by the player's stats and of course this modifier system, without messing with the original blueprint.

    You can see in the DeliverResult method at the bottom that I've had to do a bunch of hacky stuff to get it to work for the moment. Right now the system theoretically works, but only with Dex Attacks.

    Part of me was hoping this would just work if I fed it a ScriptableAbility, but I got an error telling me ScriptableObjects couldn't be instantiated from abstract classes. I tried it with one of the inherited classes, and eventually it worked. What this leads me to now is figuring out a way that this can take in any of the inherited members, and this is where I am at my wits end.

    Additionally, for the Initialize method on the ScriptableDexAttack, I had to make one that would take in a ScriptableDexAttack, as something to make the new instance of the ability not just be an alias that made applying the modifiers mess with the original blueprint. But again, that only works for DexAttacks, so another thing I would love to know is if there is a way to make an abstract method on the main ScriptableAbilites class that would allow me to initialize an instance with one of it's own kind (ie give it a ScriptableDexAttack as it's argument and it just takes that and does what it needs to do) instead of having to make an additional "Initialize" method for every possible type of inherited member with that inherited member as it's type, that all of the inherited members would have to have?
     
  8. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    So I solved the problem with the Initialize method, with some straight up mad scientist style generic variable shenanigans. But the problem remains with creating the instance itself beforehand.

    Code (CSharp):
    1.     public ScriptableAbility deliverResult(List<Concept> _modifiers)
    2.     {
    3.        
    4.         ScriptableAbility newAbility = ScriptableObject.CreateInstance<ScriptableDexAttack>();
    5.         newAbility.Initialize (abilityLv1);
    6.         List<Modifier> ApplicableModifiers = new List<Modifier> ();
    7.  
    8.         //run through the attached modifiers and apply them
    9.         if (_modifiers.Count > 0) {
    10.             for (int j = 0; j < _modifiers.Count; j++) {
    11.                 for (int k = 0; k < compatibleModifiers.Count; k++) {
    12.                     if (_modifiers [j].name == compatibleModifiers [k].name) {
    13.                         ApplicableModifiers.Add (compatibleModifiers [k]);
    14.                         ApplicableModifiers [ApplicableModifiers.Count - 1].SetLv (_modifiers [j].currentLv - 1);
    15.                     }
    16.                 }
    17.             }
    18.             newAbility.ApplyModifers (ApplicableModifiers);
    19.         }
    20.  
    21.         return newAbility;
    22.     }
    This works, but only for ScriptableDexAttacks What I want to do is set the perameter equal to the type of abilityLv1(I plan to change this later so that it pulls in the appropriate ability based on the total level of all the concepts being used, but for now, I'm just trying to get it to work.)

    Is there a way I can get the type of the ability in that variable abilityLv1 and use it as the parameter for ScriptableObject.CreateInstance?
     
  9. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    So it's not the most elegant thing in the world, but this is what I came up with

    Code (CSharp):
    1.     public ScriptableAbility deliverResult(List<Concept> _modifiers, int SkillLv)
    2.     {
    3.         ScriptableAbility newAbility;
    4.  
    5.         if (SkillLv == 3) {
    6.             if (abilityLv3 is ScriptableDexAttack) {
    7.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexAttack> ();
    8.             } else if (abilityLv3 is ScriptableDexHeal) {
    9.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexHeal> ();
    10.             } else if (abilityLv3 is ScriptableDexStatus) {
    11.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexStatus> ();
    12.             } else if (abilityLv3 is ScriptableMagicAttack) {
    13.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicAttack> ();
    14.             } else if (abilityLv3 is ScriptableMagicHeal) {
    15.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicHeal> ();
    16.             } else if (abilityLv3 is ScriptableMagicStatus) {
    17.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicStatus> ();
    18.             } else {
    19.                 newAbility = ScriptableObject.CreateInstance<ScriptableGimmickSkill> ();
    20.             }
    21.             newAbility.Initialize (abilityLv3);
    22.         }
    23.  
    24.         else if (SkillLv == 2) {
    25.             if (abilityLv2 is ScriptableDexAttack) {
    26.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexAttack> ();
    27.             } else if (abilityLv2 is ScriptableDexHeal) {
    28.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexHeal> ();
    29.             } else if (abilityLv2 is ScriptableDexStatus) {
    30.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexStatus> ();
    31.             } else if (abilityLv2 is ScriptableMagicAttack) {
    32.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicAttack> ();
    33.             } else if (abilityLv2 is ScriptableMagicHeal) {
    34.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicHeal> ();
    35.             } else if (abilityLv2 is ScriptableMagicStatus) {
    36.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicStatus> ();
    37.             } else {
    38.                 newAbility = ScriptableObject.CreateInstance<ScriptableGimmickSkill> ();
    39.             }
    40.             newAbility.Initialize (abilityLv2);
    41.         }
    42.  
    43.         else {
    44.             if (abilityLv1 is ScriptableDexAttack) {
    45.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexAttack> ();
    46.             } else if (abilityLv1 is ScriptableDexHeal) {
    47.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexHeal> ();
    48.             } else if (abilityLv1 is ScriptableDexStatus) {
    49.                 newAbility = ScriptableObject.CreateInstance<ScriptableDexStatus> ();
    50.             } else if (abilityLv1 is ScriptableMagicAttack) {
    51.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicAttack> ();
    52.             } else if (abilityLv1 is ScriptableMagicHeal) {
    53.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicHeal> ();
    54.             } else if (abilityLv1 is ScriptableMagicStatus) {
    55.                 newAbility = ScriptableObject.CreateInstance<ScriptableMagicStatus> ();
    56.             } else {
    57.                 newAbility = ScriptableObject.CreateInstance<ScriptableGimmickSkill> ();
    58.             }
    59.             newAbility.Initialize (abilityLv1);
    60.         }
    61.  
    62.  
    63.         List<Modifier> ApplicableModifiers = new List<Modifier> ();
    64.  
    65.         //run through the attached modifiers and apply them
    66.         if (_modifiers.Count > 0) {
    67.             for (int j = 0; j < _modifiers.Count; j++) {
    68.                 for (int k = 0; k < compatibleModifiers.Count; k++) {
    69.                     if (_modifiers [j].name == compatibleModifiers [k].name) {
    70.                         ApplicableModifiers.Add (compatibleModifiers [k]);
    71.                         ApplicableModifiers [ApplicableModifiers.Count - 1].SetLv (_modifiers [j].currentLv - 1);
    72.                     }
    73.                 }
    74.             }
    75.             newAbility.ApplyModifers (ApplicableModifiers);
    76.         }
    77.  
    78.         return newAbility;
    79.     }
    If someone knows a better way of accomplishing this, that would be great. But now the system takes into consideration the ability's level, which is determined by the lowest level ingredient in the mix, and can accept modifiers.

    Which means now we need to start Passive Skills. This is where I am completely stumped.

    As stated before, the passives I have in mind vary greatly in how they change the game, from small, situational stat modifications, to counter attacks, absorbing HP from attacks, stealing on attack, reviving on death conditionally, and plenty of other little tweaks and modifications to the rules of the game which activate automatically under specific conditions

    Any advice on how to approach this would be greatly appreciated, thanks
     
  10. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    Ill make a more detailed answer when i finish the new version of my skill system

    But basically my passives/buffs adds effects to certain trigger events.
    For example, when calculating damage, it'll pass through a foreach(Effect ef in player.onHittedEffects), and runs all of the while passing the current damage instance to potentially be modified too

    What is Effect? Well, it's just more ActiveSkill! So u can have a "cast stormgust or steal target when receiving damage" if u want. A special type of ActionDefinition is ActionModifier, which compares the damage flags(melee, ranged, physical, crit, miss) before deciding its qualified to modify the damage (and the flag. Say it modifies it so the hit is always miss)

    The Calculate List extension i posted above was from a very old project (u can sense the noobish in there). The current one also has the option for taking into account a "previous damage" from the stack (say, Drain skill first attacks for 50 after reduction, but the enemy only has 30. So the result is only 30, gets passed to the next stack which is heal self for that amount)
    And also baked (special for damage modifier. Say to give +20% if self species is dragon)
    And some more features

    I'll write a blog post about it as i plan to opensource it in the store, hopefully in ~2weeks