Going into stealth before attacking a goblin

"Cascading Feature Creep". Is that a term? It should be. I guess that's how I can summarize the work of these past few weeks. Joking aside, most of the work has been related to stealth, which I feel is important for the game. Not cutting corners there.

Adding stealth gameplay

After working on dynamic light, I felt that the logical connecting work was stealth gameplay. Part of the standard high-fantasy roster of heroes is the thief/rogue/assassin archetype, and you can't do that well without stealth. Since dynamic lighting is in place, the ability to hide in shadows and strike while undetected sounds like a logical step forward, and is a very excusable feature creep.

There was a lot of refactoring that was needed, as I had to scrape and rewrite the way secret doors and objects behaved, how creatures check for visibility of other creature, and also some of the rendering code had to be updated. But now there's common functionality between secret features (doors, traps) and undetected creatures. And a lot more. So how does it all work? With ...

Stealth points

Each creature has a number of maximum (and current) stealth points, based on its level and other attributes/skills. Normally the current stealth points are zero. But we can execute a skill that enables stealth mode, and fills this "bar" of stealth points. And here's where the fun begins.

Every action that a creature takes, causes a "stealth detection event". These events contain information about how much noise was produced, how visible was the action, the involved entity and the location. In the future, things like metal equipment etc would affect these emissions. All nearby entities that have senses (can see, hear) listen to those events and add them to a list. Now when these entities play their turn (let's call them "detectors"), they process these events. Based on the ambient conditions (e.g. light level) and the detector's sensitivity to light/sound, the audio/visual emissions are appropriately scaled. Distance to event is also taken into account. Based on the final scaled emission values and the level of the detector, we figure out how many stealth points will we reduce from the stealthy creature. When stealth points go to zero, the undetected entity becomes revealed.

The undetected entity regenerates a number of stealth points per action, which can depend on various factors (currently it's 10% of the max). So the stealth minigame is to deal with hostile creatures before we get detected. Also, using stealth in groups of creatures is very, very ineffective, as for 5 hostiles we'll get 5 separate stealth point reductions (of varying amount).

If a detector does notice something (so, removes a significant chunk of stealth points) but does not fully reveal the undetected entity, it will still be alerted to it, and in the case of AI, it might go and investigate the location where the event took place.

Inanimate undetected objects (traps, secret doors) differ to creatures only in the fact that the objects do not regenerate stealth. Nearby non-allied detectors slowly deplete their stealth points, and at some point they get revealed.

To implement stealth, a few more concepts were required: allegiance and awareness state.

Allegiance

Allegiance is information related to who's friendly with whom. This is needed so that an ally of an undetected entity will be able to see it and will not reveal it. Or for example, a goblin will not fall on a trap it set. Similarly, if the player sets a trap, the goblin won't be able to see it, but the player will (including a visualisation to show that it's invisible).

Creatures in a level are by default allied to each other, but that can be configured (in the near future, I want to try a scenario with group vs group of monsters and see what the AI does). There are 3 friendliness states: Allied, hostile and neutral.

Awareness state

AI now has an awareness state towards possible dangers. The different states are "neutral", "curious", "alert" and "detected threats". The different states affect the stealth detection sensitivity: it's much harder to hide from an alert creature. The alertness level drops based on the time elapsed since the last detected thread, and it raises when a stealth detection event is processed and the amount of audio/visual emissions are strong enough, but do not fully reveal the undetected entity.

Overlay

Having the awareness state, it's very useful to visualize it. So I modified the shader that displays the health/mana bars and now includes 3 more icons: friendliness to sensor, awareness state, and active buffs/debuffs summary (green arrow for "has buffs", red arrow for "has debuffs" and both arrows for both buffs/debuffs) (shown in the video below). What's currently missing is some stealth bar, but I'll figure something out, possible a horizontal purple bar above the icons.

Misc

  • Screenshake effect, will be used for critical strikes, now I'm just using it when killing a creature while in stealth (as seen in the video above)
  • Enemies don't spawn near biome entries.
  • Can't use stairs or exit to overworld if there are adjacent hostiles
  • When summoned creatures are banished, they spawn a puff of smoke instead of just disappearing, so that it doesn't look like a bug
  • Puff of smoke when entities disappear or appear due to stealth, and while in stealth, allied entities display a visual effect (as seen in the video above)
  • Console command to enable stealth on-demand for any creature/object
Overlay showing hp,mp,friendliness to player, awareness state and buffs/debuffs