Skip to content

Commit

Permalink
fix #K3902 - hug attacks against unsolid targets
Browse files Browse the repository at this point in the history
Prevent hug attacks (owlbear, python, pit fiend, several others),
attacks for wrap damage (eel and kraken, trapper and lurker above),
attacks for stick-to damage (mimic, lichen), and attacks for digestion
damage (purple worm) from succeeding against unsolid monsters (ghosts,
lights, vortices, most elementals).

Polymorph of an engulf or hold target into unsolid form has been
addressed by a couple of previous updates.
  • Loading branch information
PatR committed Apr 14, 2023
1 parent ced75cb commit 1a2d844
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 30 deletions.
3 changes: 3 additions & 0 deletions doc/fixes3-7-0.txt
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions include/extern.h
Expand Up @@ -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);
Expand Down
109 changes: 83 additions & 26 deletions src/mhitm.c
Expand Up @@ -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 */
}
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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])
Expand All @@ -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);
}
}
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;

Expand Down
11 changes: 9 additions & 2 deletions src/mhitu.c
Expand Up @@ -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);
Expand All @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down
17 changes: 15 additions & 2 deletions src/uhitm.c
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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));
Expand Down

0 comments on commit 1a2d844

Please sign in to comment.