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

@@ -11,6 +11,10 @@ extern boolean notonhead;
STATIC_DCL boolean FDECL(dog_hunger, (struct monst *, struct edog *));
STATIC_DCL int FDECL(dog_invent, (struct monst *, struct edog *, int));
STATIC_DCL int FDECL(dog_goal, (struct monst *, struct edog *, int, int, int));
STATIC_DCL struct monst* FDECL(find_targ, (struct monst *, int, int, int));
STATIC_OVL int FDECL(find_friends, (struct monst *, struct monst *, int));
STATIC_DCL struct monst* FDECL(best_target, (struct monst *));
STATIC_DCL long FDECL(score_targ, (struct monst *, struct monst *));
STATIC_DCL boolean FDECL(can_reach_location, (struct monst *, XCHAR_P,
XCHAR_P, XCHAR_P, XCHAR_P));
STATIC_DCL boolean FDECL(could_reach_item, (struct monst *, XCHAR_P, XCHAR_P));
@@ -610,6 +614,249 @@ int after, udist, whappr;
return appr;
}
STATIC_OVL struct monst *
find_targ(mtmp, dx, dy, maxdist)
register struct monst *mtmp;
int dx, dy;
int maxdist;
{
struct monst *targ = 0;
int curx = mtmp->mx, cury = mtmp->my;
int dist = 0;
/* Walk outwards */
for ( ; dist < maxdist; ++dist) {
curx += dx;
cury += dy;
if (!isok(curx, cury))
break;
/* FIXME: Check if we hit a wall/door/boulder to
* short-circuit unnecessary subsequent checks
*/
/* If we can't see up to here, forget it - will this
* mean pets in corridors don't breathe at monsters
* in rooms? If so, is that necessarily bad?
*/
if (!m_cansee(mtmp, curx, cury))
break;
targ = m_at(curx, cury);
if (curx == mtmp->mux && cury == mtmp->muy)
return &youmonst;
if (targ) {
/* Is the monster visible to the pet? */
if ((!targ->minvis || perceives(mtmp->data)) &&
!targ->mundetected)
break;
/* If the pet can't see it, it assumes it aint there */
targ = 0;
}
}
return targ;
}
STATIC_OVL int
find_friends(mtmp, mtarg, maxdist)
struct monst *mtmp, *mtarg;
int maxdist;
{
struct monst *pal;
int dx = sgn(mtarg->mx - mtmp->mx),
dy = sgn(mtarg->my - mtmp->my);
int curx = mtarg->mx, cury = mtarg->my;
int dist = distmin(mtarg->mx, mtarg->my, mtmp->mx, mtmp->my);
for ( ; dist <= maxdist; ++dist) {
curx += dx;
cury += dy;
if (!isok(curx, cury))
return 0;
/* If the pet can't see beyond this point, don't
* check any farther
*/
if (!m_cansee(mtmp, curx, cury))
return 0;
/* Does pet think you're here? */
if (mtmp->mux == curx && mtmp->muy == cury)
return 1;
pal = m_at(curx, cury);
if (pal) {
if (pal->mtame) {
/* Pet won't notice invisible pets */
if (!pal->minvis || perceives(mtmp->data))
return 1;
} else {
/* Quest leaders and guardians are always seen */
if (pal->data->msound == MS_LEADER
|| pal->data->msound == MS_GUARDIAN)
return 1;
}
}
}
return 0;
}
STATIC_OVL long
score_targ(mtmp, mtarg)
struct monst *mtmp, *mtarg;
{
long score = 0L;
/* If the monster is confused, normal scoring is disrupted -
* anything may happen
*/
/* Give 1 in 3 chance of safe breathing even if pet is confused or
* if you're on the quest start level */
if (!mtmp->mconf || !rn2(3) || Is_qstart(&u.uz)) {
aligntyp align1, align2; /* For priests, minions */
boolean faith1 = TRUE, faith2 = TRUE;
if (mtmp->isminion) align1 = EMIN(mtmp)->min_align;
else if (mtmp->ispriest) align1 = EPRI(mtmp)->shralign;
else faith1 = FALSE;
if (mtarg->isminion) align2 = EMIN(mtarg)->min_align; /* MAR */
else if (mtarg->ispriest) align2 = EPRI(mtarg)->shralign; /* MAR */
else faith2 = FALSE;
/* Never target quest friendlies */
if (mtarg->data->msound == MS_LEADER
|| mtarg->data->msound == MS_GUARDIAN)
return -5000L;
/* D: Fixed angelic beings using gaze attacks on coaligned priests */
if (faith1 && faith2 && align1 == align2 && mtarg->mpeaceful) {
score -= 5000L;
return score;
}
/* Is monster adjacent? */
if (distmin(mtmp->mx, mtmp->my, mtarg->mx, mtarg->my) <= 1) {
score -= 3000L;
return score;
}
/* Is the monster peaceful or tame? */
if (/*mtarg->mpeaceful ||*/ mtarg->mtame || mtarg == &youmonst) {
/* Pets will never be targeted */
score -= 3000L;
return score;
}
/* Is master/pet behind monster? Check up to 15 squares beyond
* pet.
*/
if (find_friends(mtmp, mtarg, 15)) {
score -= 3000L;
return score;
}
/* Target hostile monsters in preference to peaceful ones */
if (!mtarg->mpeaceful)
score += 10;
/* Is the monster passive? Don't waste energy on it, if so */
if (mtarg->data->mattk[0].aatyp == AT_NONE)
score -= 1000;
/* Even weak pets with breath attacks shouldn't take on very
* low-level monsters. Wasting breath on lichens is ridiculous.
*/
if ((mtarg->m_lev < 2 && mtmp->m_lev > 5)
|| (mtmp->m_lev > 12 && mtarg->m_lev < mtmp->m_lev - 9
&& u.ulevel > 8 && mtarg->m_lev < u.ulevel - 7))
score -= 25;
/* And pets will hesitate to attack vastly stronger foes.
* This penalty will be discarded if master's in trouble.
*/
if (mtarg->m_lev > mtmp->m_lev + 4L)
score -= (mtarg->m_lev - mtmp->m_lev) * 20L;
/* All things being the same, go for the beefiest monster. This
* bonus should not be large enough to override the pet's aversion
* to attacking much stronger monsters.
*/
score += mtarg->m_lev * 2 + mtarg->mhp / 3;
}
/* Fuzz factor to make things less predictable when very
* similar targets are abundant
*/
score += rnd(5);
/* Pet may decide not to use ranged attack when confused */
if (mtmp->mconf && !rn2(3))
score -= 1000;
return score;
}
STATIC_OVL struct monst *
best_target(mtmp)
struct monst *mtmp; /* Pet */
{
int dx, dy;
long bestscore = -40000L, currscore;
struct monst *best_targ = 0, *temp_targ = 0;
/* Help! */
if (!mtmp)
return 0;
/* If the pet is blind, it's not going to see any target */
if (!mtmp->mcansee)
return 0;
/* Search for any monsters lined up with the pet, within an arbitrary
* distance from the pet (7 squares, even along diagonals). Monsters
* are assigned scores and the best score is chosen.
*/
for (dy = -1; dy < 2; ++dy) {
for (dx = -1; dx < 2; ++dx) {
if (!dx && !dy)
continue;
/* Traverse the line to find the first monster within 7
* squares. Invisible monsters are skipped (if the
* pet doesn't have see invisible).
*/
temp_targ = find_targ(mtmp, dx, dy, 7);
/* Nothing in this line? */
if (!temp_targ)
continue;
/* Decide how attractive the target is */
currscore = score_targ(mtmp, temp_targ);
if (currscore > bestscore) {
bestscore = currscore;
best_targ = temp_targ;
}
}
}
/* Filter out targets the pet doesn't like */
if (bestscore < 0L)
best_targ = 0;
return best_targ;
}
/* return 0 (no move), 1 (move) or 2 (dead) */
int
dog_move(mtmp, after)
@@ -872,6 +1119,62 @@ int after; /* this is extra fast monster movement */
nxti:
;
}
/* Pet hasn't attacked anything but is considering moving -
* now's the time for ranged attacks. Note that the pet can
* move after it performs its ranged attack. Should this be
* changed?
*/
{
struct monst *mtarg;
int hungry = 0;
/* How hungry is the pet? */
if (!mtmp->isminion) {
struct edog *dog = EDOG(mtmp);
hungry = (monstermoves > (dog->hungrytime + 300));
}
/* Identify the best target in a straight line from the pet;
* if there is such a target, we'll let the pet attempt an
* attack.
*/
mtarg = best_target(mtmp);
/* Hungry pets are unlikely to use breath/spit attacks */
if (mtarg && (!hungry || !rn2(5))) {
int mstatus;
if (mtarg == &youmonst) {
if (mattacku(mtmp))
return 2;
} else {
mstatus = mattackm(mtmp, mtarg);
/* Shouldn't happen, really */
if (mstatus & MM_AGR_DIED) return 2;
/* Allow the targeted nasty to strike back - if
* the targeted beast doesn't have a ranged attack,
* nothing will happen.
*/
if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED)
&& rn2(4) && mtarg != &youmonst) {
/* Can monster see? If it can, it can retaliate
* even if the pet is invisible, since it'll see
* the direction from which the ranged attack came;
* if it's blind or unseeing, it can't retaliate
*/
if (mtarg->mcansee && haseyes(mtarg->data)) {
mstatus = mattackm(mtarg, mtmp);
if (mstatus & MM_DEF_DIED) return 2;
}
}
}
}
}
newdogpos:
if (nix != omx || niy != omy) {
boolean wasseen;