Rework object deletion

Make object deletion work similarly to monster deletion:
it's marked for deletion (by setting the where-field to OBJ_DELETED
and moved to specific deleted-objects chain), but they're actually
freed at the beginning of turn.

This may need some more tweaking, especially in places that iterate
over object chains, but fuzzing did not find any obvious problems.

Fix a case of accessing freed memory: a monster breathed at hero,
destroying some items.  The code stored the next item in the chain
(a cloak), but a ring of levitation was destroyed, causing hero to
plop down into lava, destroying the cloak.  The item destruction
code then tried to access the destroyed cloak object.
Make the code check the object where-field - which will be different
if the object was marked for deletion.  Also removed an extra loop
going through the whole object chain looking for the items to
destroy.
This commit is contained in:
Pasi Kallinen
2024-05-06 17:41:39 +03:00
parent 312a4bcd26
commit 1a58ed8de9
9 changed files with 56 additions and 13 deletions

View File

@@ -5849,6 +5849,7 @@ destroy_items(
int limit; /* max amount of item stacks destroyed, based on damage */
struct {
unsigned oid;
struct obj *otmp;
boolean deferred;
} items_to_destroy[MAX_ITEMS_DESTROYED];
int elig_stacks = 0; /* number of destroyable objects found so far */
@@ -5856,11 +5857,13 @@ destroy_items(
/* this is a struct obj** because we might destroy the first item in it */
struct obj **objchn = u_carry ? &gi.invent : &mon->minvent;
int dmg_out = 0; /* damage caused by items getting destroyed */
xint8 where = NOBJ_STATES;
/* initialize items_to_destroy */
for (i = 0; i < MAX_ITEMS_DESTROYED; ++i) {
/* 0 should not be a valid o_id for anything */
items_to_destroy[i].oid = 0;
items_to_destroy[i].otmp = (struct obj *) 0;
items_to_destroy[i].deferred = FALSE;
}
@@ -5923,6 +5926,11 @@ destroy_items(
continue;
}
items_to_destroy[i].oid = obj->o_id;
items_to_destroy[i].otmp = obj;
if (where == NOBJ_STATES)
where = obj->where;
else if (where != obj->where)
impossible("destroy_item: items in multiple chains");
/* if loss of this item might dump us onto a trap, hold off
until later because potential recursive destroy_items() will
@@ -5948,15 +5956,13 @@ destroy_items(
/* if we saved some items for later (most likely just a worn ring
of levitation) and they're still in inventory, handle them on the
second iteration of the loop */
struct obj *nobj;
for (obj = *objchn; obj; obj = nobj) {
nobj = obj->nobj;
for (i = 0; i < elig_stacks; ++i) {
if (obj->o_id == items_to_destroy[i].oid
&& (items_to_destroy[i].deferred == (defer == 1))) {
dmg_out += maybe_destroy_item(mon, obj, dmgtyp);
break;
}
for (i = 0; i < elig_stacks; ++i) {
obj = items_to_destroy[i].otmp;
if (obj && obj->o_id == items_to_destroy[i].oid
&& obj->where == where
&& (items_to_destroy[i].deferred == (defer == 1))) {
dmg_out += maybe_destroy_item(mon, obj, dmgtyp);
items_to_destroy[i].otmp = (struct obj *) 0;
}
}
}