7
\$\begingroup\$

I'm trying to create some easily accessible database of different skills. General idea is that every skill should do something different and be able to act on different things (ex. one skill which icreases weapon attack by 'x' [it acts on weapon] and other one which icreases chances to drop item by 'z'% [it acts on monster]).

I see it that way:

  • There is static database of skills accessible from everywhere
  • Skills have effects(methods with different return types, different parameters and calculations)
  • To use skill we add it's effect in particular calculations;

    • Example: If we want to increase drop chance of monster by 'z'% then, in place where we calculate that drop chance (in this case in Monster class), lets say (Monster.DropChance = 0.5f) we add skill effect : after skill implementation (Monster.DropChance = 0.5f + SkillDatabase.IncreaseDropChance.Effect (this))

    I came up with a code like this:

    public static class SkillData   // Static class acting as database for keeping skills in one place
    {
        // Names of skills
        public enum Name
        {
            IncreaseDropChance = 1,
            IncreaseWeaponAtk = 2
        }   
    
        // T - return type of skill effect, U - type of object on which effect works
        public class Skill<T, U>
        {
            public int Level { get; set; }
            public float ExperienceCost { get; set; }
            public Name SName;
    
            // Delegate for creating unique effects for each skill
            public delegate T EffectDel (U onWhat);
            public EffectDel Effect; 
    
            public Skill (Name name)
            {
                SName = name;
            }
        }
    
        // Custom dictionary for generic skill
        public class SkillDictionary
        {
            private Dictionary<Name, object> _dict = new Dictionary<Name, object>();
    
            public void Add<T, U>(Name key, Skill<T, U> skill)
            {
                _dict.Add(key, skill);
            }
    
            public Skill<T, U> GetValue<T, U>(Name key)
            {
                return _dict[key] as Skill<T,U>;
            }
        }
    
        // Static for easy access
        public static SkillDictionary DB = new SkillDictionary ();
    
        static SkillData ()
        {
            // Here I want to create skills one after another and add them to DB
            // This is example skill creation
            Skill <float, Player> IncrWpnAtk = new Skill<float, Player> (SName.IncreaseWeaponAtk);
            IncrWpnAtk.Level = 5;
            // Let's say we want to increase weapon attack by 100% * skillLvl
            IncrWpnAtk.Effect = (player) => { float additionalAtk = player.Eq.Weapon.Atk * IncrWpnAtk.Level; 
                                              return additionalAtk; };
            DB.Add <float, Player> (Name.IncreaseWeaponAtk, IncrWpnAtk);
        }
    }
    

Now in weapon attack calculation we add SkillData.DB.GetValue<float, Player> (IncreaseWeaponAtk).Effect (player) .
Yeah... It's terrible. Load of text for such a tiny effect. It's my second time using delegates and third time creating generic classes, I just don't know any other solution for creating same class with different methods.

How would you solve it guys? Do you know any way to maintain organized, easily accessible database of skills? Or maby this approach is generaly wrong?

\$\endgroup\$
  • \$\begingroup\$ Okay, found solution to the whole problem in general, but the approach is completly different. I still wonder if it could be handled in the way it is in posted code, just improved. \$\endgroup\$ – CantGetEnough Feb 20 '14 at 10:40
  • \$\begingroup\$ Right now I'm on idea of deleting name enum, delegates and generics, creating basic skill class and separate static class for each skill which will derive from basic skill class (all classes would be in SkillData class). Ex class IncreaseDropChance : Skill . This way I won't have to rewrite basic information, and will keep all the skills in one place with easy access (SkillData.IncreaseDropChance.Effect(monster)). Also this way I can have different calculations on different objects without complex generics and delegates. I'm just not sure about efficiency of such solution. What do you think? \$\endgroup\$ – CantGetEnough Feb 20 '14 at 14:08
3
\$\begingroup\$

Hmmm,

Well I think the way you are going about it, while functionally correct won't scale well.

Ideally you want to try more of an aggregation. Off the top of my head I would approach this like:

interface Skill
{
   string Name{get;}
   double value{get;}
}

now with this base object you can write the rest of your code doing calculations based on your skills value.

As for the modifying, well nothing else needs to know if a Skill is modified to use it. To handle that I would make a ModifiedSkill agnostic from everything else:

public interface ModifiedSkill : Skill
{
    IEnumerable<Modifier> Modifiers { get; }

    double BaseValue { get; }
    double Value { get; }

}

Now, what is a modifier you ask? well:

public interface Modifier
{
    string Name { get; }
    string Cause { get; }
    double Apply(double skill);
    Boolean Active { get; set; }
}

So that is all the interfaces you need to cover pretty much all eventualities, allowing for some potentially cool things down the line like timed modifiers or conditional modifiers or whatever.

Now, how to actually implement this?

Public class ModifiedSkillImpl : ModifiedSkill
    {
        private readonly List<Modifier> _modifiers;
        private readonly double _baseValue;

        public ModifiedSkillImpl(string name, double baseValue, IEnumerable<Modifier> modifiers)
        {
        if (name == null) throw new ArgumentNullException("name");
        if (modifiers == null) throw new ArgumentNullException("modifiers");
        Name = name;
        _baseValue = baseValue;
        _modifiers = modifiers.ToList();
    }

    public string Name { get; private set; }

    public IEnumerable<Modifier> Modifiers
    {
        get
        {
            return _modifiers;
        }
    }

    public double BaseValue
    {
        get
        {
            return _baseValue;
        }
    }

    public double Value
    {
        get
        {
            return Modifiers.Aggregate(_baseValue, (current, modifier) => modifier.Apply(current));
        }
    }
}

Now, this is only one example of course, you could do a lot of alternate skill implementations. The final step would be i suppose to create PoisonModifier/ItchyLeftEarModifier etc and then apply them with some effect like +5% -20% etc, or whatever.

This separation also means that while most things will be using the Skill.Value which in the case of a modified skill will be the modified version some special something could check if the skill is a Modified skill, if so cast it and use the base stat instead!

(one final note) I was a bit wasteful with the Value aggregation in ModifiedSkillImpl, tbh I would probably have a private variable for modified Skill and only recalculate if the modifiers had changed since last call but you know, this is just a quick POC.

\$\endgroup\$
  • \$\begingroup\$ Interface Skill? Why not stick to convention and call it ISkill? \$\endgroup\$ – Mathieu Guindon Feb 20 '14 at 15:02
  • \$\begingroup\$ @Mat'sMug Well this is one for debate, I for one am a fan of the clean code approach. prefixing I for what is essentially an end-facing class/interface is essentially hungarian notation. Whoever/whatever service consumes this , why do they need to know it's an interface? Also if you plan on passing around the interface but hiding the implementation is it not cleaner to have a Skill and SkllImpl that is never seen rather than a ISkill passed around and Skill class hidden away? \$\endgroup\$ – apieceoffruit Feb 20 '14 at 15:12
  • \$\begingroup\$ The vast majority of C# programmers will be expecting an interface to start with a capital I. I agree it's somewhat Hungarian, but it's the exception that confirms the rule. stackoverflow.com/q/2728093/1188513 \$\endgroup\$ – Mathieu Guindon Feb 20 '14 at 15:16
  • \$\begingroup\$ I use exactly this system for stats and attributes but I just don't find it to work well with skill system. Let's take a IncrDropChance example again. Monster has a drop chance of 2%, we want skill to increase it by 50% so it will be 3%. If I understood correctly, to make it work I would have to create modified skill with baseValue of 0.5, add monster's drop chance (0.02) as a modifier to this skill, (assuming we have different Value calculations for multiplication, we don't want 0.5 sum with 0.02, rather 0.5 * 0.02) and finally include it in monster's drop chance calculation. Quite alot work \$\endgroup\$ – CantGetEnough Feb 20 '14 at 15:21
  • \$\begingroup\$ @Mat'sMug well given my assumed usage of these classes I would prefer to have more fluent methods that read like a user story rather than code such as if(Skill is ModifedSkill) ... so on. My loyalty for expectation is more to conform to the glossary of the problem domain than coding convention. If done well the code reads well regardless of whether the person reading it can code or not. The exception is Developer-Centric code. eg if i role my own Modular MEF/Prism System then IModule speaks to developers but things like Skills/Accounts/Members don't lie exclusively in developer world. \$\endgroup\$ – apieceoffruit Feb 20 '14 at 15:26
2
\$\begingroup\$

Given @CantGenEnough 's comments that my previous suggestion didn't fit his needs I thought I'd try an alternate approach I was playing with.

Instead of the above you could try a similar approach with a sort of a weighted calculator instead of the actual skill approach.

So If you had a generic

interface Modification
{
  int Weight {get;}
  double Calculate(double input);
}

and had a number of calculation classes that implement it such as IncreaseByXPercent or CapAtMaximum or something (*cough better named) you could create a modifier Enumeration just like before with a small factory method with simple functions that just add the appropriate classes to the Modifier Collection then on calculation order by weight and apply. a snapshot of it might look like:

public interface SkillModifier
{
   void IncreaseByXPercent(int percent);
   void CapAtX(double value);

   double Calculate();

}

usage might look like:

Skill health = new SkillImpl(100);

health.Modify.IncreateByXPercent(5);
health.Modify.CapAt(5000);

PrintTheValue(health.Value);

whereby the Modify Accessor is a composite instance of the SkillModifier class, it adds Modification instances to an internal enumeration and orders by weight.

the Skill classes Skill.Value calls its composite Modify.Calculate(_baseValue) or something and still returns the combined result and all is safely encapsulated.


Personally I am less inclined towards this, more overhead in terms of building a fluent interface for your equation aggregation and a bit harder for newer people to grasp using but then again it reads better than any other solution at first glance regardless of ability.

So if you were to package this as a modifier API for yourself it might be the way to go (but for api additions I tend to follow the rule of 3 uses so may be premature to do so); but as a problem solve for a particular project I would choose the more straightforward above method.

\$\endgroup\$
  • 1
    \$\begingroup\$ I'm quite confused and not sure if we are on the same page. I might have named whole topic and thing wrong. By skill I do not mean something like player's sword fighting skill, or lockpicking or whatever, (in these examples your solution is perfect) but rather something that affects different things in the game world, not necessarily directly related to player. Ex. if there was some storage in game, there could be "skill" that would increase it's capacity. I know, that shouldn't be named skill, but I just tend to think of such things as "skills". Thats why I don't want to use modifiers \$\endgroup\$ – CantGetEnough Feb 20 '14 at 17:08
  • \$\begingroup\$ and try to force that "direct acting on different type objects". I just didn't want to create modifiers in those affected objects classes just to add them to the skill later used in the same class. OR I simply didn't understand well, what you've tried to show me and then this comment is just an effect of this. I'll read your answers few more times and will try to digest it. \$\endgroup\$ – CantGetEnough Feb 20 '14 at 17:11
  • \$\begingroup\$ @CantGetEnough well that naming is changeable, my understanding of your requirement and above definition of a skill is: "a changable named value with stacked mathematical calculations that can be applied and altered at runtime" where these are used and whether they are for a humidifier calculation or a player health is irrelevant. I just use game based glossary/naming convention to hopefully have an easy to relate to example. If this is incorrect what is the requirements for this system? \$\endgroup\$ – apieceoffruit Feb 20 '14 at 17:17
  • \$\begingroup\$ @CantGetEnough I think the disconnect is that I view your question as actually two questions. 1) how do i store a large number of potentially reusable modifiers and 2) how can I apply these reusable modifiers to values in my application while keeping everything well encapsulated. The two answers above answer (what I think is the harder one) question 2. The answer to question one as far as I am concerned is simply to put all of the values and names in a database and load them in and create your objects as opposed to static objects. Once you have a good answer to Q2 a factory will solve Q1. \$\endgroup\$ – apieceoffruit Feb 20 '14 at 17:23
  • \$\begingroup\$ I think it's kind of reversed. (again, firgive me, I'm a newbie and it takes some time for me to understand new stuff) In the example of storage, storage capacity would have to be Skill, and one of player's (in my understanding) "skills" would have to be modifier affecting this storage capacity. What I'm looking for is "skill" in form of a method that takes object on which it acts as parameter and return some result. Moreover, it would be great if these skills regarding their difference could still be stored in one container and be easily accessed from it. \$\endgroup\$ – CantGetEnough Feb 20 '14 at 17:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.