iterating gi.invent (github issue #1315)

GitHub issue #1315 points out that it is possible for
a downstream function to change an object's nobj field
to point to a completely different chain.

The cited example by @vultur-cadens was:

     for (obj = gi.invent; obj; obj = obj->nobj)
         if (obj->oclass != COIN_CLASS && !obj->cursed && !rn2(5)) {
             curse(obj);
             ++buc_changed;
         }

    curse() drops the weapon with drop_uswapwep(),
        which calls dropx(),
            which calls dropy(),
                which calls dropz(),
                    which calls place_object().

place_object alters the nobj pointer, to point to the floor chain:
    otmp->nobj = fobj;
    fobj = otmp;

The result was that the next loop iteration was then using floor
objects from the floor chain.

This alters several for-loops to use a more consistent approach,
particularly when the obj is being handed off to a function,
where a downstream function might, or might not, alter the nobj
field.

References:

https://github.com/NetHack/NetHack/issues/1315
https://www.reddit.com/r/nethack/comments/1gkc9ub/even_if_you_drop_an_item_before_drinking_from_the/
This commit is contained in:
nhmall
2024-11-06 16:59:51 -05:00
parent b953625e5b
commit e863583c56
10 changed files with 55 additions and 31 deletions

View File

@@ -1551,7 +1551,7 @@ trapeffect_rust_trap(
struct trap *trap,
unsigned int trflags UNUSED)
{
struct obj *otmp;
struct obj *otmp, *nextobj;
if (mtmp == &gy.youmonst) {
seetrap(trap);
@@ -1583,10 +1583,12 @@ trapeffect_rust_trap(
pline("%s you!", A_gush_of_water_hits);
/* note: exclude primary and secondary weapons from splashing
because cases 1 and 2 target them [via water_damage()] */
for (otmp = gi.invent; otmp; otmp = otmp->nobj)
for (otmp = gi.invent; otmp; otmp = nextobj) {
nextobj = otmp->nobj;
if (otmp->lamplit && otmp != uwep
&& (otmp != uswapwep || !u.twoweap))
(void) splash_lit(otmp);
}
if (uarmc)
(void) water_damage(uarmc, cloak_simple_name(uarmc), TRUE);
else if (uarm)
@@ -4764,13 +4766,14 @@ emergency_disrobe(boolean *lostsome)
int invc = inv_cnt(TRUE);
while (near_capacity() > (Punished ? UNENCUMBERED : SLT_ENCUMBER)) {
struct obj *obj, *otmp = (struct obj *) 0;
struct obj *obj, *nextobj, *otmp = (struct obj *) 0;
int i;
/* Pick a random object */
if (invc > 0) {
i = rn2(invc);
for (obj = gi.invent; obj; obj = obj->nobj) {
for (obj = gi.invent; obj; obj = nextobj) {
nextobj = obj->nobj;
/*
* Undroppables are: body armor, boots, gloves,
* amulets, and rings because of the time and effort
@@ -6589,7 +6592,7 @@ static const char lava_killer[] = "molten lava";
boolean
lava_effects(void)
{
struct obj *obj, *obj2;
struct obj *obj, *obj2, *nextobj;
boolean usurvive, boil_away;
unsigned protect_oid = 0;
int burncount = 0, burnmesgcount = 0;
@@ -6616,7 +6619,8 @@ lava_effects(void)
* emergency save file created before item destruction.
*/
if (!usurvive) {
for (obj = gi.invent; obj; obj = obj->nobj) {
for (obj = gi.invent; obj; obj = nextobj) {
nextobj = obj->nobj;
if (obj->in_use) { /* remove_worn_item() sets in_use */
/* one item can be protected from burning up [accommodates
steal(AMULET_OF_FLYING) -> remove_worn_item() -> fall
@@ -6952,10 +6956,11 @@ trapname(
void
ignite_items(struct obj *objchn)
{
struct obj *obj;
struct obj *obj, *nextobj;
boolean bynexthere = (objchn && objchn->where == OBJ_FLOOR);
for (obj = objchn; obj; obj = bynexthere ? obj->nexthere : obj->nobj) {
for (obj = objchn; obj; obj = bynexthere ? obj->nexthere : nextobj) {
nextobj = obj->nobj;
/* ignitable items like lamps and candles will catch fire */
if (!obj->lamplit && !obj->in_use)
catch_lit(obj);