Ok, this is how I solved this ultimately. I found three distinct things I wanted to define by classes:
- The player (already done by default)
- The player's class (warrior, rogue, etc)
- The abilities used by classes (berserk, stealth, etc)
This is how I did it, starting with #1 The Player:
PlayerClass playerClass;
public static ModHotKey ability1 = new ModHotKey("F");
...
Update()
{
...
else if (choice == "Warrior")
{
playerClass = new WarriorClass(player, 100);
}
...
}
KeyTriggers(TriggersSet triggersSet)
{
if (ability1.JustPressed)
{
playerClass.UseAbility(ability1);
}
}
PlayerClass is a mostly empty class that guides what methods should be called when inherited. Example WarriorClass inheriting from PlayerClass:
public class WarriorClass : PlayerClass
{
int level;
Player player = new Player();
//Ability placeholders
public IAbility primaryAbility;
public IPassive primaryPassive;
//Constructor. Needs player reference and level to set skills and stats correctly (when progression is a thing)
public WarriorClass(Player player, int level)
{
this.player = player;
this.level = level;
//Set ability manually for now
primaryAbility = new BerserkAbility();
primaryPassive = new LifeRegenPassive();
}
public override void DoPassives()
{
primaryPassive.DoPassive(player);
}
public override void UseAbility(ModHotKey key)
{
if (key == MinionTest.ability1)
{
primaryAbility.UseAbility(player);
}
}
}
It doesn't seem like much now that I've finished the entire design, but it will hold WarriorSpecific things like its progression and possibly talent tree (or maybe those will be classes themselves!).
Next up, the abilities the classes use. Notice above that the warrior doesn't know what ability it is going to use in UseAbility() until the constructor is called. Here is some ability code:
public class BerserkAbility : IAbility
{
int lifeLost;
public void UseAbility(Player player)
{
//If the buff is off completely... start the buff on stack one
if (player.HasBuff(ModContent.BuffType<BerserkBuff1>()) == false && player.HasBuff(ModContent.BuffType<BerserkBuff2>()) == false && player.HasBuff(ModContent.BuffType<BerserkBuff3>()) == false && player.HasBuff(ModContent.BuffType<BerserkBuff4>()) == false && player.HasBuff(ModContent.BuffType<BerserkBuff5>()) == false)
{
//Lose 10% of your life
lifeLost = player.statLifeMax2 / 10;
player.statLife = player.statLife - (int)lifeLost;
//Start first stack of berserk for 10 seconds
player.AddBuff(ModContent.BuffType<BerserkBuff1>(), 600, false);
}
//If it's already at stack 1, start stack two...
else if (player.HasBuff(ModContent.BuffType<BerserkBuff1>()))
...
Where UseAbility (maybe a poor choice due to not being unique project wide) is required due to the interface IAbility interface. If you are stuck on Buffs above, those are already a distinct object or class in the mod library. IAbility is just this:
namespace MinionTest.Abilities
{
public interface IAbility
{
void UseAbility(Player player);
}
//Should add cooldown here later
}
Why did I user class inheritance for player classes and an interface for abilities? I'm not really sure and wanted to try both.
Essentially what I end up with is a player that hits a key and asks the warrior to do the correct ability, and the warrior asks the ability how to do it. This adds three main benefits I see right now:
- Really nice, readable code and code architecture. You know exactly what file and class to edit to change a class or ability.
- It allows for game design choices like choosing a new class on the fly in-game, and talent trees that may change up what abilities your class can use.
- The possibility to be extended incredibly easily. People can work on an ability in and of itself. A person can work on a class in and of itself, and plug and play abilities to it.