0
\$\begingroup\$

I'm building a mod for Terraria that will add classes and their corresponding abilities to the game. You're a warrior? Push your ability key and you'll use your berserk ability.

I'm trying to make my player code as simple as possible. I'd like to set the player's in-game "class" by doing something like this at the top of the player class so the rest of the methods in it can access it later:

//Set class
WarriorClass playerClass == new WarriorClass(playerReference, level)

Herein lies the first issue. I get an error at playerReference that says:

Error   CS0236  A field initializer cannot reference the non-static field, method, or property 'ModPlayer.playerReference'  MyMod

Ok, so I looked that up and set the initialization in the WarriorClass instead:

public Player playerReference = new Player();

And I have this now in my player code:

//Set class
     WarriorClass playerClass = new WarriorClass(level);

Here's the beauty of it. In the gamepad input method within my player code all I ever have to do is:

    //On key presses
    public override void ProcessTriggers(TriggersSet triggersSet)
    {
        if (MyMod.ability1Key.JustPressed)
        {
             playerClass.UseAbility(MyMod.ability1Key);
        }

Now that the ability1 key was pressed, let the WarriorClass handle what to do:

    public void UseAbility(ModHotKey key)
    {
            //Ability 1
            if (key.JustPressed == MyMod.ability1Key)
            {
                //Start berserking
                if (player.HasBuff(ModContent.BuffType<BerserkBuff>()) == false )
                {
                    //Lose 10% of your life
                    lifeLost = player.statLifeMax2 / 10;
                    player.statLife = player.statLife - (int)lifeLost;

                    //Berserk and add damage over in the Buff code
                    player.AddBuff(ModContent.BuffType<BerserkBuff>(), 600, false);
                }

This is all great!!! I can add any amount of classes I want and this player code never changes. Except If I want to add if/then logic to select a class in say, Update() then it breaks the following reference in my gamepad input code:

playerClass.UseAbility(MyMod.ability1Key);

Of course it won't have a reference unless I hard code it at the top of the player file, but I don't know what class a player will be until they pick one.

Am I doing this fundamentally in a bad way or am I missing something else in terms of OO programming? I don't want to have to initialize every class for the character at the top of the file like this:

WarriorClass warriorClass = new WarriorClass(level);
ArcherClass archerClass = new ArcherClass(level);
...

... just to do this in the gamepad input method:

if (classChoice == "Warrior")
{
     warriorClass.UseAbility(MyMod.ability1Key);
}
else if (classChoice == "Archer")
{
     archerClass.UseAbility(MyMod.ability1Key);
}
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

What you want is baseclass inheritance or an interface.
If the UseAbility method is same or similar among "Classes", you probably want a baseclass otherwise an interface. Lets take the interface route for now.

The interface (lets call it PlayerClass) defines the UseAbility method.

interface PlayerClass {
    void UseAbility(yourKeyType key);
}

All your playerclasses (WarriorClass, ArcherClass) implement this interface.

class WarriorClass : PlayerClass {
    void UseAbility(yourKeyType key) {
        // implement ability usage
    }
}

Then in your code above you use PlayerClass playerClass; and assign it to whatever the player picks at the time the player picks it not when the player uses the ability: playerClass = new WarriorClass(level); after that, when the player uses the ability you can do

playerClass.UseAbility(MyMod.ability1Key);

and don't care about which exact class he picked in the beginning.

It works almost identical when using inheritance, you can move the UseAbility code in the baseclass to reduce duplication. This should be done if your player classes have common code.

\$\endgroup\$
8
  • \$\begingroup\$ Thanks, taking a look. I've watched a few videos on interfaces and it's not clicking yet. I'm new to OOP so it's taking a little longer than usual. Is it possible for you to relate an interface to something in real life that is similar for me? It sound like putting a different shade of glasses on. You still see the same stuff, but the way you interface with the world/vision has changed. \$\endgroup\$ Apr 29, 2020 at 6:43
  • \$\begingroup\$ Look at two pens. A red and a blue one. Both are Pens that can draw a line. They could implement a Pen interface with a DrawLine() method -> RedPen : Pen and BluePen : Pen. Again if the DrawLine of the blue and red pen is really only the color, the Pen should be a class implementing the DrawLine itself. If you look into the topic you'll often find an animal example where a dog barks and a duck quacks or a bird flies and a horse walks etc. The animal can then be told to make a sound or to move without knowing which exact animal it is. These are probably better than the pen example \$\endgroup\$
    – schneebuzz
    Apr 29, 2020 at 6:58
  • \$\begingroup\$ Hmm ok, so then what is technically happening before I set PlayerClass playerClass to anything in my Update() method that references it? Is it just calling an empty method and doing nothing? \$\endgroup\$ Apr 29, 2020 at 7:03
  • 1
    \$\begingroup\$ It will throw a NullReferenceException when unset (null by default). So you should check for that and only call it if it's not null. Alternatively assign a default-class. The default class could be what you describe a No-Operation implementation of the interface that does nothing. However then its unneccessary code since you can just check for null. Hope that makes sense. \$\endgroup\$
    – schneebuzz
    Apr 29, 2020 at 7:11
  • \$\begingroup\$ Thanks, this helps. I don't understand how inheritance directly solves the reference issue in my Update code before PlayerClass playerClass is set but I'm in the middle of a C# course and will hopefully get there soon enough. I was thinking I could do playerClass = new WarriorClass(level) from an inheritance perspective but then I read you couldn't convert classes into their children (PlayerClass object to WarriorClass). Maybe I misinterpreted it. \$\endgroup\$ Apr 29, 2020 at 7:27
0
\$\begingroup\$

Ok, this is how I solved this ultimately. I found three distinct things I wanted to define by classes:

  1. The player (already done by default)
  2. The player's class (warrior, rogue, etc)
  3. 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:

  1. Really nice, readable code and code architecture. You know exactly what file and class to edit to change a class or ability.
  2. 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.
  3. 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.
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .