diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index 231b19406..ea6be36d7 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1143,6 +1143,9 @@ if hero is engulfed and polymorphs into a monster form which is too big to be engulfed, make engulfer expel the poly'd hero give feedback if monster holding onto the hero has to let go when hero polys into a form which can't be held +prevent hug attacks and touch or engulf attacks for wrap, stick-to, and + digestion damage from succeeding against unsolid targets (ghosts, + vortices, a few others) Fixes to 3.7.0-x General Problems Exposed Via git Repository diff --git a/include/extern.h b/include/extern.h index deda4d8ec..b5758eef7 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1342,6 +1342,7 @@ extern void dump_glyphids(void); extern int fightm(struct monst *); extern int mdisplacem(struct monst *, struct monst *, boolean); extern int mattackm(struct monst *, struct monst *); +extern boolean failed_grab(struct monst *, struct monst *, struct attack *); extern boolean engulf_target(struct monst *, struct monst *); extern int mon_poly(struct monst *, struct monst *, int); extern void paralyze_monst(struct monst *, int); diff --git a/src/mhitm.c b/src/mhitm.c index 583339d8c..6c5c52f93 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -153,12 +153,14 @@ fightm(register struct monst *mtmp) if (has_u_swallowed) return 0; - /* Allow attacked monsters a chance to hit back. Primarily - * to allow monsters that resist conflict to respond. - */ - if ((result & M_ATTK_HIT) && !(result & M_ATTK_DEF_DIED) && rn2(4) - && mon->movement >= NORMAL_SPEED) { - mon->movement -= NORMAL_SPEED; + /* allow attacked monsters a chance to hit back, primarily + to allow monsters that resist conflict to respond */ + if ((result & (M_ATTK_HIT | M_ATTK_DEF_DIED)) == M_ATTK_HIT + && rn2(4) && mon->movement > rn2(NORMAL_SPEED)) { + if (mon->movement > NORMAL_SPEED) + mon->movement -= NORMAL_SPEED; + else + mon->movement = 0; gn.notonhead = 0; (void) mattackm(mon, mtmp); /* return attack */ } @@ -287,7 +289,7 @@ int mattackm(register struct monst *magr, register struct monst *mdef) { int i, /* loop counter */ - tmp, /* amour class difference */ + tmp, /* armor class difference */ strike = 0, /* hit this attack */ attk, /* attack attempted this time */ struck = 0, /* hit at least once */ @@ -360,13 +362,13 @@ mattackm(register struct monst *magr, register struct monst *mdef) for (i = 0; i < NATTK; i++) { res[i] = M_ATTK_MISS; mattk = getmattk(magr, mdef, i, res, &alt_attk); - mwep = (struct obj *) 0; - attk = 1; /* reduce verbosity for mind flayer attacking creature without a head (or worm's tail); this is similar to monster with multiple attacks after a wildmiss against displaced or invisible hero */ if (gs.skipdrin && mattk->aatyp == AT_TENT && mattk->adtyp == AD_DRIN) continue; + mwep = (struct obj *) 0; + attk = 1; switch (mattk->aatyp) { case AT_WEAP: /* "hand to hand" attacks */ @@ -420,6 +422,14 @@ mattackm(register struct monst *magr, register struct monst *mdef) if (mwep) tmp -= hitval(mwep, mdef); if (strike) { + /* for eel AT_TUCH+AD_WRAP attack: can't grab an unsolid + target; the unsolid test is redundant since failed_grab + checks it too, but is cheap and avoids calling failed_grab + for ordinary targets */ + if (unsolid(mdef->data) && failed_grab(magr, mdef, mattk)) { + strike = 0; + break; + } res[i] = hitmm(magr, mdef, mattk, mwep, dieroll); if ((mdef->data == &mons[PM_BLACK_PUDDING] || mdef->data == &mons[PM_BROWN_PUDDING]) @@ -429,13 +439,9 @@ mattackm(register struct monst *magr, register struct monst *mdef) struct monst *mclone; if ((mclone = clone_mon(mdef, 0, 0)) != 0) { - if (gv.vis && canspotmon(mdef)) { - char buf[BUFSZ]; - - Strcpy(buf, Monnam(mdef)); - pline("%s divides as %s hits it!", buf, - mon_nam(magr)); - } + if (gv.vis && canspotmon(mdef)) + pline("%s divides as %s hits it!", + Monnam(mdef), mon_nam(magr)); (void) mintrap(mclone, NO_TRAP_FLAGS); } } @@ -444,10 +450,19 @@ mattackm(register struct monst *magr, register struct monst *mdef) break; case AT_HUGS: /* automatic if prev two attacks succeed */ - strike = (i >= 2 && res[i - 1] == M_ATTK_HIT && res[i - 2] == M_ATTK_HIT); - if (strike) - res[i] = hitmm(magr, mdef, mattk, (struct obj *) 0, 0); - + strike = (i >= 2 && res[i - 1] == M_ATTK_HIT + && res[i - 2] == M_ATTK_HIT); + if (strike) { + /* note: monsters with hug attacks don't wear cloaks or gloves + so this doesn't need a special case for hugging a shade + while covered by blessed armor (which does damage but does + not achieve a successful hold); likewise, rope golems can't + wield weapons so ability to choke isn't affected by such */ + if (failed_grab(magr, mdef, mattk)) + strike = 0; + else + res[i] = hitmm(magr, mdef, mattk, (struct obj *) 0, 0); + } break; case AT_GAZE: @@ -484,12 +499,16 @@ mattackm(register struct monst *magr, register struct monst *mdef) if (distmin(magr->mx, magr->my, mdef->mx, mdef->my) > 1) continue; /* Engulfing attacks are directed at the hero if possible. -dlc */ - if (engulfing_u(magr)) + if (engulfing_u(magr)) { strike = 0; - else if ((strike = (tmp > rnd(20 + i))) != 0) - res[i] = gulpmm(magr, mdef, mattk); - else + } else if ((strike = (tmp > rnd(20 + i))) != 0) { + if (failed_grab(magr, mdef, mattk)) + strike = 0; /* purple worm can't swallow unsolid mons */ + else + res[i] = gulpmm(magr, mdef, mattk); + } else { missmm(magr, mdef, mattk); + } break; case AT_BREA: @@ -542,11 +561,49 @@ mattackm(register struct monst *magr, register struct monst *mdef) return res[i]; if (res[i] & M_ATTK_HIT) struck = 1; /* at least one hit */ - } + } /* for (;i < NATTK;) loop */ return (struck ? M_ATTK_HIT : M_ATTK_MISS); } +/* can't hold an unsolid target (ghosts, lights, vortices, most elementals) */ +boolean +failed_grab( + struct monst *magr, + struct monst *mdef, + struct attack *mattk) +{ + if (unsolid(mdef->data) + /* hug attack: most holders (owlbear, python, pit fiend, &c); + wrap damage: eel grabbing, trapper/lurker-above engulfing; + stick-to damage: mimic, lichen; + digestion damage: purple worm swallowing */ + && (mattk->aatyp == AT_HUGS || mattk->adtyp == AD_WRAP + || mattk->adtyp == AD_STCK || mattk->adtyp == AD_DGST)) { + if ((gv.vis && canspotmon(mdef)) /* mon-vs-mon */ + || magr == &gy.youmonst || mdef == &gy.youmonst) { + char magrnam[BUFSZ], mdefnam[BUFSZ]; + const char *verb = (mattk->adtyp == AD_DGST) ? "gulp" + : (mattk->adtyp == AD_STCK) ? "adhere" + : "grab"; + + /* beware of "Foo's grab passes through Bar's ghost"; + mon_nam(x_monnam) calls s_suffix() for named ghosts and + s_suffix() uses a single static buffer; make copies of + both names to overcome that */ + Strcpy(magrnam, (magr == &gy.youmonst) ? "Your" + : s_suffix(Monnam(magr))); + Strcpy(mdefnam, (mdef == &gy.youmonst) ? "you" : mon_nam(mdef)); + /* this is actually somewhat iffy--how come ordinary attacks + don't also pass right through? */ + pline("%.99s %s attempt passes right through %.99s!", + magrnam, verb, mdefnam); + } + return TRUE; + } + return FALSE; +} + /* Returns the result of mdamagem(). */ static int hitmm( @@ -726,7 +783,7 @@ engulf_target(struct monst *magr, struct monst *mdef) return FALSE; /* (hypothetical) engulfers who can pass through walls aren't - limited by rock|trees|bars */ + limited by rock|trees|bars */ if ((magr == &gy.youmonst) ? Passes_walls : passes_walls(magr->data)) return TRUE; diff --git a/src/mhitu.c b/src/mhitu.c index 08928a9fc..65f947c48 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -701,6 +701,9 @@ mattacku(register struct monst *mtmp) || !touch_petrifies(gy.youmonst.data))) { if (foundyou) { if (tmp > (j = rnd(20 + i))) { + if (unsolid(gy.youmonst.data) + && failed_grab(mtmp, &gy.youmonst, mattk)) + continue; if (mattk->aatyp != AT_KICK || !thick_skinned(gy.youmonst.data)) sum[i] = hitmu(mtmp, mattk); @@ -717,8 +720,10 @@ mattacku(register struct monst *mtmp) case AT_HUGS: /* automatic if prev two attacks succeed */ /* Note: if displaced, prev attacks never succeeded */ if ((!range2 && i >= 2 && sum[i - 1] && sum[i - 2]) - || mtmp == u.ustuck) - sum[i] = hitmu(mtmp, mattk); + || mtmp == u.ustuck) { + if (!failed_grab(mtmp, &gy.youmonst, mattk)) + sum[i] = hitmu(mtmp, mattk); + } break; case AT_GAZE: /* can affect you either ranged or not */ @@ -1182,6 +1187,8 @@ gulpmu(struct monst *mtmp, struct attack *mattk) return M_ATTK_MISS; if ((t && is_pit(t->ttyp)) && sobj_at(BOULDER, u.ux, u.uy)) return M_ATTK_MISS; + if (failed_grab(mtmp, &gy.youmonst, mattk)) + return M_ATTK_MISS; if (Punished) unplacebc(); /* ball&chain go away */ diff --git a/src/uhitm.c b/src/uhitm.c index f10accf3d..b22c9acd2 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -5254,6 +5254,11 @@ hmonas(struct monst *mon) Your("%s %s harmlessly through %s.", verb, vtense(verb, "pass"), mon_nam(mon)); } else { + /* either not a shade or no special silver/blessed damage, + other unsolid monsters are immune to AT_TUCH+AD_WRAP */ + if (failed_grab(&gy.youmonst, mon, mattk)) + break; /* miss; message already given */ + if (mattk->aatyp == AT_TENT) { Your("tentacles suck %s.", mon_nam(mon)); } else { @@ -5333,6 +5338,9 @@ hmonas(struct monst *mon) } break; } + /* can't grab unsolid creatures (checked after shade handling) */ + if (failed_grab(&gy.youmonst, mon, mattk)) + break; /* hug attack against ordinary foe */ if (mon == u.ustuck) { pline("%s is being %s%s.", Monnam(mon), @@ -5371,12 +5379,17 @@ hmonas(struct monst *mon) mon_maybe_unparalyze(mon); if ((dhit = (tmp > rnd(20 + i)))) { wakeup(mon, TRUE); + /* can't engulf unsolid creatures */ if (mon->data == &mons[PM_SHADE]) { + /* no specialdmg check needed */ Your("attempt to surround %s is harmless.", mon_nam(mon)); + } else if (failed_grab(&gy.youmonst, mon, mattk)) { + ; /* non-shade miss; message already given */ } else { sum[i] = gulpum(mon, mattk); - if (sum[i] == M_ATTK_DEF_DIED && (mon->data->mlet == S_ZOMBIE - || mon->data->mlet == S_MUMMY) + if (sum[i] == M_ATTK_DEF_DIED + && (mon->data->mlet == S_ZOMBIE + || mon->data->mlet == S_MUMMY) && rn2(5) && !Sick_resistance) { You_feel("%ssick.", (Sick) ? "very " : ""); mdamageu(mon, rnd(8));