Complete roguelike tutorial using C++ and libtcod - part 6: going berserk!

From RogueBasin
Jump to navigation Jump to search
Complete roguelike tutorial using C++ and libtcod
-originally written by Jice
Text in this tutorial was released under the Creative Commons Attribution-ShareAlike 3.0 Unported and the GNU Free Documentation License (unversioned, with no invariant sections, front-cover texts, or back-cover texts) on 2015-09-21.


In this article, we'll actually implement melee fighting. It's significantly bigger and harder than the previous articles.

Preparing for a huge diversity of actors

Those orcs and trolls have been teasing us for too long. It's time to slice them and repaint this dungeon in red... With actual fight comes the primordial question of how we handle actors specifities. Our Actor class represents everything that has a position on the map. It's easy to see that it will encompass a wide range of different entities : items, traps, doors, monsters... The question is how do we handle this in our class hierarchy ?

The old school way would be to stuff every property in the Actor class and use flags to determine what feature is active on a specific actor. This is great because any feature can be enabled on any actor. But there's a lot of wasted memory because every actor stores a lot of properties it doesn't use.

Another way is to make a base Actor class with all common attributes and a derived class for each type of actor. Item, Trap, Door, Monster. That's more efficient as each class only stores the attributes it needs. But it's very rigid. No way an item can also be a monster. What if you want to wear an acid spitting critter like a weapon ? Or populate the dungeon with enchanted flying knives that attack you on sight ?

There's a way to get most of the advantages and almost no drawback : composition. The Actor class will contain a pointer to every "pack" of features it needs. If a pack is not needed, the pointer is NULL and only waste 4 bytes (8 on a 64 bits OS).

Destructible actors, attacking actors and thinking actors

For this article, we'll create three actor features :

  • Destructible : something that can take damage and potentially break or die
  • Attacker : something that can deal damage to a Destructible
  • Ai : something that is self-updating

Now it's obvious that these classes will have to access the Actor they're linked with. We can either add an Actor * pointer field to each class (but that's 4/8 bytes wasted for each actor feature) or pass the Actor as parameter of the feature methods. We'll use the second solution.

Now that the monsters can die, some of the Actors will block the path (living creatures) and some won't (dead creatures). To make this simple, we'll add a block boolean to the Actor class.

Actor.hpp :

class Actor {
public :
   int x,y; // position on map
   int ch; // ascii code
   TCODColor col; // color
   const char *name; // the actor's name
   bool blocks; // can we walk on this actor?
   Attacker *attacker; // something that deals damages
   Destructible *destructible; // something that can be damaged
   Ai *ai; // something self-updating
     
   Actor(int x, int y, int ch, const char *name,
       const TCODColor &col);
   void update();
   void render() const;
};

Note that we removed the moveOrAttack function. This will be handled by the Ai class from now on.

The implementation is trivial. The constructor initializes all the features with the NULL value.

Actor::Actor(int x, int y, int ch, const char *name, 
   const TCODColor &col) :
   x(x),y(y),ch(ch),col(col),name(name),
   blocks(true),attacker(NULL),destructible(NULL),ai(NULL) {
}

The update function now delegates the processing to the Ai class (if the actor has one) :

void Actor::update() {
   if ( ai ) ai->update(this);
}

If ( ai ) is equivalent to if ( ai != NULL ). Remember an integer value in C is false if ==0, else true. Since a NULL pointer is an integer with value 0, it's false.