From: Pasi Kallinen Date: Fri, 3 Jun 2016 22:06:00 +0000 (+0300) Subject: Allow pets to use ranged attacks X-Git-Tag: NetHack-3.6.1_RC01~705 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=5009a672644fbadab73dbf9ec8181a4c07a9bb3c;p=nethack 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. --- diff --git a/doc/fixes36.1 b/doc/fixes36.1 index c0c09b019..b3d6a6a9f 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -436,6 +436,7 @@ Ray Chason's proper background tiles for lava and water Ray Chason's MS-DOS port restored to functionality with credit to Reddit user b_helyer for the fix to sys/share/pcmain.c Ray Chason's MSDOS port support for some VESA modes +Darshan Shaligram's pet ranged attack Code Cleanup and Reorganization diff --git a/include/extern.h b/include/extern.h index e21ba04ec..fe375b7dd 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1512,6 +1512,9 @@ E int FDECL(breamu, (struct monst *, struct attack *)); E boolean FDECL(linedup, (XCHAR_P, XCHAR_P, XCHAR_P, XCHAR_P, int)); E boolean FDECL(lined_up, (struct monst *)); E struct obj *FDECL(m_carrying, (struct monst *, int)); +E int FDECL(thrwmm, (struct monst *, struct monst *)); +E int FDECL(spitmm, (struct monst *, struct attack *, struct monst *)); +E int FDECL(breamm, (struct monst *, struct attack *, struct monst *)); E void FDECL(m_useupall, (struct monst *, struct obj *)); E void FDECL(m_useup, (struct monst *, struct obj *)); E void FDECL(m_throw, (struct monst *, int, int, int, int, int, struct obj *)); @@ -2797,6 +2800,7 @@ E struct monst *FDECL(boomhit, (struct obj *, int, int)); E int FDECL(zhitm, (struct monst *, int, int, struct obj **)); E int FDECL(burn_floor_objects, (int, int, BOOLEAN_P, BOOLEAN_P)); E void FDECL(buzz, (int, int, XCHAR_P, XCHAR_P, int, int)); +E void FDECL(dobuzz, (int, int, XCHAR_P, XCHAR_P, int, int, boolean)); E void FDECL(melt_ice, (XCHAR_P, XCHAR_P, const char *)); E void FDECL(start_melt_ice_timeout, (XCHAR_P, XCHAR_P, long)); E void FDECL(melt_ice_away, (ANY_P *, long)); diff --git a/src/dogmove.c b/src/dogmove.c index 73cc6f536..e59e7e801 100644 --- a/src/dogmove.c +++ b/src/dogmove.c @@ -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; diff --git a/src/mhitm.c b/src/mhitm.c index b52c6f226..e4c05897b 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -356,6 +356,17 @@ register struct monst *magr, *mdef; attk = 1; switch (mattk->aatyp) { case AT_WEAP: /* "hand to hand" attacks */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) { + /* D: Do a ranged attack here! */ + strike = thrwmm(magr, mdef); + if (DEADMONSTER(mdef)) + res[i] = MM_DEF_DIED; + + if (DEADMONSTER(magr)) + res[i] |= MM_AGR_DIED; + + break; + } if (magr->weapon_check == NEED_WEAPON || !MON_WEP(magr)) { magr->weapon_check = NEED_HTH_WEAPON; if (mon_wield_item(magr) != 0) @@ -379,7 +390,9 @@ register struct monst *magr, *mdef; case AT_TENT: /* Nymph that teleported away on first attack? */ if (distmin(magr->mx, magr->my, mdef->mx, mdef->my) > 1) - return MM_MISS; + /* Continue because the monster may have a ranged + * attack */ + continue; /* Monsters won't attack cockatrices physically if they * have a weapon instead. This instinct doesn't work for * players, or under conflict or confusion. @@ -428,6 +441,10 @@ register struct monst *magr, *mdef; break; case AT_EXPL: + /* D: Prevent explosions from a distance */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) + continue; + res[i] = explmm(magr, mdef, mattk); if (res[i] == MM_MISS) { /* cancelled--no attack */ strike = 0; @@ -441,6 +458,9 @@ register struct monst *magr, *mdef; strike = 0; break; } + /* D: Prevent engulf from a distance */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) + continue; /* Engulfing attacks are directed at the hero if * possible. -dlc */ @@ -454,13 +474,38 @@ register struct monst *magr, *mdef; } break; + case AT_BREA: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = breamm(magr, mattk, mdef); + + /* We don't really know if we hit or not, but pretend + * we did */ + if (strike) res[i] |= MM_HIT; + if (DEADMONSTER(mdef)) res[i] = MM_DEF_DIED; + if (DEADMONSTER(magr)) res[i] |= MM_AGR_DIED; + } + else + strike = 0; + break; + case AT_SPIT: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = spitmm(magr, mattk, mdef); + + /* We don't really know if we hit or not, but pretend + * we did */ + if (strike) res[i] |= MM_HIT; + if (DEADMONSTER(mdef)) res[i] = MM_DEF_DIED; + if (DEADMONSTER(magr)) res[i] |= MM_AGR_DIED; + } + break; default: /* no attack */ strike = 0; attk = 0; break; } - if (attk && !(res[i] & MM_AGR_DIED)) + if (attk && !(res[i] & MM_AGR_DIED) + && distmin(magr->mx,magr->my,mdef->mx,mdef->my) <= 1) res[i] = passivemm(magr, mdef, strike, res[i] & MM_DEF_DIED); if (res[i] & MM_DEF_DIED) diff --git a/src/mthrowu.c b/src/mthrowu.c index fbf9d08cf..1d9477f7c 100644 --- a/src/mthrowu.c +++ b/src/mthrowu.c @@ -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) diff --git a/src/zap.c b/src/zap.c index 069d89f67..560f9f686 100644 --- a/src/zap.c +++ b/src/zap.c @@ -3869,6 +3869,15 @@ const char *fltxt; xkilled(mon, XKILL_NOMSG | XKILL_NOCORPSE); } +void +buzz(type,nd,sx,sy,dx,dy) +int type, nd; +xchar sx,sy; +int dx,dy; +{ + dobuzz(type, nd, sx, sy, dx, dy, TRUE); +} + /* * type == 0 to 9 : you shooting a wand * type == 10 to 19 : you casting a spell @@ -3879,10 +3888,11 @@ const char *fltxt; * called with dx = dy = 0 with vertical bolts */ void -buzz(type, nd, sx, sy, dx, dy) +dobuzz(type, nd, sx, sy, dx, dy,say) register int type, nd; register xchar sx, sy; register int dx, dy; +boolean say; /* Announce out of sight hit/miss events if true */ { int range, abstype = abs(type) % 10; register xchar lsx, lsy; @@ -4009,7 +4019,8 @@ register int dx, dy; } else { if (!otmp) { /* normal non-fatal hit */ - hit(fltxt, mon, exclam(tmp)); + if (say || canseemon(mon)) + hit(fltxt, mon, exclam(tmp)); } else { /* some armor was destroyed; no damage done */ if (canseemon(mon)) @@ -4024,7 +4035,8 @@ register int dx, dy; } range -= 2; } else { - miss(fltxt, mon); + if (say || canseemon(mon)) + miss(fltxt, mon); } } else if (sx == u.ux && sy == u.uy && range >= 0) { nomul(0);