diff --git a/doc/fixes34.1 b/doc/fixes34.1 index e2b5b12b7..3eef1b57f 100644 --- a/doc/fixes34.1 +++ b/doc/fixes34.1 @@ -345,6 +345,8 @@ avoid discrepancies in size and associated armor-wearing ability between by forcing newman() if poly-target matches your_race() add missing data.base entries for caveman, healer, monk, priest, and samurai allow "grey spellbook" as alternative spelling of "gray spellbook" +handle attacks by cancelled monsters more consistently +armor worn by monsters might negate some magic attacks like it does for hero Platform- and/or Interface-Specific Fixes diff --git a/include/extern.h b/include/extern.h index 1c2920417..0b8fb70d1 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)extern.h 3.4 2002/08/22 */ +/* SCCS Id: @(#)extern.h 3.4 2003/01/02 */ /* Copyright (c) Steve Creps, 1988. */ /* NetHack may be freely redistributed. See license for details. */ @@ -954,6 +954,7 @@ E struct monst *NDECL(cloneu); E void FDECL(expels, (struct monst *,struct permonst *,BOOLEAN_P)); E struct attack *FDECL(getmattk, (struct permonst *,int,int *,struct attack *)); E int FDECL(mattacku, (struct monst *)); +E int FDECL(magic_negation, (struct monst *)); E int FDECL(gazemu, (struct monst *,struct attack *)); E void FDECL(mdamageu, (struct monst *,int)); E int FDECL(could_seduce, (struct monst *,struct monst *,struct attack *)); diff --git a/src/mhitm.c b/src/mhitm.c index 8cae9ed84..ca47ca082 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)mhitm.c 3.4 2002/12/09 */ +/* SCCS Id: @(#)mhitm.c 3.4 2003/01/02 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -573,7 +573,8 @@ mdamagem(magr, mdef, mattk) struct obj *obj; char buf[BUFSZ]; struct permonst *pa = magr->data, *pd = mdef->data; - int num, tmp = d((int)mattk->damn, (int)mattk->damd); + int armpro, num, tmp = d((int)mattk->damn, (int)mattk->damd); + boolean cancelled; if (touch_petrifies(pd) && !resists_ston(magr)) { long protector = attk_protection(mattk->aatyp), @@ -597,6 +598,10 @@ mdamagem(magr, mdef, mattk) } } + /* cancellation factor is the same as when attacking the hero */ + armpro = magic_negation(mdef); + cancelled = magr->mcan || !((rn2(3) >= armpro) || !rn2(50)); + switch(mattk->adtyp) { case AD_DGST: /* eating a Rider or its corpse is fatal */ @@ -650,18 +655,24 @@ mdamagem(magr, mdef, mattk) pline("%s %s for a moment.", Monnam(mdef), makeplural(stagger(mdef->data, "stagger"))); mdef->mstun = 1; - /* fall through */ + goto physical; + case AD_LEGS: + if (magr->mcan) { + tmp = 0; + break; + } + goto physical; case AD_WERE: case AD_HEAL: - case AD_LEGS: case AD_PHYS: - if (mattk->aatyp == AT_KICK && thick_skinned(pd)) - tmp = 0; - else if(mattk->aatyp == AT_WEAP) { + physical: + if (mattk->aatyp == AT_KICK && thick_skinned(pd)) { + tmp = 0; + } else if(mattk->aatyp == AT_WEAP) { if(otmp) { if (otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm])) - goto do_stone_goto_label; + goto do_stone; tmp += dmgval(otmp, mdef); if (otmp->oartifact) { (void)artifact_hit(magr,mdef, otmp, &tmp, dieroll); @@ -682,7 +693,7 @@ mdamagem(magr, mdef, mattk) } break; case AD_FIRE: - if (magr->mcan) { + if (cancelled) { tmp = 0; break; } @@ -713,7 +724,7 @@ mdamagem(magr, mdef, mattk) tmp += destroy_mitem(mdef, POTION_CLASS, AD_FIRE); break; case AD_COLD: - if (magr->mcan) { + if (cancelled) { tmp = 0; break; } @@ -729,7 +740,7 @@ mdamagem(magr, mdef, mattk) tmp += destroy_mitem(mdef, POTION_CLASS, AD_COLD); break; case AD_ELEC: - if (magr->mcan) { + if (cancelled) { tmp = 0; break; } @@ -762,7 +773,8 @@ mdamagem(magr, mdef, mattk) if (!rn2(6)) erode_obj(MON_WEP(mdef), TRUE, TRUE); break; case AD_RUST: - if (!magr->mcan && pd == &mons[PM_IRON_GOLEM]) { + if (magr->mcan) break; + if (pd == &mons[PM_IRON_GOLEM]) { if (vis) pline("%s falls to pieces!", Monnam(mdef)); mondied(mdef); if (mdef->mhp > 0) return 0; @@ -776,13 +788,15 @@ mdamagem(magr, mdef, mattk) tmp = 0; break; case AD_CORR: + if (magr->mcan) break; hurtmarmor(mdef, AD_CORR); mdef->mstrategy &= ~STRAT_WAITFORU; tmp = 0; break; case AD_DCAY: - if (!magr->mcan && (pd == &mons[PM_WOOD_GOLEM] || - pd == &mons[PM_LEATHER_GOLEM])) { + if (magr->mcan) break; + if (pd == &mons[PM_WOOD_GOLEM] || + pd == &mons[PM_LEATHER_GOLEM]) { if (vis) pline("%s falls to pieces!", Monnam(mdef)); mondied(mdef); if (mdef->mhp > 0) return 0; @@ -796,9 +810,10 @@ mdamagem(magr, mdef, mattk) tmp = 0; break; case AD_STON: -do_stone_goto_label: + if (magr->mcan) break; + do_stone: /* may die from the acid if it eats a stone-curing corpse */ - if (munstone(mdef, FALSE)) goto label2; + if (munstone(mdef, FALSE)) goto post_stone; if (poly_when_stoned(pd)) { mon_to_stone(mdef); tmp = 0; @@ -807,7 +822,7 @@ do_stone_goto_label: if (!resists_ston(mdef)) { if (vis) pline("%s turns to stone!", Monnam(mdef)); monstone(mdef); -label2: if (mdef->mhp > 0) return 0; + post_stone: if (mdef->mhp > 0) return 0; else if (mdef->mtame && !vis) You(brief_feeling, "peculiarly sad"); return (MM_DEF_DIED | (grow_up(magr,mdef) ? @@ -816,7 +831,7 @@ label2: if (mdef->mhp > 0) return 0; tmp = (mattk->adtyp == AD_STON ? 0 : 1); break; case AD_TLPT: - if (!magr->mcan && tmp < mdef->mhp && !tele_restrict(mdef)) { + if (!cancelled && tmp < mdef->mhp && !tele_restrict(mdef)) { char mdef_Monnam[BUFSZ]; /* save the name before monster teleports, otherwise we'll get "it" in the suddenly disappears message */ @@ -832,7 +847,7 @@ label2: if (mdef->mhp > 0) return 0; } break; case AD_SLEE: - if (!magr->mcan && !mdef->msleeping && + if (!cancelled && !mdef->msleeping && sleep_monst(mdef, rnd(10), -1)) { if (vis) { Strcpy(buf, Monnam(mdef)); @@ -843,7 +858,7 @@ label2: if (mdef->mhp > 0) return 0; } break; case AD_PLYS: - if(!magr->mcan && mdef->mcanmove) { + if(!cancelled && mdef->mcanmove) { if (vis) { Strcpy(buf, Monnam(mdef)); pline("%s is frozen by %s.", buf, mon_nam(magr)); @@ -854,7 +869,7 @@ label2: if (mdef->mhp > 0) return 0; } break; case AD_SLOW: - if (!magr->mcan && vis && mdef->mspeed != MSLOW) { + if (!cancelled && mdef->mspeed != MSLOW) { unsigned int oldspeed = mdef->mspeed; mon_adjust_speed(mdef, -1, (struct obj *)0); @@ -956,7 +971,7 @@ label2: if (mdef->mhp > 0) return 0; } break; case AD_DRLI: - if (rn2(2) && !resists_drli(mdef)) { + if (!cancelled && !rn2(3) && !resists_drli(mdef)) { tmp = d(2,6); if (vis) pline("%s suddenly seems weaker!", Monnam(mdef)); @@ -972,13 +987,13 @@ label2: if (mdef->mhp > 0) return 0; #endif case AD_SITM: /* for now these are the same */ case AD_SEDU: + if (magr->mcan) break; /* find an object to steal, non-cursed if magr is tame */ - for (obj = mdef->minvent; obj; obj = obj->nobj) { + for (obj = mdef->minvent; obj; obj = obj->nobj) if (!magr->mtame || !obj->cursed) break; - } - if (!magr->mcan && obj) { + if (obj) { char onambuf[BUFSZ], mdefnambuf[BUFSZ]; /* make a special x_monnam() call that never omits @@ -1027,7 +1042,7 @@ label2: if (mdef->mhp > 0) return 0; case AD_DRST: case AD_DRDX: case AD_DRCO: - if (!magr->mcan && !rn2(8)) { + if (!cancelled && !rn2(8)) { if (vis) pline("%s %s was poisoned!", s_suffix(Monnam(magr)), mpoisons_subj(magr, mattk)); @@ -1075,18 +1090,25 @@ label2: if (mdef->mhp > 0) return 0; s_suffix(Monnam(mdef))); break; case AD_SLIM: - if (!rn2(4) && mdef->data != &mons[PM_FIRE_VORTEX] && - mdef->data != &mons[PM_FIRE_ELEMENTAL] && - mdef->data != &mons[PM_SALAMANDER] && - mdef->data != &mons[PM_GREEN_SLIME]) { - (void) newcham(mdef, &mons[PM_GREEN_SLIME], FALSE, vis); + if (cancelled) break; /* physical damage only */ + if (!rn2(4) && mdef->data != &mons[PM_FIRE_VORTEX] && + mdef->data != &mons[PM_FIRE_ELEMENTAL] && + mdef->data != &mons[PM_SALAMANDER] && + mdef->data != &mons[PM_GREEN_SLIME]) { + (void) newcham(mdef, &mons[PM_GREEN_SLIME], FALSE, vis); mdef->mstrategy &= ~STRAT_WAITFORU; - tmp = 0; - } - break; + tmp = 0; + } + break; case AD_STCK: + if (cancelled) tmp = 0; + break; case AD_WRAP: /* monsters cannot grab one another, it's too hard */ - case AD_ENCH: /* There's no msomearmor() function, so just do damage */ + if (magr->mcan) tmp = 0; + break; + case AD_ENCH: + /* there's no msomearmor() function, so just do damage */ + /* if (cancelled) break; */ break; default: tmp = 0; break; diff --git a/src/mhitu.c b/src/mhitu.c index 4eafbe9ab..f67d8664e 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)mhitu.c 3.4 2002/12/09 */ +/* SCCS Id: @(#)mhitu.c 3.4 2003/01/02 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -789,6 +789,51 @@ struct attack *mattk; return FALSE; } +/* armor that sufficiently covers the body might be able to block magic */ +int +magic_negation(mon) +struct monst *mon; +{ + struct obj *armor; + int armpro = 0; + + armor = (mon == &youmonst) ? uarm : which_armor(mon, W_ARM); + if (armor && armpro < objects[armor->otyp].a_can) + armpro = objects[armor->otyp].a_can; + armor = (mon == &youmonst) ? uarmc : which_armor(mon, W_ARMC); + if (armor && armpro < objects[armor->otyp].a_can) + armpro = objects[armor->otyp].a_can; + armor = (mon == &youmonst) ? uarmh : which_armor(mon, W_ARMH); + if (armor && armpro < objects[armor->otyp].a_can) + armpro = objects[armor->otyp].a_can; + + /* armor types for shirt, gloves, shoes, and shield don't currently + provide any magic cancellation but we might as well be complete */ +#ifdef TOURIST + armor = (mon == &youmonst) ? uarmu : which_armor(mon, W_ARMU); + if (armor && armpro < objects[armor->otyp].a_can) + armpro = objects[armor->otyp].a_can; +#endif + armor = (mon == &youmonst) ? uarmg : which_armor(mon, W_ARMG); + if (armor && armpro < objects[armor->otyp].a_can) + armpro = objects[armor->otyp].a_can; + armor = (mon == &youmonst) ? uarmf : which_armor(mon, W_ARMF); + if (armor && armpro < objects[armor->otyp].a_can) + armpro = objects[armor->otyp].a_can; + armor = (mon == &youmonst) ? uarms : which_armor(mon, W_ARMS); + if (armor && armpro < objects[armor->otyp].a_can) + armpro = objects[armor->otyp].a_can; + +#ifdef STEED + /* this one is really a stretch... */ + armor = (mon == &youmonst) ? 0 : which_armor(mon, W_SADDLE); + if (armor && armpro < objects[armor->otyp].a_can) + armpro = objects[armor->otyp].a_can; +#endif + + return armpro; +} + /* * hitmu: monster hits you * returns 2 if monster dies (e.g. "yellow light"), 1 otherwise @@ -842,13 +887,7 @@ hitmu(mtmp, mattk) /* Use uncancelled when the cancellation factor takes into account certain * armor's special magic protection. Otherwise just use !mtmp->mcan. */ - armpro = 0; - if (uarm && armpro < objects[uarm->otyp].a_can) - armpro = objects[uarm->otyp].a_can; - if (uarmc && armpro < objects[uarmc->otyp].a_can) - armpro = objects[uarmc->otyp].a_can; - if (uarmh && armpro < objects[uarmh->otyp].a_can) - armpro = objects[uarmh->otyp].a_can; + armpro = magic_negation(&youmonst); uncancelled = !mtmp->mcan && ((rn2(3) >= armpro) || !rn2(50)); permdmg = 0; @@ -1053,14 +1092,15 @@ dopois: case AD_PLYS: hitmsg(mtmp, mattk); if (uncancelled && multi >= 0 && !rn2(3)) { - if (Free_action) You("momentarily stiffen."); - else { - if (Blind) You("are frozen!"); - else You("are frozen by %s!", mon_nam(mtmp)); - nomovemsg = 0; /* default: "you can move again" */ - nomul(-rnd(10)); - exercise(A_DEX, FALSE); - } + if (Free_action) { + You("momentarily stiffen."); + } else { + if (Blind) You("are frozen!"); + else You("are frozen by %s!", mon_nam(mtmp)); + nomovemsg = 0; /* default: "you can move again" */ + nomul(-rnd(10)); + exercise(A_DEX, FALSE); + } } break; case AD_DRLI: @@ -1076,6 +1116,7 @@ dopois: /* This case is too obvious to ignore, but Nethack is not in * general very good at considering height--most short monsters * still _can_ attack you when you're flying or mounted. + * [FIXME: why can't a flying attacker overcome this?] */ if ( #ifdef STEED @@ -1084,9 +1125,11 @@ dopois: Levitation || Flying) { pline("%s tries to reach your %s %s!", Monnam(mtmp), sidestr, body_part(LEG)); + dmg = 0; } else if (mtmp->mcan) { pline("%s nuzzles against your %s %s!", Monnam(mtmp), sidestr, body_part(LEG)); + dmg = 0; } else { if (uarmf) { if (rn2(2) && (uarmf->otyp == LOW_BOOTS || @@ -1099,6 +1142,7 @@ dopois: else { pline("%s scratches your %s boot!", Monnam(mtmp), sidestr); + dmg = 0; break; } } else pline("%s pricks your %s %s!", Monnam(mtmp), @@ -1120,7 +1164,7 @@ dopois: You_hear("%s hissing!", s_suffix(mon_nam(mtmp))); if(!rn2(10) || (flags.moonphase == NEW_MOON && !have_lizard())) { -do_stone: + do_stone: if (!Stoned && !Stone_resistance && !(poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM))) { @@ -1295,6 +1339,11 @@ do_stone: hurtarmor(AD_DCAY); break; case AD_HEAL: + /* a cancelled nurse is just an ordinary monster */ + if (mtmp->mcan) { + hitmsg(mtmp, mattk); + break; + } if(!uwep #ifdef TOURIST && !uarmu diff --git a/src/uhitm.c b/src/uhitm.c index 70682137e..f03e12aff 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)uhitm.c 3.4 2002/12/26 */ +/* SCCS Id: @(#)uhitm.c 3.4 2003/01/02 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1241,6 +1241,12 @@ register struct attack *mattk; { register struct permonst *pd = mdef->data; register int tmp = d((int)mattk->damn, (int)mattk->damd); + int armpro; + boolean negated; + + armpro = magic_negation(mdef); + /* since hero can't be cancelled, only defender's armor applies */ + negated = !((rn2(3) >= armpro) || !rn2(50)); if (is_demon(youmonst.data) && !rn2(13) && !uwep && u.umonnum != PM_SUCCUBUS && u.umonnum != PM_INCUBUS @@ -1254,11 +1260,17 @@ register struct attack *mattk; pline("%s %s for a moment.", Monnam(mdef), makeplural(stagger(mdef->data, "stagger"))); mdef->mstun = 1; - /* fall through to next case */ - case AD_WERE: /* no effect on monsters */ - case AD_HEAL: + goto physical; case AD_LEGS: + /* if (u.ucancelled) { */ + /* tmp = 0; */ + /* break; */ + /* } */ + goto physical; + case AD_WERE: /* no special effect on monsters */ + case AD_HEAL: /* likewise */ case AD_PHYS: + physical: if(mattk->aatyp == AT_WEAP) { if(uwep) tmp = 0; } else if(mattk->aatyp == AT_KICK) { @@ -1273,13 +1285,17 @@ register struct attack *mattk; } break; case AD_FIRE: + if (negated) { + tmp = 0; + break; + } if (!Blind) pline("%s is %s!", Monnam(mdef), on_fire(mdef->data, mattk)); if (pd == &mons[PM_STRAW_GOLEM] || pd == &mons[PM_PAPER_GOLEM]) { if (!Blind) - pline("%s burns completely!", Monnam(mdef)); + pline("%s burns completely!", Monnam(mdef)); xkilled(mdef,2); tmp = 0; break; @@ -1298,6 +1314,10 @@ register struct attack *mattk; tmp += destroy_mitem(mdef, POTION_CLASS, AD_FIRE); break; case AD_COLD: + if (negated) { + tmp = 0; + break; + } if (!Blind) pline("%s is covered in frost!", Monnam(mdef)); if (resists_cold(mdef)) { shieldeff(mdef->mx, mdef->my); @@ -1309,6 +1329,10 @@ register struct attack *mattk; tmp += destroy_mitem(mdef, POTION_CLASS, AD_COLD); break; case AD_ELEC: + if (negated) { + tmp = 0; + break; + } if (!Blind) pline("%s is zapped!", Monnam(mdef)); tmp += destroy_mitem(mdef, WAND_CLASS, AD_ELEC); if (resists_elec(mdef)) { @@ -1365,8 +1389,8 @@ register struct attack *mattk; tmp = 0; break; case AD_TLPT: - if(tmp <= 0) tmp = 1; - if(tmp < mdef->mhp) { + if (tmp <= 0) tmp = 1; + if (!negated && tmp < mdef->mhp) { char nambuf[BUFSZ]; boolean u_saw_mon = canseemon(mdef); /* record the name before losing sight of monster */ @@ -1403,7 +1427,7 @@ register struct attack *mattk; tmp = 0; break; case AD_DRLI: - if (rn2(2) && !resists_drli(mdef)) { + if (!negated && !rn2(3) && !resists_drli(mdef)) { int xtmp = d(2,6); pline("%s suddenly seems weaker!", Monnam(mdef)); mdef->mhpmax -= xtmp; @@ -1412,8 +1436,8 @@ register struct attack *mattk; xkilled(mdef,0); } else mdef->m_lev--; + tmp = 0; } - tmp = 0; break; case AD_RUST: if (pd == &mons[PM_IRON_GOLEM]) { @@ -1439,7 +1463,7 @@ register struct attack *mattk; case AD_DRST: case AD_DRDX: case AD_DRCO: - if (!rn2(8)) { + if (!negated && !rn2(8)) { Your("%s was poisoned!", mpoisons_subj(&youmonst, mattk)); if (resists_poison(mdef)) pline_The("poison doesn't seem to affect %s.", @@ -1498,7 +1522,7 @@ register struct attack *mattk; exercise(A_WIS, TRUE); break; case AD_STCK: - if (!sticks(mdef->data)) + if (!negated && !sticks(mdef->data)) u.ustuck = mdef; /* it's now stuck to you */ break; case AD_WRAP: @@ -1529,34 +1553,53 @@ register struct attack *mattk; } else tmp = 0; break; case AD_PLYS: - if (mdef->mcanmove && !rn2(3) && tmp < mdef->mhp) { + if (!negated && mdef->mcanmove && !rn2(3) && tmp < mdef->mhp) { if (!Blind) pline("%s is frozen by you!", Monnam(mdef)); mdef->mcanmove = 0; mdef->mfrozen = rnd(10); } break; case AD_SLEE: - if (!mdef->msleeping && sleep_monst(mdef, rnd(10), -1)) { + if (!negated && !mdef->msleeping && + sleep_monst(mdef, rnd(10), -1)) { if (!Blind) pline("%s is put to sleep by you!", Monnam(mdef)); slept_monst(mdef); } break; case AD_SLIM: - if (!rn2(4) && mdef->data != &mons[PM_FIRE_VORTEX] && - mdef->data != &mons[PM_FIRE_ELEMENTAL] && - mdef->data != &mons[PM_SALAMANDER] && - mdef->data != &mons[PM_GREEN_SLIME]) { - You("turn %s into slime.", mon_nam(mdef)); - (void) newcham(mdef, &mons[PM_GREEN_SLIME], FALSE, FALSE); - tmp = 0; - } - break; + if (negated) break; /* physical damage only */ + if (!rn2(4) && mdef->data != &mons[PM_FIRE_VORTEX] && + mdef->data != &mons[PM_FIRE_ELEMENTAL] && + mdef->data != &mons[PM_SALAMANDER] && + mdef->data != &mons[PM_GREEN_SLIME]) { + You("turn %s into slime.", mon_nam(mdef)); + (void) newcham(mdef, &mons[PM_GREEN_SLIME], FALSE, FALSE); + tmp = 0; + } + break; case AD_ENCH: /* KMH -- remove enchantment (disenchanter) */ - /* There's no msomearmor() function, so just do damage */ - break; + /* there's no msomearmor() function, so just do damage */ + /* if (negated) break; */ + break; + case AD_SLOW: + if (!negated && mdef->mspeed != MSLOW) { + unsigned int oldspeed = mdef->mspeed; + + mon_adjust_speed(mdef, -1, (struct obj *)0); + if (mdef->mspeed != oldspeed && canseemon(mdef)) + pline("%s slows down.", Monnam(mdef)); + } + break; + case AD_CONF: + if (!mdef->mconf) { + if (canseemon(mdef)) + pline("%s looks confused.", Monnam(mdef)); + mdef->mconf = 1; + } + break; default: tmp = 0; - break; + break; } mdef->mstrategy &= ~STRAT_WAITFORU; /* in case player is very fast */