From 9052bd50992972d045b3cac69d51be197029787e Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 20 May 2023 15:34:32 -0700 Subject: [PATCH] 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). --- doc/fixes3-7-0.txt | 2 +- src/dogmove.c | 17 +++++++++--- src/hack.c | 6 ++--- src/mhitm.c | 66 ++++++++++++++++++++++++++++++---------------- src/mhitu.c | 31 +++++++++++++++------- src/uhitm.c | 24 ++++++++++++----- 6 files changed, 99 insertions(+), 47 deletions(-) diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index afa642959..7721bc578 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -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 diff --git a/src/dogmove.c b/src/dogmove.c index ac19348a2..f373fbd00 100644 --- a/src/dogmove.c +++ b/src/dogmove.c @@ -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; diff --git a/src/hack.c b/src/hack.c index 17547ea31..7f7ba2eea 100644 --- a/src/hack.c +++ b/src/hack.c @@ -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(). diff --git a/src/mhitm.c b/src/mhitm.c index 0eee0e3d8..5bd612c1c 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -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; diff --git a/src/mhitu.c b/src/mhitu.c index f17538a19..e79f6d47e 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -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 diff --git a/src/uhitm.c b/src/uhitm.c index 319da0081..5748c6598 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -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)); + } } }