Roguelike Game in C++17

Combat.

Time to make player suffer some consequences for their actions. The creatures in our map are too ambivalent to player's aggression. This section is pretty large as lot of different things have to be put into place for combat to actually work. Given the number of things we need to do, lot of the code in this section might be very hacky, that will have to be reworked later.

Goals

Things to accomplish for this section.

  • Giving each entity a turn to think and act.
  • Making it so each entity can move similar to how player can move.
  • Rule set and behaviour states for non player characters. i.e. AI
  • Taking and giving 'damage'.

Creatures take turns

First thing we are going to do is rename entity_type to species. In VSCode you can press F2 on entity_type, type in species into dialog box, and accept changes in the side bar. It will update all references across the whole project.

We need to change how our actions are handled in game_actions[.cpp|.hpp]. At the moment only player is able to have actions. In order for other creatures in the world to move, they need a similar agency. So we'll first split do_action into two functions do_system_action and do_entity_actions.

do_system_action will take care of things not related to gameplay. Like toggling fullscreen state and exiting the game.

do_entity_action will take care of things entities can do. Like move, attack, heal, taunt, etc. For the moment our entities have no way to do these actions, that will come in future. Right now we are just setting things up so they can act.

In main.cpp, we change how we pass along handle_input, and make an explict call to do_system_action. Then we call take_turns.

take_turns will loop through all the entities, and call do_entity_action for each. And for player, it will also pass along the input translated data. If the player doesn't do anything, then the turn hasn't ended, so none of the entities will take their turn.

GitHub Repo

Creature learns to walk

Now immediately after player takes some action, all the entities on the map will take action. But they don't have any actions to take, so nothing interesting happens. To change that we will add some "AI" logic. Not really, but it sounds nice.

In take_turns, for all entities we'll force the default action to be move. Then in do_entity_action, if the entity is a player, then we call player_move_attack, else we call ai_move_attack.

player_move_attack is basically moving all the logic from within action::move switch-case that we had before into it's own function. Nothing to else to change there.

ai_move_attack is very similar to player_move_attack, with few changes. First we call fov::recompute for this entity. So that we know what it can see. If it can see the player then it will try to move in player's direction, else it does nothing. As well as making sure it can't phase through walls and other entities. When it manages to reach the player, it will kick player in the shins.

When moving in player's direction, we ensure it can only move one tile at a time, using unit_offset function. Movement is restricted to cardinal directions. No diagonal moving.

Oh how the tables have turned!!

GitHub Repo

Refactor and reiterate

At this point, I am going to go through the files we have and refactor some bits. For one thing, I am going to update most of the loops to use CppIterTools library. This basically means using iter::enumerate, iter::filter, etc. This greatly simplifies the code, making it easier to understand what each section does. Additionally, I am using the | (pipe) operator, to call iter:: functions.

For example, in game_map.cpp, connect_rooms function gets changed into

This refactor has effectively touched almost every .cpp file we have so far. Since all except input_handler have some loops. The next iteration of C++ (C++20) is supposed to get a ranges library that is conceptually similar, C++20 version is based on Range-V3 library. I suppose we could have just used Range-V3 from vcpkg, to be closer to what standard will have. ¯\_(ツ)_/¯

One advantage of using this library, and in-general the range-concept, is that it helps us remove if <condition> continue/return statements in most places.

For example, in console.cpp, both draw methods can be changed to

Additionally it reduces our code depth. Code-depth being how many {brackets} in we are when look at the code structure. Shallower the depth, easier it is to read, for me. Bracket Colorizer extension of VSCode also helps with this.

There are still, several aspects of the code as it is currently that aren't ideal. But as they say 'perfect is enemy of good', so I am going to leave them be, until they become a hassle. ┬─┬ノ( º _ ºノ)

GitHub Repo

Art of defense and attack

To add the ability to actually attack and reduce the health of various creatures on in the game, we need to add some new structures. vitals structure in game_entity.hpp will help us keep track of a given creature's attack power, defense strength, available hitpoints and maximum hitpoints. For the moment we are not using maximum hitpoints. That will become useful when we add items to augment entity's abilities.

game_entity now has a vitals member, along with string member. string will be used to save the name of entity, this can be player's name or randomly generated creature name.

Hitpoints member of vitals structure is marked as being mutable, so we can modify it, while rest of the members are const. I think, this will be changed in future. Not sure I like this way of marking mutable elements of a structure.

Next we have to change the generate_enemies function, such that it will populate vitals and string members of game_entity type. The values used for populating vitals are randomly generated, so each creature is a bit unique. In future we might choose to add some type specific modifiers, e.g. Ogres are generally stronger or Goblins have better defense etc. For string name variable, i am using the fmtlib's ""_format literal. This is also something that will be part of next iteration of C++ standard.

We also have to change main.cpp so that player entity has appropriate information populated.

game_entity::face method also gets updated, so we now check if this entity is still alive. If they are not alive, the color is changed to red. Similarly, get_entity_at function also checks if game_entity is alive. If it is not then we count it as not being present at that location.

game_actions.cpp there is now a new function do_attack, which takes in two game_entity objects, an attacker and defender. Player and AI move_attack function now simply call this one do_attack function. We also ensure that AI doesn't do any calculations for any entities that are dead.

do_attack function, reduces the hitpoints of the defender entity, by some value based on attacker's power and defender's defense value. Then prints appropriate message.

do_attack gets called by both player_move_attack

and ai_move_attack.

Now running the game, we can see that creatures lose health when being attacked. The rules governing how damage is taken is consistent, with only variable being the starting vitality of creature in question. You can play around with numbers and see how game becomes easier or harder for the player.

Links

GitHub Repo ╠═══
Home ╠═══
╣ Prev: Placing enemies ╠═══
╣ Next: User interface ╠═══

Author: Neel Raiyani [Roy Fokker]

Created: 2020-04-01 Wed 20:46

Validate