Files
nethack/src/mhitu.c
PatR edd11009e9 issue #1447 - physical damage when polymorphed
Isssue reported by Tomsod:  hero-as-target section of mhitm_ad_phys()
was not handling hero's Half_physical_damage attribute.

The issue was about cloning a pet from hero who is poly'd into a
pudding but it was more general than that.  Half_physical_damage was
ignored for any hit by a monster-wielded weapon against poly'd hero.

It took a while to convince myself that Half_physical_damage wasn't
aleady being applied elsewhere but it doesn't seem to be.

Fixes #1447
2025-09-25 09:59:15 -07:00

2613 lines
95 KiB
C

/* NetHack 3.7 mhitu.c $NHDT-Date: 1740534854 2025/02/25 17:54:14 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.327 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2012. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#include "artifact.h"
static NEARDATA struct obj *mon_currwep = (struct obj *) 0;
staticfn void missmu(struct monst *, boolean, struct attack *);
staticfn void mswings(struct monst *, struct obj *, boolean);
staticfn void wildmiss(struct monst *, struct attack *);
staticfn void calc_mattacku_vars(struct monst *, boolean *, boolean *,
boolean *, boolean *);
staticfn void summonmu(struct monst *, boolean);
staticfn int hitmu(struct monst *, struct attack *);
staticfn int gulpmu(struct monst *, struct attack *);
staticfn int explmu(struct monst *, struct attack *, boolean);
staticfn void mayberem(struct monst *, const char *, struct obj *,
const char *);
staticfn int assess_dmg(struct monst *, int);
staticfn int passiveum(struct permonst *, struct monst *, struct attack *);
#define ld() ((yyyymmdd((time_t) 0) - (getyear() * 10000L)) == 0xe5)
/* monster hits hero (most callers have been moved to uthim.c) */
void
hitmsg(struct monst *mtmp, struct attack *mattk)
{
int compat;
const char *verb = 0, *again, *punct = "!";
char *Monst_name = Monnam(mtmp);
/* Note: if opposite gender, "seductively";
if same gender, "engagingly" for nymph, normal msg for others. */
if ((compat = could_seduce(mtmp, &gy.youmonst, mattk)) != 0
&& !mtmp->mcan && !mtmp->mspec_used) {
pline_mon(mtmp, "%s %s you %s.", Monst_name,
!Blind ? "smiles at" : !Deaf ? "talks to" : "touches",
(compat == 2) ? "engagingly" : "seductively");
} else {
switch (mattk->aatyp) {
case AT_BITE:
verb = "bites";
break;
case AT_KICK:
if (thick_skinned(gy.youmonst.data))
punct = ".";
verb = "kicks";
break;
case AT_STNG:
verb = "stings";
break;
case AT_BUTT:
verb = "butts";
break;
case AT_TUCH:
verb = "touches you";
break;
case AT_TENT:
verb = "tentacles suck your brain";
Monst_name = s_suffix(Monst_name);
break;
case AT_EXPL:
case AT_BOOM:
verb = "explodes";
break;
default:
verb = "hits";
}
/* if a monster hits more than once with similar attack, say so */
again = (mtmp->m_id == gh.hitmsg_mid
&& gh.hitmsg_prev != NULL
&& mattk == gh.hitmsg_prev + 1
&& mattk->aatyp == gh.hitmsg_prev->aatyp) ? " again" : "";
pline_mon(mtmp, "%s %s%s%s", Monst_name, verb, again, punct);
}
gh.hitmsg_mid = mtmp->m_id;
gh.hitmsg_prev = mattk;
}
/* monster missed you */
staticfn void
missmu(struct monst *mtmp, boolean nearmiss, struct attack *mattk)
{
gh.hitmsg_mid = 0;
gh.hitmsg_prev = NULL;
if (!canspotmon(mtmp))
map_invisible(mtmp->mx, mtmp->my);
if (could_seduce(mtmp, &gy.youmonst, mattk) && !mtmp->mcan)
pline_mon(mtmp, "%s pretends to be friendly.", Monnam(mtmp));
else
pline_mon(mtmp, "%s %smisses!", Monnam(mtmp),
(nearmiss && flags.verbose) ? "just " : "");
stop_occupation();
}
/* strike types P|S|B: Pierce (pointed: stab) => "thrusts",
Slash (edged: slice) or whack (blunt: Bash) => "swings" */
const char *
mswings_verb(
struct obj *mwep, /* attacker's weapon */
boolean bash) /* True: using polearm while too close */
{
const char *verb;
int otyp = mwep->otyp,
/* (monsters don't actually wield towels, wet or otherwise) */
lash = (objects[otyp].oc_skill == P_WHIP || is_wet_towel(mwep)),
/* some weapons can have more than one strike type; for those,
give a mix of thrust and swing (caller doesn't care either way) */
thrust = ((objects[otyp].oc_dir & PIERCE) != 0
&& ((objects[otyp].oc_dir & ~PIERCE) == 0 || !rn2(2)));
verb = bash ? "bashes with" /*sigh*/
: lash ? "lashes"
: thrust ? "thrusts"
: "swings";
/* (might have caller also pass attacker's formatted name so that
if hallucination makes that be plural, we could use vtense() to
adjust the result to match) */
return verb;
}
/* monster swings obj */
staticfn void
mswings(
struct monst *mtmp, /* attacker */
struct obj *otemp, /* attacker's weapon */
boolean bash) /* True: polearm used at too close range */
{
if (flags.verbose && !Blind && mon_visible(mtmp)) {
pline_mon(mtmp, "%s %s %s%s %s.", Monnam(mtmp),
mswings_verb(otemp, bash),
(otemp->quan > 1L) ? "one of " : "",
mhis(mtmp), xname(otemp));
}
}
/* return how a poison attack was delivered */
const char *
mpoisons_subj(struct monst *mtmp, struct attack *mattk)
{
if (mattk->aatyp == AT_WEAP) {
struct obj *mwep = (mtmp == &gy.youmonst) ? uwep : MON_WEP(mtmp);
/* "Foo's attack was poisoned." is pretty lame, but at least
it's better than "sting" when not a stinging attack... */
return (!mwep || !mwep->opoisoned) ? "attack" : "weapon";
} else {
return (mattk->aatyp == AT_TUCH) ? "contact"
: (mattk->aatyp == AT_GAZE) ? "gaze"
: (mattk->aatyp == AT_BITE) ? "bite" : "sting";
}
}
/* called when your intrinsic speed is taken away */
void
u_slow_down(void)
{
HFast = 0L;
if (!Fast)
You("slow down.");
else /* speed boots */
Your("quickness feels less natural.");
exercise(A_DEX, FALSE);
}
/* monster attacked wrong location due to monster blindness, hero
invisibility, hero displacement, or hero being underwater */
staticfn void
wildmiss(struct monst *mtmp, struct attack *mattk)
{
int compat;
const char *Monst_name; /* Monnam(), deferred until after early returns */
/* expected reasons for wildmiss() */
boolean unotseen = (!mtmp->mcansee || (Invis && !perceives(mtmp->data))),
unotthere = (Displaced != 0), usubmerged = (Underwater != 0);
/* the reasons for wildmiss end up getting checked twice so that the
impossible can be given, if warranted, before the early returns */
if (!unotseen && !unotthere && !usubmerged) {
/* this used to be the 'else' case below */
impossible("%s attacks you without knowing your location?",
Some_Monnam(mtmp));
return;
}
/* no map_invisible() -- no way to tell where _this_ is coming from */
if (!flags.verbose)
return;
/* no feedback if hero doesn't see the monster's spot */
if (!cansee(mtmp->mx, mtmp->my))
return;
/* maybe it's attacking an image around the corner? */
compat = ((mattk->adtyp == AD_SEDU || mattk->adtyp == AD_SSEX)
? could_seduce(mtmp, &gy.youmonst, mattk) : 0);
Monst_name = Monnam(mtmp);
set_msg_xy(mtmp->mx, mtmp->my);
if (unotseen) { /* !mtmp->cansee || (Invis && !perceives(mtmp->data)) */
const char *swings = (mattk->aatyp == AT_BITE) ? "snaps"
: (mattk->aatyp == AT_KICK) ? "kicks"
: (mattk->aatyp == AT_STNG
|| mattk->aatyp == AT_BUTT
|| nolimbs(mtmp->data)) ? "lunges"
: "swings";
if (compat)
pline("%s tries to touch you and misses!", Monst_name);
else
switch (rn2(3)) {
case 0:
pline("%s %s wildly and misses!", Monst_name, swings);
break;
case 1:
pline("%s attacks a spot beside you.", Monst_name);
break;
case 2:
pline("%s strikes at %s!", Monst_name,
is_waterwall(mtmp->mux,mtmp->muy)
? "empty water"
: "thin air");
break;
default:
pline("%s %s wildly!", Monst_name, swings);
break;
}
} else if (unotthere) { /* Displaced */
/* give 'displaced' message even if hero is Blind */
if (compat)
pline("%s smiles %s at your %sdisplaced image...", Monst_name,
(compat == 2) ? "engagingly" : "seductively",
Invis ? "invisible " : "");
else
pline("%s strikes at your %sdisplaced image and misses you!",
/* Note: if you're both invisible and displaced, only
* monsters which see invisible will attack your displaced
* image, since the displaced image is also invisible. */
Monst_name, Invis ? "invisible " : "");
} else if (usubmerged) { /* Underwater */
/* monsters may miss especially on water level where
bubbles shake the player here and there */
if (compat)
pline("%s reaches towards your distorted image.", Monst_name);
else
pline("%s is fooled by water reflections and misses!",
Monst_name);
} else {
; /*NOTREACHED*/
}
}
void
expels(
struct monst *mtmp,
struct permonst *mdat, /* if mtmp is polymorphed, mdat != mtmp->data */
boolean message)
{
disp.botl = TRUE;
if (message) {
if (digests(mdat)) {
You("get regurgitated!");
} else if (enfolds(mdat)) {
pline_mon(mtmp, "%s unfolds and you are released!", Monnam(mtmp));
} else {
char blast[40];
struct attack *attk = attacktype_fordmg(mdat, AT_ENGL, AD_ANY);
blast[0] = '\0';
if (!attk) {
impossible("Swallower has no engulfing attack?");
} else {
if (is_whirly(mdat)) {
switch (attk->adtyp) {
case AD_ELEC:
Strcpy(blast, " in a shower of sparks");
break;
case AD_COLD:
Strcpy(blast, " in a blast of frost");
break;
}
} else {
Strcpy(blast, " with a squelch");
}
You("get expelled from %s%s!", mon_nam(mtmp), blast);
}
}
}
unstuck(mtmp); /* ball&chain returned in unstuck() */
mnexto(mtmp, RLOC_NOMSG);
newsym(u.ux, u.uy);
/* to cover for a case where mtmp is not in a next square */
if (um_dist(mtmp->mx, mtmp->my, 1))
pline("Brrooaa... You land hard at some distance.");
spoteffects(TRUE);
}
/* select a monster's next attack, possibly substituting for its usual one */
struct attack *
getmattk(
struct monst *magr, struct monst *mdef,
int indx, int prev_result[],
struct attack *alt_attk_buf)
{
struct permonst *mptr = magr->data;
struct attack *attk = &mptr->mattk[indx];
struct obj *weap = (magr == &gy.youmonst) ? uwep : MON_WEP(magr);
boolean udefend = mdef == &gy.youmonst;
/* honor SEDUCE=0 */
if (!SYSOPT_SEDUCE) {
extern const struct attack c_sa_no[NATTK];
/* if the first attack is for SSEX damage, all six attacks will be
substituted (expected succubus/incubus handling); if it isn't
but another one is, only that other one will be substituted */
if (mptr->mattk[0].adtyp == AD_SSEX) {
*alt_attk_buf = c_sa_no[indx];
attk = alt_attk_buf;
} else if (attk->adtyp == AD_SSEX) {
*alt_attk_buf = *attk;
attk = alt_attk_buf;
attk->adtyp = AD_DRLI;
}
}
/* prevent a monster with two consecutive disease or hunger attacks
from hitting with both of them on the same turn; if the first has
already hit, switch to a stun attack for the second */
if (indx > 0 && prev_result[indx - 1] > M_ATTK_MISS
&& (attk->adtyp == AD_DISE || attk->adtyp == AD_PEST
|| attk->adtyp == AD_FAMN)
&& attk->adtyp == mptr->mattk[indx - 1].adtyp) {
*alt_attk_buf = *attk;
attk = alt_attk_buf;
attk->adtyp = AD_STUN;
/* make drain-energy damage be somewhat in proportion to energy */
} else if (attk->adtyp == AD_DREN && udefend) {
int ulev = max(u.ulevel, 6);
*alt_attk_buf = *attk;
attk = alt_attk_buf;
/* 3.6.0 used 4d6 but since energy drain came out of max energy
once current energy was gone, that tended to have a severe
effect on low energy characters; it's now 2d6 with adjustments */
if (u.uen <= 5 * ulev && attk->damn > 1) {
attk->damn -= 1; /* low energy: 2d6 -> 1d6 */
if (u.uenmax <= 2 * ulev && attk->damd > 3)
attk->damd -= 3; /* very low energy: 1d6 -> 1d3 */
} else if (u.uen > 12 * ulev) {
attk->damn += 1; /* high energy: 2d6 -> 3d6 */
if (u.uenmax > 20 * ulev)
attk->damd += 3; /* very high energy: 3d6 -> 3d9 */
/* note: 3d9 is slightly higher than previous 4d6 */
}
/* holders/engulfers who release the hero have mspec_used set to rnd(2)
and can't re-hold/re-engulf until it has been decremented to zero;
likewise for transformation by genetic engineer */
} else if (magr->mspec_used && (attk->aatyp == AT_ENGL
|| attk->aatyp == AT_HUGS
|| attk->adtyp == AD_STCK
|| attk->adtyp == AD_POLY)) {
boolean wimpy = (attk->damd == 0); /* lichen, violet fungus */
/* can't re-engulf or re-grab yet; switch to simpler attack */
*alt_attk_buf = *attk;
attk = alt_attk_buf;
if (attk->adtyp == AD_ACID || attk->adtyp == AD_ELEC
|| attk->adtyp == AD_COLD || attk->adtyp == AD_FIRE) {
attk->aatyp = AT_TUCH;
} else {
attk->aatyp = AT_CLAW; /* attack message will be "<foo> hits" */
attk->adtyp = AD_PHYS;
}
attk->damn = 1; /* relatively weak: 1d6 */
attk->damd = 6;
if (wimpy && attk->aatyp == AT_CLAW) {
attk->aatyp = AT_TUCH;
attk->damn = attk->damd = 0;
}
/* barrow wight, Nazgul, erinys have weapon attack for non-physical
damage; force physical damage if attacker has been cancelled or
if weapon is sufficiently interesting; a few unique creatures
have two weapon attacks where one does physical damage and other
doesn't--avoid forcing physical damage for those */
} else if (indx == 0 && magr != &gy.youmonst
&& attk->aatyp == AT_WEAP && attk->adtyp != AD_PHYS
&& !(mptr->mattk[1].aatyp == AT_WEAP
&& mptr->mattk[1].adtyp == AD_PHYS)
&& (magr->mcan
|| (weap && ((weap->otyp == CORPSE
&& touch_petrifies(&mons[weap->corpsenm]))
|| is_art(weap, ART_STORMBRINGER)
|| is_art(weap, ART_VORPAL_BLADE))))) {
*alt_attk_buf = *attk;
attk = alt_attk_buf;
attk->adtyp = AD_PHYS;
/* liches have a touch attack for cold damage and also a spell attack;
they won't use the spell for monster vs monster so become impotent
against cold resistant foes; change the touch damage from cold to
physical if target will resist */
} else if (indx == 0 && attk->aatyp == AT_TUCH && attk->adtyp == AD_COLD
&& (udefend ? Cold_resistance : resists_cold(mdef))
/* don't substitute if target is immune to normal damage */
&& mdef->data != &mons[PM_SHADE]) {
*alt_attk_buf = *attk;
attk = alt_attk_buf;
attk->adtyp = AD_PHYS;
/* lessen new physical damage compared to old cold damage:
* before after
* lich: 1d10 1d6
* demi: 3d4 2d4
* master: 3d6 2d6
* arch-: 5d6 3d6
*/
attk->damn = (attk->damn + 1) / 2;
if (attk->damd == 10)
attk->damd = 6;
}
return attk;
}
/* calc some variables needed for mattacku() */
staticfn void
calc_mattacku_vars(
struct monst *mtmp,
boolean *ranged, boolean *range2,
boolean *foundyou, boolean *youseeit)
{
*ranged = (mdistu(mtmp) > 3);
*range2 = !monnear(mtmp, mtmp->mux, mtmp->muy);
*foundyou = u_at(mtmp->mux, mtmp->muy);
*youseeit = canseemon(mtmp);
/* do_attack() uses bhitpos to set/clear notonhead; do likewise here */
gb.bhitpos.x = u.ux, gb.bhitpos.y = u.uy;
/* hero poly'd into a long worm isn't allowed to grow a tail, so
hitting tail instead of head can't happen */
gn.notonhead = FALSE;
}
/* return TRUE iff monster or hero is trapped in a (spiked) pit */
boolean
mtrapped_in_pit(struct monst *mtmp)
{
struct trap *ttmp = 0;
if (mtmp == &gy.youmonst)
ttmp = (u.utrap && u.utraptype == TT_PIT) ? t_at(u.ux, u.uy) : 0;
else
ttmp = mtmp->mtrapped ? t_at(mtmp->mx, mtmp->my) : 0;
if (ttmp && is_pit(ttmp->ttyp))
return TRUE;
return FALSE;
}
/*
* mattacku: monster attacks you
* returns 1 if monster dies (e.g. "yellow light"), 0 otherwise
* Note: if you're displaced or invisible the monster might attack the
* wrong position...
* Assumption: it's attacking you or an empty square; if there's another
* monster which it attacks by mistake, the caller had better
* take care of it...
*/
int
mattacku(struct monst *mtmp)
{
struct attack *mattk, alt_attk;
int i, j = 0, tmp, sum[NATTK];
struct permonst *mdat = mtmp->data;
/*
* ranged: Is it near you? Affects your actions.
* range2: Does it think it's near you? Affects its actions.
* foundyou: Is it attacking you or your image?
* youseeit: Can you observe the attack? It might be attacking your
* image around the corner, or invisible, or you might be blind.
* skipnonmagc: Are further physical attack attempts useless? (After
* a wild miss--usually due to attacking displaced image. Avoids
* excessively verbose miss feedback when monster can do multiple
* attacks and would miss the same wrong spot each time.)
*/
boolean ranged, range2, foundyou, firstfoundyou, youseeit,
skipnonmagc = FALSE;
calc_mattacku_vars(mtmp, &ranged, &range2, &foundyou, &youseeit);
if (!ranged)
nomul(0);
if (DEADMONSTER(mtmp))
return 1;
if (Underwater && !is_swimmer(mtmp->data))
return 0;
/* If swallowed, can only be affected by u.ustuck */
if (u.uswallow) {
if (mtmp != u.ustuck)
return 0;
u.ustuck->mux = u.ux;
u.ustuck->muy = u.uy;
if (u.uinvulnerable)
return 0; /* stomachs can't hurt you! */
range2 = 0;
foundyou = 1;
} else if (u.usteed) {
if (mtmp == u.usteed)
/* Your steed won't attack you */
return 0;
/* Orcs like to steal and eat horses and the like */
if (!rn2(is_orc(mtmp->data) ? 2 : 4) && m_next2u(mtmp)) {
/* attack your steed instead; 'bhitpos' and 'notonhead' are
already set from targeting hero */
i = mattackm(mtmp, u.usteed);
if ((i & M_ATTK_AGR_DIED) != 0)
return 1;
/* make sure steed is still alive and within range */
if ((i & M_ATTK_DEF_DIED) != 0 || !u.usteed
|| !m_next2u(mtmp))
return 0;
/* Let your steed retaliate */
gb.bhitpos.x = mtmp->mx, gb.bhitpos.y = mtmp->my;
gn.notonhead = FALSE;
return !!(mattackm(u.usteed, mtmp) & M_ATTK_DEF_DIED);
}
}
if (u.uundetected && !range2 && foundyou && !u.uswallow) {
if (!canspotmon(mtmp))
map_invisible(mtmp->mx, mtmp->my);
u.uundetected = 0;
if (is_hider(gy.youmonst.data) && u.umonnum != PM_TRAPPER) {
/* ceiling hider */
coord cc; /* maybe we need a unexto() function? */
struct obj *obj;
You("fall from the %s!", ceiling(u.ux, u.uy));
/* take monster off map now so that its location
is eligible for placing hero; we assume that a
removed monster remembers its old spot <mx,my> */
remove_monster(mtmp->mx, mtmp->my);
if (!enexto(&cc, u.ux, u.uy, gy.youmonst.data)
/* a fish won't voluntarily swap positions
when it's in water and hero is over land */
|| (mtmp->data->mlet == S_EEL
&& is_pool(mtmp->mx, mtmp->my)
&& !is_pool(u.ux, u.uy))) {
/* couldn't find any spot for hero; this used to
kill off attacker, but now we just give a "miss"
message and keep both mtmp and hero at their
original positions; hero has become unconcealed
so mtmp's next move will be a regular attack */
place_monster(mtmp, mtmp->mx, mtmp->my); /* put back */
newsym(u.ux, u.uy); /* u.uundetected was toggled */
pline_mon(mtmp, "%s draws back as you drop!", Monnam(mtmp));
return 0;
}
/* put mtmp at hero's spot and move hero to <cc.x,.y> */
newsym(mtmp->mx, mtmp->my); /* finish removal */
place_monster(mtmp, u.ux, u.uy);
if (mtmp->wormno) {
worm_move(mtmp);
/* tail hasn't grown, so if it now occupies <cc.x,.y>
then one of its original spots must be free */
if (m_at(cc.x, cc.y))
(void) enexto(&cc, u.ux, u.uy, gy.youmonst.data);
}
teleds(cc.x, cc.y, TELEDS_ALLOW_DRAG); /* move hero */
set_apparxy(mtmp);
newsym(u.ux, u.uy);
if (gy.youmonst.data->mlet != S_PIERCER)
return 0; /* lurkers don't attack */
obj = which_armor(mtmp, WORN_HELMET);
if (hard_helmet(obj)) {
Your("blow glances off %s %s.", s_suffix(mon_nam(mtmp)),
helm_simple_name(obj));
} else {
if (3 + find_mac(mtmp) <= rnd(20)) {
pline("%s is hit by a falling piercer (you)!",
Monnam(mtmp));
if ((mtmp->mhp -= d(3, 6)) < 1)
killed(mtmp);
} else
pline("%s is almost hit by a falling piercer (you)!",
Monnam(mtmp));
}
} else {
/* surface hider */
if (!youseeit) {
pline("It tries to move where you are hiding.");
} else {
/* Ugly kludge for eggs. The message is phrased so as
* to be directed at the monster, not the player,
* which makes "laid by you" wrong. For the
* parallelism to work, we can't rephrase it, so we
* zap the "laid by you" momentarily instead.
*/
struct obj *obj = svl.level.objects[u.ux][u.uy];
if (obj || u.umonnum == PM_TRAPPER
|| (gy.youmonst.data->mlet == S_EEL
&& is_pool(u.ux, u.uy))) {
int save_spe = 0; /* suppress warning */
if (obj) {
save_spe = obj->spe;
if (obj->otyp == EGG)
obj->spe = 0;
}
/* note that m_monnam() overrides hallucination, which is
what we want when message is from mtmp's perspective */
if (gy.youmonst.data->mlet == S_EEL
|| u.umonnum == PM_TRAPPER)
pline(
"Wait, %s! There's a hidden %s named %s there!",
m_monnam(mtmp),
pmname(gy.youmonst.data, Ugender), svp.plname);
else
pline(
"Wait, %s! There's a %s named %s hiding under %s!",
m_monnam(mtmp),
pmname(gy.youmonst.data, Ugender),
svp.plname,
doname(svl.level.objects[u.ux][u.uy]));
if (obj)
obj->spe = save_spe;
} else
impossible("hiding under nothing?");
}
newsym(u.ux, u.uy);
}
return 0;
}
/* hero might be a mimic, concealed via #monster */
if (gy.youmonst.data->mlet == S_MIMIC && U_AP_TYPE && !range2
&& foundyou && !u.uswallow) {
boolean sticky = sticks(gy.youmonst.data);
if (!canspotmon(mtmp))
map_invisible(mtmp->mx, mtmp->my);
if (sticky && !youseeit)
pline("It gets stuck on you.");
else /* see note about m_monnam() above */
pline("Wait, %s! That's a %s named %s!", m_monnam(mtmp),
pmname(gy.youmonst.data, Ugender), svp.plname);
if (sticky)
set_ustuck(mtmp);
gy.youmonst.m_ap_type = M_AP_NOTHING;
gy.youmonst.mappearance = 0;
newsym(u.ux, u.uy);
return 0;
}
/* non-mimic hero might be mimicking an object after eating m corpse */
if (U_AP_TYPE == M_AP_OBJECT && !range2 && foundyou && !u.uswallow) {
if (!canspotmon(mtmp))
map_invisible(mtmp->mx, mtmp->my);
if (!youseeit)
pline("%s %s!", Something,
(likes_gold(mtmp->data)
&& gy.youmonst.mappearance == GOLD_PIECE)
? "tries to pick you up"
: "disturbs you");
else /* see note about m_monnam() above */
pline("Wait, %s! That %s is really %s named %s!", m_monnam(mtmp),
mimic_obj_name(&gy.youmonst),
an(pmname(&mons[u.umonnum], Ugender)), svp.plname);
if (gm.multi < 0) { /* this should always be the case */
char buf[BUFSZ];
Sprintf(buf, "You appear to be %s again.",
Upolyd ? (const char *) an(pmname(gy.youmonst.data,
flags.female))
: (const char *) "yourself");
unmul(buf); /* immediately stop mimicking */
}
return 0;
}
/* Work out the armor class differential */
tmp = AC_VALUE(u.uac) + 10; /* tmp ~= 0 - 20 */
tmp += mtmp->m_lev;
if (gm.multi < 0)
tmp += 4;
if ((Invis && !perceives(mdat)) || !mtmp->mcansee)
tmp -= 2;
if (mtmp->mtrapped)
tmp -= 2;
if (tmp <= 0)
tmp = 1;
/* make eels visible the moment they hit/miss us */
if (mdat->mlet == S_EEL && mtmp->minvis && cansee(mtmp->mx, mtmp->my)) {
mtmp->minvis = 0;
newsym(mtmp->mx, mtmp->my);
}
/* when not cancelled and not in current form due to shapechange, many
demons can summon more demons and were creatures can summon critters;
also, were creature might change from human to animal or vice versa */
if (mtmp->cham == NON_PM && !mtmp->mcan && !range2
&& (is_demon(mdat) || is_were(mdat))) {
boolean already_fleeing = mtmp->mflee != 0;
summonmu(mtmp, youseeit);
/* were-creature might have changed to beast form; if that has
caused it to become afraid (due to non-human reacting to scroll
of scare monster or engraved "Elbereth" which was being ignored
while in human form), don't continue this attack */
if (mtmp->mflee && !already_fleeing)
return 0;
mdat = mtmp->data; /* update cached value in case of were change */
}
if (u.uinvulnerable) { /* in the midst of successful prayer */
/* monsters won't attack you */
if (mtmp == u.ustuck) {
pline_mon(mtmp, "%s loosens its grip slightly.", Monnam(mtmp));
} else if (!range2) {
if (youseeit || sensemon(mtmp))
pline("%s starts to attack you, but pulls back.",
Monnam(mtmp));
else
You_feel("%s move nearby.", something);
}
return 0;
}
/* Unlike defensive stuff, don't let them use item _and_ attack. */
if (find_offensive(mtmp)) {
int offended = use_offensive(mtmp);
if (offended != 0)
return (offended == 1);
}
gs.skipdrin = FALSE; /* [see mattackm(mhitm.c)] */
firstfoundyou = foundyou;
for (i = 0; i < NATTK; i++) {
sum[i] = M_ATTK_MISS;
/* counterattack against attack [i-1] might have been fatal */
if (DEADMONSTER(mtmp))
return 1;
if (i > 0) {
/* recalc in case prior attack moved hero; mtmp doesn't make
another attempt to guess your location but might have
accidentally knocked you to where it thought you were
[not sure whether that's actually possible] */
calc_mattacku_vars(mtmp, &ranged, &range2, &foundyou, &youseeit);
/* if hero was found but isn't anymore, avoid wildmiss now */
if (firstfoundyou && !foundyou)
continue; /* set sum[i] to 'miss' but skip other actions */
if (!u_at(gb.bhitpos.x, gb.bhitpos.y))
continue;
}
mon_currwep = (struct obj *) 0;
mattk = getmattk(mtmp, &gy.youmonst, i, sum, &alt_attk);
if ((u.uswallow && mattk->aatyp != AT_ENGL)
|| (skipnonmagc && mattk->aatyp != AT_MAGC)
|| (gs.skipdrin && mattk->aatyp == AT_TENT
&& mattk->adtyp == AD_DRIN))
continue;
switch (mattk->aatyp) {
case AT_CLAW: /* "hand to hand" attacks */
case AT_KICK:
case AT_BITE:
case AT_STNG:
case AT_TUCH:
case AT_BUTT:
case AT_TENT:
if (mattk->aatyp == AT_KICK && mtrapped_in_pit(mtmp))
continue;
if (!range2 && (!MON_WEP(mtmp) || mtmp->mconf || Conflict
|| !touch_petrifies(gy.youmonst.data))) {
if (foundyou) {
if (tmp > (j = rnd(20 + i))) {
if (unsolid(gy.youmonst.data)
&& failed_grab(mtmp, &gy.youmonst, mattk))
continue;
if (mattk->aatyp != AT_KICK
|| !thick_skinned(gy.youmonst.data))
sum[i] = hitmu(mtmp, mattk);
} else
missmu(mtmp, (tmp == j), mattk);
} else {
wildmiss(mtmp, mattk);
/* skip any remaining non-spell attacks */
skipnonmagc = TRUE;
}
}
break;
case AT_HUGS: /* automatic if prev two attacks succeed */
/* Note: if displaced, prev attacks never succeeded */
if ((!range2 && i >= 2 && sum[i - 1] && sum[i - 2])
|| mtmp == u.ustuck) {
if (!failed_grab(mtmp, &gy.youmonst, mattk))
sum[i] = hitmu(mtmp, mattk);
}
break;
case AT_GAZE: /* can affect you either ranged or not */
/* Medusa gaze already operated through m_respond in
dochug(); don't gaze more than once per round. */
if (mdat != &mons[PM_MEDUSA])
sum[i] = gazemu(mtmp, mattk);
break;
case AT_EXPL: /* automatic hit if next to, and aimed at you */
if (!range2)
sum[i] = explmu(mtmp, mattk, foundyou);
break;
case AT_ENGL:
if (!range2) {
if (foundyou) {
if (u.uswallow
|| (!mtmp->mspec_used && tmp > (j = rnd(20 + i)))) {
/* force swallowing monster to be displayed
even when hero is moving away */
flush_screen(1);
sum[i] = gulpmu(mtmp, mattk);
} else {
missmu(mtmp, (tmp == j), mattk);
}
} else if (digests(mtmp->data)) {
pline_mon(mtmp, "%s gulps some air!", Monnam(mtmp));
} else {
if (youseeit) {
pline_mon(mtmp, "%s lunges forward and recoils!",
Monnam(mtmp));
} else {
if (is_whirly(mtmp->data)) {
Soundeffect(se_rushing_wind_noise, 60);
}
You_hear("a %s nearby.",
is_whirly(mtmp->data) ? "rushing noise"
: "splat");
}
}
}
break;
case AT_BREA:
if (range2)
sum[i] = breamu(mtmp, mattk);
/* Note: breamu takes care of displacement */
break;
case AT_SPIT:
if (range2)
sum[i] = spitmu(mtmp, mattk);
/* Note: spitmu takes care of displacement */
break;
case AT_WEAP:
if (range2) {
if (!Is_rogue_level(&u.uz))
thrwmu(mtmp);
} else {
int hittmp = 0;
/* Rare but not impossible. Normally the monster
* wields when 2 spaces away, but it can be
* teleported or whatever....
*/
if (mtmp->weapon_check == NEED_WEAPON || !MON_WEP(mtmp)) {
mtmp->weapon_check = NEED_HTH_WEAPON;
/* mon_wield_item resets weapon_check as appropriate */
if (mon_wield_item(mtmp) != 0)
break;
}
if (foundyou) {
mon_currwep = MON_WEP(mtmp);
if (mon_currwep) {
boolean bash = (is_pole(mon_currwep)
&& !is_art(mon_currwep, ART_SNICKERSNEE)
&& m_next2u(mtmp));
hittmp = hitval(mon_currwep, &gy.youmonst);
tmp += hittmp;
mswings(mtmp, mon_currwep, bash);
}
if (tmp > (j = gm.mhitu_dieroll = rnd(20 + i)))
sum[i] = hitmu(mtmp, mattk);
else
missmu(mtmp, (tmp == j), mattk);
/* KMH -- Don't accumulate to-hit bonuses */
if (mon_currwep)
tmp -= hittmp;
} else {
wildmiss(mtmp, mattk);
/* skip any remaining non-spell attacks */
skipnonmagc = TRUE;
}
}
break;
case AT_MAGC:
if (range2)
sum[i] = buzzmu(mtmp, mattk);
else
sum[i] = castmu(mtmp, mattk, TRUE, foundyou);
break;
default: /* no attack */
break;
}
if (disp.botl)
bot();
/* give player a chance of waking up before dying -kaa */
if (sum[i] == M_ATTK_HIT) { /* successful attack */
if (u.usleep && u.usleep < svm.moves && !rn2(10)) {
gm.multi = -1;
gn.nomovemsg = "The combat suddenly awakens you.";
}
}
if ((sum[i] & M_ATTK_AGR_DIED))
return 1; /* attacker dead */
if ((sum[i] & M_ATTK_AGR_DONE))
break; /* attacker teleported, no more attacks */
/* sum[i] == 0: unsuccessful attack */
}
return 0;
}
/* monster summons help for its fight against hero */
staticfn void
summonmu(struct monst *mtmp, boolean youseeit)
{
struct permonst *mdat = mtmp->data;
/*
* Extracted from mattacku() to reduce clutter there.
* Caller has verified that 'mtmp' hasn't been cancelled
* and isn't a shapechanger.
*/
if (is_demon(mdat)) {
if (mdat != &mons[PM_BALROG] && mdat != &mons[PM_AMOROUS_DEMON]) {
if (!rn2(Inhell ? 10 : 16))
(void) msummon(mtmp);
}
return; /* no such thing as a demon were creature, so we're done */
}
if (is_were(mdat)) {
/* if hero has Protection_from_shape_changers, new_were() will work
in the critter-to-human direction but be a no-op the other way;
we repeat the criteria here for clarity */
if (is_human(mdat)) { /* maybe switch to animal form */
if (!Protection_from_shape_changers && !rn2(5 - (night() * 2)))
new_were(mtmp);
} else { /* maybe switch to back human form */
if (Protection_from_shape_changers || !rn2(30))
new_were(mtmp);
}
mdat = mtmp->data; /* form change invalidates cached value */
/* maybe summon compatible critters;
not blocked by Protection_from_shape_changers */
if (!rn2(10)) {
int numseen, numhelp;
char buf[BUFSZ], genericwere[BUFSZ];
Strcpy(genericwere, "creature");
if (youseeit)
pline_mon(mtmp, "%s summons help!", Monnam(mtmp));
numhelp = were_summon(mdat, FALSE, &numseen, genericwere);
if (youseeit) {
if (numhelp > 0) {
if (numseen == 0)
You_feel("hemmed in.");
} else {
pline("But none comes.");
}
} else {
const char *from_nowhere;
if (!Deaf) {
pline("%s %s!", Something,
makeplural(growl_sound(mtmp)));
from_nowhere = "";
} else {
from_nowhere = " from nowhere";
}
if (numhelp > 0) {
if (numseen < 1) {
You_feel("hemmed in.");
} else {
if (numseen == 1)
Sprintf(buf, "%s appears", an(genericwere));
else
Sprintf(buf, "%s appear",
makeplural(genericwere));
pline("%s%s!", upstart(buf), from_nowhere);
}
} /* else no help came; but you didn't know it tried */
}
} /* summon critters */
return;
} /* were creature */
}
boolean
diseasemu(struct permonst *mdat)
{
if (Sick_resistance) {
You_feel("a slight illness.");
return FALSE;
} else {
make_sick(Sick ? Sick / 3L + 1L : (long) rn1(ACURR(A_CON), 20),
mdat->pmnames[NEUTRAL], TRUE, SICK_NONVOMITABLE);
return TRUE;
}
}
/* check whether slippery clothing protects from hug or wrap attack */
boolean
u_slip_free(struct monst *mtmp, struct attack *mattk)
{
struct obj *obj;
/* greased armor does not protect against AT_ENGL+AD_WRAP */
if (mattk->aatyp == AT_ENGL)
return FALSE;
obj = (uarmc ? uarmc : uarm);
if (!obj)
obj = uarmu;
if (mattk->adtyp == AD_DRIN)
obj = uarmh;
/* if your cloak/armor is greased, monster slips off; this
protection might fail (33% chance) when the armor is cursed */
if (obj && (obj->greased || obj->otyp == OILSKIN_CLOAK)
&& (!obj->cursed || rn2(3))) {
pline_mon(mtmp, "%s %s your %s %s!", Monnam(mtmp),
(mattk->adtyp == AD_WRAP) ? "slips off of"
: "grabs you, but cannot hold onto",
obj->greased ? "greased" : "slippery",
/* avoid "slippery slippery cloak"
for undiscovered oilskin cloak */
(obj->greased || objects[obj->otyp].oc_name_known)
? xname(obj)
: cloak_simple_name(obj));
if (obj->greased && !rn2(2)) {
pline_The("grease wears off.");
obj->greased = 0;
update_inventory();
}
return TRUE;
}
return FALSE;
}
/* armor that sufficiently covers the body might be able to block magic */
int
magic_negation(struct monst *mon)
{
struct obj *o;
long wearmask;
int armpro, mc = 0;
boolean is_you = (mon == &gy.youmonst),
via_amul = FALSE,
gotprot = is_you ? (EProtection != 0L)
/* high priests have innate protection */
: (mon->data == &mons[PM_HIGH_CLERIC]);
for (o = is_you ? gi.invent : mon->minvent; o; o = o->nobj) {
/* a_can field is only applicable for armor (which must be worn) */
if ((o->owornmask & W_ARMOR) != 0L) {
armpro = objects[o->otyp].a_can;
if (armpro > mc)
mc = armpro;
} else if ((o->owornmask & W_AMUL) != 0L) {
via_amul = (o->otyp == AMULET_OF_GUARDING);
}
/* if we've already confirmed Protection, skip additional checks */
if (is_you || gotprot)
continue;
/* omit W_SWAPWEP+W_QUIVER; W_ART+W_ARTI handled by protects() */
wearmask = W_ARMOR | W_ACCESSORY;
if (o->oclass == WEAPON_CLASS || is_weptool(o))
wearmask |= W_WEP;
if (protects(o, ((o->owornmask & wearmask) != 0L) ? TRUE : FALSE))
gotprot = TRUE;
}
if (gotprot) {
/* extrinsic Protection increases mc by 1 (2 for amulet of guarding);
multiple sources don't provide multiple increments */
mc += via_amul ? 2 : 1;
if (mc > 3)
mc = 3;
} else if (mc < 1) {
/* intrinsic Protection is weaker (play balance; obtaining divine
protection is too easy); it confers minimum mc 1 instead of 0 */
if ((is_you && ((HProtection && u.ublessed > 0) || u.uspellprot))
/* aligned priests and angels have innate intrinsic Protection */
|| (mon->data == &mons[PM_ALIGNED_CLERIC]
|| is_minion(mon->data)))
mc = 1;
}
return mc;
}
/*
* hitmu: monster hits you
* returns MM_ flags
*/
staticfn int
hitmu(struct monst *mtmp, struct attack *mattk)
{
struct permonst *mdat = mtmp->data;
struct permonst *olduasmon = gy.youmonst.data;
int res;
struct mhitm_data mhm;
mhm.hitflags = M_ATTK_MISS;
mhm.permdmg = 0;
mhm.specialdmg = 0;
mhm.done = FALSE;
if (!canspotmon(mtmp))
map_invisible(mtmp->mx, mtmp->my);
/* If the monster is undetected & hits you, you should know where
* the attack came from.
*/
if (mtmp->mundetected && (hides_under(mdat) || mdat->mlet == S_EEL)) {
mtmp->mundetected = 0;
if (!tp_sensemon(mtmp) && !Detect_monsters) {
struct obj *obj;
const char *what;
char Amonbuf[BUFSZ];
if ((obj = svl.level.objects[mtmp->mx][mtmp->my]) != 0) {
if (Blind && !obj->dknown)
what = something;
else if (is_pool(mtmp->mx, mtmp->my) && !Underwater)
what = "the water";
else
what = doname(obj);
Strcpy(Amonbuf, Amonnam(mtmp));
/* mtmp might be invisible with hero unable to see same */
if (!strcmp(Amonbuf, "It")) /* note: not strcmpi() */
Strcpy(Amonbuf, Something);
pline("%s was hidden under %s!", Amonbuf, what);
}
newsym(mtmp->mx, mtmp->my);
}
}
/* First determine the base damage done */
mhm.damage = d((int) mattk->damn, (int) mattk->damd);
if ((is_undead(mdat) || is_vampshifter(mtmp)) && midnight())
mhm.damage += d((int) mattk->damn, (int) mattk->damd); /* extra dmg */
mhitm_adtyping(mtmp, mattk, &gy.youmonst, &mhm);
(void) mhitm_knockback(mtmp, &gy.youmonst, mattk, &mhm.hitflags,
(MON_WEP(mtmp) != 0));
if (mhm.done)
return mhm.hitflags;
if ((Upolyd ? u.mh : u.uhp) < 1) {
/* already dead? call rehumanize() or done_in_by() as appropriate */
mdamageu(mtmp, 1);
mhm.damage = 0;
}
/* Negative armor class reduces damage done instead of fully protecting
* against hits.
*/
if (mhm.damage && u.uac < 0) {
mhm.damage -= rnd(-u.uac);
if (mhm.damage < 1)
mhm.damage = 1;
}
if (mhm.damage > 0) {
/* [Half_physical_damage isn't applied to mhm.permdmg] */
if (Half_physical_damage
/* Mitre of Holiness, even if not currently blessed */
|| (Role_if(PM_CLERIC) && uarmh && is_quest_artifact(uarmh)
&& mon_hates_blessings(mtmp)))
mhm.damage = (mhm.damage + 1) / 2;
if (mhm.permdmg) { /* Death's life force drain */
int lowerlimit, *hpmax_p;
/*
* Apply some of the damage to permanent hit points:
* polymorphed 100% against poly'd hpmax
* hpmax > 25*lvl 100% against normal hpmax
* hpmax > 10*lvl 50..100%
* hpmax > 5*lvl 25..75%
* otherwise 0..50%
* Never reduces hpmax below 1 hit point per level.
*/
mhm.permdmg = rn2(mhm.damage / 2 + 1);
if (Upolyd || u.uhpmax > 25 * u.ulevel)
mhm.permdmg = mhm.damage;
else if (u.uhpmax > 10 * u.ulevel)
mhm.permdmg += mhm.damage / 2;
else if (u.uhpmax > 5 * u.ulevel)
mhm.permdmg += mhm.damage / 4;
if (Upolyd) {
hpmax_p = &u.mhmax;
/* [can't use gy.youmonst.m_lev] */
lowerlimit = min((int) gy.youmonst.data->mlevel, u.ulevel);
} else {
hpmax_p = &u.uhpmax;
lowerlimit = minuhpmax(1);
}
if (*hpmax_p - mhm.permdmg > lowerlimit)
*hpmax_p -= mhm.permdmg;
else if (*hpmax_p > lowerlimit)
*hpmax_p = lowerlimit;
/* else unlikely...
* already at or below minimum threshold, do nothing to hpmax */
disp.botl = TRUE;
}
mdamageu(mtmp, mhm.damage);
}
if (mhm.damage)
res = passiveum(olduasmon, mtmp, mattk);
else
res = M_ATTK_HIT;
stop_occupation();
return res;
}
/* An interface for use when taking a blindfold off, for example,
* to see if an engulfing attack should immediately take affect, like
* a passive attack. TRUE if engulfing blindness occurred */
boolean
gulp_blnd_check(void)
{
struct attack *mattk;
if (!Blinded && u.uswallow
&& (mattk = attacktype_fordmg(u.ustuck->data, AT_ENGL, AD_BLND))
&& can_blnd(u.ustuck, &gy.youmonst, mattk->aatyp, (struct obj *) 0)) {
++u.uswldtim; /* compensate for gulpmu change */
(void) gulpmu(u.ustuck, mattk);
return TRUE;
}
return FALSE;
}
/* monster swallows you, or damage if already swallowed (u.uswallow != 0) */
staticfn int
gulpmu(struct monst *mtmp, struct attack *mattk)
{
struct trap *t = t_at(u.ux, u.uy);
int tmp = d((int) mattk->damn, (int) mattk->damd);
int tim_tmp;
struct obj *otmp2, *nextobj;
int i;
boolean physical_damage = FALSE;
if (!u.uswallow) { /* swallows you */
int omx = mtmp->mx, omy = mtmp->my;
if (!engulf_target(mtmp, &gy.youmonst))
return M_ATTK_MISS;
if ((t && is_pit(t->ttyp)) && sobj_at(BOULDER, u.ux, u.uy))
return M_ATTK_MISS;
if (failed_grab(mtmp, &gy.youmonst, mattk))
return M_ATTK_MISS;
if (Punished)
unplacebc(); /* ball&chain go away */
remove_monster(omx, omy);
mtmp->mtrapped = 0; /* no longer on old trap */
place_monster(mtmp, u.ux, u.uy);
set_ustuck(mtmp);
newsym(mtmp->mx, mtmp->my);
/* 3.7: dismount for all engulfers, not just for purple worms */
if (u.usteed) {
char buf[BUFSZ];
Strcpy(buf, mon_nam(u.usteed));
urgent_pline("%s %s forward and plucks you off %s!",
Some_Monnam(mtmp),
/* 't', purple 'w' */
is_animal(mtmp->data) ? "lunges"
/* 'v', air 'E' */
: is_whirly(mtmp->data) ? "whirls"
/* none (some 'v', already whirling) */
: unsolid(mtmp->data) ? "flows"
/* ochre 'j', Juiblex */
: amorphous(mtmp->data) ? "oozes"
/* none (all AT_ENGL are already covered) */
: "surges",
buf);
dismount_steed(DISMOUNT_ENGULFED);
} else {
urgent_pline("%s %s!", Monnam(mtmp),
digests(mtmp->data) ? "swallows you whole"
: enfolds(mtmp->data) ? "folds itself around you"
: "engulfs you");
}
stop_occupation();
reset_occupations(); /* behave as if you had moved */
if (u.utrap) {
You("are released from the %s!",
(u.utraptype == TT_WEB) ? "web" : "trap");
reset_utrap(FALSE);
}
i = number_leashed();
if (i > 0) {
const char *s = (i > 1) ? "leashes" : "leash";
pline_The("%s %s loose.", s, vtense(s, "snap"));
unleash_all();
}
if (touch_petrifies(gy.youmonst.data) && !resists_ston(mtmp)) {
/* put the attacker back where it started;
the resulting statue will end up there
[note: if poly'd hero could ride or non-poly'd hero could
acquire touch_petrifies() capability somehow, this code
would need to deal with possibility of steed having taken
engulfer's previous spot when hero was forcibly dismounted] */
remove_monster(mtmp->mx, mtmp->my); /* u.ux,u.uy */
place_monster(mtmp, omx, omy);
minstapetrify(mtmp, TRUE);
/* normally unstuck() would do this, but we're not
fully swallowed yet so that won't work here */
if (Punished)
placebc();
set_ustuck((struct monst *) 0);
return (!DEADMONSTER(mtmp)) ? M_ATTK_MISS : M_ATTK_AGR_DIED;
}
display_nhwindow(WIN_MESSAGE, FALSE);
vision_recalc(2); /* hero can't see anything */
u.uswallow = 1;
/* for digestion, shorter time is more dangerous;
for other swallowings, longer time means more
chances for the swallower to attack */
if (mattk->adtyp == AD_DGST) {
tim_tmp = 25 - (int) mtmp->m_lev;
if (tim_tmp > 0)
tim_tmp = rnd(tim_tmp) / 2;
else if (tim_tmp < 0)
tim_tmp = -(rnd(-tim_tmp) / 2);
/* having good armor & high constitution makes
it take longer for you to be digested, but
you'll end up trapped inside for longer too */
tim_tmp += -u.uac + 10 + (ACURR(A_CON) / 3 - 1);
} else {
/* higher level attacker takes longer to eject hero */
tim_tmp = rnd((int) mtmp->m_lev + 10 / 2);
}
/* u.uswldtim always set > 1 */
u.uswldtim = (unsigned) ((tim_tmp < 2) ? 2 : tim_tmp);
swallowed(1); /* update the map display, shows hero swallowed */
if (!flaming(mtmp->data)) {
for (otmp2 = gi.invent; otmp2; otmp2 = nextobj) {
nextobj = otmp2->nobj;
(void) snuff_lit(otmp2);
}
}
}
if (mtmp != u.ustuck)
return M_ATTK_MISS;
if (Punished) {
/* ball&chain are in limbo while swallowed; update their internal
location to be at swallower's spot */
if (uchain->where == OBJ_FREE)
uchain->ox = mtmp->mx, uchain->oy = mtmp->my;
if (uball->where == OBJ_FREE)
uball->ox = mtmp->mx, uball->oy = mtmp->my;
}
if (u.uswldtim > 0)
u.uswldtim -= 1;
switch (mattk->adtyp) {
case AD_DGST:
physical_damage = TRUE;
if (Slow_digestion) {
/* Messages are handled below */
u.uswldtim = 0;
tmp = 0;
} else if (u.uswldtim == 0) {
pline("%s totally digests you!", Monnam(mtmp));
tmp = u.uhp;
if (Half_physical_damage)
tmp *= 2; /* sorry */
} else {
pline("%s%s digests you!", Monnam(mtmp),
(u.uswldtim == 2) ? " thoroughly"
: (u.uswldtim == 1) ? " utterly" : "");
exercise(A_STR, FALSE);
}
break;
case AD_PHYS:
physical_damage = TRUE;
if (mtmp->data == &mons[PM_FOG_CLOUD]) {
You("are laden with moisture and %s",
flaming(gy.youmonst.data)
? "are smoldering out!"
: Breathless ? "find it mildly uncomfortable."
: amphibious(gy.youmonst.data)
? "feel comforted."
: "can barely breathe!");
if ((Amphibious || Breathless) && !flaming(gy.youmonst.data))
tmp = 0;
} else {
You("are %s!", enfolds(mtmp->data) ? "being squashed"
: "pummeled with debris");
exercise(A_STR, FALSE);
}
break;
case AD_ACID:
if (Acid_resistance) {
You("are covered with a seemingly harmless goo.");
/* NB: the monst[un]seesu calls in gulpmu are no-ops since the
hero must be currently swallowed for the attack to hit... */
monstseesu(M_SEEN_ACID);
tmp = 0;
} else {
if (Hallucination)
pline("Ouch! You've been slimed!");
else
You("are covered in slime! It burns!");
exercise(A_STR, FALSE);
monstunseesu(M_SEEN_ACID);
}
break;
case AD_BLND:
if (can_blnd(mtmp, &gy.youmonst, mattk->aatyp, (struct obj *) 0)) {
if (!Blind) {
long was_blinded = Blinded;
if (!Blinded)
You_cant("see in here!");
make_blinded((long) tmp, FALSE);
if (!was_blinded && !Blind)
Your1(vision_clears);
} else
/* keep him blind until disgorged */
incr_itimeout(&HBlinded, 1L);
}
tmp = 0;
break;
case AD_ELEC:
if (!mtmp->mcan && rn2(2)) {
pline_The("air around you crackles with electricity.");
if (Shock_resistance) {
shieldeff(u.ux, u.uy);
You("seem unhurt.");
monstseesu(M_SEEN_ELEC);
ugolemeffects(AD_ELEC, tmp);
tmp = 0;
} else {
monstunseesu(M_SEEN_ELEC);
}
} else
tmp = 0;
break;
case AD_COLD:
if (!mtmp->mcan && rn2(2)) {
if (Cold_resistance) {
shieldeff(u.ux, u.uy);
You_feel("mildly chilly.");
monstseesu(M_SEEN_COLD);
ugolemeffects(AD_COLD, tmp);
tmp = 0;
} else {
You("are freezing to death!");
monstunseesu(M_SEEN_COLD);
}
} else
tmp = 0;
break;
case AD_FIRE:
if (!mtmp->mcan && rn2(2)) {
if (Fire_resistance) {
shieldeff(u.ux, u.uy);
You_feel("mildly hot.");
monstseesu(M_SEEN_FIRE);
ugolemeffects(AD_FIRE, tmp);
tmp = 0;
} else {
You("are burning to a crisp!");
monstunseesu(M_SEEN_FIRE);
}
burn_away_slime();
} else
tmp = 0;
break;
case AD_DISE:
if (!diseasemu(mtmp->data))
tmp = 0;
break;
case AD_DREN:
/* AC magic cancellation doesn't help when engulfed */
if (!mtmp->mcan && rn2(4)) /* 75% chance */
drain_en(tmp, FALSE);
tmp = 0;
break;
default:
physical_damage = TRUE;
tmp = 0;
break;
}
if (physical_damage)
tmp = Maybe_Half_Phys(tmp);
gm.mswallower = mtmp; /* match gulpmm() */
mdamageu(mtmp, tmp);
gm.mswallower = 0;
if (tmp)
stop_occupation();
if (!u.uswallow) {
; /* life-saving has already expelled swallowed hero */
} else if (touch_petrifies(gy.youmonst.data) && !resists_ston(mtmp)) {
pline("%s very hurriedly %s you!", Monnam(mtmp),
digests(mtmp->data) ? "regurgitates"
: enfolds(mtmp->data) ? "releases"
: "expels");
expels(mtmp, mtmp->data, FALSE);
} else if (!u.uswldtim || gy.youmonst.data->msize >= MZ_HUGE) {
/* As of 3.6.2: u.uswldtim used to be set to 0 by life-saving but it
expels now so the !u.uswldtim case is no longer possible;
however, polymorphing into a huge form while already
swallowed is still possible */
You("get %s!", digests(mtmp->data) ? "regurgitated"
: enfolds(mtmp->data) ? "released"
: "expelled");
if (flags.verbose
&& (digests(mtmp->data) && Slow_digestion))
pline("Obviously %s doesn't like your taste.", mon_nam(mtmp));
expels(mtmp, mtmp->data, FALSE);
}
return M_ATTK_HIT;
}
/* monster explodes in your face */
staticfn int
explmu(struct monst *mtmp, struct attack *mattk, boolean ufound)
{
boolean kill_agr = TRUE;
boolean not_affected;
int tmp;
if (mtmp->mcan)
return M_ATTK_MISS;
tmp = d((int) mattk->damn, (int) mattk->damd);
not_affected = defended(mtmp, (int) mattk->adtyp);
if (!ufound) {
pline("%s explodes at a spot in %s!",
canseemon(mtmp) ? Monnam(mtmp) : "It",
is_waterwall(mtmp->mux,mtmp->muy) ? "empty water"
: "thin air");
} else {
hitmsg(mtmp, mattk);
}
switch (mattk->adtyp) {
case AD_COLD:
case AD_FIRE:
case AD_ELEC:
mon_explodes(mtmp, mattk);
if (!DEADMONSTER(mtmp))
kill_agr = FALSE; /* lifesaving? */
break;
case AD_BLND:
not_affected = resists_blnd(&gy.youmonst);
if (ufound && !not_affected) {
/* sometimes you're affected even if it's invisible */
if (mon_visible(mtmp) || (rnd(tmp /= 2) > u.ulevel)) {
You("are blinded by a blast of light!");
make_blinded((long) tmp, FALSE);
if (!Blind)
Your1(vision_clears);
} else if (flags.verbose)
You("get the impression it was not terribly bright.");
}
break;
case AD_HALU:
not_affected |= Blind || (u.umonnum == PM_BLACK_LIGHT
|| u.umonnum == PM_VIOLET_FUNGUS
|| dmgtype(gy.youmonst.data, AD_STUN));
if (ufound && !not_affected) {
boolean chg;
if (!Hallucination)
You("are caught in a blast of kaleidoscopic light!");
/* avoid hallucinating the black light as it dies */
mondead(mtmp); /* remove it from map now */
kill_agr = FALSE; /* already killed (maybe lifesaved) */
chg =
make_hallucinated(HHallucination + (long) tmp, FALSE, 0L);
You("%s.", chg ? "are freaked out" : "seem unaffected");
}
break;
default:
impossible("unknown exploder damage type %d", mattk->adtyp);
break;
}
if (not_affected) {
You("seem unaffected by it.");
ugolemeffects((int) mattk->adtyp, tmp);
}
if (kill_agr && !DEADMONSTER(mtmp))
mondead(mtmp);
wake_nearto(mtmp->mx, mtmp->my, 7 * 7);
return (!DEADMONSTER(mtmp)) ? M_ATTK_MISS : M_ATTK_AGR_DIED;
}
/* monster gazes at you */
int
gazemu(struct monst *mtmp, struct attack *mattk)
{
static const char *const reactions[] = {
"confused", /* [0] */
"stunned", /* [1] */
"puzzled", "dazzled", /* [2,3] */
"irritated", "inflamed", /* [4,5] */
"tired", /* [6] */
"dulled", /* [7] */
};
int react = -1;
boolean is_medusa, reflectable,
cancelled = (mtmp->mcan != 0), already = FALSE,
mcanseeu = (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my)
&& mtmp->mcansee);
if (m_seenres(mtmp, cvt_adtyp_to_mseenres(mattk->adtyp)))
return M_ATTK_MISS;
is_medusa = (mtmp->data == &mons[PM_MEDUSA]);
reflectable = (Reflecting && couldsee(mtmp->mx, mtmp->my) && is_medusa);
/* assumes that hero has to see monster's gaze in order to be
affected, rather than monster just having to look at hero;
Unaware: asleep or unconscious => not blind but won't see;
when hallucinating, hero's brain doesn't register what
it's seeing correctly so the gaze is usually ineffective
[this could be taken a lot farther and select a gaze effect
appropriate to what's currently being displayed, giving
ordinary monsters a gaze attack when hero thinks he or she
is facing a gazing creature, but let's not go that far...] */
if ((Hallucination && rn2(4)) || (Unaware && !reflectable))
cancelled = TRUE;
switch (mattk->adtyp) {
case AD_STON:
/* note: Medusa is the only monster with stoning gaze, so
'is_medusa' will always be True here */
if (cancelled || !mtmp->mcansee) {
if (!canseemon(mtmp))
break; /* silently */
if (Unaware) { /* can't see attacker even though not blind */
react = is_medusa ? 4 : 2; /* irritated or puzzled */
break;
}
if (is_medusa && Hallucination && !rn2(3))
pline("Someone seems overdue for a serpent cut.");
else
pline_mon(mtmp, "%s %s.", Monnam(mtmp),
(is_medusa && mtmp->mcan && !react)
? "doesn't look all that ugly"
: "gazes ineffectually");
break;
}
if (reflectable) {
/* hero has line of sight to Medusa and she's not blind */
boolean useeit = canseemon(mtmp);
if (useeit)
(void) ureflects("%s gaze is reflected by your %s.",
s_suffix(Monnam(mtmp)));
if (mon_reflects(mtmp, !useeit ? (char *) 0
: "The gaze is reflected away by %s %s!"))
break;
if (!m_canseeu(mtmp)) { /* probably you're invisible */
if (useeit)
pline(
"%s doesn't seem to notice that %s gaze was reflected.",
Monnam(mtmp), mhis(mtmp));
break;
}
if (useeit)
pline_mon(mtmp, "%s is turned to stone!", Monnam(mtmp));
gs.stoned = TRUE;
killed(mtmp);
if (!DEADMONSTER(mtmp))
break;
return M_ATTK_AGR_DIED;
}
if (canseemon(mtmp) && couldsee(mtmp->mx, mtmp->my)
&& !Stone_resistance && !Unaware) {
You("meet %s gaze.", s_suffix(mon_nam(mtmp)));
stop_occupation();
if (poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM))
break;
urgent_pline("You turn to stone...");
svk.killer.format = KILLED_BY;
Strcpy(svk.killer.name, pmname(mtmp->data, Mgender(mtmp)));
done(STONING);
}
break;
case AD_CONF:
if (mcanseeu && !mtmp->mspec_used && rn2(5)) {
if (cancelled) {
react = 0; /* "confused" */
already = (mtmp->mconf != 0);
} else {
int conf = d(3, 4);
mtmp->mspec_used = mtmp->mspec_used + (conf + rn2(6));
if (!Confusion)
pline_mon(mtmp, "%s gaze confuses you!",
s_suffix(Monnam(mtmp)));
else
You("are getting more and more confused.");
make_confused(HConfusion + conf, FALSE);
stop_occupation();
}
}
break;
case AD_STUN:
if (mcanseeu && !mtmp->mspec_used && rn2(5)) {
if (cancelled) {
react = 1; /* "stunned" */
already = (mtmp->mstun != 0);
} else {
int stun = d(2, 6);
mtmp->mspec_used = mtmp->mspec_used + (stun + rn2(6));
pline_mon(mtmp, "%s stares piercingly at you!", Monnam(mtmp));
make_stunned((HStun & TIMEOUT) + (long) stun, TRUE);
stop_occupation();
}
}
break;
case AD_BLND:
if (canseemon(mtmp) && !resists_blnd(&gy.youmonst)
&& mdistu(mtmp) <= BOLT_LIM * BOLT_LIM) {
if (cancelled) {
react = rn1(2, 2); /* "puzzled" || "dazzled" */
already = (mtmp->mcansee == 0);
/* Archons gaze every round; we don't want cancelled ones
giving the "seems puzzled/dazzled" message that often */
if (mtmp->mcan && mtmp->data == &mons[PM_ARCHON] && rn2(5))
react = -1;
} else {
int blnd = d((int) mattk->damn, (int) mattk->damd);
You("are blinded by %s radiance!", s_suffix(mon_nam(mtmp)));
make_blinded((long) blnd, FALSE);
stop_occupation();
/* not blind at this point implies you're wearing
the Eyes of the Overworld; make them block this
particular stun attack too */
if (!Blind) {
Your1(vision_clears);
} else {
long oldstun = (HStun & TIMEOUT), newstun = (long) rnd(3);
/* we don't want to increment stun duration every time
or sighted hero will become incapacitated */
make_stunned(max(oldstun, newstun), TRUE);
}
}
}
break;
case AD_FIRE:
if (mcanseeu && !mtmp->mspec_used && rn2(5)) {
if (cancelled) {
react = rn1(2, 4); /* "irritated" || "inflamed" */
} else {
int dmg = d(2, 6), orig_dmg = dmg, lev = (int) mtmp->m_lev;
pline_mon(mtmp, "%s attacks you with a fiery gaze!",
Monnam(mtmp));
stop_occupation();
if (Fire_resistance) {
shieldeff(u.ux, u.uy);
pline_The("fire doesn't feel hot!");
monstseesu(M_SEEN_FIRE);
ugolemeffects(AD_FIRE, d(12, 6));
dmg = 0;
} else {
monstunseesu(M_SEEN_FIRE);
}
burn_away_slime();
if (lev > rn2(20))
(void) burnarmor(&gy.youmonst);
if (lev > rn2(20)) {
(void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg);
ignite_items(gi.invent);
}
if (dmg)
mdamageu(mtmp, dmg);
}
}
break;
#ifdef PM_BEHOLDER /* work in progress */
case AD_SLEE:
if (mcanseeu && gm.multi >= 0 && !rn2(5) && !Sleep_resistance) {
if (cancelled) {
react = 6; /* "tired" */
already = (mtmp->mfrozen != 0); /* can't happen... */
} else {
fall_asleep(-rnd(10), TRUE);
pline("%s gaze makes you very sleepy...",
s_suffix(Monnam(mtmp)));
monstunseesu(M_SEEN_SLEEP);
}
}
break;
case AD_SLOW:
if (mcanseeu
&& (HFast & (INTRINSIC | TIMEOUT)) && !defended(mtmp, AD_SLOW)
&& !rn2(4)) {
if (cancelled) {
react = 7; /* "dulled" */
already = (mtmp->mspeed == MSLOW);
} else {
u_slow_down();
stop_occupation();
}
}
break;
#endif /* BEHOLDER */
default:
impossible("Gaze attack %d?", mattk->adtyp);
break;
}
if (react >= 0) {
if (Hallucination && rn2(3))
react = rn2(SIZE(reactions));
/* cancelled/hallucinatory feedback; monster might look "confused",
"stunned",&c but we don't actually set corresponding attribute */
pline_mon(mtmp, "%s looks %s%s.", Monnam(mtmp),
!rn2(3) ? "" : already ? "quite "
: (!rn2(2) ? "a bit " : "somewhat "),
reactions[react]);
}
return M_ATTK_MISS;
}
/* mtmp hits you for n points damage */
void
mdamageu(struct monst *mtmp, int n)
{
if (n < 0) {
impossible("mdamageu for negative damage? (%d)", n);
n = 0;
}
disp.botl = TRUE;
if (Upolyd) {
u.mh -= n;
showdamage(n);
/* caller might have reduced mhmax before calling mdamageu() */
if (u.mh > u.mhmax)
u.mh = u.mhmax;
if (u.mh < 1)
rehumanize();
} else {
n = saving_grace(n);
u.uhp -= n;
showdamage(n);
/* caller might have reduced uhpmax before calling mdamageu() */
if (u.uhp > u.uhpmax)
u.uhp = u.uhpmax;
if (u.uhp < 1)
done_in_by(mtmp, DIED);
}
}
/* returns 0 if seduction impossible,
* 1 if fine,
* 2 if wrong gender for nymph
*/
int
could_seduce(
struct monst *magr, struct monst *mdef,
struct attack *mattk) /* non-Null: current atk; Null: genrl capability */
{
struct permonst *pagr;
boolean agrinvis, defperc;
xint16 genagr, gendef;
int adtyp;
if (is_animal(magr->data))
return 0;
if (magr == &gy.youmonst) {
pagr = gy.youmonst.data;
agrinvis = (Invis != 0);
genagr = poly_gender();
} else {
pagr = magr->data;
agrinvis = magr->minvis;
genagr = gender(magr);
}
if (mdef == &gy.youmonst) {
defperc = (See_invisible != 0);
gendef = poly_gender();
} else {
defperc = perceives(mdef->data);
gendef = gender(mdef);
}
adtyp = mattk ? mattk->adtyp
: dmgtype(pagr, AD_SSEX) ? AD_SSEX
: dmgtype(pagr, AD_SEDU) ? AD_SEDU
: AD_PHYS;
if (adtyp == AD_SSEX && !SYSOPT_SEDUCE)
adtyp = AD_SEDU;
if (agrinvis && !defperc && adtyp == AD_SEDU)
return 0;
/* nymphs have two attacks, one for steal-item damage and the other
for seduction, both pass the could_seduce() test;
incubi/succubi have three attacks, their claw attacks for damage
don't pass the test */
if ((pagr->mlet != S_NYMPH && pagr != &mons[PM_AMOROUS_DEMON])
|| (adtyp != AD_SEDU && adtyp != AD_SSEX && adtyp != AD_SITM))
return 0;
return (genagr == 1 - gendef) ? 1 : (pagr->mlet == S_NYMPH) ? 2 : 0;
}
/* returns 1 if monster teleported (or hero leaves monster's vicinity) */
int
doseduce(struct monst *mon)
{
struct obj *ring, *nring;
boolean fem = (mon->data == &mons[PM_AMOROUS_DEMON]
&& Mgender(mon) == FEMALE); /* otherwise incubus */
boolean seewho, naked; /* True iff no armor */
int attr_tot, tried_gloves = 0;
char qbuf[QBUFSZ], Who[QBUFSZ];
if (mon->mcan || mon->mspec_used) {
pline_mon(mon, "%s acts as though %s has got a %sheadache.",
Monnam(mon), mhe(mon), mon->mcan ? "severe " : "");
return 0;
}
if (unresponsive()) {
pline_mon(mon, "%s seems dismayed at your lack of response.",
Monnam(mon));
return 0;
}
seewho = canseemon(mon);
if (!seewho)
pline("Someone caresses you...");
else
You_feel("very attracted to %s.", mon_nam(mon));
/* cache the seducer's name in a local buffer */
Strcpy(Who, (!seewho ? (fem ? "She" : "He") : Monnam(mon)));
/* if in the process of putting armor on or taking armor off,
interrupt that activity now */
(void) stop_donning((struct obj *) 0);
/* don't try to take off gloves if cursed weapon blocks them */
if (welded(uwep))
tried_gloves = 1;
for (ring = gi.invent; ring; ring = nring) {
nring = ring->nobj;
if (ring->otyp != RIN_ADORNMENT)
continue;
if (fem) {
if (ring->owornmask && uarmg) {
/* don't take off worn ring if gloves are in the way */
if (!tried_gloves++)
mayberem(mon, Who, uarmg, "gloves");
if (uarmg)
continue; /* next ring might not be worn */
}
/* confirmation prompt when charisma is high bypassed if deaf */
if (!Deaf && rn2(20) < ACURR(A_CHA)) {
(void) safe_qbuf(qbuf, "\"That ",
" looks pretty. May I have it?\"", ring,
xname, simpleonames, "ring");
makeknown(RIN_ADORNMENT);
SetVoice(mon, 0, 80, 0);
if (y_n(qbuf) == 'n')
continue;
} else
pline("%s decides she'd like %s, and takes it.",
Who, yname(ring));
makeknown(RIN_ADORNMENT);
/* might be in left or right ring slot or weapon/alt-wep/quiver */
if (ring->owornmask)
remove_worn_item(ring, FALSE);
freeinv(ring);
(void) mpickobj(mon, ring);
} else {
if (uleft && uright && uleft->otyp == RIN_ADORNMENT
&& uright->otyp == RIN_ADORNMENT)
break;
if (ring == uleft || ring == uright)
continue;
if (uarmg) {
/* don't put on ring if gloves are in the way */
if (!tried_gloves++)
mayberem(mon, Who, uarmg, "gloves");
if (uarmg)
break; /* no point trying further rings */
}
/* confirmation prompt when charisma is high bypassed if deaf */
if (!Deaf && rn2(20) < ACURR(A_CHA)) {
(void) safe_qbuf(qbuf, "\"That ",
" looks pretty. Would you wear it for me?\"",
ring, xname, simpleonames, "ring");
makeknown(RIN_ADORNMENT);
SetVoice(mon, 0, 80, 0);
if (y_n(qbuf) == 'n')
continue;
} else {
pline("%s decides you'd look prettier wearing %s,",
Who, yname(ring));
pline("and puts it on your finger.");
}
makeknown(RIN_ADORNMENT);
if (!uright) {
pline("%s puts %s on your right %s.",
Who, the(xname(ring)), body_part(HAND));
setworn(ring, RIGHT_RING);
} else if (!uleft) {
pline("%s puts %s on your left %s.",
Who, the(xname(ring)), body_part(HAND));
setworn(ring, LEFT_RING);
} else if (uright && uright->otyp != RIN_ADORNMENT) {
/* note: the "replaces" message might be inaccurate if
hero's location changes and the process gets interrupted,
but trying to figure that out in advance in order to use
alternate wording is not worth the effort */
pline("%s replaces %s with %s.",
Who, yname(uright), yname(ring));
Ring_gone(uright);
/* ring removal might cause loss of levitation which could
drop hero onto trap that transports hero somewhere else */
if (u.utotype || !m_next2u(mon))
return 1;
setworn(ring, RIGHT_RING);
} else if (uleft && uleft->otyp != RIN_ADORNMENT) {
/* see "replaces" note above */
pline("%s replaces %s with %s.",
Who, yname(uleft), yname(ring));
Ring_gone(uleft);
if (u.utotype || !m_next2u(mon))
return 1;
setworn(ring, LEFT_RING);
} else
impossible("ring replacement");
Ring_on(ring);
prinv((char *) 0, ring, 0L);
}
}
naked = (!uarmc && !uarmf && !uarmg && !uarms && !uarmh && !uarmu);
urgent_pline("%s %s%s.", Who,
Deaf ? "seems to murmur into your ear"
: naked ? "murmurs sweet nothings into your ear"
: "murmurs in your ear",
naked ? "" : ", while helping you undress");
mayberem(mon, Who, uarmc, cloak_simple_name(uarmc));
if (!uarmc)
mayberem(mon, Who, uarm, suit_simple_name(uarm));
mayberem(mon, Who, uarmf, "boots");
if (!tried_gloves)
mayberem(mon, Who, uarmg, "gloves");
mayberem(mon, Who, uarms, "shield");
mayberem(mon, Who, uarmh, helm_simple_name(uarmh));
if (!uarmc && !uarm)
mayberem(mon, Who, uarmu, "shirt");
/* removing armor (levitation boots, or levitation ring to make
room for adornment ring with incubus case) might result in the
hero falling through a trap door or landing on a teleport trap
and changing location, so hero might not be adjacent to seducer
any more (mayberem() has its own adjacency test so we don't need
to check after each potential removal) */
if (u.utotype || !m_next2u(mon))
return 1;
if (uarm || uarmc) {
if (!Deaf) {
if (!(ld() && mon->female)) {
SetVoice(mon, 0, 80, 0);
verbalize("You're such a %s; I wish...",
flags.female ? "sweet lady" : "nice guy");
} else {
struct obj *yourgloves = u_carried_gloves();
/* have her call your gloves by their correct
name, possibly revealing them to you */
if (yourgloves)
yourgloves->dknown = 1;
verbalize("Well, then you owe me %s%s!",
yourgloves ? yname(yourgloves)
: "twelve pairs of gloves",
yourgloves ? " and eleven more pairs of gloves"
: "");
}
} else if (seewho)
pline_mon(mon, "%s appears to sigh.", Monnam(mon));
/* else no regret message if can't see or hear seducer */
if (!tele_restrict(mon))
(void) rloc(mon, RLOC_MSG);
return 1;
}
if (u.ualign.type == A_CHAOTIC)
adjalign(1);
/* by this point you have discovered mon's identity, blind or not... */
urgent_pline(
"Time stands still while you and %s lie in each other's arms...",
noit_mon_nam(mon));
/* 3.6.1: a combined total for charisma plus intelligence of 35-1
used to guarantee successful outcome; now total maxes out at 32
as far as deciding what will happen; chance for bad outcome when
Cha+Int is 32 or more is 2/35, a bit over 5.7% */
attr_tot = ACURR(A_CHA) + ACURR(A_INT);
if (rn2(35) > min(attr_tot, 32)) {
/* Don't bother with mspec_used here... it didn't get tired! */
pline("%s seems to have enjoyed it more than you...",
noit_Monnam(mon));
switch (rn2(5)) {
case 0:
You_feel("drained of energy.");
u.uen = 0;
u.uenmax -= rnd(Half_physical_damage ? 5 : 10);
exercise(A_CON, FALSE);
if (u.uenmax < 0)
u.uenmax = 0;
break;
case 1:
You("are down in the dumps.");
(void) adjattrib(A_CON, -1, TRUE);
exercise(A_CON, FALSE);
disp.botl = TRUE;
break;
case 2:
Your("senses are dulled.");
(void) adjattrib(A_WIS, -1, TRUE);
exercise(A_WIS, FALSE);
disp.botl = TRUE;
break;
case 3:
if (!resists_drli(&gy.youmonst)) {
You_feel("out of shape.");
losexp("overexertion");
} else {
You("have a curious feeling...");
}
exercise(A_CON, FALSE);
exercise(A_DEX, FALSE);
exercise(A_WIS, FALSE);
break;
case 4: {
int tmp;
You_feel("exhausted.");
exercise(A_STR, FALSE);
tmp = rn1(10, 6);
losehp(Maybe_Half_Phys(tmp), "exhaustion", KILLED_BY);
break;
} /* case 4 */
} /* switch */
} else {
mon->mspec_used = rnd(100); /* monster is worn out */
You("seem to have enjoyed it more than %s...", noit_mon_nam(mon));
switch (rn2(5)) {
case 0:
You_feel("raised to your full potential.");
exercise(A_CON, TRUE);
u.uen = (u.uenmax += rnd(5));
if (u.uenmax > u.uenpeak)
u.uenpeak = u.uenmax;
break;
case 1:
You_feel("good enough to do it again.");
(void) adjattrib(A_CON, 1, TRUE);
exercise(A_CON, TRUE);
disp.botl = TRUE;
break;
case 2:
You("will always remember %s...", noit_mon_nam(mon));
(void) adjattrib(A_WIS, 1, TRUE);
exercise(A_WIS, TRUE);
disp.botl = TRUE;
break;
case 3:
pline("That was a very educational experience.");
pluslvl(FALSE);
exercise(A_WIS, TRUE);
break;
case 4:
You_feel("restored to health!");
u.uhp = u.uhpmax;
if (Upolyd)
u.mh = u.mhmax;
exercise(A_STR, TRUE);
disp.botl = TRUE;
break;
}
}
if (mon->mtame) { /* don't charge */
;
} else if (rn2(20) < ACURR(A_CHA)) {
pline("%s demands that you pay %s, but you refuse...",
noit_Monnam(mon), noit_mhim(mon));
} else if (u.umonnum == PM_LEPRECHAUN) {
pline_mon(mon, "%s tries to take your gold, but fails...",
noit_Monnam(mon));
} else {
long cost;
long umoney = money_cnt(gi.invent);
if (umoney > (long) LARGEST_INT - 10L)
cost = (long) rnd(LARGEST_INT) + 500L;
else
cost = (long) rnd((int) umoney + 10) + 500L;
if (mon->mpeaceful) {
cost /= 5L;
if (!cost)
cost = 1L;
}
if (cost > umoney)
cost = umoney;
if (!cost) {
SetVoice(mon, 0, 80, 0);
verbalize("It's on the house!");
} else {
pline_mon(mon, "%s takes %ld %s for services rendered!",
noit_Monnam(mon), cost, currency(cost));
money2mon(mon, cost);
disp.botl = TRUE;
}
}
if (!rn2(25))
mon->mcan = 1; /* monster is worn out */
if (!tele_restrict(mon))
(void) rloc(mon, RLOC_MSG);
return 1;
}
/* 'mon' tries to remove a piece of hero's armor */
staticfn void
mayberem(struct monst *mon,
const char *seducer, /* only used for alternate message */
struct obj *obj, const char *str)
{
char qbuf[QBUFSZ];
if (!obj || !obj->owornmask)
return;
/* removal of a previous item might have sent the hero elsewhere
(loss of levitation that leads to landing on a transport trap) */
if (u.utotype || !m_next2u(mon))
return;
/* being deaf overrides confirmation prompt for high charisma */
if (Deaf) {
pline("%s takes off your %s.", seducer, str);
} else if (rn2(20) < ACURR(A_CHA)) {
SetVoice(mon, 0, 80, 0); /* y_n aka yn_function is set up for this */
Sprintf(qbuf, "\"Shall I remove your %s, %s?\"", str,
(!rn2(2) ? "lover" : !rn2(2) ? "dear" : "sweetheart"));
if (y_n(qbuf) == 'n')
return;
} else {
char hairbuf[BUFSZ];
Sprintf(hairbuf, "let me run my fingers through your %s",
body_part(HAIR));
SetVoice(mon, 0, 80, 0);
verbalize("Take off your %s; %s.", str,
(obj == uarm)
? "let's get a little closer"
: (obj == uarmc || obj == uarms)
? "it's in the way"
: (obj == uarmf)
? "let me rub your feet"
: (obj == uarmg)
? "they're too clumsy"
: (obj == uarmu)
? "let me massage you"
/* obj == uarmh */
: hairbuf);
}
remove_worn_item(obj, TRUE);
}
staticfn int
assess_dmg(struct monst *mtmp, int tmp)
{
if ((mtmp->mhp -= tmp) <= 0) {
pline_mon(mtmp, "%s dies!", Monnam(mtmp));
xkilled(mtmp, XKILL_NOMSG);
if (!DEADMONSTER(mtmp))
return M_ATTK_HIT;
return M_ATTK_AGR_DIED;
}
return M_ATTK_HIT;
}
#if 0
/* returns True if monster has a range attack in its repertoire
that it will actually utilize. Caller provides the assessment
callback (optional). Callback returns 0 if the attack is
active */
boolean ranged_attk_assessed(
struct monst *mtmp,
boolean (*assessfunc)(struct monst *, int))
{
int i;
struct permonst *ptr = mtmp->data;
for (i = 0; i < NATTK; i++)
if (DISTANCE_ATTK_TYPE(ptr->mattk[i].aatyp)) {
if (!assessfunc || (*assessfunc)(mtmp, i) == 0)
return TRUE;
}
return FALSE;
}
#endif
/* can be used as ranged_attk_assessed() callback.
Returns TRUE if monster is avoiding use of this attack */
boolean
mon_avoiding_this_attack(struct monst *mtmp, int attkidx)
{
struct permonst *ptr = mtmp->data;
int typ = -1;
if (attkidx >= 0
&& (typ = get_atkdam_type(ptr->mattk[attkidx].adtyp)) >= 0
&& m_seenres(mtmp, cvt_adtyp_to_mseenres(typ)))
return TRUE;
return FALSE;
}
/*
* This would be equivalent to:
* ranged_attk_assessed(mtmp, mon_avoiding_this_attack)
* but without the added assessment function call overhead.
*/
boolean ranged_attk_available(struct monst *mtmp)
{
int i, typ = -1;
struct permonst *ptr = mtmp->data;
for (i = 0; i < NATTK; i++) {
if (DISTANCE_ATTK_TYPE(ptr->mattk[i].aatyp)
&& (typ = get_atkdam_type(ptr->mattk[i].adtyp)) >= 0
&& m_seenres(mtmp, cvt_adtyp_to_mseenres(typ)) == 0)
return TRUE;
}
return FALSE;
}
/* FIXME:
* sequencing issue: a monster's attack might cause poly'd hero
* to revert to normal form. The messages for passive counterattack
* would look better if they came before reverting form, but we need
* to know whether hero reverted in order to decide whether passive
* damage applies.
*/
staticfn int
passiveum(
struct permonst *olduasmon,
struct monst *mtmp,
struct attack *mattk)
{
int i, tmp;
struct attack *oldu_mattk = 0;
/*
* mattk == mtmp's attack that hit you;
* oldu_mattk == your passive counterattack (even if mtmp's attack
* has already caused you to revert to normal form).
*/
for (i = 0; !oldu_mattk; i++) {
if (i >= NATTK)
return M_ATTK_HIT;
if (olduasmon->mattk[i].aatyp == AT_NONE
|| olduasmon->mattk[i].aatyp == AT_BOOM)
oldu_mattk = &olduasmon->mattk[i];
}
if (oldu_mattk->damn)
tmp = d((int) oldu_mattk->damn, (int) oldu_mattk->damd);
else if (oldu_mattk->damd)
tmp = d((int) olduasmon->mlevel + 1, (int) oldu_mattk->damd);
else
tmp = 0;
/* These affect the enemy even if you were "killed" (rehumanized) */
switch (oldu_mattk->adtyp) {
case AD_ACID:
if (!rn2(2)) {
pline_mon(mtmp, "%s is splashed by %s%s!", Monnam(mtmp),
/* temporary? hack for sequencing issue: "your acid"
looks strange coming immediately after player has
been told that hero has reverted to normal form */
!Upolyd ? "" : "your ", hliquid("acid"));
if (resists_acid(mtmp)) {
pline_mon(mtmp, "%s is not affected.", Monnam(mtmp));
tmp = 0;
}
} else
tmp = 0;
if (!rn2(30))
erode_armor(mtmp, ERODE_CORRODE);
if (!rn2(6))
acid_damage(MON_WEP(mtmp));
return assess_dmg(mtmp, tmp);
case AD_STON: /* cockatrice */
{
long protector = attk_protection((int) mattk->aatyp),
wornitems = mtmp->misc_worn_check;
/* wielded weapon gives same protection as gloves here */
if (MON_WEP(mtmp) != 0)
wornitems |= W_ARMG;
if (!resists_ston(mtmp)
&& (protector == 0L
|| (protector != ~0L
&& (wornitems & protector) != protector))) {
if (poly_when_stoned(mtmp->data)) {
mon_to_stone(mtmp);
return 1;
}
pline_mon(mtmp, "%s turns to stone!", Monnam(mtmp));
gs.stoned = 1;
xkilled(mtmp, XKILL_NOMSG);
if (!DEADMONSTER(mtmp))
return M_ATTK_HIT;
return M_ATTK_AGR_DIED;
}
return M_ATTK_HIT;
}
case AD_ENCH: /* KMH -- remove enchantment (disenchanter) */
if (mon_currwep) {
/* by_you==True: passive counterattack to hero's action
is hero's fault */
(void) drain_item(mon_currwep, TRUE);
/* No message */
}
return M_ATTK_HIT;
default:
break;
}
if (!Upolyd)
return M_ATTK_HIT;
/* These affect the enemy only if you are still a monster */
if (rn2(3))
switch (oldu_mattk->adtyp) {
case AD_PHYS:
if (oldu_mattk->aatyp == AT_BOOM) {
You("explode!");
/* KMH, balance patch -- this is okay with unchanging */
rehumanize();
return assess_dmg(mtmp, tmp);
}
break;
case AD_PLYS: /* Floating eye */
if (tmp > 127)
tmp = 127;
if (u.umonnum == PM_FLOATING_EYE) {
if (!rn2(4))
tmp = 127;
if (mtmp->mcansee && haseyes(mtmp->data) && rn2(3)
&& (perceives(mtmp->data) || !Invis)) {
if (Blind) {
pline("As a blind %s, you cannot defend yourself.",
pmname(gy.youmonst.data,
flags.female ? FEMALE : MALE));
} else {
if (mon_reflects(mtmp,
"Your gaze is reflected by %s %s."))
return 1;
pline_mon(mtmp, "%s is frozen by your gaze!",
Monnam(mtmp));
paralyze_monst(mtmp, tmp);
return M_ATTK_AGR_DONE;
}
}
} else { /* gelatinous cube */
pline_mon(mtmp, "%s is frozen by you.", Monnam(mtmp));
paralyze_monst(mtmp, tmp);
return M_ATTK_AGR_DONE;
}
return M_ATTK_HIT;
case AD_COLD: /* Brown mold or blue jelly */
if (resists_cold(mtmp)) {
shieldeff(mtmp->mx, mtmp->my);
pline_mon(mtmp, "%s is mildly chilly.", Monnam(mtmp));
golemeffects(mtmp, AD_COLD, tmp);
tmp = 0;
break;
}
pline_mon(mtmp, "%s is suddenly very cold!", Monnam(mtmp));
u.mh += (tmp + rn2(2)) / 2;
if (u.mhmax < u.mh)
u.mhmax = u.mh;
if (u.mhmax > (((int) gy.youmonst.data->mlevel + 1) * 8))
(void) split_mon(&gy.youmonst, mtmp);
break;
case AD_STUN: /* Yellow mold */
if (!mtmp->mstun) {
mtmp->mstun = 1;
pline_mon(mtmp, "%s %s.", Monnam(mtmp),
makeplural(stagger(mtmp->data, "stagger")));
}
tmp = 0;
break;
case AD_FIRE: /* Red mold */
if (resists_fire(mtmp)) {
shieldeff(mtmp->mx, mtmp->my);
pline_mon(mtmp, "%s is mildly warm.", Monnam(mtmp));
golemeffects(mtmp, AD_FIRE, tmp);
tmp = 0;
break;
}
pline_mon(mtmp, "%s is suddenly very hot!", Monnam(mtmp));
break;
case AD_ELEC:
if (resists_elec(mtmp)) {
shieldeff(mtmp->mx, mtmp->my);
pline_mon(mtmp, "%s is slightly tingled.", Monnam(mtmp));
golemeffects(mtmp, AD_ELEC, tmp);
tmp = 0;
break;
}
pline_mon(mtmp, "%s is jolted with your electricity!",
Monnam(mtmp));
break;
default:
tmp = 0;
break;
}
else
tmp = 0;
return assess_dmg(mtmp, tmp);
}
struct monst *
cloneu(void)
{
struct monst *mon;
int mndx = monsndx(gy.youmonst.data);
if (u.mh <= 1)
return (struct monst *) 0;
if (svm.mvitals[mndx].mvflags & G_EXTINCT)
return (struct monst *) 0;
mon = makemon(gy.youmonst.data, u.ux, u.uy,
NO_MINVENT | MM_EDOG | MM_NOMSG);
if (!mon)
return NULL;
mon->mcloned = 1;
mon = christen_monst(mon, svp.plname);
initedog(mon, TRUE);
mon->m_lev = gy.youmonst.data->mlevel;
mon->mhpmax = u.mhmax;
mon->mhp = u.mh / 2;
u.mh -= mon->mhp;
disp.botl = TRUE;
return mon;
}
/*mhitu.c*/