fix #K3925 - u.ustuck of long worm tail

Don't allow stick/wrap/engulf attacks directed at long worm tails
to succeed.  Achieved by making sure that 'notonhead' is up do date
in a bunch of places and utilizing the fairly recent can't-{stick,wrap,
engulf}-unsolid-monsters code.

Should prevent a 'sanity_check' warning about being too far from
u.ustuck that would happen when holding the tail while the head was
not adjacent to the hero.

Also don't let pet ranged attacks from choosing a long worm's tail
as target.  They'll still be able to target long worms provided that
the head is lined up and not shielded by tail segment(s).
This commit is contained in:
PatR
2023-05-20 15:34:32 -07:00
parent f176318c45
commit 9052bd5099
6 changed files with 99 additions and 47 deletions

View File

@@ -1145,7 +1145,7 @@ 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)
vortices, a few others) or against worm tails
wand of speed gives temporary speed, potion gives intrinsic
some monsters (riders, shopkeepers, priests, quest leader) can break boulders
corpse-eating monsters will go out of their way to eat corpses on the floor

View File

@@ -660,8 +660,9 @@ find_targ(
if ((targ = m_at(curx, cury)) != 0) {
/* Is the monster visible to the pet? */
if ((!targ->minvis || perceives(mtmp->data))
&& !targ->mundetected)
if ((!targ->minvis || perceives(mtmp->data)) && !targ->mundetected
/* if a long worm, only accept the head as a target */
&& targ->mx == curx && targ->my == cury) /* not tail */
break;
/* If the pet can't see it, it assumes it aint there */
targ = 0;
@@ -898,6 +899,8 @@ pet_ranged_attk(struct monst *mtmp)
*/
mstatus = M_ATTK_HIT;
} else {
gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my;
gn.notonhead = FALSE;
mstatus = mattackm(mtmp, mtarg);
/* Shouldn't happen, really */
@@ -917,8 +920,11 @@ pet_ranged_attk(struct monst *mtmp)
* if it's blind or unseeing, it can't retaliate
*/
if (mtarg->mcansee && haseyes(mtarg->data)) {
int mresp = mattackm(mtarg, mtmp);
int mresp;
gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my;
gn.notonhead = FALSE;
mresp = mattackm(mtarg, mtmp);
if (mresp & M_ATTK_DEF_DIED)
return MMOVE_DIED;
}
@@ -1107,7 +1113,8 @@ dog_move(
if (after)
return MMOVE_NOTHING; /* hit only once each move */
gn.notonhead = 0;
gb.bhitpos.x = nx, gb.bhitpos.y = ny;
gn.notonhead = mtmp2->mx != nx || mtmp2->my != ny;
mstatus = mattackm(mtmp, mtmp2);
/* aggressor (pet) died */
@@ -1120,6 +1127,8 @@ dog_move(
&& !onscary(mtmp->mx, mtmp->my, mtmp2)
/* monnear check needed: long worms hit on tail */
&& monnear(mtmp2, mtmp->mx, mtmp->my)) {
gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my;
gn.notonhead = FALSE;
mstatus = mattackm(mtmp2, mtmp); /* return attack */
if (mstatus & M_ATTK_DEF_DIED)
return MMOVE_DIED;

View File

@@ -1748,9 +1748,9 @@ domove_attackmon_at(
coordxy x, coordxy y,
boolean *displaceu)
{
/* only attack if we know it's there */
/* or if we used the 'F' command to fight blindly */
/* or if it hides_under, in which case we call do_attack() to print
/* only attack if we know it's there
* or if we used the 'F' command to fight blindly
* or if it hides_under, in which case we call do_attack() to print
* the Wait! message.
* This is different from ceiling hiders, who aren't handled in
* do_attack().

View File

@@ -139,9 +139,8 @@ fightm(register struct monst *mtmp)
}
/* mtmp can be killed */
gb.bhitpos.x = mon->mx;
gb.bhitpos.y = mon->my;
gn.notonhead = 0;
gb.bhitpos.x = mon->mx, gb.bhitpos.y = mon->my;
gn.notonhead = FALSE;
result = mattackm(mtmp, mon);
if (result & M_ATTK_AGR_DIED)
@@ -161,7 +160,8 @@ fightm(register struct monst *mtmp)
mon->movement -= NORMAL_SPEED;
else
mon->movement = 0;
gn.notonhead = 0;
gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my;
gn.notonhead = FALSE;
(void) mattackm(mon, mtmp); /* return attack */
}
@@ -177,8 +177,10 @@ fightm(register struct monst *mtmp)
* returns same results as mattackm().
*/
int
mdisplacem(register struct monst *magr, register struct monst *mdef,
boolean quietly)
mdisplacem(
struct monst *magr,
struct monst *mdef,
boolean quietly)
{
struct permonst *pa, *pd;
int tx, ty, fx, fy;
@@ -286,7 +288,9 @@ mdisplacem(register struct monst *magr, register struct monst *mdef,
* In the case of exploding monsters, the monster dies as well.
*/
int
mattackm(register struct monst *magr, register struct monst *mdef)
mattackm(
register struct monst *magr,
register struct monst *mdef)
{
int i, /* loop counter */
tmp, /* armor class difference */
@@ -569,14 +573,15 @@ mattackm(register struct monst *magr, register struct monst *mdef)
return (struck ? M_ATTK_HIT : M_ATTK_MISS);
}
/* can't hold an unsolid target (ghosts, lights, vortices, most elementals) */
/* can't hold an unsolid target (ghosts, lights, vortices, most elementals)
or a long worm tail */
boolean
failed_grab(
struct monst *magr,
struct monst *mdef,
struct attack *mattk)
{
if (unsolid(mdef->data)
if ((unsolid(mdef->data) || gn.notonhead)
/* hug attack: most holders (owlbear, python, pit fiend, &c);
wrap damage: eel grabbing, trapper/lurker-above engulfing;
stick-to damage: mimic, lichen;
@@ -586,21 +591,30 @@ failed_grab(
if ((gv.vis && canspotmon(mdef)) /* mon-vs-mon */
|| magr == &gy.youmonst || mdef == &gy.youmonst) {
char magrnam[BUFSZ], mdefnam[BUFSZ];
boolean tailmiss = gn.notonhead;
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 */
s_suffix() uses a single static buffer; make copies of both
names to overcome that [note: comment predates 'tailmiss'] */
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);
if (!tailmiss) {
Strcpy(mdefnam, (mdef == &gy.youmonst) ? "you"
: mon_nam(mdef));
} else {
/* hero poly'd into long worm can't grow tail
so no 'youmonst' handling is needed here */
Sprintf(mdefnam, "%s tail", s_suffix(some_mon_nam(mdef)));
}
/* unsolid grab misses are actually somewhat iffy--how come
ordinary attacks don't also pass right through? */
pline("%.99s %s attempt %s %.99s!", magrnam, verb,
!tailmiss ? "passes right through" : "fails to hold",
mdefnam);
}
return TRUE;
}
@@ -972,8 +986,12 @@ explmm(struct monst *magr, struct monst *mdef, struct attack *mattk)
* See comment at top of mattackm(), for return values.
*/
static int
mdamagem(struct monst *magr, struct monst *mdef,
struct attack *mattk, struct obj *mwep, int dieroll)
mdamagem(
struct monst *magr,
struct monst *mdef,
struct attack *mattk,
struct obj *mwep,
int dieroll)
{
struct permonst *pa = magr->data, *pd = mdef->data;
struct mhitm_data mhm;
@@ -1247,11 +1265,15 @@ mswingsm(
* handled above. Returns same values as mattackm.
*/
static int
passivemm(register struct monst *magr, register struct monst *mdef,
boolean mhitb, int mdead, struct obj *mwep)
passivemm(
struct monst *magr,
struct monst *mdef,
boolean mhitb,
int mdead,
struct obj *mwep)
{
register struct permonst *mddat = mdef->data;
register struct permonst *madat = magr->data;
struct permonst *mddat = mdef->data;
struct permonst *madat = magr->data;
char buf[BUFSZ];
int i, tmp;
int mhit = mhitb ? M_ATTK_HIT : M_ATTK_MISS;

View File

@@ -391,14 +391,21 @@ getmattk(struct monst *magr, struct monst *mdef,
/* calc some variables needed for mattacku() */
static void
calc_mattacku_vars(struct monst *mtmp,
boolean *ranged, boolean *range2,
boolean *foundyou, boolean *youseeit)
calc_mattacku_vars(
struct monst *mtmp,
boolean *ranged, boolean *range2,
boolean *foundyou, boolean *youseeit)
{
*ranged = (mdistu(mtmp) > 3);
*range2 = !monnear(mtmp, mtmp->mux, mtmp->muy);
*foundyou = u_at(mtmp->mux, mtmp->muy);
*youseeit = canseemon(mtmp);
/* do_attack() uses bhitpos to set/clear notonhead; do likewise here */
gb.bhitpos.x = u.ux, gb.bhitpos.y = u.uy;
/* hero poly'd into a long worm isn't allowed to grow a tail, so
hitting tail instead of head can't happen */
gn.notonhead = FALSE;
}
/*
@@ -453,17 +460,19 @@ mattacku(register struct monst *mtmp)
/* Your steed won't attack you */
return 0;
/* Orcs like to steal and eat horses and the like */
if (!rn2(is_orc(mtmp->data) ? 2 : 4)
&& next2u(mtmp->mx, mtmp->my)) {
/* Attack your steed instead */
if (!rn2(is_orc(mtmp->data) ? 2 : 4) && next2u(mtmp->mx, mtmp->my)) {
/* attack your steed instead; 'bhitpos' and 'notonhead' are
already set from tagetting hero */
i = mattackm(mtmp, u.usteed);
if ((i & M_ATTK_AGR_DIED))
if ((i & M_ATTK_AGR_DIED) != 0)
return 1;
/* make sure steed is still alive and within range */
if ((i & M_ATTK_DEF_DIED) || !u.usteed
if ((i & M_ATTK_DEF_DIED) != 0 || !u.usteed
|| !next2u(mtmp->mx, mtmp->my))
return 0;
/* Let your steed retaliate */
gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my;
gn.notonhead = FALSE;
return !!(mattackm(u.usteed, mtmp) & M_ATTK_DEF_DIED);
}
}
@@ -565,8 +574,10 @@ mattacku(register struct monst *mtmp)
else
pline(
"Wait, %s! There's a %s named %s hiding under %s!",
m_monnam(mtmp), pmname(gy.youmonst.data, Ugender),
gp.plname, doname(gl.level.objects[u.ux][u.uy]));
m_monnam(mtmp),
pmname(gy.youmonst.data, Ugender),
gp.plname,
doname(gl.level.objects[u.ux][u.uy]));
if (obj)
obj->spe = save_spe;
} else

View File

@@ -646,6 +646,7 @@ hitum_cleave(
static boolean clockwise = FALSE;
int i;
coord save_bhitpos;
boolean save_notonhead;
int count, umort, x = u.ux, y = u.uy;
/* find the direction toward primary target */
@@ -661,6 +662,7 @@ hitum_cleave(
i = clockwise ? DIR_LEFT2(i) : DIR_RIGHT2(i);
umort = u.umortality; /* used to detect life-saving */
save_bhitpos = gb.bhitpos;
save_notonhead = gn.notonhead;
/*
* Three attacks: adjacent to primary, primary, adjacent on other
@@ -692,8 +694,8 @@ hitum_cleave(
mon_maybe_unparalyze(mtmp);
dieroll = rnd(20);
mhit = (tmp > dieroll);
gb.bhitpos.x = tx, gb.bhitpos.y = ty; /* normally set up by
do_attack() */
gb.bhitpos.x = tx, gb.bhitpos.y = ty; /* normally set by do_attack() */
gn.notonhead = (mtmp->mx != tx || mtmp->my != ty);
(void) known_hitum(mtmp, uwep, &mhit, tmp, armorpenalty,
uattk, dieroll);
(void) passive(mtmp, uwep, mhit, !DEADMONSTER(mtmp), AT_WEAP, !uwep);
@@ -706,7 +708,8 @@ hitum_cleave(
/* set up for next time */
clockwise = !clockwise; /* alternate */
gb.bhitpos = save_bhitpos; /* in case somebody relies on bhitpos
* designating the primary target */
* designating the primary target */
gn.notonhead = save_notonhead;
/* return False if primary target died, True otherwise; note: if 'target'
was nonNull upon entry then it's still nonNull even if *target died */
@@ -3116,7 +3119,9 @@ mhitm_ad_wrap(
if (magr == &gy.youmonst) {
/* uhitm */
if (!sticks(pd)) {
if (!u.ustuck && !rn2(10)) {
boolean tailmiss = !gn.notonhead;
if (!u.ustuck && !tailmiss && !rn2(10)) {
if (m_slips_free(mdef, mattk)) {
mhm->damage = 0;
} else {
@@ -3124,7 +3129,7 @@ mhitm_ad_wrap(
coil ? "coil" : "swing", mon_nam(mdef));
set_ustuck(mdef);
}
} else if (u.ustuck == mdef) {
} else if (u.ustuck == mdef && !tailmiss) {
/* Monsters don't wear amulets of magical breathing */
if (is_pool(u.ux, u.uy) && !cant_drown(pd)) {
You("drown %s...", mon_nam(mdef));
@@ -3134,11 +3139,11 @@ mhitm_ad_wrap(
} else {
mhm->damage = 0;
if (Verbose(4, mhitm_ad_wrap1)) {
if (coil)
if (coil && !tailmiss)
You("brush against %s.", mon_nam(mdef));
else
You("brush against %s %s.", s_suffix(mon_nam(mdef)),
mbodypart(mdef, LEG));
tailmiss ? "tail" : mbodypart(mdef, LEG));
}
}
} else
@@ -3187,6 +3192,11 @@ mhitm_ad_wrap(
/* mhitm */
if (magr->mcan)
mhm->damage = 0;
if (!mhm->damage && (canseemon(magr) || canseemon(mdef))) {
pline("%s brushes against %s.",
Some_Monnam(magr), some_mon_nam(mdef));
}
}
}