In the Stendhal world, there are several types of active entities, such as players, creatures and NPCs.
All those entities have some properties and program code in common, e. g. they may exists at a distinct place in the world. But some properties are specific to each type of entity. For example players are controlled by humans and NPC may chat.
Okay, with this background knowledge, let's go back several years in Stendhal history, to version 0.42:
A new feature was added allowing players to heal themselves by eating and to get poisoned by venom creatures. This feature was implemented in the logic method of players.
So, every turn the player object checks, whether it is poisoned. In this case, it checks, whether it was time to deduct some hp in this turn.
We like to add huge features in small steps, so it made sense to restrict poison to player initially. Furthermore, the poison effect uses multiple variables and adding them to every creature would increase the required memory significantly.
Stendhal is a turn based game. This may be a surprise to you because the turns are really fast, just 0.3 seconds. In each turn, each entity is allowed to execute some program logic.
But with the rapidly growing world, more and more creatures and other entities are added. As a result, the 0.3 seconds turn time was getting too short to let all entities execute some logic.
In many cases the entities just checked whether it was time to do something, but found that they had nothing to do just yet. For example a fighting creature is not hitting its enemy in every turn, but only once in a while.
The solution are Turn notifiers. So instead of checking every turn, whether there was something to do, an entity can say: "Call me again in 3 turns". That's when I am going to hit again, for example.
We keep those requests in a list indexed by turn number, so this is a huge performance improvement.
With turn notifiers in place, we put creatures into hibernation when no player or pet is near. It does not make sense to let them walk around and consume processor time when nobody sees that anyway.
Turn notifiers have another nice feature: They are only created on demand and they may contain variables. So adding healing-abilities to certain creatures is very easy now and uses very little additional memory.
We wanted to add more status effect, most notable confusion and shock, and we have more ideas for future extensions. Those status effects should work on both players and creatures, and perhaps even NPCs for some nice quests.
But to recap the situation: There is old program code in the logic-method of players to handle healing and being poisoned. And there is distinct implementation based on TurnNotifiers to heal creatures.
Continuing this path would lead to a total mess with lots of duplicated code. So we need to do some clean up first.
We started by implementing an infrastructure to handle any kind of status effect. This code is applicable to all types of active entities, although it is based on the TurnNotifier approach used to heal creatures.
Then we rewrote the existing code for healing creatures to use this new infrastructure. We did the same with the existing code to heal players and handle poison. Implementing shock and confusion as new status effects was a piece of cake from here.
Okay, at this point we need to have a look at how death happens in Stendhal: When hp is equal to or less than 0, a creature or player dies.
The active entity is removed from the world and a corpse is created and filled with items. In the case of players, the items are taken from the actual player object and moved to the corpse. In the case of creatures, however, the items are created at this moment. Again, this is a way to save memory because living creatures don't have to store a list of items.
When an active entity is removed from the world, its enemies cannot target it any longer. So the dead entity is not hit again.
But the TurnNotifier, which handles poison, is still active. And it has a direct references to the entity, it belongs to. Unlike the target, the reference is not lost on removal of the entity from the world. Using this references, the TurnNotifier removes some hp.
Oh wait, the hp is below 0 now, so the entity is dead. Let's remove it from the world and add a corpse instead. The poison effect is stackable, so the entity may die again and again.
When we implemented the new system to handle status effects, we tested it extensively. This raises the questions: Why did this problem not occur during our tests?
It turns out that players are not effected. There was some left over code from the original implementation in the player entity type which removed the poison effect on death of a player. This code does neither exist in the the creature implementation nor the shared active entity code block.
So the bug only effects dead creatures which got poisoned. But players cannot poison creatures at this time, although there are some rough idea about adding poisonous arrows in the future.
Do you remember summon scrolls? Haizen teases you with a scroll for a giant red dragon, but you are only able to summon very weak creatures. Nice for a party gag, but no real help in a huge battle. Weak creatures such as rats, snakes,... Snakes! They are poisonous. And with the status effect changes in place, they can poison creatures now.
**Boom**, lots of corpse from a single creature.
We fixed the issue in Stendhal 1.15.1 by clearing all status effects on death. And we added some safe guards to ensure that a dead entity may not die again.