diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index 27930384b..db14f6a6f 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1363,6 +1363,7 @@ if confused #loot while on a throne moved whole stack of quivered gold into when filling quiver slot, don't bother asking "what do you want to ready?" if invent is empty no secret doors or corridors on the first two dungeon levels +the number of items destroyed by elemental effects is based on the damage Fixes to 3.7.0-x General Problems Exposed Via git Repository diff --git a/include/extern.h b/include/extern.h index 3a9c2fa90..de46c15c2 100644 --- a/include/extern.h +++ b/include/extern.h @@ -3785,8 +3785,7 @@ extern boolean break_statue(struct obj *) NONNULLARG1; extern int u_adtyp_resistance_obj(int); extern boolean inventory_resistance_check(int); extern char *item_what(int); -extern void destroy_item(int, int); -extern int destroy_mitem(struct monst *, int, int) NONNULLARG1; +extern int destroy_items(struct monst *, int, int) NONNULLARG1; extern int resist(struct monst *, char, int, int) NONNULLARG1; extern void makewish(void); extern const char *flash_str(int, boolean) NONNULL; diff --git a/src/apply.c b/src/apply.c index 2bad9976c..b5a7632be 100644 --- a/src/apply.c +++ b/src/apply.c @@ -3782,7 +3782,7 @@ discard_broken_wand(void) { struct obj *obj; - obj = gc.current_wand; /* [see dozap() and destroy_item()] */ + obj = gc.current_wand; /* [see dozap() and destroy_items()] */ gc.current_wand = 0; if (obj) delobj(obj); @@ -3840,8 +3840,8 @@ do_break_wand(struct obj *obj) costly_alteration(obj, COST_DSTROY); } - gc.current_wand = obj; /* destroy_item might reset this */ - freeinv(obj); /* hide it from destroy_item instead... */ + gc.current_wand = obj; /* destroy_items might reset this */ + freeinv(obj); /* hide it from destroy_items instead... */ setnotworn(obj); /* so we need to do this ourselves */ if (!zappable(obj)) { diff --git a/src/artifact.c b/src/artifact.c index 021391946..84685a4db 100644 --- a/src/artifact.c +++ b/src/artifact.c @@ -1390,14 +1390,12 @@ artifact_hit( ? "vaporizes part of" : "burns", hittee, !gs.spec_dbon_applies ? '.' : '!'); - if (!rn2(4)) - (void) destroy_mitem(mdef, POTION_CLASS, AD_FIRE); - if (!rn2(4)) - (void) destroy_mitem(mdef, SCROLL_CLASS, AD_FIRE); - if (!rn2(7)) - (void) destroy_mitem(mdef, SPBOOK_CLASS, AD_FIRE); - if (!rn2(4)) + if (!rn2(4)) { + int itemdmg = destroy_items(mdef, AD_FIRE, *dmgptr); + if (!youdefend) + *dmgptr += itemdmg; /* item destruction dmg */ ignite_items(mdef->minvent); + } if (youdefend && Slimed) burn_away_slime(); return realizes_damage; @@ -1407,8 +1405,11 @@ artifact_hit( pline_The("ice-cold blade %s %s%c", !gs.spec_dbon_applies ? "hits" : "freezes", hittee, !gs.spec_dbon_applies ? '.' : '!'); - if (!rn2(4)) - (void) destroy_mitem(mdef, POTION_CLASS, AD_COLD); + if (!rn2(4)) { + int itemdmg = destroy_items(mdef, AD_COLD, *dmgptr); + if (!youdefend) + *dmgptr += itemdmg; /* item destruction dmg */ + } return realizes_damage; } if (attacks(AD_ELEC, otmp)) { @@ -1418,10 +1419,11 @@ artifact_hit( hittee, !gs.spec_dbon_applies ? '.' : '!'); if (gs.spec_dbon_applies) wake_nearto(mdef->mx, mdef->my, 4 * 4); - if (!rn2(5)) - (void) destroy_mitem(mdef, RING_CLASS, AD_ELEC); - if (!rn2(5)) - (void) destroy_mitem(mdef, WAND_CLASS, AD_ELEC); + if (!rn2(5)) { + int itemdmg = destroy_items(mdef, AD_ELEC, *dmgptr); + if (!youdefend) + *dmgptr += itemdmg; /* item destruction dmg */ + } return realizes_damage; } if (attacks(AD_MAGM, otmp)) { diff --git a/src/explode.c b/src/explode.c index 3faf99147..75b8a12f3 100644 --- a/src/explode.c +++ b/src/explode.c @@ -209,7 +209,6 @@ explode( boolean visible, any_shield; int uhurt = 0; /* 0=unhurt, 1=items damaged, 2=you and items damaged */ const char *str = (const char *) 0; - int idamres, idamnonres; struct monst *mtmp, *mdef = 0; uchar adtyp; int explmask[3][3]; /* 0=normal explosion, 1=do shieldeff, 2=do nothing */ @@ -457,6 +456,8 @@ explode( if (dam) { for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { + int itemdmg = 0; + if (explmask[i][j] == EXPL_SKIP) continue; xx = x + i - 1; @@ -473,7 +474,7 @@ explode( /* for inside_engulfer, only is affected */ continue; } - idamres = idamnonres = 0; + /* Affect the floor unless the player caused the explosion * from inside their engulfer. */ if (!(u.uswallow && !gc.context.mon_moving)) @@ -507,19 +508,22 @@ explode( pline("%s is caught in the %s!", Monnam(mtmp), str); } + itemdmg = destroy_items(mtmp, (int) adtyp, dam); if (adtyp == AD_FIRE) { (void) burnarmor(mtmp); ignite_items(mtmp->minvent); } - idamres += destroy_mitem(mtmp, SCROLL_CLASS, (int) adtyp); - idamres += destroy_mitem(mtmp, SPBOOK_CLASS, (int) adtyp); - idamnonres += destroy_mitem(mtmp, POTION_CLASS, (int) adtyp); - idamnonres += destroy_mitem(mtmp, RING_CLASS, (int) adtyp); - idamnonres += destroy_mitem(mtmp, WAND_CLASS, (int) adtyp); if ((explmask[i][j] & EXPL_MON) != 0) { - golemeffects(mtmp, (int) adtyp, dam + idamres); - mtmp->mhp -= idamnonres; + /* damage from ring/wand explosion isn't itself + * electrical in nature, nor is damage from freezing potion + * really cold in nature, nor is damage from boiling potion + * or exploding oil; only burning items damage is the "same + * type" as the explosion. Because this is imperfect and + * marginal (burning items only deal 1 damage), ignore it + * for golemeffects(). */ + golemeffects(mtmp, (int) adtyp, dam); + mtmp->mhp -= itemdmg; /* item destruction dmg */ } else { /* call resist with 0 and do damage manually so 1) we can * get out the message before doing the damage, and 2) we @@ -546,8 +550,7 @@ explode( mdam *= 2; else if (resists_fire(mtmp) && adtyp == AD_COLD) mdam *= 2; - mtmp->mhp -= mdam; - mtmp->mhp -= (idamres + idamnonres); + mtmp->mhp -= mdam + itemdmg; } if (DEADMONSTER(mtmp)) { int xkflg = ((adtyp == AD_FIRE @@ -611,11 +614,7 @@ explode( (void) burnarmor(&gy.youmonst); ignite_items(gi.invent); } - destroy_item(SCROLL_CLASS, (int) adtyp); - destroy_item(SPBOOK_CLASS, (int) adtyp); - destroy_item(POTION_CLASS, (int) adtyp); - destroy_item(RING_CLASS, (int) adtyp); - destroy_item(WAND_CLASS, (int) adtyp); + (void) destroy_items(&gy.youmonst, (int) adtyp, dam); ugolemeffects((int) adtyp, damu); if (uhurt == 2) { diff --git a/src/invent.c b/src/invent.c index 4a6664d0d..97825059e 100644 --- a/src/invent.c +++ b/src/invent.c @@ -4850,7 +4850,7 @@ mergable( || obj->greased != otmp->greased) return FALSE; - if ((obj->oclass == WEAPON_CLASS || obj->oclass == ARMOR_CLASS) + if ((erosion_matters(obj)) && (obj->oerodeproof != otmp->oerodeproof || (obj->rknown != otmp->rknown && (Blind || Hallucination)))) return FALSE; diff --git a/src/mcastu.c b/src/mcastu.c index 161758ca5..88271b034 100644 --- a/src/mcastu.c +++ b/src/mcastu.c @@ -621,6 +621,7 @@ DISABLE_WARNING_FORMAT_NONLITERAL static void cast_cleric_spell(struct monst *mtmp, int dmg, int spellnum) { + int orig_dmg = 0; if (dmg == 0 && !is_undirected_spell(AD_CLRC, spellnum)) { impossible("cast directed cleric spell (%d) with dmg=0?", spellnum); return; @@ -642,21 +643,20 @@ cast_cleric_spell(struct monst *mtmp, int dmg, int spellnum) break; case CLC_FIRE_PILLAR: pline("A pillar of fire strikes all around you!"); + orig_dmg = dmg = d(8, 6); if (Fire_resistance) { shieldeff(u.ux, u.uy); monstseesu(M_SEEN_FIRE); dmg = 0; } else { - dmg = d(8, 6); monstunseesu(M_SEEN_FIRE); } if (Half_spell_damage) dmg = (dmg + 1) / 2; burn_away_slime(); (void) burnarmor(&gy.youmonst); - destroy_item(SCROLL_CLASS, AD_FIRE); - destroy_item(POTION_CLASS, AD_FIRE); - destroy_item(SPBOOK_CLASS, AD_FIRE); + /* item destruction dmg */ + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); ignite_items(gi.invent); /* burn up flammable items on the floor, melt ice terrain */ mon_spell_hits_spot(mtmp, AD_FIRE, u.ux, u.uy); @@ -667,6 +667,7 @@ cast_cleric_spell(struct monst *mtmp, int dmg, int spellnum) Soundeffect(se_bolt_of_lightning, 80); pline("A bolt of lightning strikes down at you from above!"); reflects = ureflects("It bounces off your %s%s.", ""); + orig_dmg = dmg = d(8, 6); if (reflects || Shock_resistance) { shieldeff(u.ux, u.uy); dmg = 0; @@ -677,13 +678,11 @@ cast_cleric_spell(struct monst *mtmp, int dmg, int spellnum) monstunseesu(M_SEEN_REFL); monstseesu(M_SEEN_ELEC); } else { - dmg = d(8, 6); monstunseesu(M_SEEN_ELEC | M_SEEN_REFL); } if (Half_spell_damage) dmg = (dmg + 1) / 2; - destroy_item(WAND_CLASS, AD_ELEC); - destroy_item(RING_CLASS, AD_ELEC); + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); /* lightning might destroy iron bars if hero is on such a spot; reflection protects terrain here [execution won't get here due to 'if (reflects) break' above] but hero resistance doesn't; diff --git a/src/mhitu.c b/src/mhitu.c index f06cae015..5e83af60c 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -1733,7 +1733,7 @@ gazemu(struct monst *mtmp, struct attack *mattk) if (cancelled) { react = rn1(2, 4); /* "irritated" || "inflamed" */ } else { - int dmg = d(2, 6), lev = (int) mtmp->m_lev; + int dmg = d(2, 6), orig_dmg = dmg, lev = (int) mtmp->m_lev; pline("%s attacks you with a fiery gaze!", Monnam(mtmp)); stop_occupation(); @@ -1749,14 +1749,10 @@ gazemu(struct monst *mtmp, struct attack *mattk) burn_away_slime(); if (lev > rn2(20)) (void) burnarmor(&gy.youmonst); - if (lev > rn2(20)) - destroy_item(SCROLL_CLASS, AD_FIRE); - if (lev > rn2(20)) - destroy_item(POTION_CLASS, AD_FIRE); - if (lev > rn2(25)) - destroy_item(SPBOOK_CLASS, AD_FIRE); - if (lev > rn2(20)) + if (lev > rn2(20)) { + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); ignite_items(gi.invent); + } if (dmg) mdamageu(mtmp, dmg); } diff --git a/src/polyself.c b/src/polyself.c index 8b3c1756f..d09d9af30 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -1685,21 +1685,17 @@ dogaze(void) Monnam(mtmp)); mtmp->mconf = 1; } else if (adtyp == AD_FIRE) { - int dmg = d(2, 6), lev = (int) u.ulevel; + int dmg = d(2, 6), orig_dmg = dmg, lev = (int) u.ulevel; You("attack %s with a fiery gaze!", mon_nam(mtmp)); if (resists_fire(mtmp)) { pline_The("fire doesn't burn %s!", mon_nam(mtmp)); dmg = 0; } - if (lev > rn2(20)) - (void) destroy_mitem(mtmp, SCROLL_CLASS, AD_FIRE); - if (lev > rn2(20)) - (void) destroy_mitem(mtmp, POTION_CLASS, AD_FIRE); - if (lev > rn2(25)) - (void) destroy_mitem(mtmp, SPBOOK_CLASS, AD_FIRE); - if (lev > rn2(20)) + if (lev > rn2(20)) { + dmg += destroy_items(mtmp, AD_FIRE, orig_dmg); ignite_items(mtmp->minvent); + } if (dmg) mtmp->mhp -= dmg; if (DEADMONSTER(mtmp)) diff --git a/src/shk.c b/src/shk.c index 396575e64..a2ab74c8f 100644 --- a/src/shk.c +++ b/src/shk.c @@ -2809,7 +2809,7 @@ add_to_billobjs(struct obj *obj) in_use by dodrink/dopotion but isn't being used up yet because it stays on the bill; only object sanity checking actually cares */ obj->in_use = 0; - /* ... same for bypass by destroy_item */ + /* ... same for bypass by destroy_items */ obj->bypass = 0; } diff --git a/src/trap.c b/src/trap.c index 8d7a53bd1..0026dea13 100644 --- a/src/trap.c +++ b/src/trap.c @@ -1667,6 +1667,7 @@ trapeffect_fire_trap( boolean see_it = cansee(tx, ty); boolean trapkilled = FALSE; struct permonst *mptr = mtmp->data; + int orig_dmg = d(2, 4); if (in_sight) pline("A %s erupts from the %s under %s!", tower_of_flame, @@ -1681,7 +1682,7 @@ trapeffect_fire_trap( pline("%s is uninjured.", Monnam(mtmp)); } } else { - int num = d(2, 4), alt; + int num = orig_dmg, alt; boolean immolate = FALSE; /* paper burns very fast, assume straw is tightly packed @@ -1717,10 +1718,15 @@ trapeffect_fire_trap( mtmp->mhpmax -= rn2(num + 1); } if (burnarmor(mtmp) || rn2(3)) { - (void) destroy_mitem(mtmp, SCROLL_CLASS, AD_FIRE); - (void) destroy_mitem(mtmp, SPBOOK_CLASS, AD_FIRE); - (void) destroy_mitem(mtmp, POTION_CLASS, AD_FIRE); + int xtradmg = destroy_items(mtmp, AD_FIRE, orig_dmg); ignite_items(mtmp->minvent); + if (!DEADMONSTER(mtmp)) { + mtmp->mhp -= xtradmg; + if (DEADMONSTER(mtmp)) { /* NOW it's dead */ + monkilled(mtmp, "", AD_FIRE); + trapkilled = TRUE; + } + } } if (burn_floor_objects(tx, ty, see_it, FALSE) && !see_it && distu(tx, ty) <= 3 * 3) @@ -4029,7 +4035,8 @@ dofiretrap( struct obj *box) /* null for floor trap */ { boolean see_it = !Blind; - int num, alt; + int orig_dmg, num, alt; + orig_dmg = num = d(2, 4); /* Bug: for box case, the equivalent of burn_floor_objects() ought * to be done upon its contents. @@ -4051,7 +4058,6 @@ dofiretrap( monstseesu(M_SEEN_FIRE); num = rn2(2); } else if (Upolyd) { - num = d(2, 4); switch (u.umonnum) { case PM_PAPER_GOLEM: alt = u.mhmax; @@ -4099,9 +4105,7 @@ dofiretrap( burn_away_slime(); if (burnarmor(&gy.youmonst) || rn2(3)) { - destroy_item(SCROLL_CLASS, AD_FIRE); - destroy_item(SPBOOK_CLASS, AD_FIRE); - destroy_item(POTION_CLASS, AD_FIRE); + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); ignite_items(gi.invent); } if (!box && burn_floor_objects(u.ux, u.uy, see_it, TRUE) && !see_it) @@ -6153,7 +6157,7 @@ chest_trap( case 8: case 7: case 6: { - int dmg; + int dmg = d(4, 4), orig_dmg = dmg; You("are jolted by a surge of electricity!"); if (Shock_resistance) { @@ -6162,11 +6166,9 @@ chest_trap( monstseesu(M_SEEN_ELEC); dmg = 0; } else { - dmg = d(4, 4); monstunseesu(M_SEEN_ELEC); } - destroy_item(RING_CLASS, AD_ELEC); - destroy_item(WAND_CLASS, AD_ELEC); + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); if (dmg) losehp(dmg, "electric shock", KILLED_BY_AN); break; @@ -6508,7 +6510,7 @@ lava_effects(void) boolean usurvive, boil_away; unsigned protect_oid = 0; int burncount = 0, burnmesgcount = 0; - int dmg = d(6, 6); /* only applicable for water walking */ + const int dmg = d(6, 6); /* only applicable for water walking */ if (iflags.in_lava_effects) { debugpline0("Skipping recursive lava_effects()."); @@ -6524,7 +6526,7 @@ lava_effects(void) * A timely interrupt might manage to salvage your life * but not your gear. For scrolls and potions this * will destroy whole stacks, where fire resistant hero - * survivor only loses partial stacks via destroy_item(). + * survivor only loses partial stacks via destroy_items(). * * Flag items to be destroyed before any messages so * that player causing hangup at --More-- won't get an @@ -6688,9 +6690,7 @@ lava_effects(void) } burn_stuff: - destroy_item(SCROLL_CLASS, AD_FIRE); - destroy_item(SPBOOK_CLASS, AD_FIRE); - destroy_item(POTION_CLASS, AD_FIRE); + (void) destroy_items(&gy.youmonst, AD_FIRE, dmg); ignite_items(gi.invent); return FALSE; } diff --git a/src/uhitm.c b/src/uhitm.c index 35698459c..5c90b8c29 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -2429,6 +2429,7 @@ mhitm_ad_fire( struct monst *mdef, struct mhitm_data *mhm) { struct permonst *pd = mdef->data; + const int orig_dmg = mhm->damage; /* damage coming into the function */ if (magr == &gy.youmonst) { /* uhitm */ @@ -2454,8 +2455,6 @@ mhitm_ad_fire( return; /* Don't return yet; keep hp<1 and mhm.damage=0 for pet msg */ } - mhm->damage += destroy_mitem(mdef, SCROLL_CLASS, AD_FIRE); - mhm->damage += destroy_mitem(mdef, SPBOOK_CLASS, AD_FIRE); if (resists_fire(mdef) || defended(mdef, AD_FIRE)) { if (!Blind) pline_The("fire doesn't heat %s!", mon_nam(mdef)); @@ -2463,8 +2462,7 @@ mhitm_ad_fire( shieldeff(mdef->mx, mdef->my); mhm->damage = 0; } - /* only potions damage resistant players in destroy_item */ - mhm->damage += destroy_mitem(mdef, POTION_CLASS, AD_FIRE); + mhm->damage += destroy_items(mdef, AD_FIRE, orig_dmg); ignite_items(mdef->minvent); } else if (mdef == &gy.youmonst) { /* mhitu */ @@ -2484,14 +2482,10 @@ mhitm_ad_fire( } else { monstunseesu(M_SEEN_FIRE); } - if ((int) magr->m_lev > rn2(20)) - destroy_item(SCROLL_CLASS, AD_FIRE); - if ((int) magr->m_lev > rn2(20)) - destroy_item(POTION_CLASS, AD_FIRE); - if ((int) magr->m_lev > rn2(25)) - destroy_item(SPBOOK_CLASS, AD_FIRE); - if ((int) magr->m_lev > rn2(20)) + if ((int) magr->m_lev > rn2(20)) { + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); ignite_items(gi.invent); + } burn_away_slime(); } else { mhm->damage = 0; @@ -2522,8 +2516,6 @@ mhitm_ad_fire( mhm->done = TRUE; return; } - mhm->damage += destroy_mitem(mdef, SCROLL_CLASS, AD_FIRE); - mhm->damage += destroy_mitem(mdef, SPBOOK_CLASS, AD_FIRE); if (resists_fire(mdef) || defended(mdef, AD_FIRE)) { if (gv.vis && canseemon(mdef)) pline_The("fire doesn't seem to burn %s!", mon_nam(mdef)); @@ -2531,8 +2523,7 @@ mhitm_ad_fire( golemeffects(mdef, AD_FIRE, mhm->damage); mhm->damage = 0; } - /* only potions damage resistant players in destroy_item */ - mhm->damage += destroy_mitem(mdef, POTION_CLASS, AD_FIRE); + mhm->damage += destroy_items(mdef, AD_FIRE, orig_dmg); ignite_items(mdef->minvent); } } @@ -2542,6 +2533,8 @@ mhitm_ad_cold( struct monst *magr, struct attack *mattk, struct monst *mdef, struct mhitm_data *mhm) { + const int orig_dmg = mhm->damage; + if (magr == &gy.youmonst) { /* uhitm */ if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { @@ -2557,7 +2550,7 @@ mhitm_ad_cold( golemeffects(mdef, AD_COLD, mhm->damage); mhm->damage = 0; } - mhm->damage += destroy_mitem(mdef, POTION_CLASS, AD_COLD); + mhm->damage += destroy_items(mdef, AD_COLD, orig_dmg); } else if (mdef == &gy.youmonst) { /* mhitu */ hitmsg(magr, mattk); @@ -2571,7 +2564,7 @@ mhitm_ad_cold( monstunseesu(M_SEEN_COLD); } if ((int) magr->m_lev > rn2(20)) - destroy_item(POTION_CLASS, AD_COLD); + (void) destroy_items(&gy.youmonst, AD_COLD, orig_dmg); } else mhm->damage = 0; } else { @@ -2589,7 +2582,7 @@ mhitm_ad_cold( golemeffects(mdef, AD_COLD, mhm->damage); mhm->damage = 0; } - mhm->damage += destroy_mitem(mdef, POTION_CLASS, AD_COLD); + mhm->damage += destroy_items(mdef, AD_COLD, orig_dmg); } } @@ -2598,6 +2591,8 @@ mhitm_ad_elec( struct monst *magr, struct attack *mattk, struct monst *mdef, struct mhitm_data *mhm) { + const int orig_dmg = mhm->damage; + if (magr == &gy.youmonst) { /* uhitm */ if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) { @@ -2606,7 +2601,6 @@ mhitm_ad_elec( } if (!Blind) pline("%s is zapped!", Monnam(mdef)); - mhm->damage += destroy_mitem(mdef, WAND_CLASS, AD_ELEC); if (resists_elec(mdef) || defended(mdef, AD_ELEC)) { if (!Blind) pline_The("zap doesn't shock %s!", mon_nam(mdef)); @@ -2614,8 +2608,7 @@ mhitm_ad_elec( shieldeff(mdef->mx, mdef->my); mhm->damage = 0; } - /* only rings damage resistant players in destroy_item */ - mhm->damage += destroy_mitem(mdef, RING_CLASS, AD_ELEC); + mhm->damage += destroy_items(mdef, AD_ELEC, orig_dmg); } else if (mdef == &gy.youmonst) { /* mhitu */ hitmsg(magr, mattk); @@ -2629,9 +2622,7 @@ mhitm_ad_elec( monstunseesu(M_SEEN_ELEC); } if ((int) magr->m_lev > rn2(20)) - destroy_item(WAND_CLASS, AD_ELEC); - if ((int) magr->m_lev > rn2(20)) - destroy_item(RING_CLASS, AD_ELEC); + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); } else mhm->damage = 0; } else { @@ -2642,7 +2633,6 @@ mhitm_ad_elec( } if (gv.vis && canseemon(mdef)) pline("%s gets zapped!", Monnam(mdef)); - mhm->damage += destroy_mitem(mdef, WAND_CLASS, AD_ELEC); if (resists_elec(mdef) || defended(mdef, AD_ELEC)) { if (gv.vis && canseemon(mdef)) pline_The("zap doesn't shock %s!", mon_nam(mdef)); @@ -2650,8 +2640,7 @@ mhitm_ad_elec( golemeffects(mdef, AD_ELEC, mhm->damage); mhm->damage = 0; } - /* only rings damage resistant players in destroy_item */ - mhm->damage += destroy_mitem(mdef, RING_CLASS, AD_ELEC); + mhm->damage += destroy_items(mdef, AD_ELEC, orig_dmg); } } diff --git a/src/zap.c b/src/zap.c index 57220f387..6fe542df1 100644 --- a/src/zap.c +++ b/src/zap.c @@ -35,7 +35,8 @@ static int zap_ok(struct obj *) NO_NNARGS; * boxlock_invent() calls boxlock() which has nonnull arg. */ static void boxlock_invent(struct obj *) NONNULLARG1; static int spell_hit_bonus(int); -static void destroy_one_item(struct obj *, int, int) NONNULLARG1; +static int maybe_destroy_item(struct monst *, struct obj *, int) NONNULLPTRS; + static void wishcmdassist(int); #define ZT_MAGIC_MISSILE (AD_MAGM - 1) @@ -2071,10 +2072,9 @@ bhito(struct obj *obj, struct 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. + * destroy_items() - 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 * gc.context.bypasses is set. @@ -2560,7 +2560,7 @@ dozap(void) /* Are we having fun yet? * weffects -> buzz(obj->otyp) -> zhitm (temple priest) -> * attack -> hitum -> known_hitum -> ghod_hitsu -> - * buzz(AD_ELEC) -> destroy_item(WAND_CLASS) -> + * buzz(AD_ELEC) -> destroy_items(AD_ELEC) -> * useup -> obfree -> dealloc_obj -> free(obj) */ gc.current_wand = obj; @@ -2598,6 +2598,7 @@ zapyourself(struct obj *obj, boolean ordinary) { boolean learn_it = FALSE; int damage = 0; + int orig_dmg = 0; /* for passing to destroy_items() */ switch (obj->otyp) { case WAN_STRIKING: @@ -2620,19 +2621,19 @@ zapyourself(struct obj *obj, boolean ordinary) case WAN_LIGHTNING: learn_it = TRUE; + orig_dmg = d(12, 6); if (!Shock_resistance) { You("shock yourself!"); - damage = d(12, 6); + damage = orig_dmg; exercise(A_CON, FALSE); monstunseesu(M_SEEN_ELEC); } else { shieldeff(u.ux, u.uy); You("zap yourself, but seem unharmed."); monstseesu(M_SEEN_ELEC); - ugolemeffects(AD_ELEC, d(12, 6)); + ugolemeffects(AD_ELEC, orig_dmg); } - destroy_item(WAND_CLASS, AD_ELEC); - destroy_item(RING_CLASS, AD_ELEC); + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg); (void) flashburn((long) rnd(100)); break; @@ -2643,22 +2644,20 @@ zapyourself(struct obj *obj, boolean ordinary) case WAN_FIRE: case FIRE_HORN: learn_it = TRUE; + orig_dmg = d(12, 6); if (Fire_resistance) { shieldeff(u.ux, u.uy); You_feel("rather warm."); monstseesu(M_SEEN_FIRE); - ugolemeffects(AD_FIRE, d(12, 6)); + ugolemeffects(AD_FIRE, orig_dmg); } else { pline("You've set yourself afire!"); - damage = d(12, 6); + damage = orig_dmg; monstunseesu(M_SEEN_FIRE); } burn_away_slime(); (void) burnarmor(&gy.youmonst); - destroy_item(SCROLL_CLASS, AD_FIRE); - destroy_item(POTION_CLASS, AD_FIRE); - destroy_item(SPBOOK_CLASS, AD_FIRE); - destroy_item(FOOD_CLASS, AD_FIRE); /* only slime for now */ + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg); ignite_items(gi.invent); break; @@ -2666,17 +2665,18 @@ zapyourself(struct obj *obj, boolean ordinary) case SPE_CONE_OF_COLD: case FROST_HORN: learn_it = TRUE; + orig_dmg = d(12, 6); if (Cold_resistance) { shieldeff(u.ux, u.uy); You_feel("a little chill."); monstseesu(M_SEEN_COLD); - ugolemeffects(AD_COLD, d(12, 6)); + ugolemeffects(AD_COLD, orig_dmg); } else { You("imitate a popsicle!"); - damage = d(12, 6); + damage = orig_dmg; monstunseesu(M_SEEN_COLD); } - destroy_item(POTION_CLASS, AD_COLD); + (void) destroy_items(&gy.youmonst, AD_COLD, orig_dmg); break; case WAN_MAGIC_MISSILE: @@ -4093,7 +4093,7 @@ zhitm( int nd, /* number of hit dice to use */ struct obj **ootmp) /* to return worn armor for caller to disintegrate */ { - int tmp = 0; /* damage amount */ + register int tmp = 0, orig_dmg = 0; /* damage amount */ int damgtype = zaptype(type) % 10; boolean sho_shieldeff = FALSE; boolean spellcaster = is_hero_spell(type); /* maybe get a bonus! */ @@ -4115,20 +4115,16 @@ zhitm( break; } tmp = d(nd, 6); - if (resists_cold(mon)) - tmp += 7; if (spellcaster) tmp = spell_damage_bonus(tmp); + orig_dmg = tmp; /* includes spell bonus but not monster vuln to fire */ + if (resists_cold(mon)) + tmp += 7; if (burnarmor(mon)) { - if (!rn2(3)) - (void) destroy_mitem(mon, POTION_CLASS, AD_FIRE); - if (!rn2(3)) - (void) destroy_mitem(mon, SCROLL_CLASS, AD_FIRE); - if (!rn2(5)) - (void) destroy_mitem(mon, SPBOOK_CLASS, AD_FIRE); - if (!rn2(3)) + if (!rn2(3)) { + tmp += destroy_items(mon, AD_FIRE, orig_dmg); ignite_items(mon->minvent); - destroy_mitem(mon, FOOD_CLASS, AD_FIRE); /* carried slime */ + } } break; case ZT_COLD: @@ -4137,12 +4133,13 @@ zhitm( break; } tmp = d(nd, 6); - if (resists_fire(mon)) - tmp += d(nd, 3); if (spellcaster) tmp = spell_damage_bonus(tmp); + orig_dmg = tmp; /* includes spell bonus but not monster vuln to cold */ + if (resists_fire(mon)) + tmp += d(nd, 3); if (!rn2(3)) - (void) destroy_mitem(mon, POTION_CLASS, AD_COLD); + tmp += destroy_items(mon, AD_COLD, orig_dmg); break; case ZT_SLEEP: /* possibly resistance and shield effect handled by sleep_monst() */ @@ -4195,14 +4192,15 @@ zhitm( tmp = mon->mhp + 1; break; case ZT_LIGHTNING: + tmp = d(nd, 6); + if (spellcaster) + tmp = spell_damage_bonus(tmp); + orig_dmg = tmp; if (resists_elec(mon) || defended(mon, AD_ELEC)) { sho_shieldeff = TRUE; tmp = 0; /* can still blind the monster */ - } else - tmp = d(nd, 6); - if (spellcaster && tmp) - tmp = spell_damage_bonus(tmp); + } if (!resists_blnd(mon) && !(type > 0 && engulfing_u(mon)) && nd > 2) { @@ -4215,10 +4213,7 @@ zhitm( mon->mblinded += rnd_tmp; } if (!rn2(3)) - (void) destroy_mitem(mon, WAND_CLASS, AD_ELEC); - /* not actually possible yet */ - if (!rn2(3)) - (void) destroy_mitem(mon, RING_CLASS, AD_ELEC); + tmp += destroy_items(mon, AD_ELEC, orig_dmg); break; case ZT_POISON_GAS: if (resists_poison(mon) || defended(mon, AD_DRST)) { @@ -4261,6 +4256,7 @@ zhitu( coordxy sx, coordxy sy) { int dam = 0, abstyp = zaptype(type); + int orig_dam = 0; switch (abstyp % 10) { case ZT_MAGIC_MISSILE: @@ -4275,40 +4271,37 @@ zhitu( } break; case ZT_FIRE: + orig_dam = d(nd, 6); if (Fire_resistance) { shieldeff(sx, sy); You("don't feel hot!"); monstseesu(M_SEEN_FIRE); - ugolemeffects(AD_FIRE, d(nd, 6)); + ugolemeffects(AD_FIRE, orig_dam); } else { - dam = d(nd, 6); + dam = orig_dam; monstunseesu(M_SEEN_FIRE); } burn_away_slime(); if (burnarmor(&gy.youmonst)) { /* "body hit" */ if (!rn2(3)) - destroy_item(POTION_CLASS, AD_FIRE); - if (!rn2(3)) - destroy_item(SCROLL_CLASS, AD_FIRE); - if (!rn2(5)) - destroy_item(SPBOOK_CLASS, AD_FIRE); + (void) destroy_items(&gy.youmonst, AD_FIRE, orig_dam); if (!rn2(3)) ignite_items(gi.invent); - destroy_item(FOOD_CLASS, AD_FIRE); } break; case ZT_COLD: + orig_dam = d(nd, 6); if (Cold_resistance) { shieldeff(sx, sy); You("don't feel cold."); monstseesu(M_SEEN_COLD); - ugolemeffects(AD_COLD, d(nd, 6)); + ugolemeffects(AD_COLD, orig_dam); } else { - dam = d(nd, 6); + dam = orig_dam; monstunseesu(M_SEEN_COLD); } if (!rn2(3)) - destroy_item(POTION_CLASS, AD_COLD); + (void) destroy_items(&gy.youmonst, AD_COLD, orig_dam); break; case ZT_SLEEP: if (Sleep_resistance) { @@ -4367,20 +4360,19 @@ zhitu( done(DIED); return; /* lifesaved */ case ZT_LIGHTNING: + orig_dam = d(nd, 6); if (Shock_resistance) { shieldeff(sx, sy); You("aren't affected."); monstseesu(M_SEEN_ELEC); - ugolemeffects(AD_ELEC, d(nd, 6)); + ugolemeffects(AD_ELEC, orig_dam); } else { - dam = d(nd, 6); + dam = orig_dam; exercise(A_CON, FALSE); monstunseesu(M_SEEN_ELEC); } if (!rn2(3)) - destroy_item(WAND_CLASS, AD_ELEC); - if (!rn2(3)) - destroy_item(RING_CLASS, AD_ELEC); + (void) destroy_items(&gy.youmonst, AD_ELEC, orig_dam); break; case ZT_POISON_GAS: poisoned("blast", A_DEX, "poisoned blast", 15, FALSE); @@ -5426,6 +5418,51 @@ break_statue(struct obj *obj) return TRUE; } +/* Return TRUE if obj is eligible to pass to maybe_destroy_item given the type of + * elemental damage it's being subjected to. + * Note that things like the Book of the Dead are eligible even though they + * won't get destroyed, because it will attempt to be destroyed but print a + * special message instead. */ +static boolean +destroyable(struct obj *obj, int adtyp) +{ + if (obj->oartifact) { + /* don't destroy artifacts */ + return FALSE; + } + if (obj->in_use && obj->quan == 1L) { + /* not available for destroying */ + return FALSE; + } + if (adtyp == AD_FIRE) { + /* fire-magic items are immune */ + if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL) { + return FALSE; + } + if (obj->otyp == GLOB_OF_GREEN_SLIME || obj->oclass == POTION_CLASS + || obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS) { + return TRUE; + } + } else if (adtyp == AD_COLD) { + /* non-water potions don't freeze and shatter */ + if (obj->oclass == POTION_CLASS && obj->otyp != POT_OIL) { + return TRUE; + } + } else if (adtyp == AD_ELEC) { + if (obj->oclass != RING_CLASS && obj->oclass != WAND_CLASS) { + return FALSE; + } + /* electric-magic items are immune */ + if (obj->otyp != RIN_SHOCK_RESISTANCE && obj->otyp != WAN_LIGHTNING) { + return TRUE; + } + /* There used to be a commented out bit of code that would exclude + * gc.current_wand, but it wasn't used, so it wasn't moved into this + * function. */ + } + return FALSE; +} + /* convert attack damage AD_foo to property resistance */ static int adtyp_to_prop(int dmgtyp) @@ -5558,14 +5595,25 @@ const char *const destroy_strings[][3] = { { "breaks apart and explodes", "", "exploding wand" }, }; -/* guts of destroy_item(), which ought to be called maybe_destroy_items(); - caller must decide whether obj is eligible */ -static void -destroy_one_item(struct obj *obj, int osym, int dmgtyp) +/* guts of destroy_items(); + caller must decide whether obj is eligible, though there's one case (Book of + the Dead) in which an eligible item shouldn't be destroyed (it prints a + special message instead). + Returns the amount of damage done, but this is used differently depending on + whether it's the player or a monster having an item destroyed: players lose + the HP and possibly die in this function, and the return value is unused, + whereas monsters return the damage to their caller to be taken off later */ +static int +maybe_destroy_item( + struct monst *carrier, + struct obj *obj, + int dmgtyp) { long i, cnt, quan; int dmg, xresist, skip, dindx; const char *mult; + boolean u_carry = (carrier == &gy.youmonst); + boolean vis = !u_carry && canseemon(carrier); boolean chargeit = FALSE; xresist = skip = 0; @@ -5575,30 +5623,29 @@ destroy_one_item(struct obj *obj, int osym, int dmgtyp) /* external worn item protects inventory? */ if (inventory_resistance_check(dmgtyp)) - return; + return 0; switch (dmgtyp) { case AD_COLD: - if (osym == POTION_CLASS && obj->otyp != POT_OIL) { - quan = obj->quan; - dindx = 0; - dmg = rnd(4); - } else - skip++; + quan = obj->quan; + dindx = 0; + dmg = rnd(4); 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++; + xresist = (obj->oclass != POTION_CLASS + && obj->otyp != GLOB_OF_GREEN_SLIME + && (u_carry ? Fire_resistance : resists_fire(carrier))); if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { - skip++; - if (!Blind) + skip = 1; + if (u_carry ? !Blind : vis) { pline("%s glows a strange %s, but remains intact.", - The(xname(obj)), hcolor("dark red")); + The(u_carry ? xname(obj) : distant_name(obj, xname)), + hcolor("dark red")); + } + break; } quan = obj->quan; - switch (osym) { + switch (obj->oclass) { case POTION_CLASS: dindx = (obj->otyp != POT_OIL) ? 1 : 2; dmg = rnd(6); @@ -5611,23 +5658,17 @@ destroy_one_item(struct obj *obj, int osym, int dmgtyp) 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++; + case FOOD_CLASS: /* only GLOB_OF_GREEN_SLIME */ + dindx = 1; /* boil and explode */ + dmg = (obj->owt + 19) / 20; break; } break; case AD_ELEC: - xresist = (Shock_resistance && obj->oclass != RING_CLASS); + xresist = (obj->oclass != RING_CLASS + && (u_carry ? Shock_resistance : resists_elec(carrier))); quan = obj->quan; - switch (osym) { + switch (obj->oclass) { case RING_CLASS: if (((obj->owornmask & W_RING) && uarmg && !is_metallic(uarmg)) || obj->otyp == RIN_SHOCK_RESISTANCE) { @@ -5641,29 +5682,22 @@ destroy_one_item(struct obj *obj, int osym, int dmgtyp) dmg = 0; break; case WAND_CLASS: - if (obj->otyp == WAN_LIGHTNING) { - skip++; - break; - } -#if 0 - if (obj == gc.current_wand) { skip++; break; } -#endif dindx = 6; dmg = rnd(10); break; - default: - skip++; - break; } break; default: - skip++; + skip = 1; /* just in case ineligible damage type gets through... */ + impossible("maybe_destroy_item with unexpected dmgtyp %d", dmgtyp); break; } if (chargeit) { recharge(obj, 0); } else if (!skip) { + char osym = obj->oclass; /* for checking glob of slime after it's + destroyed */ if (obj->in_use) --quan; /* one will be used up elsewhere */ for (i = cnt = 0L; i < quan; i++) @@ -5671,30 +5705,44 @@ destroy_one_item(struct obj *obj, int osym, int dmgtyp) 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(gy.youmonst.data) || haseyes(gy.youmonst.data)) + return 0; + + if (u_carry || vis) { + mult = (cnt == 1L) + ? ((quan == 1L) ? "" /* 1 of 1 */ + : "One of ") /* 1 of N */ + : ((cnt < quan) ? "Some of " /* n of N */ + : (quan == 2L) ? "Both of " /* 2 of 2 */ + : "All of "); /* N of N */ + pline("%s%s %s!", mult, + (cnt == 1L && quan == 1L) ? Yname2(obj) : yname(obj), + destroy_strings[dindx][(cnt > 1L)]); + } + if (u_carry) { /* effects that happen only to the player */ + if (osym == POTION_CLASS && dmgtyp != AD_COLD + && (!breathless(gy.youmonst.data) || haseyes(gy.youmonst.data))) { potionbreathe(obj); + } + if (obj->owornmask) { /* m_useup handles these for monster */ + if (obj->owornmask & W_RING) /* ring being worn */ + Ring_gone(obj); + else + setnotworn(obj); + } + if (obj == gc.current_wand) { + gc.current_wand = 0; /* destroyed */ + } } - if (obj->owornmask) { - if (obj->owornmask & W_RING) /* ring being worn */ - Ring_gone(obj); + for (i = 0; i < cnt; i++) { + if (u_carry) + useup(obj); else - setnotworn(obj); + m_useup(carrier, obj); } - if (obj == gc.current_wand) - gc.current_wand = 0; /* destroyed */ - for (i = 0; i < cnt; i++) - useup(obj); if (dmg) { + if (!u_carry) { + return xresist ? 0 : dmg; + } if (xresist) { You("aren't hurt!"); } else { @@ -5709,26 +5757,66 @@ destroy_one_item(struct obj *obj, int osym, int dmgtyp) } } } + return dmg; } -/* target items of specified class for possible destruction */ -void -destroy_item(int osym, int dmgtyp) +/* scaling factor; dmg/5 stacks of items will be subjected to destroy_items() */ +#define DMG_DESTROY_SCALE 5 +/* largest amount of stacks that will be destroyed in a single call */ +#define MAX_ITEMS_DESTROYED 20 + +/* target items of specified class in mon's inventory for possible destruction + * return total amount of damage inflicted, though this is unused if mon is the + * player */ +int +destroy_items( + struct monst *mon, /* monster whose invent is being subjected to + * destruction */ + int dmgtyp, /* AD_**** - currently only cold, fire, elec */ + int dmg_in) /* the amount of HP damage the attack dealt */ { 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[invlet_gold + invlet_basic + invlet_overflow]; + int i, defer; + int limit; /* max amount of item stacks destroyed, based on damage */ + struct { + unsigned oid; + boolean deferred; + } items_to_destroy[MAX_ITEMS_DESTROYED]; + int elig_stacks = 0; /* number of destroyable objects found so far */ + boolean u_carry = (mon == &gy.youmonst); + /* 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 */ - (void) memset((genericptr_t) deferrals, 0, sizeof deferrals); - /* - * Sometimes destroying an item can change inventory aside from + /* 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].deferred = FALSE; + } + + /* Don't straight up destroy all items with an equal chance; limit it based + * on the amount of damage being dealt by the source of the item + * destruction. */ + limit = dmg_in / DMG_DESTROY_SCALE; + if (dmg_in % DMG_DESTROY_SCALE > rn2(DMG_DESTROY_SCALE)) { + limit++; /* dmg = 9: 20% chance of limit=1, 80% of limit=2, etc */ + } + if (limit > MAX_ITEMS_DESTROYED) { + /* in case of incredibly high damage, prevent from overflowing + * items_to_destroy */ + limit = MAX_ITEMS_DESTROYED; + } + if (limit < 1) { + return 0; /* nothing destroyed */ + } + + /* 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() + * Unlike other uses of the object bypass mechanism, destroy_items() * 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. @@ -5751,21 +5839,27 @@ destroy_item(int osym, int dmgtyp) * of o_id and quantity of what is targeted for destruction, * second pass to handle the destruction.] */ - bypass_objlist(gi.invent, FALSE); /* clear bypass bit for invent */ + bypass_objlist(*objchn, FALSE); /* clear bypass bit for invent */ - while ((obj = nxt_unbypassed_obj(gi.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 */ + while ((obj = nxt_unbypassed_obj(*objchn)) != 0) { + if (!destroyable(obj, dmgtyp)) + continue; /* this dmg type can't destroy this obj */ + + /* obj is eligible; maybe add it to items_to_destroy */ + i = (elig_stacks < limit) ? elig_stacks : rn2(elig_stacks); + /* do this afterwards to avoid not filling items_to_destroy[0] */ + elig_stacks++; + if (i >= limit) { + /* random index was too high */ + continue; + } + items_to_destroy[i].oid = obj->o_id; /* if loss of this item might dump us onto a trap, hold off - until later because potential recursive destroy_item() will + until later because potential recursive destroy_items() 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) + if (u_carry && ((obj->owornmask != 0L && (objects[obj->otyp].oc_oprop == LEVITATION || objects[obj->otyp].oc_oprop == FLYING)) @@ -5773,142 +5867,35 @@ destroy_item(int osym, int dmgtyp) polymorph so don't need to be deferred */ || (obj->otyp == POT_WATER && ismnum(u.ulycn) && (Upolyd ? obj->blessed : obj->cursed)))) { - deferrals[deferral_indx++] = obj->o_id; - continue; + items_to_destroy[i].deferred = TRUE; + } + else { + items_to_destroy[i].deferred = FALSE; } - /* 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 = gi.invent; obj; obj = obj->nobj) - if (obj->o_id == deferrals[i]) { - destroy_one_item(obj, osym, dmgtyp); - break; - } + if (elig_stacks > limit) { + elig_stacks = limit; /* so we can loop up to elig_stacks */ } - return; -} - -int -destroy_mitem(struct monst *mtmp, int osym, int dmgtyp) -{ - struct obj *obj; - int skip, tmp = 0; - long i, cnt, quan; - int dindx; - boolean vis; - - if (mtmp == &gy.youmonst) { /* this simplifies artifact_hit() */ - destroy_item(osym, dmgtyp); - return 0; /* arbitrary; value doesn't matter to artifact_hit() */ - } - - vis = canseemon(mtmp); - - /* see destroy_item(); object destruction could disrupt inventory list */ - bypass_objlist(mtmp->minvent, FALSE); /* clear bypass bit for minvent */ - - while ((obj = nxt_unbypassed_obj(mtmp->minvent)) != 0) { - if (obj->oclass != osym) - continue; /* test only objs of type osym */ - skip = 0; - quan = 0L; - dindx = 0; - - switch (dmgtyp) { - case AD_COLD: - if (osym == POTION_CLASS && obj->otyp != POT_OIL) { - quan = obj->quan; - dindx = 0; - tmp++; - } else - skip++; - break; - case AD_FIRE: - if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL) - skip++; - if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { - skip++; - if (vis) - pline("%s glows a strange %s, but remains intact.", - The(distant_name(obj, xname)), hcolor("dark red")); - } - quan = obj->quan; - switch (osym) { - case POTION_CLASS: - dindx = (obj->otyp != POT_OIL) ? 1 : 2; - tmp++; - break; - case SCROLL_CLASS: - dindx = 3; - tmp++; - break; - case SPBOOK_CLASS: - dindx = 4; - tmp++; - break; - case FOOD_CLASS: - if (obj->otyp == GLOB_OF_GREEN_SLIME) { - dindx = 1; /* boil and explode */ - tmp += (obj->owt + 19) / 20; - } else { - skip++; - } - break; - default: - skip++; - break; - } - break; - case AD_ELEC: - quan = obj->quan; - switch (osym) { - case RING_CLASS: - if (obj->otyp == RIN_SHOCK_RESISTANCE) { - skip++; + for (defer = 0; defer <= 1; ++defer) { + /* 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; } - dindx = 5; - break; - case WAND_CLASS: - if (obj->otyp == WAN_LIGHTNING) { - skip++; - break; - } - dindx = 6; - tmp++; - break; - default: - skip++; - break; } - break; - default: - skip++; - break; - } - if (!skip) { - for (i = cnt = 0L; i < quan; i++) - if (!rn2(3)) - cnt++; - - if (!cnt) - continue; - if (vis) - pline("%s%s %s!", - (cnt == obj->quan) ? "" : (cnt > 1L) ? "Some of " - : "One of ", - (cnt == obj->quan) ? Yname2(obj) : yname(obj), - destroy_strings[dindx][(cnt > 1L)]); - for (i = 0; i < cnt; i++) - m_useup(mtmp, obj); } } - return tmp; + /* almost certainly not everything was destroyed; remove bypass bit after it + * was set earlier */ + bypass_objlist(*objchn, FALSE); + return dmg_out; } int