This is an old revision of the document!


Death

Name dcss:brainstorm:internal: death
Summary How we can improve death effect.
Further informationVarious death effects.
Added by due
Added on 2011-02-04 00:58

The issue: Currently death effects are done on a per-monster-dying per-level basis.

That is, when a monster dies who has a death effect associated with them, their function is immediately called and applied to in-scope monsters. This is problematic: what happens to monsters that are relevant but who are currently out of scope, because they are marshalled on another level?

An example from the game, when you kill Pikel off-level: his slaves on the other level (the one they generated on, for instance) remain hostile when you return. Likewise with Kirke's hogs. To a lesser extent this also applies to the twins, though they currently have a shim in place to prevent them leaving the level.

This is bad and desperately needs fixing.

Overhaul plan

Now that Global Monster Index has been implemented, we should rewrite all of the death effect functions as follows:

  1. Implement a “death monster queue”. This is vector of std::pair containing a monster's ID and the relevant function that should be applied to them, stored as an enum. For instance: queue.push_back(death_pair(hogged_human→mid, DQUEUE_KIRKE_HOGS));
  2. Implement a “death queue”. This is a vector of death_queue_types (ie, DQUEUE_KIRKE_HOGS as described above); when Kirke dies, for instance, you call queue.push_back(DQUEUE_KIRKE_HOGS).
  3. Implement a translation table from death_queue_type (ie DQUEUE_KIRKE_HOGS) into a function that can be called with an mid.
  4. Re-implement all of the death effect functions into stateless functions that take a single monster and apply changes to it, with sanity checking.
  5. Re-implement death effects to use the queue as described in 2.
  6. Re-implement any code that alters a monster or affects a monster which needs to be reversed at a later date to push that monster's mid and the relevant queue type onto the death_monster_queue.

This results in the following behaviour:

Action: Kirke converts something into a hog. Effect: hog→mid, DQUEUE_KIRKE_HOGS is pushed onto death_monster_queue.

Action: Kirke dies, step 1. Effect: DQUEUE_KIRKE_HOGS is pushed onto death_queue.

Action: Kirke dies, step 2. Effect: check_monster_death_queue is called.

Action: You change levels (or more specifically, saved monsters are unmarshalled). Effect: check_monster_death_queue is called.

Action: check_monster_death_queue is called. Effect: Every in-scope monster is compared against the contents of death_monster_queue; for every matching serial number, where the relevant death_queue_type has been pushed onto the death_queue, translate_death_queue is called to find the relevant function; finally, this function is called with that monster, and that monster is popped off the death_monster_queue (never to be seen again!).

The resulting effect is identical to previous times, but is more flexible. For instance using Pikel's slaves, their serial numbers are pushed onto the death_monster_queue as soon as they are generated; whenever Pikel dies off level, the slaves are queued up to be neutralised as soon as they are unmarshalled.

Extension

What about the twins? The function to cause death effects is dependent on knowing how the twin died. This is problematic if doing a stateless system, as the monster on the other level merely knows that they died, rather than how they died.

Proposal 1

Proposal: extend the queue system to use flags rather than just enums.

Therefore, when generated, MONS_SLAVE's serial number and DQUEUE_TYPE_PIKEL is pushed back onto the queue.

When Pikel is neutralised, DQUEUE_TYPE_PIKEL_NEUTRAL is pushed onto the queue.

When checking against each monster, DQUEUE_TYPE_PIKEL is a parent of DQUEUE_TYPE_PIKEL_NEUTRAL, and therefore this is considered a match. Therefore, translate_function could be called with DQUEUE_TYPE_PIKEL_NEUTRAL and return a *different* function than had it been called with DQUEUE_TYPE_PIKEL_KILLED.

Wouldn't it be easier to directly use the already existing monster types and kill types as parameters rather than define new values that combine both? Call a function depending on monster type, that delegates to functions depending on kill type. If the passed kill type doesn't have special handling defined, fall back on “monster was killed” behaviour. — jpeg 2011-02-04 12:23

Proposal 1b

Proposal: as above, but using bit-flags instead of some sort of enum translation.

Therefore, DQUEUE_TYPE_PIKEL = DQUEUE_TYPE_PIKEL_NEUTRAL | DQUEUE_TYPE_PIKEL_KILLEd.

Proposal 1c

Proposal: as above, but the translation function is returned the same instead of different depending on queue type.

Instead, the actual queue type is passed as a parameter to the function.

Proposal 1d

A combination of all of the above: queue translation can either return different functions (all of which take the queue type as a parameter), which allows for code segregation between the different types of death effects (such as elven twin banishment versus neutralisation versus killing the other versus one killing the other while confused, etc) into different functions if necessary, or having them all combined in the one function if this is simpler and better in the long run.

Summary

In summary: the above system should be extremely cheap, regardless of whether or not we decide to go for a flag system or just plain enums. The queue will be short in most instances, perhaps never exceeding more than five or six entries at a time. Likewise, the actual information in the queue will be quite small, the total being something akin to: size of monster id (unsure, believe uint_64t), size of queue type * number of monsters + size of queue type * total number of queues.

Negative things: there will be a small amount of work in writing the code for the queue. There will be more code in adjusting all of the functions to be (relatively) stateless and accept just a single monster, rather than collecting the monsters themselves.

Regardless of the negative impact of these things, I think the positive benefits are enormous. The system will be infinitely scalable and make any further death effect changes easier to write. It may even have further applications that I have not thought of.

Obviously, there may be issues with this system, so I would appreciate suggestions and comments.

dactions

Why won't you use the existing delayed action system? They trigger once for every already existing level, the next time it is loaded.

They don't currently allow arguments, but then, it's not like there's more than one Pikel and more than one Kirke. And if arguments are needed, they could be easily added. — KiloByte 2011-02-04 01:39

  1. I'd never heard of them! Oops? — due 2011-02-05 07:14
Logged in as: Anonymous (VIEWER)
dcss/brainstorm/internal/death.1299405383.txt.gz · Last modified: 2011-03-06 10:56 by Napkin
 
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki