]> granicus.if.org Git - nethack/commitdiff
Allow pets to use ranged attacks
authorPasi Kallinen <paxed@alt.org>
Fri, 3 Jun 2016 22:06:00 +0000 (01:06 +0300)
committerPasi Kallinen <paxed@alt.org>
Fri, 3 Jun 2016 22:06:07 +0000 (01:06 +0300)
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.

doc/fixes36.1
include/extern.h
src/dogmove.c
src/mhitm.c
src/mthrowu.c
src/zap.c

index c0c09b01926aa279b811a7f377fda0e333188fca..b3d6a6a9fb76007e3bdf1e8dd03d98150b484b31 100644 (file)
@@ -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
index e21ba04ec01bcf31e2477fd514b2770a2ed91515..fe375b7dd4be636a2a15867fb97467249b24292b 100644 (file)
@@ -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));
index 73cc6f536a307fc5b7cd224aa3ecb2b562ce7097..e59e7e8015c8145d8f328b241a10955bd8e84aa0 100644 (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;
index b52c6f2268337b4a782eab2e91b4c693144e6bad..e4c05897b745d120390604fde91dc4272ad3d48c 100644 (file)
@@ -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)
index fbf9d08cf22aadf69e43da3ebe6965426ff7da73..1d9477f7c06057e62b9a335a610b07e68d3b8dce 100644 (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)
index 069d89f678da725a7aa1c24d09cb296912e4a3e3..560f9f686117156d48e3fef3efea4d227b8d9425 100644 (file)
--- 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);