recursive destroy_item()
Make the sequence: be zapped by lightning, have worn ring of levitation be destroyed, fall onto fire trap work better. The fire trap handling will mark everything in inventory as already processed; anything vulnerable to lightning past the destroyed ring would not be checked. So delay destroying such a ring until after all of inventory has been subjected to lightning.
This commit is contained in:
369
src/zap.c
369
src/zap.c
@@ -1,4 +1,4 @@
|
||||
/* NetHack 3.6 zap.c $NHDT-Date: 1544146046 2018/12/07 01:27:26 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.300 $ */
|
||||
/* NetHack 3.6 zap.c $NHDT-Date: 1544230271 2018/12/08 00:51:11 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.301 $ */
|
||||
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
||||
/*-Copyright (c) Robert Patrick Rankin, 2013. */
|
||||
/* NetHack may be freely redistributed. See license for details. */
|
||||
@@ -28,11 +28,12 @@ STATIC_DCL void FDECL(zhitu, (int, int, const char *, XCHAR_P, XCHAR_P));
|
||||
STATIC_DCL void FDECL(revive_egg, (struct obj *));
|
||||
STATIC_DCL boolean FDECL(zap_steed, (struct obj *));
|
||||
STATIC_DCL void FDECL(skiprange, (int, int *, int *));
|
||||
|
||||
STATIC_DCL int FDECL(zap_hit, (int, int));
|
||||
STATIC_OVL void FDECL(disintegrate_mon, (struct monst *, int, const char *));
|
||||
STATIC_DCL void FDECL(backfire, (struct obj *));
|
||||
STATIC_DCL int FDECL(spell_hit_bonus, (int));
|
||||
STATIC_DCL void FDECL(destroy_one_item, (struct obj *, int, int));
|
||||
STATIC_DCL void FDECL(wishcmdassist, (int));
|
||||
|
||||
#define ZT_MAGIC_MISSILE (AD_MAGM - 1)
|
||||
#define ZT_FIRE (AD_FIRE - 1)
|
||||
@@ -1846,6 +1847,10 @@ struct obj *obj, *otmp;
|
||||
* menu_drop(), askchain() - inventory traversal where multiple
|
||||
* Drop can alter the invent chain while traversal
|
||||
* is in progress (bhito isn't involved).
|
||||
* destroy_item(), destroy_mitem() - inventory traversal where
|
||||
* item destruction can trigger drop or destruction of
|
||||
* other item(s) and alter the invent or mon->minvent
|
||||
* chain, possibly recursively.
|
||||
*
|
||||
* The bypass bit on all objects is reset each turn, whenever
|
||||
* context.bypasses is set.
|
||||
@@ -4715,178 +4720,234 @@ const char *const destroy_strings[][3] = {
|
||||
{ "breaks apart and explodes", "", "exploding wand" },
|
||||
};
|
||||
|
||||
void
|
||||
destroy_item(osym, dmgtyp)
|
||||
register int osym, dmgtyp;
|
||||
/* guts of destroy_item(), which ought to be called maybe_destroy_items();
|
||||
caller must decide whether obj is eligible */
|
||||
STATIC_OVL void
|
||||
destroy_one_item(obj, osym, dmgtyp)
|
||||
struct obj *obj;
|
||||
int osym, dmgtyp;
|
||||
{
|
||||
register struct obj *obj;
|
||||
int dmg, xresist, skip;
|
||||
long i, cnt, quan;
|
||||
int dindx;
|
||||
int dmg, xresist, skip, dindx;
|
||||
const char *mult;
|
||||
boolean physical_damage;
|
||||
|
||||
/*
|
||||
* Sometimes destroying an item can change inventory aside from the
|
||||
* item itself (cited case was a potion of polymorph; when destroyed,
|
||||
* potion_breathe() caused hero to transform and that resulted in
|
||||
* destruction of some worn armor). Unlike other uses of the object
|
||||
* bybass mechanism, destroy_item() can be called multiple times for
|
||||
* same event. So we have to explicitly clear it before each use and
|
||||
* hope no other section of code expects it to retain previous value.
|
||||
*
|
||||
* FIXME? Destruction of a ring of levitation could drop hero onto
|
||||
* a fire trap which could destroy other items and we'll get called
|
||||
* recursively. This should still work, but items beyond the ring
|
||||
* which survive the fire will be marked as already processed by the
|
||||
* inner call, so will always survive the remainder of the outer call
|
||||
* instead of being subjected to original chance of destruction.
|
||||
*/
|
||||
bypass_objlist(invent, FALSE); /* clear bypass bit for invent */
|
||||
physical_damage = FALSE;
|
||||
xresist = skip = 0;
|
||||
/* lint suppression */
|
||||
dmg = dindx = 0;
|
||||
quan = 0L;
|
||||
|
||||
while ((obj = nxt_unbypassed_obj(invent)) != 0) {
|
||||
physical_damage = FALSE;
|
||||
if (obj->oclass != osym)
|
||||
continue; /* test only objs of type osym */
|
||||
if (obj->oartifact)
|
||||
continue; /* don't destroy artifacts */
|
||||
if (obj->in_use && obj->quan == 1L)
|
||||
continue; /* not available */
|
||||
xresist = skip = 0;
|
||||
/* lint suppression */
|
||||
dmg = dindx = 0;
|
||||
quan = 0L;
|
||||
|
||||
switch (dmgtyp) {
|
||||
case AD_COLD:
|
||||
if (osym == POTION_CLASS && obj->otyp != POT_OIL) {
|
||||
quan = obj->quan;
|
||||
dindx = 0;
|
||||
dmg = rnd(4);
|
||||
} else
|
||||
skip++;
|
||||
break;
|
||||
case AD_FIRE:
|
||||
xresist = (Fire_resistance && obj->oclass != POTION_CLASS
|
||||
&& obj->otyp != GLOB_OF_GREEN_SLIME);
|
||||
|
||||
if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL)
|
||||
skip++;
|
||||
if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
|
||||
skip++;
|
||||
if (!Blind)
|
||||
pline("%s glows a strange %s, but remains intact.",
|
||||
The(xname(obj)), hcolor("dark red"));
|
||||
}
|
||||
switch (dmgtyp) {
|
||||
case AD_COLD:
|
||||
if (osym == POTION_CLASS && obj->otyp != POT_OIL) {
|
||||
quan = obj->quan;
|
||||
switch (osym) {
|
||||
case POTION_CLASS:
|
||||
dindx = (obj->otyp != POT_OIL) ? 1 : 2;
|
||||
dmg = rnd(6);
|
||||
break;
|
||||
case SCROLL_CLASS:
|
||||
dindx = 3;
|
||||
dmg = 1;
|
||||
break;
|
||||
case SPBOOK_CLASS:
|
||||
dindx = 4;
|
||||
dmg = 1;
|
||||
break;
|
||||
case FOOD_CLASS:
|
||||
if (obj->otyp == GLOB_OF_GREEN_SLIME) {
|
||||
dindx = 1; /* boil and explode */
|
||||
dmg = (obj->owt + 19) / 20;
|
||||
} else {
|
||||
skip++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
dindx = 0;
|
||||
dmg = rnd(4);
|
||||
} else
|
||||
skip++;
|
||||
break;
|
||||
case AD_FIRE:
|
||||
xresist = (Fire_resistance && obj->oclass != POTION_CLASS
|
||||
&& obj->otyp != GLOB_OF_GREEN_SLIME);
|
||||
if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL)
|
||||
skip++;
|
||||
if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
|
||||
skip++;
|
||||
if (!Blind)
|
||||
pline("%s glows a strange %s, but remains intact.",
|
||||
The(xname(obj)), hcolor("dark red"));
|
||||
}
|
||||
quan = obj->quan;
|
||||
switch (osym) {
|
||||
case POTION_CLASS:
|
||||
dindx = (obj->otyp != POT_OIL) ? 1 : 2;
|
||||
dmg = rnd(6);
|
||||
break;
|
||||
case AD_ELEC:
|
||||
xresist = (Shock_resistance && obj->oclass != RING_CLASS);
|
||||
quan = obj->quan;
|
||||
switch (osym) {
|
||||
case RING_CLASS:
|
||||
if (obj->otyp == RIN_SHOCK_RESISTANCE) {
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
dindx = 5;
|
||||
dmg = 0;
|
||||
break;
|
||||
case WAND_CLASS:
|
||||
if (obj->otyp == WAN_LIGHTNING) {
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
#if 0
|
||||
if (obj == current_wand) { skip++; break; }
|
||||
#endif
|
||||
dindx = 6;
|
||||
dmg = rnd(10);
|
||||
break;
|
||||
default:
|
||||
case SCROLL_CLASS:
|
||||
dindx = 3;
|
||||
dmg = 1;
|
||||
break;
|
||||
case SPBOOK_CLASS:
|
||||
dindx = 4;
|
||||
dmg = 1;
|
||||
break;
|
||||
case FOOD_CLASS:
|
||||
if (obj->otyp == GLOB_OF_GREEN_SLIME) {
|
||||
dindx = 1; /* boil and explode */
|
||||
dmg = (obj->owt + 19) / 20;
|
||||
} else {
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!skip) {
|
||||
if (obj->in_use)
|
||||
--quan; /* one will be used up elsewhere */
|
||||
for (i = cnt = 0L; i < quan; i++)
|
||||
if (!rn2(3))
|
||||
cnt++;
|
||||
|
||||
if (!cnt)
|
||||
continue;
|
||||
mult = (cnt == 1L)
|
||||
? (quan == 1L) ? "Your" /* 1 of 1 */
|
||||
: "One of your" /* 1 of N */
|
||||
: (cnt < quan) ? "Some of your" /* n of N */
|
||||
: (quan == 2L) ? "Both of your" /* 2 of 2 */
|
||||
: "All of your"; /* N of N */
|
||||
pline("%s %s %s!", mult, xname(obj),
|
||||
destroy_strings[dindx][(cnt > 1L)]);
|
||||
if (osym == POTION_CLASS && dmgtyp != AD_COLD) {
|
||||
if (!breathless(youmonst.data) || haseyes(youmonst.data))
|
||||
potionbreathe(obj);
|
||||
break;
|
||||
case AD_ELEC:
|
||||
xresist = (Shock_resistance && obj->oclass != RING_CLASS);
|
||||
quan = obj->quan;
|
||||
switch (osym) {
|
||||
case RING_CLASS:
|
||||
if (obj->otyp == RIN_SHOCK_RESISTANCE) {
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
if (obj->owornmask) {
|
||||
if (obj->owornmask & W_RING) /* ring being worn */
|
||||
Ring_gone(obj);
|
||||
else
|
||||
setnotworn(obj);
|
||||
dindx = 5;
|
||||
dmg = 0;
|
||||
break;
|
||||
case WAND_CLASS:
|
||||
if (obj->otyp == WAN_LIGHTNING) {
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
if (obj == current_wand)
|
||||
current_wand = 0; /* destroyed */
|
||||
for (i = 0; i < cnt; i++)
|
||||
useup(obj);
|
||||
if (dmg) {
|
||||
if (xresist) {
|
||||
You("aren't hurt!");
|
||||
} else {
|
||||
const char *how = destroy_strings[dindx][2];
|
||||
boolean one = (cnt == 1L);
|
||||
#if 0
|
||||
if (obj == current_wand) { skip++; break; }
|
||||
#endif
|
||||
dindx = 6;
|
||||
dmg = rnd(10);
|
||||
break;
|
||||
default:
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (dmgtyp == AD_FIRE && osym == FOOD_CLASS)
|
||||
how = "exploding glob of slime";
|
||||
if (physical_damage)
|
||||
dmg = Maybe_Half_Phys(dmg);
|
||||
losehp(dmg, one ? how : (const char *) makeplural(how),
|
||||
one ? KILLED_BY_AN : KILLED_BY);
|
||||
exercise(A_STR, FALSE);
|
||||
}
|
||||
if (!skip) {
|
||||
if (obj->in_use)
|
||||
--quan; /* one will be used up elsewhere */
|
||||
for (i = cnt = 0L; i < quan; i++)
|
||||
if (!rn2(3))
|
||||
cnt++;
|
||||
|
||||
if (!cnt)
|
||||
return;
|
||||
mult = (cnt == 1L)
|
||||
? ((quan == 1L) ? "Your" /* 1 of 1 */
|
||||
: "One of your") /* 1 of N */
|
||||
: ((cnt < quan) ? "Some of your" /* n of N */
|
||||
: (quan == 2L) ? "Both of your" /* 2 of 2 */
|
||||
: "All of your"); /* N of N */
|
||||
pline("%s %s %s!", mult, xname(obj),
|
||||
destroy_strings[dindx][(cnt > 1L)]);
|
||||
if (osym == POTION_CLASS && dmgtyp != AD_COLD) {
|
||||
if (!breathless(youmonst.data) || haseyes(youmonst.data))
|
||||
potionbreathe(obj);
|
||||
}
|
||||
if (obj->owornmask) {
|
||||
if (obj->owornmask & W_RING) /* ring being worn */
|
||||
Ring_gone(obj);
|
||||
else
|
||||
setnotworn(obj);
|
||||
}
|
||||
if (obj == current_wand)
|
||||
current_wand = 0; /* destroyed */
|
||||
for (i = 0; i < cnt; i++)
|
||||
useup(obj);
|
||||
if (dmg) {
|
||||
if (xresist) {
|
||||
You("aren't hurt!");
|
||||
} else {
|
||||
const char *how = destroy_strings[dindx][2];
|
||||
boolean one = (cnt == 1L);
|
||||
|
||||
if (dmgtyp == AD_FIRE && osym == FOOD_CLASS)
|
||||
how = "exploding glob of slime";
|
||||
if (physical_damage)
|
||||
dmg = Maybe_Half_Phys(dmg);
|
||||
losehp(dmg, one ? how : (const char *) makeplural(how),
|
||||
one ? KILLED_BY_AN : KILLED_BY);
|
||||
exercise(A_STR, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* target items of specified class for possible destruction */
|
||||
void
|
||||
destroy_item(osym, dmgtyp)
|
||||
int osym, dmgtyp;
|
||||
{
|
||||
register struct obj *obj;
|
||||
int i, deferral_indx = 0;
|
||||
/* 1+52+1: try to handle a full inventory; it doesn't matter if
|
||||
inventory actually has more, even if everything should be deferred */
|
||||
unsigned short deferrals[1 + 52 + 1]; /* +1: gold, overflow */
|
||||
|
||||
(void) memset((genericptr_t) deferrals, 0, sizeof deferrals);
|
||||
/*
|
||||
* Sometimes destroying an item can change inventory aside from
|
||||
* the item itself (cited case was a potion of unholy water; when
|
||||
* boiled, potionbreathe() caused hero to transform into were-beast
|
||||
* form and that resulted in dropping or destroying some worn armor).
|
||||
*
|
||||
* Unlike other uses of the object bybass mechanism, destroy_item()
|
||||
* can be called multiple times for the same event. So we have to
|
||||
* explicitly clear it before each use and hope no other section of
|
||||
* code expects it to retain previous value.
|
||||
*
|
||||
* Destruction of a ring of levitation or form change which pushes
|
||||
* off levitation boots could drop hero onto a fire trap that
|
||||
* could destroy other items and we'll get called recursively. Or
|
||||
* onto a trap which transports hero elsewhere, which won't disrupt
|
||||
* traversal but could yield message sequencing issues. So we
|
||||
* defer handling such things until after rest of inventory has
|
||||
* been processed. If some other combination of items and events
|
||||
* triggers a recursive call, rest of inventory after the triggering
|
||||
* item will be skipped by the outer call since the inner one will
|
||||
* have set the bypass bits of the whole list.
|
||||
*
|
||||
* [Unfortunately, death while poly'd into flyer and subsequent
|
||||
* rehumanization could also drop hero onto a trap, and there's no
|
||||
* straightforward way to defer that. Things could be improved by
|
||||
* redoing this to use two passes, first to collect a list or array
|
||||
* of o_id and quantity of what is targetted for destruction,
|
||||
* second pass to handle the destruction.]
|
||||
*/
|
||||
bypass_objlist(invent, FALSE); /* clear bypass bit for invent */
|
||||
|
||||
while ((obj = nxt_unbypassed_obj(invent)) != 0) {
|
||||
if (obj->oclass != osym)
|
||||
continue; /* test only objs of type osym */
|
||||
if (obj->oartifact)
|
||||
continue; /* don't destroy artifacts */
|
||||
if (obj->in_use && obj->quan == 1L)
|
||||
continue; /* not available */
|
||||
|
||||
/* if loss of this item might dump us onto a trap, hold off
|
||||
until later because potential recursive destroy_item() will
|
||||
result in setting bypass bits on whole chain--we would skip
|
||||
the rest as already processed once control returns here */
|
||||
if (deferral_indx < SIZE(deferrals)
|
||||
&& ((obj->owornmask != 0L
|
||||
&& (objects[obj->otyp].oc_oprop == LEVITATION
|
||||
|| objects[obj->otyp].oc_oprop == FLYING))
|
||||
/* destroyed wands and potions of polymorph don't trigger
|
||||
polymorph so don't need to be deferred */
|
||||
|| (obj->otyp == POT_WATER && u.ulycn >= LOW_PM
|
||||
&& (Upolyd ? obj->blessed : obj->cursed)))) {
|
||||
deferrals[deferral_indx++] = obj->o_id;
|
||||
continue;
|
||||
}
|
||||
/* obj is eligible; maybe destroy it */
|
||||
destroy_one_item(obj, osym, dmgtyp);
|
||||
}
|
||||
/* if we saved some items for later (most likely just a worn ring
|
||||
of levitation) and they're still in inventory, handle them now */
|
||||
for (i = 0; i < deferral_indx; ++i) {
|
||||
/* note: obj->nobj is only referenced when obj is skipped;
|
||||
having obj be dropped or destroyed won't affect traversal */
|
||||
for (obj = invent; obj; obj = obj->nobj)
|
||||
if (obj->o_id == deferrals[i]) {
|
||||
destroy_one_item(obj, osym, dmgtyp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4909,7 +4970,7 @@ int osym, dmgtyp;
|
||||
vis = canseemon(mtmp);
|
||||
|
||||
/* see destroy_item(); object destruction could disrupt inventory list */
|
||||
bypass_objlist(mtmp->minvent, FALSE); /* clear bypass bit for invent */
|
||||
bypass_objlist(mtmp->minvent, FALSE); /* clear bypass bit for minvent */
|
||||
|
||||
while ((obj = nxt_unbypassed_obj(mtmp->minvent)) != 0) {
|
||||
if (obj->oclass != osym)
|
||||
|
||||
Reference in New Issue
Block a user