Complete roguelike tutorial using C++ and libtcod - extra 3: scent tracking

From RogueBasin
Revision as of 12:59, 23 October 2015 by Joel Pera (talk | contribs) (pasted →‎Tiles that smell)
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.


This article is an optional "extra" that will bring scent tracking to the monster Ai. In can be applied on the article 6 source code.

Even with the wall sliding trick, the monsters are still quite dumb. We'll implement scent tracking so that they can track the player even when they don't see him.

Tiles that smell

The first thing to do is to add a scent level to the tile struct, in Map.hpp :

struct Tile {
   bool explored; // has the player already seen this tile ?
   unsigned int scent; // amount of player scent on this cell
   Tile() : explored(false),scent(0) {}
};

We also add a currentSmellValue to the Map and a helper to get the scent level of a cell :

unsigned int currentScentValue;
unsigned int getScent(int x, int y) const;

The base idea is that every time the player moves, currentScentValue is increased and the scent is updated in every cell in the field of view with the value currentScentValue - distance to player. The monster will track the player based on their surrounding cell scent. The closer a cell's scent is to currentScentValue the higher the scent is for the monster. This way, the closer you are to the player, the higher is the scent. And scent is decreased every new turn in a cell that is not in the player field of view without having to update the scent values in the whole map.

So the first thing to do is to increase the current scent value every new turn, in Engine::update :

if ( gameStatus == NEW_TURN ) {
   map->currentScentValue++;

The Map::getScent method is simple :

unsigned int Map::getScent(int x, int y) const {
   return tiles[x+y*width].scent;
}

The core of the scent field computation is in Map::computeFov :

void Map::computeFov() {
   map->computeFov(engine.player->x,engine.player->y,
       engine.fovRadius);
   // update scent field
   for (int x=0; x < width; x++) {
       for (int y=0; y < height; y++) {
           if (isInFov(x,y)) {
               unsigned int oldScent=getScent(x,y);
               int dx=x-engine.player->x;
               int dy=y-engine.player->y;
               long distance=(int)sqrt(dx*dx+dy*dy);
               unsigned int newScent=currentScentValue-distance;
               if (newScent > oldScent) {
                   tiles[x+y*width].scent = newScent;
               }
           }
       }
   }
}