Tutorial Part 11: Enemy Entities, Combat & GoRogue Integration

Important note: These tutorials were written for SadConsole version 7.x. The version 8.x tutorials are here.

<Hoonius> And you’re at part 10 and no glyph has been yet killed. You going for pacifistlike? 😉

Well er… good point. I guess it’s time that we define what an enemy is, and come up with some clever ways to make them disappear from the screen. I’ll be borrowing extensively from the RogueSharp tutorials again, because the architecture is very understandable for a first-time RL creator.

The overall plan is to:

Let’s sort out our project a bit, if you haven’t already. We’re going to be creating more Entities aside from the Player. Create an Entities sub-folder in your project, and move the Actor and Player classes into it. Don’t forget to  update their namespaces accordingly:

namespace SadConsoleRLTutorial.Entities

This also means adding the following using to the CommandManager, World and UIManager classes:

using SadConsoleRLTutorial.Entities;

Monster Class

Create a new class called Monster in the Entities sub-folder. Monster will inherit the Actor class. Let’s keep this class extremely simple for now; we’ll come back to it later:

using System;
using Microsoft.Xna.Framework;

namespace SadConsoleRLTutorial.Entities
{
    //A generic monster capable of
    //combat and interaction
    //yields treasure upon death
    public class Monster : Actor
    {
        public Monster(Color foreground, Color background) : base(foreground, background, 'M')
        {

        }
    }
}

Let’s make some modifications to our Actor class, since combat and stats will apply to both Monsters and the Player:

    public abstract class Actor : SadConsole.Entities.Entity
    {
        public int Health { get; set; } // current health
        public int MaxHealth { get; set; } // maximum health
        public int Attack { get; set; } // attack strength
        public int AttackChance { get; set; } // percent chance of successful hit
        public int Defense { get; set; } // defensive strength
        public int DefenseChance { get; set; } // percent chance of successfully blocking a hit
        public int Gold { get; set; } // amount of gold carried
...
}

Notice that I’ve removed the hand-written getters and setters for Health and MaxHealth, and replaced them with auto-properties. It saves space, and makes the class easier on the eyes. Besides, we don’t need the getters/setters to do anything special beyond communicating with the class’s private data.

Command: Attack

Modify the CommandManager to include:

using System.Text;

We’ll be using the Stringbuilder to add nice text descriptions of our attack sequence to the Message Log.

        // Executes an attack from an attacking actor
        // on a defending actor, and then describes
        // the outcome of the attack in the Message Log
        public void Attack(Actor attacker, Actor defender)
        {
            // Create two messages that describe the outcome
            // of the attack and defense
            StringBuilder attackMessage = new StringBuilder();
            StringBuilder defenseMessage = new StringBuilder();

            // Count up the amount of attacking damage done
            // and the number of successful blocks
            int hits = ResolveAttack(attacker, defender, attackMessage);
            int blocks = ResolveDefense(defender, hits, attackMessage, defenseMessage);

            // Display the outcome of the attack & defense
            GameLoop.UIManager.MessageLog.Add(attackMessage.ToString());
            if (!string.IsNullOrWhiteSpace(defenseMessage.ToString()))
            {
                GameLoop.UIManager.MessageLog.Add(defenseMessage.ToString());
            }

            int damage = hits - blocks;

            // The defender now takes damage
            ResolveDamage(defender, damage);
        }

The StringBuilder is a fantastic class — it allows us to create and modify strings that are mutable. Strings in C# are normally immutable or “non-modifiable”. Each time you modify a plain string, you are actually creating a new string. This feels counterintuitive at the best of times, or just plain annoying most of the time. The StringBuilder allows us to modify a string right on the spot, without creating a new output string.

In this case, we’re creating an empty StringBuilder that we’ll populate with characters later. We call the ResolveAttack and ResolveDefense methods to do the actual damage calculations, as well as create our fancy MessageLog messages. We’ll get to implementing those methods next. The ResolveDamage method will just subtract the total damage from the defender’s Health.

Ah, now we finally have a need for the GoRogue library. I’ve been waiting to integrate this sucker for weeks now. GoRogue (documentation here) is a collection of engine-agnostic tools that take care of a ton of jobs that I personally don’t want to write code for. One of those little jobs is Dicing. We could write our own dicing manager, but why re-invent the wheel? There are a ton of them around already, and most follow a nice D&D-style dice notation system.

So, add the “GoRogue” nuget to your packages.

If Visual Studio gives you the error, “Could not install package ‘GoRogue 1.6.2’. You are trying to install this package into a project that targets ‘.NETFramework,Version=v4.5’, but the package does not contain any assembly references or content files that are compatible with that framework…” the solution is simple: go to Project Options -> General -> and change your Target Framework to .NET Framework version 4.7

We’ll be using GoRogue a lot more in the future. You don’t have to use it, but you’ll be rolling your own dicing system from here on in.

Add a new using to the CommandManager:

using GoRogue.DiceNotation;

Let’s build our ResolveAttack method. Ours a simplified version of the RogueSharp method.

        // Calculates the outcome of an attacker's attempt
        // at scoring a hit on a defender, using the attacker's
        // AttackChance and a random d100 roll as the basis.
        // Modifies a StringBuilder message that will be displayed
        // in the MessageLog
        private static int ResolveAttack(Actor attacker, Actor defender, StringBuilder attackMessage)
        {
            // Create a string that expresses the attacker and defender's names
            int hits = 0;
            attackMessage.AppendFormat("{0} attacks {1}, ", attacker.Name, defender.Name);

            // The attacker's Attack value determines the number of D100 dice rolled
            for (int dice = 0; dice < attacker.Attack; dice++)
            {
                //Roll a single D100 and add its results to the attack Message
                int diceOutcome = Dice.Roll("1d100");

                //Resolve the dicing outcome and register a hit, governed by the
                //attacker's AttackChance value.
                if (diceOutcome >= 100 - attacker.AttackChance)
                    hits++;
            }

            return hits;
        }

It’s worth noting that I’ve greatly simplified RogueSharp’s style of dicing outcomes with a for loop. GoRogue does offer a much more efficient means for resolving multiple dice without re-rolls, but I find that style less habitable. After all, we’re here to build readable code, not an Enzo Ferrari.

We resolve the dicing outcome by comparing the roll of a single D100 dice (“1d100”) vs. 100 – attacker.AttackChance. In other words: if the attacker has an AttackChance of 20, it will need to roll at least a 20 in order to register a hit. Let’s move on to the ResolveDefense method:

        // Calculates the outcome of a defender's attempt
        // at blocking incoming hits.
        // Modifies a StringBuilder messages that will be displayed
        // in the MessageLog, expressing the number of hits blocked.
        private static int ResolveDefense(Actor defender, int hits, StringBuilder attackMessage, StringBuilder defenseMessage)
        {
            int blocks = 0;
            if (hits > 0)
            {
                // Create a string that displays the defender's name and outcomes
                attackMessage.AppendFormat("scoring {0} hits.", hits);
                defenseMessage.AppendFormat(" {0} defends and rolls: ", defender.Name);

                //The defender's Defense value determines the number of D100 dice rolled
                for (int dice = 0; dice < defender.Defense; dice++)
                {
                    //Roll a single D100 and add its results to the defense Message
                    int diceOutcome = Dice.Roll("1d100");

                    //Resolve the dicing outcome and register a block, governed by the
                    //attacker's DefenceChance value.
                    if (diceOutcome >= 100 - defender.DefenseChance)
                        blocks++;
                }
                defenseMessage.AppendFormat("resulting in {0} blocks.", blocks);
            }
            else
            {
                attackMessage.Append("and misses completely!");
            }
            return blocks;
        }

This method is almost the same as our ResolveAttack method, except that we’re counting the number of successful blocks in the for loop. Notice that we’re using DefenseChance as our modifier for successful blocks. Now we can move on to resolving the damage taken per hit:

        // Calculates the damage a defender takes after a successful hit
        // and subtracts it from its Health
        // Then displays the outcome in the MessageLog.
private static void ResolveDamage(Actor defender, int damage)
        {
            if (damage > 0)
            {
                defender.Health = defender.Health - damage;
                GameLoop.UIManager.MessageLog.Add($" {defender.Name} was hit for {damage} damage");
                if (defender.Health <= 0)
                {
                    ResolveDeath(defender);
                }
            }
            else
            {
                GameLoop.UIManager.MessageLog.Add($"{defender.Name} blocked all damage!");
            }
        }

And now we can add the ResolveDeath command:

        // Removes an Actor that has died
        // and displays a message showing
        // the number of Gold dropped.
        private static void ResolveDeath(Actor defender)
        {
            GameLoop.EntityManager.Entities.Remove(defender);

            if (defender is Player)
            {
                GameLoop.UIManager.MessageLog.Add($" {defender.Name} was killed.");
            }
            else if (defender is Monster)
            {
                GameLoop.UIManager.MessageLog.Add($"{defender.Name} died and dropped {defender.Gold} gold coins.");
            }
        }

That’s a fairly simple way of dealing with Actor death. We can gussy it up with something prettier later.

Now to actually generate some Monsters! Modify the World class, and we’ll spawn our monsters in there for now. Eventually, we’ll want our own monster generation class.

        // Create some random monsters with random attack and defense values
        // and drop them all over the map in
        // random places.
        private void CreateMonsters()
        {
            // number of monsters to create
            int numMonsters = 10;

            // random position generator
            Random rndNum = new Random();

            // Create several monsters and 
            // pick a random position on the map to place them.
            // check if the placement spot is blocking (e.g. a wall)
            // and if it is, try a new position
            for (int i=0; i < numMonsters; i++)
            {
                int monsterPosition=0;
                Monster newMonster = new Monster(Color.Blue, Color.Transparent);
                while (CurrentMap.Tiles[monsterPosition].IsBlockingMove)
                {
                    // pick a random spot on the map
                    monsterPosition = rndNum.Next(0, CurrentMap.Width * CurrentMap. Height);
                }

                // plug in some magic numbers for attack and defense values
                newMonster.Defense = rndNum.Next(0, 10);
                newMonster.DefenseChance = rndNum.Next(0, 50);
                newMonster.Attack = rndNum.Next(0, 10);
                newMonster.AttackChance = rndNum.Next(0, 50);
                newMonster.Name = "a common troll";

                // Set the monster's new position
                // Note: this fancy math will be replaced by a new helper method
                // in the next revision of SadConsole
                newMonster.Position = new Point(monsterPosition % CurrentMap.Width, monsterPosition / CurrentMap.Width);
                GameLoop.EntityManager.Entities.Add(newMonster);
            }
        }

The while loop in this method makes sure that the monster will be placed in a walkable tile position. This is not the preferred nor recommended method for monster placement, because it’s possible that your map has no available unblocked spots and would cause an endless loop. We’re just using this loop for the time being, until we create our own monster generation class.

We’re also giving the new monster some random defence and attack attributes. I picked magical numbers out of my head as “maximums” for the random number generator, so be sure to modify these to your liking.

Finally, we set the monster’s position using a snazzy algorithm for determining X and Y coordinates from an array index. The % or remainder operator isn’t used very often, yet it is one of the most powerful and useful operators there is. It just computes the reminder of division between the first and second terms. In this case, it’s computing the remainder from the division of monsterPosition by the map Width. The reminder is the X coordinate of the monster. The Y coordinate of the monster is the dividend given by division of the monsterPosition by the map Width.

Then, as always, we need to add the new Entity to our EntityManager’s list of Entities. Without that, our monster won’t be added to the game map.

Run your project. You should have several M’s spawned all over the map in random places. Try bumping into them – you just step right through them. Let’s add a bump attack next.

EntityManager Class

I put it off for as long as I could, but we now need our own specialized EntityManager. We’ll inherit SadConsole.Entities.EntityManager and save ourselves a ton of work, and then add some functionality to the class in order to manage our burgeoning cast of actors.

using System;
using System.Linq;
using Microsoft.Xna.Framework;

namespace SadConsoleRLTutorial.Entities
{
    // Manages a collection of Entities
    // using SadConsole's EntityManager
    // as well as provides extra functions
    // like searching for entities by type
    // and/or location
    public class EntityManager : SadConsole.Entities.EntityManager
    {
//empty constructor for now
        public EntityManager()
        {
        }

        // Checking whether a certain type of
        // entity is at a specified location the manager's list of entities
        // and if it exists, return that Entity
        public T GetEntityAt<T>(Point location) where T : SadConsole.Entities.Entity
        {
            return (T)Entities.FirstOrDefault(entity => entity is T && entity.Position == location);
        }
    }
}

We’re using all of SadConsole.Entity.EntityManager’s features, and adding one of our own (for now). The GetEntityAt method searches through the collection of Entities for a specific type of Entity at a specific location. If it finds an Entity of the given type at that location, it returns the object to the caller.

Now, let’s get down to the nitty-gritty of that nasty-looking Linq expression. Let’s break down the method signature first:

public T GetEntityAt<T>(Point location) where T : SadConsole.Entities.Entity

The T guarantees that the method will return an object of type T. What’s T? It is a variable that stands in for the type of object we’re interested in finding. Generics allow us to deal with a wide range of object types, while still guaranteeing type safety to the compiler. Think of it this way: say we want to search through our Entities list for a monster at some X and Y coordinate. We’d write a method definition that looks like this:

public Monster GetMonsterAt(Point location)
{
...
}

Okay, but what if we want to search for a Player at some X/Y location? Then we need to write a new method definition for that type:

public Player GetPlayerAt(Point location)
{
...
}

And so on. Every Actor type we’re interested in would require its own method, and soon our EntityManager would be full of copypasta-repeated code. Generics allows us to write a single method that takes care of all types of Entities.

Back to our method signature. The <T> tells the compiler to generate code of the requested type specifically of type T. In other words, the compiler is actually generating new instances of the GetEntityAt method for each type of object we’re searching for — doing our typing for us, and saving us a ton of space in the class.

The where T : SadConsole.Entities.Entity keyword is tricky. This is a type-safety constraint that I’ve added to the definition in order to 100% ensure that we’re only passing objects of the right kinds to the method. This will prevent a coked-up programmer from trying to search for a Console in the Entities list. Is that a likely thing to happen? You tell me. It’s not my job to judge your addiction. I just refuse to play the enabler.

Generics are confusing at first, because it seems impossible that we can pass something called T to the compiler and that it will understand this is a meaningful thing. We’ll use generics when necessary, but not too frequently – they have a way of making code impenetrable.

That’s it for our EntityManager. Let’s modify our GameLoop to acknowledge the existence of our new manager – change it from a SadConsole.Entities.EntityManager to:

        public static EntityManager EntityManager;

And modify the Init method similarly:

//Instantiate the EntityManager
EntityManager = new EntityManager();

Alright, we’re into the final stretch. Let’s modify our Actor class to create the ubiquitous bump attack. Modify the MoveBy method like this:

        // Moves the Actor BY positionChange tiles in any X/Y direction
        // returns true if actor was able to move, false if failed to move
        public bool MoveBy(Point positionChange)
        {
            // Check the current map if we can move to this new position
            if (GameLoop.World.CurrentMap.IsTileWalkable(Position + positionChange))
            {
                // if there's a monster here,
                // do a bump attack
                Monster monster = GameLoop.EntityManager.GetEntityAt<Monster>(Position + positionChange);
                if (monster != null)
                {
                    GameLoop.CommandManager.Attack(this, monster);
                    return true;
                }

                Position += positionChange;
                return true;
            }
            else
                return false;
        }

This isn’t the prettiest logic, but it gets the job done. We’re using our GetEntityAt method, of pokémon-type Monster at the Actor’s intended position. If there is a Monster there, tell the CommandManager to execute a battle round between this actor (the player!) and the monster we found.

Give it a run. You can walk around, attack monsters, and watch them asplode. Try modifying the Player’s Attack and AttackChance attributes to see how they change combat outcomes. If your Player is constantly missing, it’s likely that your Player class is missing values for the Attack and AttackChance fields:

        public Player(Color foreground, Color background) : base(foreground, background, '@')
        {
            Attack = 10;
            AttackChance = 40;
            Defense = 5;
            DefenseChance = 20;
            Name = "Milhouse";
        }

Our monsters don’t move, and they don’t even drop their treasure on the ground. That’s for a future tutorial.

Download the final source code for this tutorial here.

Published on December 12, 2018