Class Organization Question/Thoughts (D&D themed!)

So this is a dungeons and dragons coding sideproject, but this presents some interesting organizational considerations.

A “feat” is like a special trait, or “cheat”.

Feats are different but all pre-made - new feats cannot be made up. (At least we don’t want that for this project)
So my approach was to make a super class “feat” and then make derived classes for each pre-made feat.

So - I made a class. “Feat”

(I’m in C# currently)

public abstract class Feat {

    //these are all properties that a feat must have, so I made abstract properties for all of them.
    public abstract string Description();   //short description of feat
    public abstract string Prerequisite();  //is there a prerequisite to obtain the feat?
    public abstract string Benefit();       //benefit of the feat
    public abstract string Normal();        //what happens without the feat?
    public abstract string Special();       //is there a special characteristic?
    public abstract bool FighterBonusFeat(); //is this a valid fighter bonus feat?
    public PlayerCharacter myPlayerCharacter;  ///when an instance of a feat is created, we will assign it its player Owner

}

So that is a super-class for our feats that defines some basic properties.
Now - let’s make a derived class of a specific pre-made feat.

public class Deceitful : Feat{
        public override bool FighterBonusFeat() { return false; }
        public override string Prerequisite() { return null; }
        public override string Description() { return "You have a knack for disguising the truth."; }
        public override string Benefit() { return "You get a +2 bonus on all Disguise checks and Forgery checks."; }
        public override string Normal() { return null; }
        public override string Special() { return null; }

        public Deceitful(){} //constructor for when we want to access this feats properties via an instance, but not attach it to a player when we do so
        public Deceitful(PlayerCharacter player){
            myPlayerCharacter = player;
            player.disguise.AdjustMiscMod(2);
            player.forgery.AdjustMiscMod(2);
        }
    }

So the question is - is there a better way to organize this? Is this inefficient or perhaps not logically correct?
I at first wanted to make the derived classes property values static, since these would always be the same and pre-defined,
but ran into some confusion when realizing that i could not access these properties when making instances of the feat. (due to static modifier obviously)

It is an interesting situation because the derived classes properties should be permanently defined - like a dictionary - but we still want to
make instances of these feats to attach to playerCharacters - like copying words out of a dictionary.

Thoughts?

My first reaction is that if this is Data, and not Behavior, then defining these as ScriptableObjects feels like a reasonable approach. If you’re not familiar with ScriptableObjects, they’re essentially class definitions that allow you to create distinct Assets in your game that have those properties, then customize them. Here’s an example of how you might define your feat as a ScriptableObject:

    [CreateAssetMenu(menuName = "MyAssets/Feat")]
    public class Feat : ScriptableObject
    {
        public bool FighterBonusFeat;
        public string Prerequisite;
        public string Description;
        public string Benefit;
        public string Normal;
        public string Special;

        public int DisguiseModifier;
        public int ForgeryModifier;
    }

With that class in your project, you can then create instances of these Feats in your project:

Then, the inspector will let you fill in the values:

If you have a public property of type “Feat” on any other MonoBehavior, you can then assign this Asset in the inspector.

This might not be a perfect fit for your purposes, but I figured it’s worth mentioning, as I do this all the time for configuration and settings values that have the same general form. Another useful thing about ScriptableObjects is that you can edit them in Play Mode, and the changes persist, unlike properties of MonoBehaviors.

1 Like

This should be interesting. D&D feats are pretty tricky to code, as they can modify things in so many different ways. I agree with the ScriptableObject approach, but trying to put settings for all possible feats in a single class will turn into a huge mess. The most extensible option would be to represent a feat as a collection of subcomponents, but that’s going to get complex fast.

A simpler option is to have a base feat and then derive a class for each variety of feat that has distinct behavior. So for example (leaving things public for simplicity, but you may want to make these SerializedField private with get properties instead):

public class BaseFeat : ScriptableObject
{
   public string Name;
   public List<string> Tags;  // As used in Pathfinder.  More flexible than individual toggles, IMO.
   public List<BasePrerequisite> Prerequisites;
   public string Description;

   public abstract void Apply (Character target);
 
   public bool Eligible (Character candidate)
   {
      return Prerequisites.All(x => x.Eligible(candidate));
   }
}

public class SkillModFeat : BaseFeat
{
   [SerializeField] List<NamedMod> skillMods;

   public override void Apply (Character target)
   {
      foreach(var mod in skillMods)
      {
         target.AddSkillMod(mod.Name, mod.Value, mod.Type);
      }
   }
}

NamedMod can just be a simple serializable object, but prerequisites will likely need multiple classes as feats do, so likely also a ScriptableObject. By default, this will mean they’re separate assets, but it’s possible to embed them within the feat asset and display them in-line by using a custom inspector.

While static modification feats (such as Deceitful) are a good place to start, keep in mind that they’re definitely the “easy mode” of feats. Implementing things like Power Attack or Improved Trip is going to require Character to expose a lot of hooks for modifying behavior.

1 Like