Allow pets to use ranged attacks

This is the Pet ranged attack -patch by Darshan Shaligram,
with the spellcaster parts removed to keep it simpler.

Pets will now throw, spit and breathe at other monsters.
This commit is contained in:
Pasi Kallinen
2016-06-04 01:06:00 +03:00
parent 4d594f77c2
commit 5009a67264
6 changed files with 626 additions and 11 deletions

View File

@@ -5,12 +5,15 @@
#include "hack.h"
STATIC_DCL int FDECL(drop_throw, (struct obj *, BOOLEAN_P, int, int));
STATIC_DCL boolean FDECL(m_lined_up, (struct monst *, struct monst *));
#define URETREATING(x, y) \
(distmin(u.ux, u.uy, x, y) > distmin(u.ux0, u.uy0, x, y))
#define POLE_LIM 5 /* How far monsters can use pole-weapons */
#define PET_MISSILE_RANGE2 36 /* Square of distance within which pets shoot */
/*
* Keep consistent with breath weapons in zap.c, and AD_* in monattk.h.
*/
@@ -129,6 +132,11 @@ int x, y;
return retvalu;
}
/* The monster that's being shot at when one monster shoots at another */
STATIC_OVL struct monst *target = 0;
/* The monster that's doing the shooting/throwing */
STATIC_OVL struct monst *archer = 0;
/* an object launched by someone/thing other than player attacks a monster;
return 1 if the object has stopped moving (hit or its range used up) */
int
@@ -143,17 +151,27 @@ boolean verbose; /* give message(s) even when you can't see what happened */
int damage, tmp;
boolean vis, ismimic;
int objgone = 1;
struct obj *mon_launcher = archer ? MON_WEP(archer) : NULL;
notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my);
ismimic = mtmp->m_ap_type && mtmp->m_ap_type != M_AP_MONSTER;
vis = cansee(bhitpos.x, bhitpos.y);
tmp = 5 + find_mac(mtmp) + omon_adj(mtmp, otmp, FALSE);
/* High level monsters will be more likely to hit */
/* This check applies only if this monster is the target
* the archer was aiming at. */
if (archer && target == mtmp) {
if (archer->m_lev > 5)
tmp += archer->m_lev - 5;
if (mon_launcher && mon_launcher->oartifact)
tmp += spec_abon(mon_launcher, mtmp);
}
if (tmp < rnd(20)) {
if (!ismimic) {
if (vis)
miss(distant_name(otmp, mshot_xname), mtmp);
else if (verbose)
else if (verbose && !target)
pline("It is missed.");
}
if (!range) { /* Last position; object drops */
@@ -177,7 +195,7 @@ boolean verbose; /* give message(s) even when you can't see what happened */
mtmp->msleeping = 0;
if (vis)
hit(distant_name(otmp, mshot_xname), mtmp, exclam(damage));
else if (verbose)
else if (verbose && !target)
pline("%s is hit%s", Monnam(mtmp), exclam(damage));
if (otmp->opoisoned && is_poisonable(otmp)) {
@@ -199,24 +217,24 @@ boolean verbose; /* give message(s) even when you can't see what happened */
&& mon_hates_silver(mtmp)) {
if (vis)
pline_The("silver sears %s flesh!", s_suffix(mon_nam(mtmp)));
else if (verbose)
else if (verbose && !target)
pline("Its flesh is seared!");
}
if (otmp->otyp == ACID_VENOM && cansee(mtmp->mx, mtmp->my)) {
if (resists_acid(mtmp)) {
if (vis || verbose)
if (vis || (verbose && !target))
pline("%s is unaffected.", Monnam(mtmp));
damage = 0;
} else {
if (vis)
pline_The("acid burns %s!", mon_nam(mtmp));
else if (verbose)
else if (verbose && !target)
pline("It is burned!");
}
}
mtmp->mhp -= damage;
if (mtmp->mhp < 1) {
if (vis || verbose)
if (vis || (verbose && !target))
pline("%s is %s!", Monnam(mtmp),
(nonliving(mtmp->data) || is_vampshifter(mtmp)
|| !canspotmon(mtmp))
@@ -482,6 +500,230 @@ struct obj *obj; /* missile (or stack providing it) */
}
}
int
thrwmm(mtmp, mtarg) /* Monster throws item at monster */
struct monst *mtmp, *mtarg;
{
struct obj *otmp, *mwep;
register xchar x, y;
boolean ispole;
schar skill;
int multishot = 1;
/* Polearms won't be applied by monsters against other monsters */
if (mtmp->weapon_check == NEED_WEAPON || !MON_WEP(mtmp)) {
mtmp->weapon_check = NEED_RANGED_WEAPON;
/* mon_wield_item resets weapon_check as appropriate */
if(mon_wield_item(mtmp) != 0) return 0;
}
/* Pick a weapon */
otmp = select_rwep(mtmp);
if (!otmp) return 0;
ispole = is_pole(otmp);
skill = objects[otmp->otyp].oc_skill;
x = mtmp->mx;
y = mtmp->my;
mwep = MON_WEP(mtmp); /* wielded weapon */
if(!ispole && m_lined_up(mtarg, mtmp)) {
/* WAC Catch this since rn2(0) is illegal */
int chance = (BOLT_LIM-distmin(x,y,mtarg->mx,mtarg->my) > 0) ?
BOLT_LIM-distmin(x,y,mtarg->mx,mtarg->my) : 1;
if(!mtarg->mflee || !rn2(chance)) {
const char *verb = "throws";
if (otmp->otyp == ARROW
|| otmp->otyp == ELVEN_ARROW
|| otmp->otyp == ORCISH_ARROW
|| otmp->otyp == YA
|| otmp->otyp == CROSSBOW_BOLT) verb = "shoots";
if (ammo_and_launcher(otmp, mwep) && is_launcher(mwep)) {
if (dist2(mtmp->mx, mtmp->my, mtarg->mx, mtarg->my) >
PET_MISSILE_RANGE2)
return 0; /* Out of range */
}
if (canseemon(mtmp)) {
pline("%s %s %s!", Monnam(mtmp), verb,
obj_is_pname(otmp) ?
the(singular(otmp, xname)) :
an(singular(otmp, xname)));
}
/* Multishot calculations */
if ((ammo_and_launcher(otmp, mwep) || skill == P_DAGGER
|| skill == -P_DART || skill == -P_SHURIKEN)
&& !mtmp->mconf) {
/* Assumes lords are skilled, princes are expert */
if (is_lord(mtmp->data)) multishot++;
if (is_prince(mtmp->data)) multishot += 2;
/* Elven Craftsmanship makes for light, quick bows */
if (otmp->otyp == ELVEN_ARROW && !otmp->cursed)
multishot++;
if (mwep && mwep->otyp == ELVEN_BOW &&
!mwep->cursed) multishot++;
/* 1/3 of object enchantment */
if (mwep && mwep->spe > 1)
multishot += (long) rounddiv(mwep->spe,3);
/* Some randomness */
if (multishot > 1L)
multishot = (long) rnd((int) multishot);
switch (monsndx(mtmp->data)) {
case PM_RANGER:
multishot++;
break;
case PM_ROGUE:
if (skill == P_DAGGER) multishot++;
break;
case PM_SAMURAI:
if (otmp->otyp == YA && mwep &&
mwep->otyp == YUMI) multishot++;
break;
default:
break;
}
{ /* racial bonus */
if (is_elf(mtmp->data) &&
otmp->otyp == ELVEN_ARROW &&
mwep && mwep->otyp == ELVEN_BOW)
multishot++;
else if (is_orc(mtmp->data) &&
otmp->otyp == ORCISH_ARROW &&
mwep && mwep->otyp == ORCISH_BOW)
multishot++;
}
}
if (otmp->quan < multishot) multishot = (int)otmp->quan;
if (multishot < 1) multishot = 1;
/* Set target monster */
target = mtarg;
archer = mtmp;
while (multishot-- > 0)
m_throw(mtmp, mtmp->mx, mtmp->my,
sgn(tbx), sgn(tby),
distmin(mtmp->mx, mtmp->my,
mtarg->mx, mtarg->my),
otmp);
archer = (struct monst *)0;
target = (struct monst *)0;
nomul(0);
return 1;
}
}
return 0;
}
/* monster spits substance at monster */
int
spitmm(mtmp, mattk, mtarg)
struct monst *mtmp, *mtarg;
struct attack *mattk;
{
struct obj *otmp;
if (mtmp->mcan) {
if (!Deaf)
pline("A dry rattle comes from %s throat.",
s_suffix(mon_nam(mtmp)));
return 0;
}
if (m_lined_up(mtarg, mtmp)) {
switch (mattk->adtyp) {
case AD_BLND:
case AD_DRST:
otmp = mksobj(BLINDING_VENOM, TRUE, FALSE);
break;
default:
impossible("bad attack type in spitmu");
/* fall through */
case AD_ACID:
otmp = mksobj(ACID_VENOM, TRUE, FALSE);
break;
}
if (!rn2(BOLT_LIM-distmin(mtmp->mx,mtmp->my,mtarg->mx,mtarg->my))) {
if (canseemon(mtmp))
pline("%s spits venom!", Monnam(mtmp));
target = mtarg;
m_throw(mtmp, mtmp->mx, mtmp->my, sgn(tbx), sgn(tby),
distmin(mtmp->mx,mtmp->my,mtarg->mx,mtarg->my), otmp);
target = (struct monst *)0;
nomul(0);
/* If this is a pet, it'll get hungry. Minions and
* spell beings won't hunger */
if (mtmp->mtame && !mtmp->isminion) {
struct edog *dog = EDOG(mtmp);
/* Hunger effects will catch up next move */
if (dog->hungrytime > 1)
dog->hungrytime -= 5;
}
return 1;
}
}
return 0;
}
int
breamm(mtmp, mattk, mtarg) /* monster breathes at monster (ranged) */
struct monst *mtmp, *mtarg;
struct attack *mattk;
{
/* if new breath types are added, change AD_ACID to max type */
int typ = (mattk->adtyp == AD_RBRE) ? rnd(AD_ACID) : mattk->adtyp ;
if (m_lined_up(mtarg, mtmp)) {
if (mtmp->mcan) {
if (!Deaf) {
if (canseemon(mtmp))
pline("%s coughs.", Monnam(mtmp));
else
You_hear("a cough.");
}
return(0);
}
if (!mtmp->mspec_used && rn2(3)) {
if ((typ >= AD_MAGM) && (typ <= AD_ACID)) {
if (canseemon(mtmp))
pline("%s breathes %s!", Monnam(mtmp),
breathwep[typ-1]);
dobuzz((int) (-20 - (typ-1)), (int)mattk->damn,
mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), FALSE);
nomul(0);
/* breath runs out sometimes. Also, give monster some
* cunning; don't breath if the target fell asleep.
*/
mtmp->mspec_used = 6+rn2(18);
/* If this is a pet, it'll get hungry. Minions and
* spell beings won't hunger */
if (mtmp->mtame && !mtmp->isminion) {
struct edog *dog = EDOG(mtmp);
/* Hunger effects will catch up next move */
if (dog->hungrytime >= 10)
dog->hungrytime -= 10;
}
} else impossible("Breath weapon %d used", typ-1);
} else
return (0);
}
return(1);
}
/* remove an entire item from a monster's inventory; destroy that item */
void
m_useupall(mon, obj)
@@ -803,6 +1045,14 @@ int boulderhandling; /* 0=block, 1=ignore, 2=conditionally block */
return FALSE;
}
STATIC_OVL boolean
m_lined_up(mtarg, mtmp)
struct monst *mtarg, *mtmp;
{
return (linedup(mtarg->mx, mtarg->my, mtmp->mx, mtmp->my, 0));
}
/* is mtmp in position to use ranged attack? */
boolean
lined_up(mtmp)