02 Aug, 2010, David Haley wrote in the 61st comment:
Votes: 0
ATT_Turan said:
If you were going to do something like this, wouldn't it be possibly easier to change damage() so that it returns a value? It's pretty easy to tell within the scope of the damage function whether you've killed off the character, in which case you tell it to return NULL. That would let you hurt people with the statement
victim=damage(ch, victim,…); at which point you can simply check if (victim) for anything else. You also wouldn't have to worry about changing anything else in your code to worry about dealing with pointers to pointers.

Note that not only victim but also ch (and potentially other people in the room) can die from a call to damage, if there is some kind of counter-damage spell/effect going on.

Runter said:
Never trust david haley when it comes to programming. Question his very experience of the problem with boldness if all else fails.

Moo.
02 Aug, 2010, Runter wrote in the 62nd comment:
Votes: 0
I think ideally you want to be able to validly damage things even once life has left them.

You hit a rat two times. The first kills it. The second mangles its corpse further. Logically there's no reason to throw the brakes between the damage if its an edge case. Ie some residual damage occuring as the rat dies.
02 Aug, 2010, Rarva.Riendf wrote in the 63rd comment:
Votes: 0
Davion said:
If you were going to do something like this, wouldn't it be possibly easier to change damage() so that it returns a value? It's pretty easy to tell within the scope of the damage function whether you've killed off the character, in which case you tell it to return NULL. That would let you hurt people with the statement
victim=damage(ch, victim,…); at which point you can simply check if (victim) for anything else. You also wouldn't have to worry about changing anything else in your code to worry about dealing with pointers to pointers.


That does not work for a simple reason, it could kill ch or the victim or both (think of spell that just return the damage to the caster, or skill like redirecting…I know I have both ;)
What I implemented is that damage returns what happened (DAM_DODGE, DAM_CONTACT (for parrying and such), DAM_CH_DEAD, DAM_VICTIM_DEAD, DAM_BOTH_DEAD
But it is not a complete solution.

Indeed it does not help when you call a method that in the end will call damage, but you do not know it (and as Davion pinpoint, a char is not in POS_DEAD for a long time as he ends up resting in a room after he died.
(I am adding commentary all over my code so I know wich method can kill a char, then see where it is called…it is hell really)
Like hunt_victim (it tracks and eventually multi_hit the target, that may kill him, ot both as a matter of fact).

The more I think of it the more I think the only solution is to add a 'died_in_this_round' flag that will be common to both pc and npc.
And reset this flag when you are in the loop to extract the invalid char (basically the npc that died or have to disappear for any reason)

I cannot see any other solution that works in ANY case and easy enough to check.
02 Aug, 2010, Davion wrote in the 64th comment:
Votes: 0
ATT_Turan said:
Davion said:
Unless you're passing a pointer to pointer to damage(), you're never going to be able to change the value of in scope 'ch' upon death. However, I think this may be one of the better solutions! Pass CHAR_DATA **ch, to damage, if the player dies somewhere in damage, THEN you can set ch to NULL and the value of the ch in the scope could then be checked with "ch == NULL". The reason I say this, instead of using something ON the character itself (eg. ch->valid or ch->pos == POS_DEAD), is that, when extract_char() comes around on a dying character, they aren't recycled and given a new, clean slate. They're just transported to death room with low hp, minus some exp.


If you were going to do something like this, wouldn't it be possibly easier to change damage() so that it returns a value? It's pretty easy to tell within the scope of the damage function whether you've killed off the character, in which case you tell it to return NULL. That would let you hurt people with the statement
victim=damage(ch, victim,…); at which point you can simply check if (victim) for anything else. You also wouldn't have to worry about changing anything else in your code to worry about dealing with pointers to pointers.


IIRC damage() returns a bool depending on if a person is successfully hit or not. So you'd have to change how the function works and find some other way to determine if a players was hit (maybe comparing HP afterwards?). My suggested change would be pretty minimal syntactically, and at compile time it'll show you all the incompatible instances. You'd then just need to check to see if ch is NULL afterwords (assuming you're trying to use ch after being damaged).
02 Aug, 2010, Tyche wrote in the 65th comment:
Votes: 0
I have an unexpected answer. It doesn't work at all. At least just from reading the code in tbaMud 3.5.
In order for the DGScripts trigger HITPRCNT to be called the routine hit() must be called and the only time it will be called
when a spell is cast is if the spell fails with the character being the target of the mob. It doesn't look like spell damage will
ever trigger it, only other fight code like "kill"

/* You throws the dice and you takes your chances.. 101% is total failure */
if (rand_number(0, 101) > GET_SKILL(ch, spellnum)) {
WAIT_STATE(ch, PULSE_VIOLENCE);
if (!tch || !skill_message(0, ch, tch, spellnum))
send_to_char(ch, "You lost your concentration!\r\n");
if (mana > 0)
GET_MANA(ch) = MAX(0, MIN(GET_MAX_MANA(ch), GET_MANA(ch) - (mana / 2)));
if (SINFO.violent && tch && IS_NPC(tch))
hit(tch, ch, TYPE_UNDEFINED);
} else { /* cast spell returns 1 on success; subtract mana & set waitstate */
if (cast_spell(ch, tch, tobj, spellnum)) {
WAIT_STATE(ch, PULSE_VIOLENCE);
if (mana > 0)
GET_MANA(ch) = MAX(0, MIN(GET_MAX_MANA(ch), GET_MANA(ch) - mana));
}
}


It looks like earthquake is the only spell that will run through a room list. The code is pretty much the same as Rom, except Ciicle spells like this won't allow the caster to be damaged ala Chain Lightning.
/* Every spell that affects an area (room) runs through here.  These are
* generally offensive spells. This calls mag_damage to do the actual damage.
* All spells listed here must also have a case in mag_damage() in order for
* them to work. Area spells have limited targets within the room. */
void mag_areas(int level, struct char_data *ch, int spellnum, int savetype)
{
struct char_data *tch, *next_tch;
const char *to_char = NULL, *to_room = NULL;

if (ch == NULL)
return;

/* to add spells just add the message here plus an entry in mag_damage for
* the damaging part of the spell. */
switch (spellnum) {
case SPELL_EARTHQUAKE:
to_char = "You gesture and the earth begins to shake all around you!";
to_room ="$n gracefully gestures and the earth begins to shake violently!";
break;
}

if (to_char != NULL)
act(to_char, FALSE, ch, 0, 0, TO_CHAR);
if (to_room != NULL)
act(to_room, FALSE, ch, 0, 0, TO_ROOM);


for (tch = world[IN_ROOM(ch)].people; tch; tch = next_tch) {
next_tch = tch->next_in_room;

/* The skips: 1: the caster
* 2: immortals
* 3: if no pk on this mud, skips over all players
* 4: pets (charmed NPCs) */
if (tch == ch)
continue;
if (!IS_NPC(tch) && GET_LEVEL(tch) >= LVL_IMMORT)
continue;
if (!CONFIG_PK_ALLOWED && !IS_NPC(ch) && !IS_NPC(tch))
continue;
if (!IS_NPC(ch) && IS_NPC(tch) && AFF_FLAGGED(tch, AFF_CHARM))
continue;

/* Doesn't matter if they die here so we don't check. -gg 6/24/98 */
mag_damage(level, ch, tch, spellnum, 1);
}
}
02 Aug, 2010, ralgith wrote in the 66th comment:
Votes: 0
Rarva.Riendf said:
That means all of your area spell works the same, except for the damages ? Or do I miss something ?.
As en example, how do you code than chain lightning will iterate through all targets, decreasing in damage, then reiteratinf over them till damage potential is depleted, up to hit the own caster itself if there is only one other people in the room ?

Or an earthquake that only hits non flying people ?

Or a breath that will hit harder the direct victim but less the other people , AND have special effects like targeting objects ?

All of this is coded in the skill, and the engine can only help you have valid target, give their rightful position etc, but not much otherwise you quite limitate yourlsef to lookalike spells.

I may miss something as I only know of ROM. But I doubt ANY engine can do the work. Without a lot of limitation that is.


Easier if I just show you I suppose. Though I should note that our current earthquake (keep in mind my MUD is still under development) hits people who fly. This would be an easy fix in the valid targets portion of the mag_areas() function. You'll also see I have a bit more than stock tbaMUD does (I also started with Circle 3.1, not tbaMUD…)

mag_areas()
/*
* Every spell that affects an area (room) runs through here. These are
* generally offensive spells. This calls mag_damage to do the actual
* damage – all spells listed here must also have a case in mag_damage()
* in order for them to work.
*
* area spells have limited targets within the room.
*/
void mag_areas(int level, struct char_data *ch, int spellnum, int savetype)
{
struct char_data *tch, *next_tch;
const char *to_char = NULL, *to_room = NULL;

if (ch == NULL)
return;

/*
* to add spells to this fn, just add the message here plus an entry
* in mag_damage for the damaging part of the spell.
*/
switch (spellnum) {
case SPELL_EARTHQUAKE:
to_char = "You gesture and the earth begins to shake all around you!";
to_room ="$n gracefully gestures and the earth begins to shake violently!";
break;
case SPELL_CALL_LIGHTNING:
to_char = "You raise your hands to the heavens and call down lightning upon your enemies!";
to_room = "$n raises $s hands to the heavns and calls down lightning; it rains down upon the area!";
break;
case SPELL_BLIZZARD:
to_char = "You close your eyes and call out to the northern ice fields, summoning a raging blizzard!";
to_room = "$n closes $s eyes and calls out to the northern ice fields, summoning a raging blizzard!";
break;
}

if (to_char != NULL)
act(to_char, FALSE, ch, 0, 0, TO_CHAR);
if (to_room != NULL)
act(to_room, FALSE, ch, 0, 0, TO_ROOM);


for (tch = world[IN_ROOM(ch)].people; tch; tch = next_tch) {
next_tch = tch->next_in_room;

/*
* The skips: 1: the caster
* 2: immortals
* 3: if no pk on this mud, skips over all players
* 4: pets (charmed NPCs)
* 5: NPC's that aren't charmed will not hit others that aren't
* 6: NPC's that ARE charmed will not hit others that are
* 7: !KILL flagged mobs are skipped… no message to player.
*/

if (tch == ch)
continue;
if (!IS_NPC(tch) && GET_LEVEL(tch) >= LVL_IMMORT && IS_IMMORT(tch) == TRUE)
continue;
if (!CONFIG_PK_ALLOWED && !IS_NPC(ch) && !IS_NPC(tch))
continue;
if (!IS_NPC(ch) && IS_NPC(tch) && AFF_FLAGGED(tch, AFF_CHARM))
continue;
if (IS_NPC(ch) && IS_NPC(tch) && !AFF_FLAGGED(ch, AFF_CHARM) && !AFF_FLAGGED(tch, AFF_CHARM))
continue;
if (IS_NPC(ch) && IS_NPC(tch) && AFF_FLAGGED(ch, AFF_CHARM) && AFF_FLAGGED(tch, AFF_CHARM))
continue;
if (IS_NPC(tch) && MOB_FLAGGED(tch, MOB_NOKILL))
continue;

/* We check for skill improvement on every successfull smack down */
improve_skill(ch, spellnum, TRUE);
/* Doesn't matter if they die here so we don't check. -gg 6/24/98 */
mag_damage(level, ch, tch, spellnum, 1);
}
}


mag_damage()
/*
* Every spell that does damage comes through here. This calculates the
* amount of damage, adds in any modifiers, determines what the saves are,
* tests for save and calls damage().
*
* -1 = dead, otherwise the amount of damage done.
*/
long mag_damage(int level, struct char_data *ch, struct char_data *victim,
int spellnum, int savetype)
{
long dam = 0;
int i;
bool save = mag_savingthrow(victim, savetype, 0);

if (victim == NULL || ch == NULL)
return (0);

switch (spellnum) {
/* Mostly mages */
case SPELL_LIGHTNING_BOLT:
dam = 100;
break;
case SPELL_COLOR_SPRAY:
dam = 250;
break;
case SPELL_FIREBALL:
dam = 500;
break;
case SPELL_DISINTIGRATE:
dam = 1500;
/* If we didn't save & aren't protected, then splat a random number of pieces of equipment that aren't protected from it */
if (affected_by_spell(victim, SPELL_FREE_ACTION))
send_to_char(victim, "Your free action protects you from disintigration!\r\n");
else if (save == FALSE)
destroy_equip(ch, victim, rand_number(1, 5), TRUE, "disintigrates under the intense onslaught!");
break;

/* Area spells */
case SPELL_EARTHQUAKE:
dam = 35 * level;
SET_BIT(TMP_FLAGS(victim), TMP_SPELL_STUN);
WAIT_STATE(victim, PULSE_VIOLENCE * spell_info[spellnum].stun);
break;
case SPELL_CALL_LIGHTNING:
dam = 20 * level;
break;
case SPELL_BLIZZARD:
dam = 35 * level;
SET_BIT(TMP_FLAGS(victim), TMP_SPELL_STUN);
WAIT_STATE(victim, PULSE_VIOLENCE * spell_info[spellnum].stun);
break;

} /* switch(spellnum) */

/* divide damage by two if victim makes his saving throw */
if (save)
dam /= 2;

/* and finally, inflict the damage */
return (damage(ch, victim, dam, spellnum));
}


Tyche said:
quixadhal said:
Tyche said:
So what happens on tbaMUD when one casts an area spell that loops through the room damaging characters (like a chain lightning),
and a HITPRCNT trigger is run off a damaged mob that %teleports% the caster out of the room?
The *smart* way to do that kind of thing would be to decide if you want spells like this to be channeled, or fire-and-forget.
Nobody seems to know what happens. I'm betting the mud crashes.


The MUD does not crash. You move to the room but your spell continues to affect your original room. End of Story.

Tyche said:
I have an unexpected answer. It doesn't work at all. At least just from reading the code in tbaMud 3.5.
In order for the DGScripts trigger HITPRCNT to be called the routine hit() must be called and the only time it will be called
when a spell is cast is if the spell fails with the character being the target of the mob. It doesn't look like spell damage will
ever trigger it, only other fight code like "kill"


Correct, in the stock code. This is an oversight, the call to the trigger should be in the damage() function in fight.c, not in the hit() function of fight.c. Or perhaps it was intentional to avoid an issue. However, if it WAS an issue, for example if you moved the trigger call into the damage() function, you would continue to cast upon your original room. Look at the mag_areas and mag_damage code I pasted above. The loop is already chosen, and won't interrupt for anything unless you code it to do so.

—–

Now, another option with CircleMUD/tbaMUD is to use a "MANUAL SPELL" which doesn't use these pre-generated routines. But those are crap for most things, the exceptions being spells like identify and create water where you have a pressing need to seriously break out of the routines and do something totally different.
02 Aug, 2010, Rarva.Riendf wrote in the 67th comment:
Votes: 0
@ralgith: no offense but your spells are very basics, and that is why it works. Would you implement anything into the damage method or one that is called by it that could effect others player in the room before you iterated over them and you would enter a world of hurt ;p (think of a char that 'explode' when killed, and damage everyone in the room, or even kill them triggering a chain reaction).
I doubt your engine handle that gracefully, because of the loop over people in the room.
02 Aug, 2010, Sharmair wrote in the 68th comment:
Votes: 0
ralgith said:
Tyche said:
quixadhal said:
Tyche said:
So what happens on tbaMUD when one casts an area spell that loops through the room damaging characters (like a chain lightning),
and a HITPRCNT trigger is run off a damaged mob that %teleports% the caster out of the room?
The *smart* way to do that kind of thing would be to decide if you want spells like this to be channeled, or fire-and-forget.
Nobody seems to know what happens. I'm betting the mud crashes.


The MUD does not crash. You move to the room but your spell continues to affect your original room. End of Story.


True, it is rather unlikely to crash, though it may or may not continue to run in the room it started in.

ralgith said:
…you would continue to cast upon your original room. Look at the mag_areas and mag_damage code I pasted above. The loop is already chosen, and won't interrupt for anything unless you code it to do so.


Maybe… lets look at this a bit closer. Lets say the caster is right after the mob with the script to
transport the caster. At the start of the loop iteration, tch is the mob with the script and the first
line of the loop makes next_tch the caster. When the script transports the caster, the caster is
moved and the next_in_room pointer of the caster is updated to reflect the new room. The transport
code should also update the next_in_room pointer of the mob to what the caster's next_in_room
was before the transport, but the loop is already locked in to use the caster as the next target.
So, if the code works like circle at least did in the past and insert to the head of the list, the loop
will jump lists and continue in the new room.

ralgith said:
Now, another option with CircleMUD/tbaMUD is to use a "MANUAL SPELL" which doesn't use these pre-generated routines. But those are crap for most things, the exceptions being spells like identify and create water where you have a pressing need to seriously break out of the routines and do something totally different.

So, you're saying as long as you just use cookie cutter spells that follow an established pattern you are ok?
Well to each their own I suppose…
03 Aug, 2010, ATT_Turan wrote in the 69th comment:
Votes: 0
David Haley said:
Note that not only victim but also ch (and potentially other people in the room) can die from a call to damage, if there is some kind of counter-damage spell/effect going on.

Yes, but that would happen from nested damage calls, so the NULLification should recurse its way back up (unless you're doing counter-damage via a function other than damage which, well, you shouldn't in this case :wink: )

Davion said:
IIRC damage() returns a bool depending on if a person is successfully hit or not. So you'd have to change how the function works and find some other way to determine if a players was hit (maybe comparing HP afterwards?). My suggested change would be pretty minimal syntactically, and at compile time it'll show you all the incompatible instances. You'd then just need to check to see if ch is NULL afterwords (assuming you're trying to use ch after being damaged).


You do not recall correctly (or it was completely rewritten between baser RoM and the codebase I'm using). Damage is void, it's only called at the point where you know the person is being hurt. The actual hit calculation is done in one_hit.
03 Aug, 2010, ralgith wrote in the 70th comment:
Votes: 0
Rarva.Riendf said:
@ralgith: no offense but your spells are very basics, and that is why it works.


Right, which is why Circle/tba uses delayed extraction. In the damage function is a check: if someone is already dead, but not extracted, they get skipped.

Quote
Would you implement anything into the damage method or one that is called by it that could effect others player in the room before you iterated over them and you would enter a world of hurt ;p (think of a char that 'explode' when killed, and damage everyone in the room, or even kill them triggering a chain reaction).
I doubt your engine handle that gracefully, because of the loop over people in the room.


Again, thats why the option is there to use manual spells as well. In my MUD there are currently 12 Manual Spells defined. And combined with the delayed extraction it isn't an issue. I can do pretty much whatever my imagination desires with spells. Even within the constraints of the engine I can do a lot.



And Sharmair is correct, I hadn't considered the case of if the caster was teleported before their position in the list was reached. In which case then yes, it WOULD jump rooms.



ATT_Turan said:
You do not recall correctly (or it was completely rewritten between baser RoM and the codebase I'm using). Damage is void, it's only called at the point where you know the person is being hurt. The actual hit calculation is done in one_hit.


God I so prefer circle's method ;)
In stock circle/tba code damage returns an int. I expanded it to a long because I definitely break the 16 bit integer so I used long for safety.
03 Aug, 2010, Davion wrote in the 71st comment:
Votes: 0
ATT_Turan said:
You do not recall correctly (or it was completely rewritten between baser RoM and the codebase I'm using). Damage is void, it's only called at the point where you know the person is being hurt. The actual hit calculation is done in one_hit.



Just checked out ROM and I guess you have rewritten yours. Stock ROM's damage function returns a bool for sure! This problem is quite perplexing. I'm not even so sure adding another layer of pointers JUST over damage() will do it. You'd likely have to do it for all the functions the damage call is nested it (assuming the root function will modify a player after being damaged).

There's a crazy bug involved in this should you cast chain lightning in the death room (the place where people go when they die). It can produce up to 10000 corpses of Antharin if you use the wand of MORE BEES! ;)
03 Aug, 2010, Rarva.Riendf wrote in the 72nd comment:
Votes: 0
Quote
I'm not even so sure adding another layer of pointers JUST over damage() will do it. You'd likely have to do it for all the functions the damage call is nested it (assuming the root function will modify a player after being damaged).

Exactly that is why using the pointer only will not work. Way too many methods can call damage in ROM.
The circleMUD solution of delayed extraction is the right one.

But while Ralgith MUD only test for people being dead in damage (I think it is too late), for a real robust engine, you basically have to test for this case after every method that could call damage at some point, as you could be dead after movingg or getting an object…basically every method could kill you.
Since char and mobs are not treated the same by deaths, you have to add something common to both (a mob will become invalid, a char will be in the temple, resting, with 1hp naked etc, but this could happen to a 'not dead' char as well).

I am thinking of a deadThisRound flag, but I am not that it would actually works yet..
I already found a flaw I can fix by reseting this flag to false after each 'update' method there is in update_handler, instead of only waiting for the last method where you extract the 'invalid' mobs, but I have a feeling there is soemthing else.
03 Aug, 2010, David Haley wrote in the 73rd comment:
Votes: 0
ATT_Turan said:
Yes, but that would happen from nested damage calls, so the NULLification should recurse its way back up (unless you're doing counter-damage via a function other than damage which, well, you shouldn't in this case :wink: )

How do you tell apart the situations where ch died vs. when victim died? What about if both die?

victim = damage(ch, victim, …)
// ch is dead now, how do we tell?
03 Aug, 2010, KaVir wrote in the 74th comment:
Votes: 0
Rarva.Riendf said:
But while Ralgith MUD only test for people being dead in damage (I think it is too late), for a real robust engine, you basically have to test for this case after every method that could call damage at some point, as you could be dead after movingg or getting an object…

Or you could discard the concept of instantly destroying the mob and replacing it with a corpse "object", and instead simply use a flag to indicate if a creature is dead or not.
03 Aug, 2010, Rarva.Riendf wrote in the 75th comment:
Votes: 0
Quote
Or you could discard the concept of instantly destroying the mob and replacing it with a corpse "object", and instead simply use a flag to indicate if a creature is dead or not.

The 'flag' for mobs already exist it is the 'valid' boolean
But a dead player is still valid.
Players are the real problems, mobs are easy to deal with. Mob is dead; he is set invalid, corpse is left.
Player dead -> he ends up in recal, but that could happen in any of the update loops, whatever the command he typed etc.
And the death status of a player really does not last long (basically he is only in POS_DEAD after the damage is inflicted, but before you even return from the damage method, it will have a corpse, and be resting in the repop area at 1hp 1mana etc) But you cannot take that as a dead status cause a player that died a little before could very well be like this for a long time if he does not move, and cannot regenerate for any reason (vampire, too hungry or thirsty etc).

So you need something to identify for sure he is dead AND reset it as soon as it is safe to reset. (I am thinking that should be after any update loop)

So you have only one thing to test for for both mobs and char and that would be my boolean
if (ch->deadThisRound)
continue; (or return…depends on zhere you test)

I don't know why but I have a feeling there is a problem with that as well….
03 Aug, 2010, Oliver wrote in the 76th comment:
Votes: 0
… wouldn't it work if, instead of extracting characters immediately, you just change their position to POS_DEAD; then, make a function called update_death(). Iterate through all characters every pulse, extract the dead ones.

Wouldn't that solve the problem pretty fluently?
03 Aug, 2010, Rarva.Riendf wrote in the 77th comment:
Votes: 0
Oliver said:
… wouldn't it work if, instead of extracting characters immediately, you just change their position to POS_DEAD; then, make a function called update_death(). Iterate through all characters every pulse, extract the dead ones.

Wouldn't that solve the problem pretty fluently?

Humm you should read from the beginning.
but in short:
1-chars are not extracted anyway upon death, only mobs are.
2-their position do not stay POS_DEAD very long, position is not something you can use for that.

That is why I am thinking of another flag (or integrate this in affect, whatever, but something in common to both chars and mobs that can be tested in CHAR_DATA that indicate you should not use the char.

Question is, do anyone see anything that could backfire in a solution like this ?
03 Aug, 2010, David Haley wrote in the 78th comment:
Votes: 0
That's basically what SMAUG does, yes, and is more or less what everybody has been referring to with the term delayed extraction.
03 Aug, 2010, Rarva.Riendf wrote in the 79th comment:
Votes: 0
David Haley said:
That's basically what SMAUG does, yes, and is more or less what everybody has been referring to with the term delayed extraction.


Humm why did you not say it first !
Because everybody was talking about circleMUD with this delayed extraction but every snippet that was given to me had the same exact flaw.

So question is, does it works perfectly with SMAUG ? (if so, I will implement it ASAP heh)
And the only thing to do to fix ROM engine would be this.
03 Aug, 2010, David Haley wrote in the 80th comment:
Votes: 0
I thought I said earlier that delayed extraction was a fairly common solution to this, but it looks like I never said explicitly that SMAUG uses it, sorry. :wink:

I don't know if I'd say it works perfectly because SMAUG has a number of bugs of its own, but it does basically solve this problem. IIRC, SMAUG keeps a list of characters that have died this round, so that you can tell if a player character died (because as you have said, they don't get extracted now or later, at least until they quit). I haven't looked at it in a while, but you can probably just grab a copy of SmaugFUSS and check out how it handles this.
60.0/108