Skip to content

Commit e57ea79

Browse files
committed
Fix: pet kills of unique monsters were missing from the livelog.
Unique-monster death livelogs are written in mondead(), gated on the player_killed/pet_killer statics. Two systemic problems: many pet-kill paths (breath and wand rays, thrown projectiles, beheading, passive counterattacks, petrification, explosions, gas clouds, psychic waves) never called set_pet_killer(), silently dropping the livelog entry, and monkilled() never cleared player_killed afterward, so a later death that reached mondead()/mondied() directly (drowning, traps) could inherit the stale flag and write a spurious "killed <unique>" entry. Rework the flow so mondead() snapshots and clears both statics on entry: attribution is set immediately before a death call and can never leak past it. * src/mon.c: - mondead() consumes player_killed/pet_killer into locals as its first statements; the livelog block reads only the snapshot and its gate becomes (hero kill || pet kill). - monkilled() no longer converts or resets the flags; xkilled() sets player_killed immediately before its death dispatch (it previously set it before an early return that could leak TRUE) and drops its now-redundant tail resets. - monstone() clears attribution at its two early returns that skip mondead() (vampshifter revert, lifesaved). - corpse_chance() credits a swallowed pet gas spore that rips its engulfer open. - m_detach() clears the new exploder global like buzzer/thrower. * src/zap.c: - credit the beam originator (buzzer) at the dobuzz() beam kill, disintegrate_mon(), the resist() m_using kill, and the clay golem death in cancel_monst() - tame dragon breath and pet wand zaps now livelog. - zap_over_floor() poison gas clouds pass an explicit creator; the hero predicate matches the old set_heros_fault() logic so hero credit for spell-explosion and trap-beam clouds is unchanged. * src/muse.c: set buzzer around every mbhit()-based wand zap (not just the buzz-type wands) so striking/cancellation deaths can be attributed; muse_unslime()'s scroll-of-fire explosion and the monster stinking-cloud scroll thread the responsible monster. * src/mthrowu.c: ohitmon() credits the throwing pet at its kill site and around potionhit() (cleared afterward since the potion may not kill). * src/mhitm.c: - credit pets for beheading and 1-in-30 deadly poison kills (both previously bare mondied()), the armor-material sear in missmm(), every passivemm() counterattack kill including the assess_dmg tail, and all four monster-vs-monster petrification sites (monstone() now reaches the livelog with attribution). - the gray dragon-scaled armor passive cancel passed youattack=TRUE in a monster-vs-monster context, handing the hero credit and blame when it killed a clay golem attacker; pass FALSE and credit the wearer instead. - wrap the melee and passive-hide cancellation calls so a tame canceller gets credit for a clay golem death. * src/monmove.c: credit a tame mind flayer's psychic wave kills; mb_trapped() door-trap blasts and hezrou stench clouds thread the responsible monster. * src/eat.c: eat_brains() credits a tame petrifier when the brain eater turns to stone. * src/explode.c, src/decl.c, include/decl.h: new exploder global (same lifecycle as buzzer/thrower, saved and restored around nested calls so chained gas spore blasts keep the right actor); mon_explodes() sets it after its internal mondead() so m_detach() does not clear it; the explode() monkilled branch credits a tame exploder. * src/mcastu.c, src/artifact.c: thread the caster/wielder through the acid blast, fire/ice bolt, and Tempest/Harbinger/Dichotomy surge explosions. * src/region.c, include/region.h, include/extern.h, src/read.c, src/mkmaze.c: create_gas_cloud() takes an explicit creator (&youmonst, a monster, or NULL for environment) and records a creator m_id in NhRegion for pet credit at the gas cloud kill site. The new field is deliberately not saved (both allocators zero the struct), so the save format is unchanged; attribution is dropped across save/reload. * src/spell.c: the spell 'Power Word Kill' used monkilled(), denying XP, conduct effects, and the livelog for a directly targeted hero kill; use xkilled(). Delayed-death attribution (withering, zombie sickness, disease, petrification timers) still only tracks hero-vs-not bits; per-monster source tracking needs new saved fields and is left for a future save-breaking version.
1 parent c5cd679 commit e57ea79

19 files changed

Lines changed: 261 additions & 70 deletions

‎doc/evilhack-changelog.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5537,4 +5537,5 @@ The following changes to date are:
55375537
- Fix: double m_detach when monster-cast dispel evil kills
55385538
- Fix: hidden water lurker reveal said "under the something"
55395539
- Accidental bumps into unseen peacefuls cost luck, not alignment
5540+
- Fix: pet kills of unique monsters were missing from the livelog
55405541

‎include/decl.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ E NEARDATA struct u_realtime urealtime;
308308

309309
E NEARDATA const struct monst zeromonst; /* for init of new or temp monsters */
310310
E NEARDATA struct monst youmonst; /* monster details when hero is poly'd */
311-
E NEARDATA struct monst *mydogs, *migrating_mons, *buzzer, *thrower;
311+
E NEARDATA struct monst *mydogs, *migrating_mons, *buzzer, *thrower,
312+
*exploder;
312313
E NEARDATA struct monst* ukiller;
313314

314315
E NEARDATA struct mvitals {

‎include/extern.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2410,7 +2410,8 @@ E void FDECL(show_region, (NhRegion *, XCHAR_P, XCHAR_P));
24102410
E void FDECL(save_regions, (int, int));
24112411
E void FDECL(rest_regions, (int, BOOLEAN_P));
24122412
E void FDECL(region_stats, (const char *, char *, long *, long *));
2413-
E NhRegion *FDECL(create_gas_cloud, (XCHAR_P, XCHAR_P, int, int));
2413+
E NhRegion *FDECL(create_gas_cloud, (XCHAR_P, XCHAR_P, int, int,
2414+
struct monst *));
24142415
E boolean NDECL(region_danger);
24152416
E void NDECL(region_safety);
24162417

‎include/region.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ typedef struct {
6161
int glyph; /* Which glyph to use if visible */
6262
anything arg; /* Optional user argument (Ex: strength of
6363
force field, damage of a fire zone, ...*/
64+
unsigned int creator_mid; /* m_id of the monster that created this
65+
region (0 = hero or environment), for
66+
pet kill attribution; deliberately NOT
67+
saved/restored (both create_region and
68+
rest_regions zero the struct), so
69+
attribution is dropped across reload */
6470
} NhRegion;
6571

6672
/* Size caps used by rest_regions() to reject corrupt save prefixes */

‎src/artifact.c‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,6 +2201,7 @@ int dieroll; /* needed for Magicbane and vorpal blades */
22012201
char hittee[BUFSZ];
22022202
struct artifact *atmp;
22032203
struct obj *target;
2204+
struct monst *save_exploder;
22042205
int j, k, l, permdmg = 0;
22052206
int hurtle_distance;
22062207
boolean def_underwater;
@@ -2481,11 +2482,14 @@ int dieroll; /* needed for Magicbane and vorpal blades */
24812482
} else {
24822483
pline("A massive surge of energy courses through the %s!",
24832484
simpleonames(otmp));
2485+
save_exploder = exploder;
2486+
exploder = magr;
24842487
explode(youdefend ? u.ux : mdef->mx,
24852488
youdefend ? u.uy : mdef->my,
24862489
(youattack ? ZT_BREATH(ZT_LIGHTNING)
24872490
: -ZT_BREATH(ZT_LIGHTNING)), d(6, 6),
24882491
(youattack ? 0 : MON_CASTBALL), EXPL_SHOCK);
2492+
exploder = save_exploder;
24892493
return TRUE;
24902494
}
24912495
}
@@ -2665,11 +2669,14 @@ int dieroll; /* needed for Magicbane and vorpal blades */
26652669
} else {
26662670
pline("A surge of acid flows through the %s!",
26672671
simpleonames(otmp));
2672+
save_exploder = exploder;
2673+
exploder = magr;
26682674
explode(youdefend ? u.ux : mdef->mx,
26692675
youdefend ? u.uy : mdef->my,
26702676
(youattack ? ZT_BREATH(ZT_ACID)
26712677
: -ZT_BREATH(ZT_ACID)), d(6, 6),
26722678
(youattack ? 0 : MON_CASTBALL), EXPL_ACID);
2679+
exploder = save_exploder;
26732680
return TRUE;
26742681
}
26752682
}
@@ -2982,21 +2989,27 @@ int dieroll; /* needed for Magicbane and vorpal blades */
29822989
} else if (rn2(2)) {
29832990
if (!(resists_fire(mdef) || defended(mdef, AD_FIRE))) {
29842991
pline("A surge of flame flows through the blade!");
2992+
save_exploder = exploder;
2993+
exploder = magr;
29852994
explode(youdefend ? u.ux : mdef->mx,
29862995
youdefend ? u.uy : mdef->my,
29872996
(youattack ? ZT_BREATH(ZT_FIRE)
29882997
: -ZT_BREATH(ZT_FIRE)), d(4, 6),
29892998
(youattack ? 0 : MON_CASTBALL), EXPL_FIERY);
2999+
exploder = save_exploder;
29903000
return TRUE;
29913001
}
29923002
} else {
29933003
if (!(resists_cold(mdef) || defended(mdef, AD_COLD))) {
29943004
pline("A surge of frost flows through the blade!");
3005+
save_exploder = exploder;
3006+
exploder = magr;
29953007
explode(youdefend ? u.ux : mdef->mx,
29963008
youdefend ? u.uy : mdef->my,
29973009
(youattack ? ZT_BREATH(ZT_COLD)
29983010
: -ZT_BREATH(ZT_COLD)), d(4, 6),
29993011
(youattack ? 0 : MON_CASTBALL), EXPL_FROSTY);
3012+
exploder = save_exploder;
30003013
return TRUE;
30013014
}
30023015
}

‎src/decl.c‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,11 @@ NEARDATA struct monst *migrating_mons = (struct monst *) 0;
261261
NEARDATA struct monst *buzzer = (struct monst *) 0;
262262
/* monster throwing/firing/thrusting a weapon at the hero (for thitu()) */
263263
NEARDATA struct monst *thrower = (struct monst *) 0;
264+
/* monster responsible for the current explode() call, for crediting
265+
pet-caused explosion kills; same tight set/clear discipline as
266+
buzzer/thrower, with callers saving and restoring the previous value
267+
so chained explosions (gas spores) cannot inherit the wrong actor */
268+
NEARDATA struct monst *exploder = (struct monst *) 0;
264269
NEARDATA struct autopickup_exception *apelist =
265270
(struct autopickup_exception *)0;
266271

‎src/eat.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,8 @@ int *dmg_p; /* for dishing out extra damage in lieu of Int loss */
641641
mind flayers don't have those capabilities */
642642
if (visflag && canseemon(magr))
643643
pline("%s turns to stone!", Monnam(magr));
644+
if (mdef->mtame)
645+
set_pet_killer(mdef);
644646
monstone(magr);
645647
if (!DEADMONSTER(magr)) {
646648
/* life-saved; don't continue eating the brains */

‎src/explode.c‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,8 @@ int expltype;
542542
left; adtyp itself must NOT be mutated --
543543
later iterations and the post-loop player
544544
block still read it */
545+
if (exploder && exploder->mtame)
546+
set_pet_killer(exploder);
545547
monkilled(mtmp, "",
546548
xkflg ? -AD_RBRE : (int) adtyp);
547549
}
@@ -1005,6 +1007,7 @@ mon_explodes(mon, mattk)
10051007
struct monst *mon;
10061008
struct attack *mattk;
10071009
{
1010+
struct monst *save_exploder;
10081011
int dmg;
10091012
int type;
10101013
if (mattk->damn)
@@ -1036,8 +1039,15 @@ struct attack *mattk;
10361039
Sprintf(killer.name, "%s explosion", s_suffix(mon->data->mname));
10371040
killer.format = KILLED_BY_AN;
10381041

1042+
/* credit the exploding monster for blast kills (set after the
1043+
mondead() above so m_detach() doesn't clear it; a dead pet is
1044+
still valid for the mtame check until dmonsfree()); save and
1045+
restore so a chained explosion cannot clobber an outer one */
1046+
save_exploder = exploder;
1047+
exploder = mon;
10391048
explode(mon->mx, mon->my, type, dmg, MON_EXPLODE,
10401049
adtyp_to_expltype(mattk->adtyp));
1050+
exploder = save_exploder;
10411051

10421052
/* reset killer */
10431053
killer.name[0] = '\0';

‎src/mcastu.c‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,7 @@ struct monst *caster, *target;
10101010
int dmg, spellnum;
10111011
short learned_otyp; /* pre-selected learned spell, or 0 */
10121012
{
1013+
struct monst *save_exploder;
10131014
boolean yours = (caster == &youmonst);
10141015
boolean youdefend = (target == &youmonst);
10151016
boolean seecaster = (yours || canseemon(caster)
@@ -1151,8 +1152,11 @@ short learned_otyp; /* pre-selected learned spell, or 0 */
11511152
if (youdefend) {
11521153
if (seecaster)
11531154
pline("%s douses you in a torrent of acid!", Monnam(caster));
1155+
save_exploder = exploder;
1156+
exploder = caster;
11541157
explode(u.ux, u.uy, ZT_ACID, d((ml / 2) + 4, 8),
11551158
MON_CASTBALL, EXPL_ACID);
1159+
exploder = save_exploder;
11561160
if (how_resistant(ACID_RES) == 100) {
11571161
shieldeff(u.ux, u.uy);
11581162
pline("The acid dissipates harmlessly.");
@@ -1172,9 +1176,12 @@ short learned_otyp; /* pre-selected learned spell, or 0 */
11721176
else if (seecaster)
11731177
pline("%s douses %s in a torrent of acid!",
11741178
Monnam(caster), mon_nam(target));
1179+
save_exploder = exploder;
1180+
exploder = caster;
11751181
explode(target->mx, target->my, ZT_ACID,
11761182
d((ml / 2) + 4, 8),
11771183
yours ? 0 : MON_CASTBALL, EXPL_ACID);
1184+
exploder = save_exploder;
11781185
if (DEADMONSTER(target)) {
11791186
dmg = 0;
11801187
break;
@@ -1457,12 +1464,15 @@ short learned_otyp; /* pre-selected learned spell, or 0 */
14571464
if (seecaster)
14581465
pline("%s blasts you with %s!", Monnam(caster),
14591466
(spellnum == MGC_FIRE_BOLT) ? "fire" : "ice");
1467+
save_exploder = exploder;
1468+
exploder = caster;
14601469
explode(u.ux, u.uy,
14611470
(spellnum == MGC_FIRE_BOLT) ? ZT_FIRE : ZT_COLD,
14621471
resist_reduce(d((ml / 5) + 1, 8),
14631472
(spellnum == MGC_FIRE_BOLT) ? FIRE_RES : COLD_RES),
14641473
MON_CASTBALL,
14651474
(spellnum == MGC_FIRE_BOLT) ? EXPL_FIERY : EXPL_FROSTY);
1475+
exploder = save_exploder;
14661476
if (how_resistant((spellnum == MGC_FIRE_BOLT) ? FIRE_RES : COLD_RES) == 100) {
14671477
shieldeff(u.ux, u.uy);
14681478
monstseesu((spellnum == MGC_FIRE_BOLT) ? M_SEEN_FIRE : M_SEEN_COLD);
@@ -1477,11 +1487,14 @@ short learned_otyp; /* pre-selected learned spell, or 0 */
14771487
else if (seecaster)
14781488
pline("%s blasts %s with %s!", Monnam(caster), mon_nam(target),
14791489
(spellnum == MGC_FIRE_BOLT) ? "fire" : "ice");
1490+
save_exploder = exploder;
1491+
exploder = caster;
14801492
explode(target->mx, target->my,
14811493
(spellnum == MGC_FIRE_BOLT) ? ZT_FIRE : ZT_COLD,
14821494
d((ml / 5) + 1, 8),
14831495
yours ? 0 : MON_CASTBALL,
14841496
(spellnum == MGC_FIRE_BOLT) ? EXPL_FIERY : EXPL_FROSTY);
1497+
exploder = save_exploder;
14851498
if (DEADMONSTER(target)) {
14861499
dmg = 0;
14871500
break;

0 commit comments

Comments
 (0)