From 0e1f1c653b43e91c66dd4a7e0ec25d835bc4a794 Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 10 Dec 2022 17:48:55 -0800 Subject: [PATCH] fix #K3802 - sanity_check: boulder not on top This should fix the problem of polymorphing or stone-to-fleshing a pile of multiple boulders and having some underneath ones which get changed resist and not get changed, producing a pile with one or more non-boulders above one or more boulders. If that situation arises, re-stack the pile so that boulders are moved to the top. This also revises zapping up or down while hiding under something (if that is even possible; the types of creatures which can hide under things can't zap wands or cast spells; maybe there are some exceptions?). Zapping up used to hit only the top item, but zapping down hit the whole stack. Now up still hits only the top, but down skips the top and hits the rest. Caveat: not adquately tested. --- include/extern.h | 1 + src/mkobj.c | 23 +++++++++++++++++++ src/zap.c | 58 +++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/include/extern.h b/include/extern.h index 571f8386e..7b10a786b 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1481,6 +1481,7 @@ extern void set_bknown(struct obj *, unsigned); extern boolean is_flammable(struct obj *); extern boolean is_rottable(struct obj *); extern void place_object(struct obj *, coordxy, coordxy); +extern void recreate_pile_at(coordxy, coordxy); extern void remove_object(struct obj *); extern void discard_minvent(struct monst *, boolean); extern void obj_extract_self(struct obj *); diff --git a/src/mkobj.c b/src/mkobj.c index 03211c354..fc8682518 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -2159,6 +2159,29 @@ place_object(struct obj *otmp, coordxy x, coordxy y) obj_timer_checks(otmp, x, y, 0); } +/* tear down the object pile at and create it again, so that any + boulders which are present get forced to the top */ +void +recreate_pile_at(coordxy x, coordxy y) +{ + struct obj *otmp, *next_obj, *reversed = 0; + + /* remove all objects at , saving a reversed temporary list */ + for (otmp = gl.level.objects[x][y]; otmp; otmp = next_obj) { + next_obj = otmp->nexthere; + remove_object(otmp); /* obj_extract_self() for floor */ + otmp->nobj = reversed; + reversed = otmp; + } + /* pile at is now empty; create new one, re-reversing to restore + original order; place_object() handles making boulders be on top */ + for (otmp = reversed; otmp; otmp = next_obj) { + next_obj = otmp->nobj; + otmp->nobj = 0; /* obj->where is OBJ_FREE */ + place_object(otmp, x, y); + } +} + #define ROT_ICE_ADJUSTMENT 2 /* rotting on ice takes 2 times as long */ /* If ice was affecting any objects correct that now diff --git a/src/zap.c b/src/zap.c index 1742385b5..b77e6a024 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1853,12 +1853,12 @@ poly_obj(struct obj *obj, int id) static int stone_to_flesh_obj(struct obj *obj) { - int res = 1; /* affected object by default */ struct permonst *ptr; struct monst *mon, *shkp; struct obj *item; coordxy oox, ooy; boolean smell = FALSE, golem_xform = FALSE; + int res = 1; /* affected object by default */ if (objects[obj->otyp].oc_material != MINERAL && objects[obj->otyp].oc_material != GEMSTONE) @@ -2263,16 +2263,26 @@ bhito(struct obj *obj, struct obj *otmp) /* returns nonzero if something was hit */ int bhitpile( - struct obj *obj, /* wand or fake spellbook for type of zap */ + struct obj *obj, /* wand or fake spellbook for type of zap */ int (*fhito)(OBJ_P, OBJ_P), /* callback for each object being hit */ - coordxy tx, coordxy ty, /* target location */ - schar zz) /* direction for up/down zaps */ + coordxy tx, coordxy ty, /* target location */ + schar zz) /* direction for up/down zaps */ { - int hitanything = 0; register struct obj *otmp, *next_obj; + boolean hidingunder, first; + int prevotyp, hitanything = 0; + + if (!gl.level.objects[tx][ty]) + return 0; + + /* if hiding underneath an object and zapping up or down, the top item + is either the only thing hit (up) or is skipped (down) */ + hidingunder = (zz != 0 && u.uundetected && hides_under(gy.youmonst.data)); + first = TRUE; if (obj->otyp == SPE_FORCE_BOLT || obj->otyp == WAN_STRIKING) { struct trap *t = t_at(tx, ty); + struct obj *topofpile = gl.level.objects[tx][ty]; /* We can't settle for the default calling sequence of bhito(otmp) -> break_statue(otmp) -> activate_statue_trap(ox,oy) @@ -2282,21 +2292,49 @@ bhitpile( if (t && t->ttyp == STATUE_TRAP && activate_statue_trap(t, tx, ty, TRUE)) learnwand(obj); + /* assume zapping up or down while hiding under the top item can + still activate the trap even if it's below (when zapping up) + or above (when zapping down) */ + if (gl.level.objects[tx][ty] != topofpile) + first = FALSE; /* top item was statue which activated */ } gp.poly_zapped = -1; for (otmp = gl.level.objects[tx][ty]; otmp; otmp = next_obj) { next_obj = otmp->nexthere; - /* for zap downwards, don't hit object poly'd hero is hiding under */ - if (zz > 0 && u.uundetected && otmp == gl.level.objects[u.ux][u.uy] - && hides_under(gy.youmonst.data)) - continue; - + if (hidingunder) { + if (first) { + first = FALSE; /* reset for next item */ + if (zz < 0) /* down when hiding-under skips first item */ + continue; + } else { + /* !first */ + if (zz > 0) /* up when hiding-under skips rest of pile */ + continue; + } + } hitanything += (*fhito)(otmp, obj); } + if (gp.poly_zapped >= 0) create_polymon(gl.level.objects[tx][ty], gp.poly_zapped); + /* when boulders are present they're expected to be on top; with + multiple boulders it's possible for some to have been changed into + non-boulders (polymorph, stone-to-flesh) while ones beneath resist, + so re-stack pile if there are any non-boulders above boulders */ + prevotyp = BOULDER; + for (otmp = gl.level.objects[tx][ty]; otmp; otmp = otmp->nexthere) { + if (otmp->otyp == BOULDER && prevotyp != BOULDER) { + recreate_pile_at(tx, ty); + break; + } + prevotyp = otmp->otyp; + } + + if (hidingunder) /* pile might have been destroyed or dispersed */ + maybe_unhide_at(tx, ty); + return hitanything; }