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

@@ -315,18 +315,20 @@ drinkfountain(void)
dowaterdemon();
break;
case 24: { /* Maybe curse some items */
struct obj *obj;
struct obj *obj, *nextobj;
int buc_changed = 0;
pline("This water's no good!");
morehungry(rn1(20, 11));
exercise(A_CON, FALSE);
/* this is more severe than rndcurse() */
for (obj = gi.invent; obj; obj = obj->nobj)
for (obj = gi.invent; obj; obj = nextobj) {
nextobj = obj->nobj;
if (obj->oclass != COIN_CLASS && !obj->cursed && !rn2(5)) {
curse(obj);
++buc_changed;
}
}
if (buc_changed)
update_inventory();
break;
@@ -498,13 +500,14 @@ dipfountain(struct obj *obj)
pline("An urge to take a bath overwhelms you.");
{
long money = money_cnt(gi.invent);
struct obj *otmp;
struct obj *otmp, *nextobj;
if (money > 10) {
/* Amount to lose. Might get rounded up as fountains don't
* pay change... */
money = somegold(money) / 10;
for (otmp = gi.invent; otmp && money > 0; otmp = otmp->nobj)
for (otmp = gi.invent; otmp && money > 0; otmp = nextobj) {
nextobj = otmp->nobj;
if (otmp->oclass == COIN_CLASS) {
int denomination = objects[otmp->otyp].oc_cost;
long coin_loss =
@@ -515,6 +518,7 @@ dipfountain(struct obj *obj)
if (!otmp->quan)
delobj(otmp);
}
}
You("lost some of your gold in the fountain!");
CLEAR_FOUNTAIN_LOOTED(u.ux, u.uy);
exercise(A_WIS, FALSE);