I can’t say we’re done with inventory in our roguelike game project; we only have a few items including the Amulet. There are a lot of potential inventory items in a roguelike, however, and I don’t want to spend the next ten chapters detailing them before we get on to the heart and challenge of the game – defeating monsters. Fighting monsters and taking damage is also the reason for many of the inventory items such as the potion of Restore Strength so we’ll come back to these items as we need them. For now, let’s take a look at what’s involved in adding monsters to this game.
This post is part of a series on creating a roguelike game in C#. For the latest chapters and more information, please visit the Official Project Page on ComeauSoftware.com. The current code for this project is also available on Github.
It’s a whole new game ….
There’s actually a lot that goes into programming monsters in the game and having our character square off against them. Since this series is about development strategy as much as it is about coding, let’s do an overview of what you’re going to see in the next few chapters as we open the dungeon to an array of creatures and make the player’s life more interesting.
The Monster class
We’ll need a new class to encapsulate all the properties and functions that are specific to monsters. This class can be patterned after the Inventory and Player classes to a certain extent. There are 26 monsters in classic Rogue, corresponding to the letters of the alphabet that are used to display them on the map. Just like with inventory, the Monster class can use a template list to supply the new object definitions for each monster along with a cloning constructor that will ensure a new monster is created and not just a copy of the same one.
Another possibility is the use of inheritance to further organize our code. There might be enough properties that monsters share with the player, such as hit points, that a third class could be created to house these properties. That class could then be inherited by both the Player and Monster classes. Then, when necessary, our code can perform operations on both the player and the monsters at the same time because they will be objects that ultimately inherit from the same class. It’s also a good demonstration of inheritance and polymorphism in C#.
Adding the monsters to the map
The monster class can use a probability property to manage how often each monster appears but monsters in Rogue also have a specific range of levels at which they show up. Hobgoblins and Emus show up on Level 1 but Centaurs don’t start appearing until around Level 8 or 9. New monsters also spawn around every 80 turns if a player remains on the same level.
Everyone gets a turn
We have basic turn management worked out in the game but now, every time the player moves, each monster will also need to have a turn, even if it decides to stay right where it is and do nothing. Different monsters behave differently and turn management needs to cycle through all the monsters currently on the level which means that there needs to be a main listing of some kind. Inventory was simply placed on the map, although we created another property in the MapSpace class to hold it.
The MapSpace.DisplayCharacter property is already reserved for the player and any monsters but I’m not sure that cycling through 2000 MapSpaces to check for !null is the best way to do things. A List<Monster> instance at class level in the current game or current map object might be the way to go.
Rogue Fight Club
This is the main event and it involves everything from detecting surrounding items to managing character stats that we haven’t even touched yet.
Our player enters the same room as one or more monsters – if the room is darkened, does the player see the monsters before other items in the room? Sometimes the monsters are aggressive and immediately head for the player, sometimes they just sit there until the player gets to an adjacent space or maybe they ignore the player altogether unless attacked and then continue attacking until defeated. If the player runs, the monster will usually follow but at a slower pace.
Each monster has its own stats including hit points, damage potential and armor class which will need to be factored into any attack or defense. The player’s stats will also need to accounted for. For each turn, there will usually be two attacks – one from the player and one from the monster – maybe more if one of them is moving faster because of an enchantment or natural ability. Many monsters have special attacks like the freezing attack from the Ice Monster or the Dragon’s fire breath and these will need to happen based on probability in addition to regular attacks.
Monsters don’t fight each other in the classic Rogue game but I’m thinking they should, at least under certain conditions. Maybe Emus and Orcs don’t like each other; maybe Trolls don’t like anyone. Maybe there’s a scroll that will make any monsters in the area riot while the player slips out the back or a potion that, when thrown at a monster, will make them go berserk on other monsters. There are plenty of possibilities. I’m thinking some monsters should talk and taunt the player. The game itself has it’s own verbiage based on the severity of attacks which needs to be coded in.
Finally, monsters can hold at least one or two items of inventory and some will steal things, including gold, and then run away. That will need to be based on some level of probability.
The player’s hit points, strength, armor and experience might all need to be adjusted after a turn and the monster’s hit points will also need to be calculated. At least one monster can regenerate during a fight.
After a fight, assuming the player survives, whatever hit point damage they’ve taken needs to regenerate as they move. HP regenerates faster as a player gains experience from fights and other activities so there will need to be a formula for this. In the classic game, strength was usually only affected by rattlesnakes and then a potion was needed to restore it. This doesn’t make sense to me so I’m thinking of ways in which it can respond to other things and regenerate on its own or when the player eats. It might also need to increase with experience.
The game status display also needs an upgrade so that fight progress can be seen more clearly. The original game made the player hit the space bar to continue some messages and this could get really annoying sometimes but there are ways we could make it work a bit better.
The list of monsters in Rogue changed with the first few versions. For now, I’m going with the list in the DOS version of the game released in 1984. I might change some of them up later and draw on other sources of mythology. The Unicode symbols have also expanded quite a bit since ’84 so there are some possibilities with the character sets for other languages.
As I said, many of the monsters have special abilities and attacks that do a lot more than subtract hit points. Here’s an overview of some of them and the parts of the program they’re going to affect.
- Aquators rust a player’s armor so the class property of whatever armor the player is wearing will need to be reduced.
- Bats move around randomly.
- The Flytrap holds the player for a few turns and the Ice Monster freezes the player at random so the Player.Immobile property will need to be set.
- The Leprechaun steals gold and the Nymph steals a random item from the player’s inventory.
- The Medusa’s gaze confuses the player so the Player.Confused property will be used.
- Orcs head straight for any gold in the room to keep the player from getting it. They drop it when defeated.
- Phantoms are invisible.
- The Rattlesnake occasionally bites the player and reduces the strength by one.
- The Vampire will reduce a player’s maximum HP. The Wraith will reduce their experience level and max HP.
- Xerocs can imitate objects.
Some player attacks on a monster can have similar effects if a player is using a potion or scroll so, based on the variety of properties mentioned above, whatever attack function we develop should probably just have general access to the current Player object and the object for the monster involved in the fight.
In the next chapter, we’ll start building the actual class based on the information above and more details from the game.