throw-and-return weapons used by monsters

Resolves #1338
This commit is contained in:
nhmall
2025-03-16 15:37:49 -04:00
parent 7c43654580
commit 0bdf9830e6
8 changed files with 301 additions and 54 deletions

View File

@@ -12,6 +12,7 @@ staticfn const char *breathwep_name(int);
staticfn boolean drop_throw(struct obj *, boolean, coordxy, coordxy);
staticfn boolean blocking_terrain(coordxy, coordxy);
staticfn int m_lined_up(struct monst *, struct monst *) NONNULLARG12;
staticfn void return_from_mtoss(struct monst *, struct obj *, boolean);
#define URETREATING(x, y) \
(distmin(u.ux, u.uy, x, y) > distmin(u.ux0, u.uy0, x, y))
@@ -558,6 +559,10 @@ m_throw(
boolean forcehit;
char sym = obj->oclass;
int hitu = 0, oldumort, blindinc = 0;
const struct throw_and_return_weapon *arw = autoreturn_weapon(obj);
boolean tethered_weapon =
(obj == MON_WEP(mon) && arw && arw->tethered != 0),
return_flightpath = FALSE;
gb.bhitpos.x = x;
gb.bhitpos.y = y;
@@ -619,8 +624,30 @@ m_throw(
* early to avoid the dagger bug, anyone who modifies this code should
* be careful not to use either one after it's been freed.
*/
if (sym)
tmp_at(DISP_FLASH, obj_to_glyph(singleobj, rn2_on_display_rng));
if (sym) {
if (!tethered_weapon) {
tmp_at(DISP_FLASH, obj_to_glyph(singleobj, rn2_on_display_rng));
} else {
tmp_at(DISP_TETHER, obj_to_glyph(singleobj, rn2_on_display_rng));
/*
* Considerations for a tethered object based on in throwit()/bhit() :
* - wall of water/lava will stop items, and triggers return.
* - iron bars will stop items, and triggers return.
* - pass harmlessly through shades.
* X stops forward motion at hit monster/hero, triggers return.
* - closed door will stop item's forward motion, triggers return.
* - sinks stop forward motion, triggers fall, then return.
* - object can get tangled in a web, no return (tether snaps?).
* On return:
* X rn2(100) chance of returning to thrower's location.
* X if impaired and rn2(100) == 0,
* -50/50 chance of landing on the ground.
* -50/50 chance of hitting the thrower and causing
* rnd(3) damage.
*
*/
}
}
while (range-- > 0) { /* Actually the loop is always exited by break */
singleobj->ox = gb.bhitpos.x += dx;
singleobj->oy = gb.bhitpos.y += dy;
@@ -730,7 +757,12 @@ m_throw(
}
stop_occupation();
if (hitu) {
(void) drop_throw(singleobj, hitu, u.ux, u.uy);
if (!tethered_weapon) {
(void) drop_throw(singleobj, hitu, u.ux, u.uy);
} else {
/* ready for return journey */
return_flightpath = TRUE;
}
break;
}
}
@@ -751,8 +783,12 @@ m_throw(
&& (cansee(gb.bhitpos.x, gb.bhitpos.y)
|| (gm.marcher && canseemon(gm.marcher))))
pline("%s misses.", The(mshot_xname(singleobj)));
(void) drop_throw(singleobj, 0, gb.bhitpos.x, gb.bhitpos.y);
if (!tethered_weapon) {
(void) drop_throw(singleobj, 0, gb.bhitpos.x, gb.bhitpos.y);
} else {
/*ready for return journey */
return_flightpath = TRUE;
}
}
break;
}
@@ -761,7 +797,11 @@ m_throw(
}
tmp_at(gb.bhitpos.x, gb.bhitpos.y);
nh_delay_output();
tmp_at(DISP_END, 0);
if (arw && return_flightpath)
return_from_mtoss(mon, singleobj, tethered_weapon);
/* mon could be DEADMONSTER now */
else
tmp_at(DISP_END, 0);
gm.mesg_given = 0; /* reset */
if (blindinc) {
@@ -777,6 +817,121 @@ m_throw(
#undef MT_FLIGHTCHECK
staticfn void
return_from_mtoss(struct monst *magr, struct obj *otmp, boolean tethered_weapon)
{
boolean impaired = (magr->mconf || magr->mstun || magr->mblinded),
notcaught = FALSE, hits_thrower = FALSE;
coordxy x = gb.bhitpos.x, y = gb.bhitpos.y;
int made_it_back = rn2(100), dmg = 0;
if (otmp && made_it_back) {
/* it made it back to thrower's location */
if (tethered_weapon) {
tmp_at(DISP_END, BACKTRACK);
} else {
int dx = sgn(x - magr->mx),
dy = sgn(y - magr->my);
if (x != magr->mx || y != magr->my) {
tmp_at(DISP_FLASH, obj_to_glyph(otmp, rn2_on_display_rng));
while (isok(x, y) && (x != magr->mx || y != magr->my)) {
tmp_at(x, y);
nh_delay_output();
x -= dx;
y -= dy;
}
tmp_at(DISP_END, 0);
}
}
x = magr->mx;
y = magr->my;
if (!impaired && rn2(100)) {
static long do_not_annoy = 0;
if (!do_not_annoy || (svm.moves - do_not_annoy) > 500) {
pline("%s to %s %s!", Tobjnam(otmp, "return"),
s_suffix(mon_nam(magr)), mbodypart(magr, HAND));
do_not_annoy = svm.moves;
}
if (otmp) {
add_to_minv(magr, otmp);
if (tethered_weapon) {
magr->mw = otmp;
otmp->owornmask |= W_WEP;
}
}
if (cansee(x, y))
newsym(x, y);
} else {
boolean mlevitating = FALSE; /* msg future-proofing only */
dmg = rn2(2);
if (!dmg) {
if (!Blind) {
pline("%s back to %s, landing %s %s %s.",
Tobjnam(otmp, "return"), mon_nam(magr),
mlevitating ? "beneath" : "at", mhis(magr),
makeplural(mbodypart(magr, FOOT)));
} else if (!Deaf) {
You_hear("%s land near %s.", Something, mon_nam(magr));
}
} else {
dmg += rnd(3);
if (!Blind) {
pline("%s back toward %s, hitting %s %s!",
Tobjnam(otmp, "fly"),
mon_nam(magr),
mhis(magr),
body_part(ARM));
} else if (!Deaf) {
You_hear("%s hit %s with a thud!", something,
mon_nam(magr));
}
hits_thrower = TRUE;
}
notcaught = TRUE;
}
} else {
/* it didn't make it back to thrower's location */
if (tethered_weapon)
tmp_at(DISP_END, 0);
You_hear("a loud snap!");
notcaught = TRUE;
}
if (otmp) {
if (hits_thrower) {
if (otmp->oartifact)
(void) artifact_hit((struct monst *) 0, magr, otmp, &dmg, 0);
magr->mhp -= dmg;
/* magr could be a DEADMONSTER now */
}
if (notcaught) {
(void) snuff_candle(otmp);
if (!ship_object(otmp, x, y, FALSE)) {
if (flooreffects(otmp, x, y, "drop")) {
if (cansee(x, y))
newsym(x, y);
return;
}
place_object(otmp, x, y);
stackobj(otmp);
}
if (!Deaf && !Underwater) {
/* Some sound effects when item lands in water or lava */
if (is_pool(x, y) || (is_lava(x, y) && !is_flammable(otmp))) {
Soundeffect(se_splash, 50);
pline((weight(otmp) > 9) ? "Splash!" : "Plop!");
}
}
if (obj_sheds_light(otmp))
gv.vision_full_recalc = 1;
}
}
if (cansee(x, y))
newsym(x, y);
}
/* Monster throws item at another monster */
int
thrwmm(struct monst *mtmp, struct monst *mtarg)
@@ -988,6 +1143,9 @@ thrwmu(struct monst *mtmp)
struct obj *otmp, *mwep;
coordxy x, y;
const char *onm;
int rang;
const struct throw_and_return_weapon *arw;
boolean always_toss = FALSE;
/* Rearranged beginning so monsters can use polearms not in a line */
if (mtmp->weapon_check == NEED_WEAPON || !MON_WEP(mtmp)) {
@@ -1003,10 +1161,10 @@ thrwmu(struct monst *mtmp)
return;
if (is_pole(otmp)) {
int dam, hitv, rang;
int dam, hitv;
if (otmp != MON_WEP(mtmp))
return; /* polearm must be wielded */
return; /* polearm, aklys must be wielded */
/*
* MON_POLE_DIST encompasses knight's move range (5): two spots
@@ -1047,6 +1205,11 @@ thrwmu(struct monst *mtmp)
(void) thitu(hitv, dam, &otmp, (char *) 0);
stop_occupation();
return;
} else if ((arw = autoreturn_weapon(otmp)) != 0 && !mwelded(otmp)) {
rang = dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy);
if (rang > arw->range || !couldsee(mtmp->mx, mtmp->my))
return; /* Out of range, or intervening wall */
always_toss = TRUE;
}
x = mtmp->mx;
@@ -1058,7 +1221,8 @@ thrwmu(struct monst *mtmp)
*/
if (!lined_up(mtmp)
|| (URETREATING(x, y)
&& rn2(BOLT_LIM - distmin(x, y, mtmp->mux, mtmp->muy))))
&& (!always_toss
&& rn2(BOLT_LIM - distmin(x, y, mtmp->mux, mtmp->muy)))))
return;
mwep = MON_WEP(mtmp); /* wielded weapon */