Skip to content

Commit 537e481

Browse files
committed
Fix: polymorphed monsters ignored their terrain until their next move.
A monster polymorphed into a form that cannot survive its location kept standing there until its next action: newcham() never re-checked terrain, and minliquid() only runs at the start of each monster's own move. A flying pit fiend polymorphed into a revenant over open air hung in midair, and a salamander in lava polymorphed into a gnoll witherling did not burn - for slow forms the gap lasted several turns. newcham() now calls minliquid() on the surviving monster just before returning, so an unsuitable new form drowns, burns, or plummets immediately (or teleports clear, for survivors), with the usual kill attribution. The hero's polymon() already works this way via spoteffects(). Shape changes made inside death processing or level restoration go through a new newcham_deferred() wrapper that defers the check to the monster's next move, since running minliquid() there could re-enter monster death handling. Because newcham() can now kill the monster much more often, every caller was audited and the unguarded ones hardened: * mon.c: - newcham() tail-calls minliquid() unless defer_minliquid is set; newcham_deferred() (save/restore, safe under nesting) is used by the mondead() vampshifter/changeling and Izchak reverts, both vamp_stone() reverts, restore_cham(), icequeenrevive(), and become_flayer()'s Izchak revert. - mcalcdistress(): skip the rest of a monster's per-turn upkeep if decide_to_shapeshift() left it dead. - meatmetal()/meatobj()/meatcorpse(): report a death from eating polyfodder through the existing "ptr == NULL means it died" idiom instead of returning "ate and still alive". - become_flayer(): bail out with MM_AGR_DIED if the new mind flayer dies (flight does not protect in the Plane of Water's water). - rescham(): stop processing a chameleon that died reverting. * mhitm.c: AD_DGST digestion transforms now return MM_DEF_DIED | MM_AGR_DIED instead of running grow_up() on a dead eater. * mhitu.c: passiveum() AD_SLIM returns 2 (attacker died) when turning the attacker into a green slime kills it. * muse.c: the wand/potion self-polymorph and polymorph-trap handlers return 1 (died) rather than 2 (did something) when the new form dies, so pet movement does not keep processing the dead monster and re-pocket its dropped inventory (object chain corruption). * trap.c: mintrap() POLY_TRAP_SET sets trapkilled if the new form dies; poly traps can sit on puddles or sewage, which are lethal to iron golem and long worm forms.
1 parent 8d53a86 commit 537e481

6 files changed

Lines changed: 80 additions & 15 deletions

File tree

‎doc/evilhack-changelog.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5542,4 +5542,5 @@ The following changes to date are:
55425542
- Fix: paranoid swim prompt fired while flying via amulet of flying
55435543
- Fix: paying an angry shopkeeper away from his shop hit an impossible
55445544
- Fix: crystal plate mail made its wearer immune to disintegration
5545+
- Fix: polymorphed monsters ignored their terrain until their next move
55455546

‎src/mhitm.c‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3078,6 +3078,10 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */
30783078
} else if (pd == &mons[PM_NURSE]) {
30793079
magr->mhp = magr->mhpmax;
30803080
}
3081+
/* the transform might have killed the eater (new form
3082+
unable to survive water, lava, or open air) */
3083+
if (DEADMONSTER(magr))
3084+
return (MM_DEF_DIED | MM_AGR_DIED);
30813085
}
30823086
/* caveat: above digestion handling doesn't keep `pa' up to date */
30833087

‎src/mhitu.c‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5088,7 +5088,11 @@ struct attack *mattk;
50885088
if (!munslime(mtmp, FALSE) && !DEADMONSTER(mtmp)) {
50895089
if (newcham(mtmp, &mons[PM_GREEN_SLIME], FALSE,
50905090
(boolean) (canseemon(mtmp))))
5091-
mtmp->mstrategy &= ~STRAT_WAITFORU;
5091+
mtmp->mstrategy &= ~STRAT_WAITFORU;
5092+
/* the transform might have killed it (green slime
5093+
unable to survive water, lava, or open air) */
5094+
if (DEADMONSTER(mtmp))
5095+
return 2;
50925096
}
50935097
}
50945098
}

‎src/mon.c‎

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ STATIC_VAR boolean rise_msg, disintegested, player_killed;
2020
#define ACCIDENT_COLLISION 2 /* involuntary knockback collision */
2121
STATIC_VAR int accidental_bump;
2222
STATIC_VAR struct monst *pet_killer; /* pet that killed monster, for livelog */
23+
/* suppress newcham()'s immediate terrain check when a shape change is
24+
part of death processing or level restoration; those monsters get the
25+
regular minliquid() check on their next move instead */
26+
STATIC_VAR boolean defer_minliquid;
2327

2428
/* Set the pet responsible for a kill (call before monkilled) */
2529
void
@@ -39,6 +43,8 @@ STATIC_DCL int FDECL(pickvampshape, (struct monst *));
3943
STATIC_DCL boolean FDECL(isspecmon, (struct monst *));
4044
STATIC_DCL boolean FDECL(validspecmon, (struct monst *, int));
4145
STATIC_DCL struct permonst *FDECL(accept_newcham_form, (struct monst *, int));
46+
STATIC_DCL int FDECL(newcham_deferred,
47+
(struct monst *, struct permonst *, BOOLEAN_P));
4248
STATIC_DCL struct obj *FDECL(make_corpse, (struct monst *, unsigned));
4349
STATIC_DCL short FDECL(m_chooserace, (unsigned long));
4450
STATIC_OVL long FDECL(mm_2way_aggression, (struct monst *, struct monst *));
@@ -364,7 +370,7 @@ struct monst* mdef; /* victim */
364370
pline("But wait! %s transforms into his true form!",
365371
Monnam(mdef));
366372
}
367-
newcham(mdef, &mons[PM_ARCHANGEL], FALSE, FALSE);
373+
newcham_deferred(mdef, &mons[PM_ARCHANGEL], FALSE);
368374
mdef->mcanmove = 1;
369375
mdef->mfrozen = 0;
370376
mdef->mstone = 0;
@@ -405,6 +411,9 @@ struct monst* mdef; /* victim */
405411

406412
/* Transform the larva (aggressor) into a mind flayer */
407413
if (newcham(magr, &mons[PM_MIND_FLAYER], FALSE, FALSE)) {
414+
/* the transform might have killed the new mind flayer */
415+
if (DEADMONSTER(magr))
416+
return MM_AGR_DIED;
408417
/* Give the mind flayer the victim's name */
409418
if (victimname[0] != '\0') {
410419
christen_monst(magr, victimname);
@@ -1504,10 +1513,15 @@ mcalcdistress()
15041513
}
15051514

15061515
/* possibly polymorph shapechangers and lycanthropes */
1507-
if (mtmp->cham >= LOW_PM)
1516+
if (mtmp->cham >= LOW_PM) {
15081517
decide_to_shapeshift(mtmp, (canspotmon(mtmp)
15091518
|| (u.uswallow && mtmp == u.ustuck))
15101519
? SHIFT_MSG : 0);
1520+
/* the shape change might have killed it (new form unable
1521+
to survive water, lava, or open air) */
1522+
if (DEADMONSTER(mtmp))
1523+
continue;
1524+
}
15111525
were_change(mtmp);
15121526

15131527
/* special handling for Izchak */
@@ -1836,7 +1850,8 @@ struct monst *mtmp;
18361850
ptr = mtmp->data;
18371851
if (poly) {
18381852
if (newcham(mtmp, (struct permonst *) 0, FALSE, FALSE))
1839-
ptr = mtmp->data;
1853+
ptr = DEADMONSTER(mtmp) ? (struct permonst *) 0
1854+
: mtmp->data;
18401855
} else if (grow) {
18411856
ptr = grow_up(mtmp, (struct monst *) 0);
18421857
} else if (mstone) {
@@ -1969,7 +1984,8 @@ struct monst *mtmp;
19691984
ptr = mtmp->data;
19701985
if (poly) {
19711986
if (newcham(mtmp, (struct permonst *) 0, FALSE, FALSE))
1972-
ptr = mtmp->data;
1987+
ptr = DEADMONSTER(mtmp) ? (struct permonst *) 0
1988+
: mtmp->data;
19731989
} else if (grow) {
19741990
ptr = grow_up(mtmp, (struct monst *) 0);
19751991
} else if (heal) {
@@ -2163,7 +2179,8 @@ struct monst *mtmp;
21632179
ptr = mtmp->data;
21642180
if (poly) {
21652181
if (newcham(mtmp, (struct permonst *) 0, FALSE, FALSE))
2166-
ptr = mtmp->data;
2182+
ptr = DEADMONSTER(mtmp) ? (struct permonst *) 0
2183+
: mtmp->data;
21672184
} else if (grow) {
21682185
ptr = grow_up(mtmp, (struct monst *) 0);
21692186
} else if (heal) {
@@ -4355,7 +4372,7 @@ struct monst *mtmp;
43554372
mtmp->mstun = 0;
43564373
if (!mtmp->mpeaceful)
43574374
hot_pursuit(mtmp);
4358-
newcham(mtmp, &mons[PM_ARCHANGEL], FALSE, FALSE);
4375+
newcham_deferred(mtmp, &mons[PM_ARCHANGEL], FALSE);
43594376
free_erac(mtmp);
43604377
mtmp->mhp = mtmp->mhpmax = 1500;
43614378
if (mtmp == u.ustuck) {
@@ -4521,7 +4538,7 @@ struct monst *mtmp;
45214538
rloc_to(mtmp, new_xy.x, new_xy.y);
45224539
}
45234540
}
4524-
newcham(mtmp, &mons[mndx], FALSE, FALSE);
4541+
newcham_deferred(mtmp, &mons[mndx], FALSE);
45254542
if (is_changeling(mtmp))
45264543
mtmp->mcan = TRUE;
45274544
if (mtmp->data == &mons[mndx])
@@ -5592,7 +5609,7 @@ struct monst *mtmp;
55925609
pline("%s!", buf);
55935610
display_nhwindow(WIN_MESSAGE, FALSE);
55945611
}
5595-
newcham(mtmp, &mons[mndx], FALSE, FALSE);
5612+
newcham_deferred(mtmp, &mons[mndx], FALSE);
55965613
if (mtmp->data == &mons[mndx])
55975614
mtmp->cham = NON_PM;
55985615
else
@@ -5613,7 +5630,7 @@ struct monst *mtmp;
56135630
if (mtmp->mhpmax <= 0)
56145631
mtmp->mhpmax = 10;
56155632
mtmp->mhp = mtmp->mhpmax;
5616-
(void) newcham(mtmp, &mons[mtmp->cham], FALSE, TRUE);
5633+
(void) newcham_deferred(mtmp, &mons[mtmp->cham], TRUE);
56175634
newsym(mtmp->mx, mtmp->my);
56185635
return FALSE; /* didn't petrify */
56195636
}
@@ -6361,6 +6378,10 @@ rescham()
63616378
if (mcham >= LOW_PM) {
63626379
(void) newcham(mtmp, &mons[mcham], FALSE, FALSE);
63636380
mtmp->cham = NON_PM;
6381+
/* reverting might have killed it (base form unable to
6382+
survive water, lava, or open air) */
6383+
if (DEADMONSTER(mtmp))
6384+
continue;
63646385
}
63656386
if (is_were(mtmp->data) && mtmp->data->mlet != S_HUMAN)
63666387
new_were(mtmp);
@@ -6405,7 +6426,7 @@ struct monst *mon;
64056426
mcham = (int) mon->cham;
64066427
if (mcham >= LOW_PM) {
64076428
mon->cham = NON_PM;
6408-
(void) newcham(mon, &mons[mcham], FALSE, FALSE);
6429+
(void) newcham_deferred(mon, &mons[mcham], FALSE);
64096430
} else if (is_were(mon->data) && !racial_human(mon)) {
64106431
new_were(mon);
64116432
}
@@ -7298,9 +7319,38 @@ boolean msg; /* "The oldmon turns into a newmon!" */
72987319
}
72997320
}
73007321

7322+
/* the new form might not be able to survive its current location
7323+
(non-flyer over water, lava, or open air); apply liquid and
7324+
open-air effects now instead of waiting for the monster's next
7325+
move, unless this shape change is part of death processing or
7326+
level restoration */
7327+
if (!DEADMONSTER(mtmp) && !defer_minliquid)
7328+
(void) minliquid(mtmp);
7329+
73017330
return 1;
73027331
}
73037332

7333+
/* newcham() for shape changes that happen inside death processing
7334+
(vampshifter, changeling, and Izchak reverts) or during level
7335+
restoration; defers the immediate terrain check so those paths
7336+
cannot re-enter monster death handling. save/restore rather than
7337+
set/clear keeps nested shape changes (mselftouch petrification can
7338+
trigger vamp_stone) deferred as well */
7339+
STATIC_OVL int
7340+
newcham_deferred(mtmp, mdat, msg)
7341+
struct monst *mtmp;
7342+
struct permonst *mdat;
7343+
boolean msg;
7344+
{
7345+
boolean save_defer = defer_minliquid;
7346+
int result;
7347+
7348+
defer_minliquid = TRUE;
7349+
result = newcham(mtmp, mdat, FALSE, msg);
7350+
defer_minliquid = save_defer;
7351+
return result;
7352+
}
7353+
73047354
/* sometimes an egg will be special */
73057355
#define BREEDER_EGG (!rn2(77))
73067356

@@ -7815,7 +7865,7 @@ struct monst *mtmp;
78157865

78167866
mvitals[PM_KATHRYN_THE_ICE_QUEEN].died++;
78177867
livelog_printf(LL_UMONST, "defeated %s", livelog_mon_nam(mtmp));
7818-
newcham(mtmp, &mons[PM_KATHRYN_THE_ENCHANTRESS], FALSE, FALSE);
7868+
newcham_deferred(mtmp, &mons[PM_KATHRYN_THE_ENCHANTRESS], FALSE);
78197869

78207870
mtmp->mcanmove = 1;
78217871
mtmp->mfrozen = 0;

‎src/muse.c‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3753,7 +3753,7 @@ struct monst *mtmp;
37533753
(void) newcham(mtmp, muse_newcham_mon(mtmp), TRUE, FALSE);
37543754
if (oseen)
37553755
makeknown(WAN_POLYMORPH);
3756-
return 2;
3756+
return (DEADMONSTER(mtmp)) ? 1 : 2;
37573757
case MUSE_WAN_WISHING:
37583758
/* wear any armor items previously wished for before
37593759
* using another wish */
@@ -3774,7 +3774,7 @@ struct monst *mtmp;
37743774
(void) newcham(mtmp, muse_newcham_mon(mtmp), FALSE, FALSE);
37753775
if (oseen)
37763776
makeknown(POT_POLYMORPH);
3777-
return 2;
3777+
return (DEADMONSTER(mtmp)) ? 1 : 2;
37783778
case MUSE_SCR_REMOVE_CURSE:
37793779
mreadmsg(mtmp, otmp);
37803780
if (canseemon(mtmp)) {
@@ -3816,7 +3816,7 @@ struct monst *mtmp;
38163816
newsym(trapx, trapy);
38173817

38183818
(void) newcham(mtmp, (struct permonst *) 0, FALSE, FALSE);
3819-
return 2;
3819+
return (DEADMONSTER(mtmp)) ? 1 : 2;
38203820
case MUSE_BULLWHIP:
38213821
/* attempt to disarm hero */
38223822
{

‎src/trap.c‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3344,6 +3344,12 @@ struct monst *mtmp;
33443344
/* we're done with mptr but keep it up to date */
33453345
mptr = mtmp->data;
33463346
}
3347+
/* the transform might have killed it (new form unable
3348+
to survive shallow water at the trap's location) */
3349+
if (DEADMONSTER(mtmp)) {
3350+
trapkilled = TRUE;
3351+
break;
3352+
}
33473353
/* Is the monster riding another monster? */
33483354
if (has_erid(mtmp)
33493355
&& (!humanoid(mtmp->data) || bigmonst(mtmp->data))) {

0 commit comments

Comments
 (0)