Skills based system

You’ll call that method from whatever class needs to know if you passed a skill check. Say you’re chopping down that Yew tree, and you need to know “did that work, or not?”, then you’ll call the player’s SkillStore.SkillCheck(Skill.TreeCutting, 60);

You’ll really be more like replacing it…

If you’re heart is set on getting a cease and desist letter from Jagex (kidding, mostly), then you could add a method GetCombatLevel() to the SkillStore, which calculates the level based on the skills…
Back when I used to play (we’re talking 20 years ago!), the Combat level was calculated differently than it is now. It was a formula involving the Melee, Ranged, and Magic, but I don’t recall exactly what it was… it may have been as simple as (Melee + Ranged + Magic) / 3.

What was tricky about this was in PVP, if you went up against a guy who was level 30, it could be a well rounded character with 30 in each, or it could be a character with level 90 Melee and the rest low, in which case, It’s been very nice knowing you…

In any event, just put in a GetCombatLevel() in the SkillStore that performs the appropriate calculation.

You’re still not going to get away from things like Equipment conditions, because instead, you’ll be checking conditions against the Combat Level, or perhaps simply a bow might check condition against the Ranged Skill. All you’re doing is removing the standard “Experience” from the equation. In SkillStore, you’ll need to have an IPredicateEvaluator that can check a skill, and check the overall Combat or Player level.

My concern here is that this should be well understood by now… you’re diving deep into a complicated system, and without literally cloning your project (ain’t gonna happen), my ability to help will be limited as the skills get added, as I won’ t have time to get back up to speed every time.

Oh, delete that. In fact, you’ll have to, because otherwise, your character will be an AMAZING blacksmith at level 1. That was simply for testing the experience formula I had implemented. The first go round used n! (a multiplication of all numbers under the given level) rather than a summation of all the numbers under a given level, and it blew past int32.MaxValue somewhere in the mid 80s…

Yes, that’s it exactly.

For the moment, I just create Unity Packages to get the day rolling. External storage on a flash drive

For the rest I’ll give it a thorough read and be back to working on it xD

Players gonna need at least MaxValue hours to get to level 200 this way :stuck_out_tongue_winking_eye:

Different game engine, different code… If anyone would sue me, it’s probably gonna be you if you wanted to (let’s talk this through :sweat_smile:). They did ban 6 of my accounts back in 2015 though, on a bot hunting night, and I wasn’t happy about it lel (I did overcome my addiction though (to some extent), otherwise I wouldn’t be here :stuck_out_tongue_winking_eye:)

Hmm, alright that makes sense, but for weapons (because this is probably going to demand the most attention tbh, so we can start with that), how do we get this current system to differentiate between Ranged, Magic and Melee Weapons, and how do we swap this line out:

if (!equipableItem.CanEquip(equipLocation, playerEquipment)) return 0;  // IF WE CANT EQUIP A WEAPON, BASED ON TRAITS IN IPredicateEvaluator.cs, ASSIGNED IN 'EquipableItem.cs', DONT EQUIP IT!
            

in ‘EquipmentSlotUI.MaxAcceptable()’ method (I’m assuming that’s the line responsible for the equipment, if I’m not mistaken)

For something that checks for all types of weapons in their respective classes? Because again, this currently relies on the traits system, and we want it to rely on the skilling systems’ attack Enum (for weapons), defence enum (for armour), magic enum (for magic weapons) or ranged enum (for ranged weapons)

Each Equipment item has it’s own Condition.

You’ll need an IPredicateEvaluator interface on the SkillStore, and that will have to test something like

HasSkill Skill Level

Check out how HasTrait is set up, as this is pretty much the template for how this would work.

Well, there is one IPredicateEvaluator Evaluate() method in ‘TraitStore.cs’, I’m guessing that’s the one we’re talking about here?

Alright I’ll give this a go and update my comment :slight_smile: (but there’s one thing I noticed, it still relies on the ‘EPredicate’ rather than the new ‘Skill’ interface… Should I rewrite the IPredicateEvaluator now?)

Alright here’s a rough attempt with the ‘Attack’ skill (although I’m not sure how will we get this to be changed on the sword/melee weapons), but to no one’s surprise, it’s not working as expected. Can you please have a look and give me feedback on how to make it work, and how would the other 3 stats (defence, ranged, magic) need to change to get it to work properly?

public bool? Evaluate(Skill skill, string[] parameters)
        {

            if (skill == Skill.Attack) {

                if (Enum.TryParse<Skill>(parameters[0], out Skill _skill)) {

                    return GetSkillLevel(_skill) >= Int32.Parse(parameters[1]);

                }

            }

            return true;
        }

And needless to say, the compiler is complaining that it’s not an EPredicate we’re using because of the interface, so my guess is that we will re-write another interface, right?

If you’re using the improved condition editor, you’ll need to add a new entry HasSkill to the EPredicate enum.

That will be your case statement in the Evaluate? method

No. IPredicateEvaluators always use an EPredicate

I don’t have time to go through this tonight, it’s bed time. Go through the Improved Condition Editor topic carefully. Did you do just the EPredicate part or the full property drawers?
If you just did the EPredicate part, then look at how the Traits are evaluated. It checks to see if it’s EPredicate.HasTrait, and if it is, then it converts the Trait parameter to a Trait and the value to an integrer and then returns true or false based on the level of the trait.

Similarly, in SkillStore, you’re going to check to see if the predicate is EPredicate.HasSkill, and then convert parameter 1 to the Skill, and parameter 2 to an int and compare.

Wait, before you sleep (apologies, I didn’t mean this in a rude way), I re-did the entire thing (as in, I still have the old system, just tried reworking what I know of so far), you might find this a little interesting:

I created an ‘ESkillEvaluator.cs’ script, with these values:

public enum ESkill
{

    Attack,      // the ability to wield better weapons, as your attack level grows
    Defence,    // the ability to wear better armor and defend yourself better (can be combined with magic, ranged or attack levels to determine the type of armor you can put on)
    Magic,      // the ability to cast better spells, for personal benefits or magical attacks
    Ranged,     // the ability to wield and use better bows and arrows
    Woodcutting,    // the ability to cut down better trees, at faster rates
    Firemaking, // the ability to create bigger, longer-lasting fire
    Mining,     // the ability to mine better ores
    Harvesting, // the ability to harvest better crops from the ground, to be used in to make medicine, better food, etc
    Crafting,   // the ability to craft finer products, as your level rises
    Agility,    // the ability to cross narrower paths, or be faster in your movement
    Smithing,   // the ability to smith better armor
    Cooking,    // the ability to cook food and get higher levels of health. It gets better as the levels grow
    Fishing,    // the ability to fish for... well, fish
    Hunting,    // the ability to hunt animals for food. Better meat at higher levels
    Bartering,  // the ability to negotiate with the shopkeeper for better prices on finer products (max level gives you a 50% discount total)
    Construction,   // the ability to build houses in a big game world, using materials that get finer as we grow, that can corrode over time or stay permanently alive
    Teleportation,  // the ability to teleport from one place to the other
    Equestrianism,   // the skill of riding animals, specifically horses (better animals as your level grows... can be dragons, werewolves, etc)
    Gambling,    // the chance of being able to lose less money and gain more (VERY EXPENSIVE SKILL, but it can be trained)
    Thieving,    // the ability to rob stuff from people, such as money for example
    Swimming    // the better you are at swimming, the less likely that bigger fish will attack you, and the more likely you are to get underwater quests done

}

public interface ESkillEvaluator {

    bool? EvaluateSkill(ESkill skill, string[] parameters);

}

(Please don’t question me on my skill choice xD)

and then I went to SkillStore.cs, and tried this script out:

public bool? EvaluateSkill(ESkill skill, string[] parameters)
        {
            
            if (skill == ESkill.Attack) {

                if (Enum.TryParse<Skill>(parameters[0], out Skill _skill)) {

                    return GetSkillLevel(_skill) >= Int32.Parse(parameters[1]);

                }

            }

            return null;

        }

Thoughts? No compiler errors or anything, but I want to ensure it works for now

The if (skill == ESkill.Attack) won’t work with anything but attack, the Tryparse will just be called in that event and will be skill…

public bool? Evaluate(EPredicate predicate, string[] parameters)
{
     if(predicate = EPredicate.HasSkill)
     {
           if(Enum.TryParse<Skill>(parameters[0], out Skill_skill) && Int32.TryParse(parameters[1], out int level))
           { 
                 return GetSkillLevel(skill)>=level;
           }
      }
      return null;
}

Alright I’ll check this again in a bit, class in 5 minutes (I implemented the code, yet to test it). Let’s continue our chat tomorrow, thanks again Brian :slight_smile: (and I deleted the ESkill thing, I placed the value in the IPredicateEvaluator instead)

Alright I’ll go through the Property drawer class again, because my skills Property drawer literally looks like this:

There’s no skill, level check or anything… (anything else we need to do to ensure it works properly?)

Alright so I have tried fixing the predicate, and it looks fine to me. I just want to reconfirm everything is right, because most of it was copied off the ‘Trait’ code, but tuned accordingly. Here are the changes I did to the Property drawer:

In ‘PredicatePropertyDrawer’, I changed 3 functions:

In ‘GetPropertyHeight()’, I added this line:

case EPredicate.HasSkill: return propHeight * 4.0f;

in ‘OnGUI()’ I added this ‘if’ statement:

            if (selectedPredicate == EPredicate.HasSkill) {

                position.y += propHeight;
                DrawSkill(position, parameterZero);
                position.y += propHeight;
                DrawIntSlider(position, "Minimum", parameterOne, 1, 200);

            }

and this is the ‘DrawSkill()’ function I created at the end of ‘PredicatePropertyDrawer.cs’ script:

void DrawSkill(Rect position, SerializedProperty element) {

            if (!Enum.TryParse(element.stringValue, out Skill skill)) {

                skill = Skill.Attack;

            }

            EditorGUI.BeginProperty(position, new GUIContent("Skill"), element);
            Skill newSkill = (Skill) EditorGUI.EnumPopup(position, "Skill: ", skill);

            if (newSkill != skill) element.stringValue = $"{newSkill}";

            EditorGUI.EndProperty();

        }

And it seems to be showing up perfectly fine…

What’s the next step to get these skills to properly show up and get trained, or at least… how do I set this whole system up and get some XP for multiple skills…? I tried making that predicate the only condition for me to wield my sword, but it’s not even checking for it to begin with. For the moment, I’m just trying out the combat stuff (I’ll probably need to add booleans as well somewhere to flip between training attack and defence because… well… they’re two different skills that need to be trained through melee combat, or just split the XP for defence with whatever skill we train from attack, ranged and mage, since it’s something that happens naturally apparently))… Anyway, how do we get the XP system to work? :slight_smile:

As for the UI, I’m still trying to set this up (if this line isn’t deleted, then it isn’t working for me)

It looks like you have gotten the Property drawer down correctly. Good job on that.

That is up to each system and skill, Combat is a good place to start. When you kill a character, for example, you’ll have to add the Skill Experience to the player’s SkillExperience component with the appropriate Skill rather than simply adding experience to the course’s Experience component.

This means that you’ll have to pass the skill you used on to the Health component so that it can award the property experience category. This means TakeDamage will need a parameter for the skill.

This means that Fighter will need to know what that skill is to pass it through to TakeDamage.

This means that the WeaponConfig will need a field for which skill is used to use it, which the Fighter can retrieve.

When an enemy dies, you’ll basically be doing the same thing in Health we were doing before, but getting the damage dealer’s SkillExperience and calling GainExperience(skill, experience) on that SkillExperience. I would use the BaseStats GetStat(Stat.ExperienceGained) for the experience value.

I’ll go through this all over again properly, for now the skilling system gave me a stack overflow on @bixarrio 's crafting system. Give me a few minutes and I’ll show you what he told me :slight_smile:

Alright so based on what @bixarrio said, the problem lies in the ‘skill’ getter in “SkillStore.cs”, because apparently I’m requesting the Crafting skill back many times. Here is what my getter looks like:

/// <summary>
/// Use this skills' Dictionary, not the backing field... This dictionary is of all skills with levels set
/// </summary>
Dictionary<Skill, int> skills {

    get {

        if (_skills == null) {

            _skills = new Dictionary<Skill, int>();
            foreach (SkillRecord skill in startingSkills) {

                _skills.Add(skill.skill, skill.value);

            }

        }

        return skills; // <-- Here! You are calling this same 'getter' again
    }
}

I’m not sure if ‘null’ is the second best option, but I can’t think of any other value that this dictionarys’ getter can return. Any suggestions?

Edit: Ahh nevermind, I fixed it. The skill return was supposed to be “_skills” not “skills”… Silly me :sweat_smile:

OK so let me just get my head around this, because I am way… WAY… WAY too confused right now, so here’s what I grasped:

  1. In the ‘WeaponConfig.cs’, add another serialized field of type ‘Skill Formula’ that’ll store the ‘Skill Formula’ asset we created, and for each weapon we’ll add that formula, right?
  2. In ‘Fighter.TriggerAttack()’, in the final else statement, in ‘takeDamage()’, we will need to get the component of that skill (probably in Awake is my guess), so we know which weapon uses what skill to develop the XP accordingly
  3. In ‘Health.TakeDamage()’, add a third parameter known as ‘Skill skillToUpdate’ (somehow this one isn’t part of an event, and I don’t know why…), and when the enemy dies in the end, we implement a line known as ‘skillToUpdate.GainExperience(Skill.Attack)’ for example? (my question here is, do we put any if-else statements to check for what weapon did majority of the damage, and level that stat up accordingly?

I just want to make sure these steps are right before I attempt any sort of implementations (last night, with a bit of help from @bixarrio and some coding off my side, we managed to get the “Crafting System” to run on the Skill-based system, so I call that a HUGE win, but Combat is still a bit baffling)

Oh, and can we design a ‘Total Experience’ function on the fly? Please :slight_smile:

And one last question, does the “Skill Formula” go into a ‘Resources’ folder? I’m still not sure what the point of this asset is tbh. I know we can change the skill formula with it, I’m just not sure how do we use it after we created it and placed a new formula in it

I didn’t have it as skills, did I?

No, in the WeaponConfig, you will just store the skill. For example, a bow might be Skill.Ranged, a Sword might be Skill.Melee.

Before calling TakeDamage, get the Skill from the WeaponConfig and pass it into the Take Damage component

That’s getting complex… Think about that later, for now focus on getting the skill from the weapon into the TakeDamage command.

No, it shouldn’t need to. The whole point of the Skill Formula is just to facilitate different experience starting points for individual skills, nothing more. I wrote SkillStore so that if no SkillFormula is attached, then it just uses a default starting point for the formula.

That was a mistake from my side, apologies

For that, I have a question:

  • For Ranged weapons, we check for projectiles and Ammunition
  • For Magic weapons, we just check for projectiles
  • For melee weapons, both must be absent

Right? How about this function in ‘WeaponConfig.cs’? :

public Skill CheckForSkill() {

            // If it has ammo, it's automatically a ranged weapon
            if (GetAmmunitionItem()) return Skill.Ranged;
            else if (!GetAmmunitionItem() && HasProjectile()) return Skill.Magic;
            else if (!GetAmmunitionItem() && !HasProjectile()) return Skill.Attack;
            else return 0;

        }

I’m trying to do that, but now everyone using ‘TakeDamage()’ complains that it’s missing a skill. How do we go over that?

And how is that connected to everything else? I’m not very familiar with that script tbh

Edit: OK so here’s what I tried doing:

In WeaponConfig.cs, I did 2 things. First, I added a weapon type getter, and a skill check function, as shown below:

public Weapon GetWeapon() {

            return equippedPrefab;

        }

        public Skill CheckForSkill() {

            // If it has ammo, it's automatically a ranged weapon
            if (GetAmmunitionItem() == true) return Skill.Ranged;
            // If it has a projectile but no ammo, it's automatically a magic weapon
            else if (!GetAmmunitionItem() && HasProjectile()) return Skill.Magic;
            // If it doesn't have either ammo or projectiles, it's automatically a melee weapon (so we can attack)
            // The line below will be tuned to 'CheckAttackOrDefence()' later, a function that checks if we are training attack or defence, connected to a button of some sort, to add the required XP:
            else if (!GetAmmunitionItem() && !HasProjectile()) return Skill.Attack;
            else return 0;

        }

And then in ‘Health.TakeDamage()’, I added a ‘WeaponConfig’ reference:

    [SerializeField] WeaponConfig weaponConfig;

And then I checked for the type of weapon and called the function we created in ‘WeaponConfig.cs’ that checks for the skill, as follows (although I want the XP to be given on death, based on the ‘XPReward’ variable assigned to ‘AIController.cs’ rather than when the player hits the enemy… it just makes the fight more fair and rewarding imo):

public void TakeDamage(GameObject instigator, float damage) {
            
    // Check for the type of weapon the player is currently holding, and the type of skill it needs
    weaponConfig.GetWeapon();
    weaponConfig.CheckForSkill();
    
// Rest of my code...

Whenever you have the time, please let me know if this can be improved or not. In the meanwhile I’ll go add a UI display for them whenever I’m free next to ensure they all work as expected, and edit (to report) any problems I encounter

Edit 2: Nope, it goes wild and terribly wrong… please send help code :sweat_smile:

Alternatively…

[SerializeField] Skill associatedSkill;

public Skill GetSkill() => associatedSkill;

Welcome to refactoring nightmares. :slight_smile:

There are two approaches you could take…
The first is to make the Skill an optional parameter… this can only be done on the last parameters in a method (you can have more than one optional parameter, but they all must be after the required ones…

For this, in your Skill declaration in Health.TakeDamage(), the Skill parameter will look like this

Skill skill = Skill.Melee

Now, if a method doesn’t pass in a Skill, it’s assumed to be Melee

I don’t prefer this method generally, because it can lead to unintended consequences. For example, a projectile may be doing the damage, but since you made Skill optional, you hide the fact that it’s not passing a skill, and now your arrows are doing Melee damage and giving you Melee experience…

The good news about the compiler complaining about the changed method signature is that you now can find those methods and update them… For example, since your projectile is spawned by the Weapon config, it can pass the appropriate Skill to the projectile which will store it and pass it on to TakeDamage.

While I think manually setting the skill as I demonstrated above is preferable (and it allows you to have magic skills with reagents… For instance a Fireball spell may require you to use a pinch of sulfur…), I need to simplify this CheckForSkill…

It can be presumed that a Weapon is either going to be ranged, magic, or melee…
If the first clause is true, then it will never get to the second clause, so there is no need to check again for GetAmmunitionItem()
If it gets past the 2nd clause, then by definition GetAmmunitionItem() and HasProjectile() are both false, so it will always be Skill.Attack in this case. Since all the clauses end in return statements, else is redundant.

public Skill CheckForSkill()
{
    if(GetAmmunitionItem()) return Skill.Ranged;
    if(HasProjectile()) return Skill.Magic;
    return Skill.Attack;
}

Privacy & Terms