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
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 *));
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));
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));
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)
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;
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)
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.
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;
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
*/
}
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)
#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.
*/
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
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 */
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)) {
&& 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))
}
}
+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)
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)
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
* 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;
} 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))
}
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);