Files
nethack/src/uhitm.c
2024-02-28 20:15:56 -08:00

6152 lines
223 KiB
C

/* NetHack 3.7 uhitm.c $NHDT-Date: 1699813308 2023/11/12 18:21:48 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.419 $ */
/* 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"
static const char brief_feeling[] =
"have a %s feeling for a moment, then it passes.";
static boolean mhitm_mgc_atk_negated(struct monst *, struct monst *,
boolean) NONNULLPTRS;
static boolean known_hitum(struct monst *, struct obj *, int *, int, int,
struct attack *, int) NONNULLARG13;
static boolean theft_petrifies(struct obj *) NONNULLARG1;
static void steal_it(struct monst *, struct attack *) NONNULLARG1;
/* hitum_cleave() has contradictory information. There's a comment
* beside the 1st arg 'target' stating non-null, but later on there
* is a test for 'target' being null */
static boolean hitum_cleave(struct monst *, struct attack *) NO_NNARGS;
static boolean double_punch(void);
static boolean hitum(struct monst *, struct attack *) NONNULLARG1;
static void hmon_hitmon_barehands(struct _hitmon_data *,
struct monst *) NONNULLARG12;
static void hmon_hitmon_weapon_ranged(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG123;
static void hmon_hitmon_weapon_melee(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG123;
static void hmon_hitmon_weapon(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG123;
static void hmon_hitmon_potion(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG123;
static void hmon_hitmon_misc_obj(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG123;
static void hmon_hitmon_do_hit(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG12;
static void hmon_hitmon_dmg_recalc(struct _hitmon_data *, struct obj *);
static void hmon_hitmon_poison(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG123;
static void hmon_hitmon_jousting(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG123;
static void hmon_hitmon_stagger(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG12;
static void hmon_hitmon_pet(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG12;
static void hmon_hitmon_splitmon(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG12;
static void hmon_hitmon_msg_hit(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG12;
static void hmon_hitmon_msg_silver(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG12;
static void hmon_hitmon_msg_lightobj(struct _hitmon_data *, struct monst *,
struct obj *) NONNULLARG12;
static boolean hmon_hitmon(struct monst *, struct obj *, int, int) NONNULLARG1;
static int joust(struct monst *, struct obj *) NONNULLARG12;
static void demonpet(void);
static boolean m_slips_free(struct monst *, struct attack *) NONNULLPTRS;
static void start_engulf(struct monst *) NONNULLARG1;
static void end_engulf(void);
static int gulpum(struct monst *, struct attack *) NONNULLPTRS;
static boolean hmonas(struct monst *) NONNULLARG1;
static void nohandglow(struct monst *) NONNULLARG1;
static boolean mhurtle_to_doom(struct monst *, int,
struct permonst **) NONNULLARG13;
static void first_weapon_hit(struct obj *) NONNULLARG1;
static boolean shade_aware(struct obj *) NO_NNARGS;
#define PROJECTILE(obj) ((obj) && is_ammo(obj))
static boolean
mhitm_mgc_atk_negated(
struct monst *magr, struct monst *mdef,
boolean verbosely) /* give mesg if magical cancellation prevents damage */
{
int armpro;
boolean negated;
/* mcan doesn't apply to youmonst; hero can't be cancelled */
if (magr != &gy.youmonst && magr->mcan)
return TRUE; /* no message if attacker has been cancelled */
armpro = magic_negation(mdef);
negated = !(rn2(10) >= 3 * armpro);
if (negated) {
/* attack has been thwarted by negation, aka magical cancellation */
if (verbosely) {
if (mdef == &gy.youmonst)
You("avoid harm.");
else if (gv.vis && canseemon(mdef))
pline("%s avoids harm.", Monnam(mdef));
}
return TRUE;
}
return FALSE;
}
/* multi_reason is usually a literal string; here we generate one that
has the causing monster's type included */
void
dynamic_multi_reason(struct monst *mon, const char *verb, boolean by_gaze)
{
/* combination of noname_monnam() and m_monnam(), more or less;
accurate regardless of visibility or hallucination (only seen
if game ends) and without personal name (M2_PNAME excepted) */
char *who = x_monnam(mon, ARTICLE_A, (char *) 0,
(SUPPRESS_IT | SUPPRESS_INVISIBLE
| SUPPRESS_HALLUCINATION | SUPPRESS_SADDLE
| SUPPRESS_NAME),
FALSE),
*p = gm.multireasonbuf;
/* prefix info for done_in_by() */
Sprintf(p, "%u:", mon->m_id);
p = eos(p);
Sprintf(p, "%s by %s%s", verb,
!by_gaze ? who : s_suffix(who),
!by_gaze ? "" : " gaze");
gm.multi_reason = p;
}
void
erode_armor(struct monst *mdef, int hurt)
{
struct obj *target;
/* What the following code does: it keeps looping until it
* finds a target for the rust monster.
* Head, feet, etc... not covered by metal, or covered by
* rusty metal, are not targets. However, your body always
* is, no matter what covers it.
*/
while (1) {
switch (rn2(5)) {
case 0:
target = which_armor(mdef, W_ARMH);
if (!target
|| erode_obj(target, xname(target), hurt, EF_GREASE)
== ER_NOTHING)
continue;
break;
case 1:
target = which_armor(mdef, W_ARMC);
if (target) {
(void) erode_obj(target, xname(target), hurt,
EF_GREASE | EF_VERBOSE);
break;
}
if ((target = which_armor(mdef, W_ARM)) != (struct obj *) 0) {
(void) erode_obj(target, xname(target), hurt,
EF_GREASE | EF_VERBOSE);
} else if ((target = which_armor(mdef, W_ARMU))
!= (struct obj *) 0) {
(void) erode_obj(target, xname(target), hurt,
EF_GREASE | EF_VERBOSE);
}
break;
case 2:
target = which_armor(mdef, W_ARMS);
if (!target
|| erode_obj(target, xname(target), hurt, EF_GREASE)
== ER_NOTHING)
continue;
break;
case 3:
target = which_armor(mdef, W_ARMG);
if (!target
|| erode_obj(target, xname(target), hurt, EF_GREASE)
== ER_NOTHING)
continue;
break;
case 4:
target = which_armor(mdef, W_ARMF);
if (!target
|| erode_obj(target, xname(target), hurt, EF_GREASE)
== ER_NOTHING)
continue;
break;
}
break; /* Out of while loop */
}
}
/* FALSE means it's OK to attack */
boolean
attack_checks(
struct monst *mtmp, /* target */
struct obj *wep) /* uwep for do_attack(), null for kick_monster() */
{
int glyph;
/* if you're close enough to attack, alert any waiting monster */
mtmp->mstrategy &= ~STRAT_WAITMASK;
if (engulfing_u(mtmp))
return FALSE;
if (gc.context.forcefight) {
/* Do this in the caller, after we checked that the monster
* didn't die from the blow. Reason: putting the 'I' there
* causes the hero to forget the square's contents since
* both 'I' and remembered contents are stored in .glyph.
* If the monster dies immediately from the blow, the 'I' will
* not stay there, so the player will have suddenly forgotten
* the square's contents for no apparent reason.
if (!canspotmon(mtmp)
&& !glyph_is_invisible(levl[gb.bhitpos.x][gb.bhitpos.y].glyph))
map_invisible(gb.bhitpos.x, gb.bhitpos.y);
*/
return FALSE;
}
/* cache the shown glyph;
cases which might change it (by placing or removing
'remembered, unseen monster' glyph or revealing a mimic)
always return without further reference to this */
glyph = glyph_at(gb.bhitpos.x, gb.bhitpos.y);
/* Put up an invisible monster marker, but with exceptions for
* monsters that hide and monsters you've been warned about.
* The former already prints a warning message and
* prevents you from hitting the monster just via the hidden monster
* code below; if we also did that here, similar behavior would be
* happening two turns in a row. The latter shows a glyph on
* the screen, so you know something is there.
*/
if (!canspotmon(mtmp)
&& !glyph_is_warning(glyph) && !glyph_is_invisible(glyph)
&& !(!Blind && mtmp->mundetected && hides_under(mtmp->data))) {
pline("Wait! There's %s there you can't see!", something);
map_invisible(gb.bhitpos.x, gb.bhitpos.y);
/* if it was an invisible mimic, treat it as if we stumbled
* onto a visible mimic
*/
if (M_AP_TYPE(mtmp) && !Protection_from_shape_changers) {
if (!u.ustuck && !mtmp->mflee && dmgtype(mtmp->data, AD_STCK)
/* applied pole-arm attack is too far to get stuck */
&& m_next2u(mtmp))
set_ustuck(mtmp);
}
/* #H7329 - if hero is on engraved "Elbereth", this will end up
* assessing an alignment penalty and removing the engraving
* even though no attack actually occurs. Since it also angers
* peacefuls, we're operating as if an attack attempt did occur
* and the Elbereth behavior is consistent.
*/
wakeup(mtmp, TRUE); /* always necessary; also un-mimics mimics */
return TRUE;
}
if (M_AP_TYPE(mtmp) && !Protection_from_shape_changers && !sensemon(mtmp)
&& !glyph_is_warning(glyph)) {
/* If a hidden mimic was in a square where a player remembers
* some (probably different) unseen monster, the player is in
* luck--he attacks it even though it's hidden.
*/
if (glyph_is_invisible(glyph)) {
seemimic(mtmp);
return FALSE;
}
stumble_onto_mimic(mtmp);
return TRUE;
}
if (mtmp->mundetected && !canseemon(mtmp)
&& !glyph_is_warning(glyph)
&& (hides_under(mtmp->data) || mtmp->data->mlet == S_EEL)) {
mtmp->mundetected = mtmp->msleeping = 0;
newsym(mtmp->mx, mtmp->my);
if (glyph_is_invisible(glyph)) {
seemimic(mtmp);
return FALSE;
}
if (!tp_sensemon(mtmp) && !Detect_monsters) {
struct obj *obj;
char lmonbuf[BUFSZ];
boolean notseen;
Strcpy(lmonbuf, l_monnam(mtmp));
/* might be unseen if invisible and hero can't see invisible */
notseen = !strcmp(lmonbuf, "it"); /* note: not strcmpi() */
if (!Blind && Hallucination)
pline("A %s %s %s!", mtmp->mtame ? "tame" : "wild",
notseen ? "creature" : (const char *) lmonbuf,
notseen ? "is present" : "appears");
else if (Blind || (is_pool(mtmp->mx, mtmp->my) && !Underwater))
pline("Wait! There's a hidden monster there!");
else if ((obj = gl.level.objects[mtmp->mx][mtmp->my]) != 0)
pline("Wait! There's %s hiding under %s!",
notseen ? something : (const char *) an(lmonbuf),
doname(obj));
return TRUE;
}
}
/*
* make sure to wake up a monster from the above cases if the
* hero can sense that the monster is there.
*/
if ((mtmp->mundetected || M_AP_TYPE(mtmp)) && sensemon(mtmp)) {
mtmp->mundetected = 0;
wakeup(mtmp, TRUE);
}
if (flags.confirm && mtmp->mpeaceful
&& !Confusion && !Hallucination && !Stunned) {
/* Intelligent chaotic weapons (Stormbringer) want blood */
if (is_art(wep, ART_STORMBRINGER)) {
go.override_confirmation = TRUE;
return FALSE;
}
if (canspotmon(mtmp)) {
char qbuf[QBUFSZ];
Sprintf(qbuf, "Really attack %s?", mon_nam(mtmp));
if (!paranoid_query(ParanoidHit, qbuf)) {
gc.context.move = 0;
return TRUE;
}
}
}
return FALSE;
}
/* it is unchivalrous for a knight to attack the defenseless or from behind */
void
check_caitiff(struct monst *mtmp)
{
if (u.ualign.record <= -10)
return;
if (Role_if(PM_KNIGHT) && u.ualign.type == A_LAWFUL
&& !is_undead(mtmp->data)
&& (helpless(mtmp)
|| (mtmp->mflee && !mtmp->mavenge))) {
You("caitiff!");
adjalign(-1);
} else if (Role_if(PM_SAMURAI) && mtmp->mpeaceful) {
/* attacking peaceful creatures is bad for the samurai's giri */
You("dishonorably attack the innocent!");
adjalign(-1);
}
}
/* maybe unparalyze monster */
void
mon_maybe_unparalyze(struct monst *mtmp)
{
if (!mtmp->mcanmove) {
if (!rn2(10)) {
mtmp->mcanmove = 1;
mtmp->mfrozen = 0;
}
}
}
/* how easy it is for hero to hit a monster,
using attack type aatyp and/or weapon.
larger value == easier to hit */
int
find_roll_to_hit(
struct monst *mtmp,
uchar aatyp, /* usually AT_WEAP or AT_KICK */
struct obj *weapon, /* uwep or uswapwep or NULL */
int *attk_count,
int *role_roll_penalty)
{
int tmp, tmp2;
*role_roll_penalty = 0; /* default is `none' */
tmp = 1 + Luck + abon() + find_mac(mtmp) + u.uhitinc
+ maybe_polyd(gy.youmonst.data->mlevel, u.ulevel);
/* some actions should occur only once during multiple attacks */
if (!(*attk_count)++) {
/* knight's chivalry or samurai's giri */
check_caitiff(mtmp);
}
/* adjust vs. monster state */
if (mtmp->mstun)
tmp += 2;
if (mtmp->mflee)
tmp += 2;
if (mtmp->msleeping)
tmp += 2;
if (!mtmp->mcanmove)
tmp += 4;
/* role/race adjustments */
if (Role_if(PM_MONK) && !Upolyd) {
if (uarm)
tmp -= (*role_roll_penalty = gu.urole.spelarmr);
else if (!uwep && !uarms)
tmp += (u.ulevel / 3) + 2;
}
if (is_orc(mtmp->data)
&& maybe_polyd(is_elf(gy.youmonst.data), Race_if(PM_ELF)))
tmp++;
/* encumbrance: with a lot of luggage, your agility diminishes */
if ((tmp2 = near_capacity()) != 0)
tmp -= (tmp2 * 2) - 1;
if (u.utrap)
tmp -= 3;
/*
* hitval applies if making a weapon attack while wielding a weapon;
* weapon_hit_bonus applies if doing a weapon attack even bare-handed
* or if kicking as martial artist
*/
if (aatyp == AT_WEAP || aatyp == AT_CLAW) {
if (weapon)
tmp += hitval(weapon, mtmp);
tmp += weapon_hit_bonus(weapon);
} else if (aatyp == AT_KICK && martial_bonus()) {
tmp += weapon_hit_bonus((struct obj *) 0);
}
return tmp;
}
/* temporarily override 'safepet' (by faking use of 'F' prefix) when possibly
unintentionally attacking peaceful monsters and optionally pets */
boolean
force_attack(struct monst *mtmp, boolean pets_too)
{
boolean attacked, save_Forcefight;
save_Forcefight = gc.context.forcefight;
/* always set forcefight On for hostiles and peacefuls, maybe for pets */
if (pets_too || !mtmp->mtame)
gc.context.forcefight = TRUE;
attacked = do_attack(mtmp);
gc.context.forcefight = save_Forcefight;
return attacked;
}
/* try to attack; return False if monster evaded;
u.dx and u.dy must be set */
boolean
do_attack(struct monst *mtmp)
{
struct permonst *mdat = mtmp->data;
/* This section of code provides protection against accidentally
* hitting peaceful (like '@') and tame (like 'd') monsters.
* Protection is provided as long as player is not: blind, confused,
* hallucinating or stunned.
* changes by wwp 5/16/85
* More changes 12/90, -dkh-. if its tame and safepet, (and protected
* 07/92) then we assume that you're not trying to attack. Instead,
* you'll usually just swap places if this is a movement command
*/
/* Intelligent chaotic weapons (Stormbringer) want blood */
if (is_safemon(mtmp) && !gc.context.forcefight) {
if (!u_wield_art(ART_STORMBRINGER)) {
/* There are some additional considerations: this won't work
* if in a shop or Punished or you miss a random roll or
* if you can walk thru walls and your pet cannot (KAA) or
* if your pet is a long worm with a tail.
* There's also a chance of displacing a "frozen" monster:
* sleeping monsters might magically walk in their sleep.
* This block of code used to only be called for pets; now
* that it also applies to peacefuls, non-pets mustn't be
* forced to flee.
*/
boolean foo = (Punished || !rn2(7)
|| (is_longworm(mtmp->data) && mtmp->wormno)
|| (IS_ROCK(levl[u.ux][u.uy].typ)
&& !passes_walls(mtmp->data))),
inshop = FALSE;
char *p;
/* only check for in-shop if don't already have reason to stop */
if (!foo) {
for (p = in_rooms(mtmp->mx, mtmp->my, SHOPBASE); *p; p++)
if (tended_shop(&gr.rooms[*p - ROOMOFFSET])) {
inshop = TRUE;
break;
}
}
if (inshop || foo) {
char buf[BUFSZ];
if (!gc.context.travel && !gc.context.run)
if (canspotmon(mtmp) && mtmp->isshk)
return ECMD_TIME | dopay();
if (mtmp->mtame) /* see 'additional considerations' above */
monflee(mtmp, rnd(6), FALSE, FALSE);
Strcpy(buf, y_monnam(mtmp));
buf[0] = highc(buf[0]);
You("stop. %s is in the way!", buf);
end_running(TRUE);
return TRUE;
} else if (mtmp->mfrozen || helpless(mtmp)
|| (mtmp->data->mmove == 0 && rn2(6))) {
pline("%s doesn't seem to move!", Monnam(mtmp));
end_running(TRUE);
return TRUE;
} else
return FALSE;
}
}
/* possibly set in attack_checks;
examined in known_hitum, called via hitum or hmonas below */
go.override_confirmation = FALSE;
/* attack_checks() used to use <u.ux+u.dx,u.uy+u.dy> directly, now
it uses gb.bhitpos instead; it might map an invisible monster there */
gb.bhitpos.x = u.ux + u.dx;
gb.bhitpos.y = u.uy + u.dy;
gn.notonhead = (gb.bhitpos.x != mtmp->mx || gb.bhitpos.y != mtmp->my);
if (attack_checks(mtmp, uwep))
return TRUE;
if (Upolyd && noattacks(gy.youmonst.data)) {
/* certain "pacifist" monsters don't attack */
You("have no way to attack monsters physically.");
mtmp->mstrategy &= ~STRAT_WAITMASK;
goto atk_done;
}
if (check_capacity("You cannot fight while so heavily loaded.")
/* consume extra nutrition during combat; maybe pass out */
|| overexertion())
goto atk_done;
if (u.twoweap && !can_twoweapon())
untwoweapon();
if (gu.unweapon) {
gu.unweapon = FALSE;
if (flags.verbose) {
if (uwep)
You("begin bashing monsters with %s.", yname(uwep));
else if (!cantwield(gy.youmonst.data))
You("begin %s monsters with your %s %s.",
ing_suffix(Role_if(PM_MONK) ? "strike" : "bash"),
uarmg ? "gloved" : "bare", /* Del Lamb */
makeplural(body_part(HAND)));
}
}
exercise(A_STR, TRUE); /* you're exercising muscles */
/* andrew@orca: prevent unlimited pick-axe attacks */
u_wipe_engr(3);
/* Is the "it died" check actually correct? */
if (mdat->mlet == S_LEPRECHAUN && !mtmp->mfrozen && !helpless(mtmp)
&& !mtmp->mconf && mtmp->mcansee && !rn2(7)
&& (m_move(mtmp, 0) == MMOVE_DIED /* it died */
|| mtmp->mx != u.ux + u.dx
|| mtmp->my != u.uy + u.dy)) { /* it moved */
You("miss wildly and stumble forwards.");
return FALSE;
}
if (Upolyd)
(void) hmonas(mtmp);
else
(void) hitum(mtmp, gy.youmonst.data->mattk);
mtmp->mstrategy &= ~STRAT_WAITMASK;
atk_done:
/* see comment in attack_checks() */
/* we only need to check for this if we did an attack_checks()
* and it returned 0 (it's okay to attack), and the monster didn't
* evade.
*/
if (gc.context.forcefight && !DEADMONSTER(mtmp) && !canspotmon(mtmp)
&& !glyph_is_invisible(levl[u.ux + u.dx][u.uy + u.dy].glyph)
&& !engulfing_u(mtmp))
map_invisible(u.ux + u.dx, u.uy + u.dy);
return TRUE;
}
/* really hit target monster; returns TRUE if it still lives */
static boolean
known_hitum(
struct monst *mon, /* target */
struct obj *weapon, /* uwep or uswapwep */
int *mhit, /* *mhit is 1 or 0; hit (1) gets changed to miss (0)
* for decapitation attack against headless target */
int rollneeded, /* rollneeded and armorpenalty are used for monks +*/
int armorpenalty, /*+ wearing suits so miss message can vary for missed
* because of penalty vs would have missed anyway */
struct attack *uattk,
int dieroll)
{
boolean malive = TRUE,
/* hmon() might destroy weapon; remember aspect for cutworm */
slice_or_chop = (weapon && (is_blade(weapon) || is_axe(weapon)));
if (go.override_confirmation) {
/* this may need to be generalized if weapons other than
Stormbringer acquire similar anti-social behavior... */
if (flags.verbose)
Your("bloodthirsty blade attacks!");
}
if (!*mhit) {
missum(mon, uattk, (rollneeded + armorpenalty > dieroll));
} else {
int oldhp = mon->mhp;
long oldweaphit = u.uconduct.weaphit;
/* KMH, conduct */
if (weapon && (weapon->oclass == WEAPON_CLASS || is_weptool(weapon)))
u.uconduct.weaphit++;
/* we hit the monster; be careful: it might die or
be knocked into a different location */
gn.notonhead = (mon->mx != gb.bhitpos.x || mon->my != gb.bhitpos.y);
malive = hmon(mon, weapon, HMON_MELEE, dieroll);
if (malive) {
/* monster still alive */
if (!rn2(25) && mon->mhp < mon->mhpmax / 2
&& !engulfing_u(mon)) {
/* maybe should regurgitate if swallowed? */
monflee(mon, !rn2(3) ? rnd(100) : 0, FALSE, TRUE);
if (u.ustuck == mon && !u.uswallow
&& !sticks(gy.youmonst.data))
set_ustuck((struct monst *) 0);
}
/* Vorpal Blade hit converted to miss */
/* could be headless monster or worm tail */
if (mon->mhp == oldhp) {
*mhit = 0;
/* a miss does not break conduct */
u.uconduct.weaphit = oldweaphit;
}
if (mon->wormno && *mhit)
cutworm(mon, gb.bhitpos.x, gb.bhitpos.y, slice_or_chop);
}
}
return malive;
}
/* hit the monster next to you and the monsters to the left and right of it;
return False if the primary target is killed, True otherwise */
static boolean
hitum_cleave(
struct monst *target, /* non-Null; forcefight at nothing doesn't cleave +*/
struct attack *uattk) /*+ but we don't enforce that here; Null works ok */
{
/* swings will be delivered in alternate directions; with consecutive
attacks it will simulate normal swing and backswing; when swings
are non-consecutive, hero will sometimes start a series of attacks
with a backswing--that doesn't impact actual play, just spoils the
simulation attempt a bit */
static boolean clockwise = FALSE;
int i;
coord save_bhitpos;
boolean save_notonhead;
int count, umort, x = u.ux, y = u.uy;
/* find the direction toward primary target */
i = xytod(u.dx, u.dy);
if (i == DIR_ERR) {
impossible("hitum_cleave: unknown target direction [%d,%d,%d]?",
u.dx, u.dy, u.dz);
return TRUE; /* target hasn't been killed */
}
/* adjust direction by two so that loop's increment (for clockwise)
or decrement (for counter-clockwise) will point at the spot next
to primary target */
i = clockwise ? DIR_LEFT2(i) : DIR_RIGHT2(i);
umort = u.umortality; /* used to detect life-saving */
save_bhitpos = gb.bhitpos;
save_notonhead = gn.notonhead;
/*
* Three attacks: adjacent to primary, primary, adjacent on other
* side. Primary target must be present or we wouldn't have gotten
* here (forcefight at thin air won't 'cleave'). However, the
* first attack might kill it (gas spore explosion, weak long worm
* occupying both spots) so we don't assume that it's still present
* on the second attack.
*/
for (count = 3; count > 0; --count) {
struct monst *mtmp;
int tx, ty, tmp, dieroll, mhit, attknum = 0, armorpenalty;
/* ++i, wrap 8 to i=0 /or/ --i, wrap -1 to i=7 */
i = clockwise ? DIR_RIGHT(i) : DIR_LEFT(i);
tx = x + xdir[i], ty = y + ydir[i]; /* current target location */
if (!isok(tx, ty))
continue;
mtmp = m_at(tx, ty);
if (!mtmp) {
if (glyph_is_invisible(levl[tx][ty].glyph))
(void) unmap_invisible(tx, ty);
continue;
}
tmp = find_roll_to_hit(mtmp, uattk->aatyp, uwep,
&attknum, &armorpenalty);
mon_maybe_unparalyze(mtmp);
dieroll = rnd(20);
mhit = (tmp > dieroll);
gb.bhitpos.x = tx, gb.bhitpos.y = ty; /* normally set by do_attack() */
gn.notonhead = (mtmp->mx != tx || mtmp->my != ty);
(void) known_hitum(mtmp, uwep, &mhit, tmp, armorpenalty,
uattk, dieroll);
(void) passive(mtmp, uwep, mhit, !DEADMONSTER(mtmp), AT_WEAP, !uwep);
/* stop attacking if weapon is gone or hero got paralyzed or
killed (and then life-saved) by passive counter-attack */
if (!uwep || gm.multi < 0 || u.umortality > umort)
break;
}
/* set up for next time */
clockwise = !clockwise; /* alternate */
gb.bhitpos = save_bhitpos; /* in case somebody relies on bhitpos
* designating the primary target */
gn.notonhead = save_notonhead;
/* return False if primary target died, True otherwise; note: if 'target'
was nonNull upon entry then it's still nonNull even if *target died */
return (target && DEADMONSTER(target)) ? FALSE : TRUE;
}
/* returns True if hero is fighting without a weapon and without a shield and
has sufficient skill in bare-handed/martial arts to attack twice */
static boolean
double_punch(void)
{
/* note: P_BARE_HANDED_COMBAT and P_MARTIAL_ARTS are equivalent */
int skl_lvl = P_SKILL(P_BARE_HANDED_COMBAT);
/*
* Chance to attempt a second bare-handed or martial arts hit:
* restricted (0), [not applicable; no one is restricted]
* unskilled (1) : 0%
* basic (2) : 0%
* skilled (3) : 20%
* expert (4) : 40%
* master (5) : 60%
* grandmaster (6) : 80%
*/
if (!uwep && !uarms && skl_lvl > P_BASIC)
return (skl_lvl - P_BASIC) > rn2(5);
return FALSE;
}
/* hit target monster; returns TRUE if it still lives */
static boolean
hitum(struct monst *mon, struct attack *uattk)
{
boolean malive, wep_was_destroyed = FALSE;
struct obj *wepbefore = uwep,
*secondwep = u.twoweap ? uswapwep : (struct obj *) 0;
int tmp, dieroll, mhit, armorpenalty, attknum = 0,
x = u.ux + u.dx, y = u.uy + u.dy, oldumort = u.umortality;
/* Cleaver attacks three spots, 'mon' and one on either side of 'mon';
it can't be part of dual-wielding but we guard against that anyway;
cleave return value reflects status of primary target ('mon') */
if (u_wield_art(ART_CLEAVER) && !u.twoweap
&& !u.uswallow && !u.ustuck && !NODIAG(u.umonnum))
return hitum_cleave(mon, uattk);
/* 0: single hit, 1: first of two hits; affects strength bonus and
silver rings; known_hitum() -> hmon() -> hmon_hitmon() will copy
gt.twohits into struct _hitmon_data hmd.twohits */
gt.twohits = (uwep ? u.twoweap : double_punch()) ? 1 : 0;
tmp = find_roll_to_hit(mon, uattk->aatyp, uwep, &attknum, &armorpenalty);
mon_maybe_unparalyze(mon);
dieroll = rnd(20);
mhit = (tmp > dieroll || u.uswallow);
if (tmp > dieroll)
exercise(A_DEX, TRUE);
/* gb.bhitpos is set up by caller */
malive = known_hitum(mon, uwep, &mhit, tmp, armorpenalty, uattk, dieroll);
if (wepbefore && !uwep)
wep_was_destroyed = TRUE;
(void) passive(mon, uwep, mhit, malive, AT_WEAP, wep_was_destroyed);
/* second attack for two-weapon combat or skilled unarmed combat;
won't occur if Stormbringer overrode confirmation (assumes
Stormbringer is primary weapon), or if hero became paralyzed by
passive counter-attack, or if hero was killed by passive
counter-attack and got life-saved, or if monster was killed or
knocked to different location */
if (gt.twohits && !(go.override_confirmation
|| gm.multi < 0 || u.umortality > oldumort
|| !malive || m_at(x, y) != mon)) {
gt.twohits = 2; /* second of 2 hits */
tmp = find_roll_to_hit(mon, uattk->aatyp, uswapwep, &attknum,
&armorpenalty);
mon_maybe_unparalyze(mon);
dieroll = rnd(20);
mhit = (tmp > dieroll || u.uswallow);
malive = known_hitum(mon, secondwep, &mhit, tmp, armorpenalty, uattk,
dieroll);
/* second passive counter-attack only occurs if second attack hits */
if (mhit)
(void) passive(mon, secondwep, mhit, malive, AT_WEAP,
secondwep && !uswapwep);
}
gt.twohits = 0;
return malive;
}
/* general "damage monster" routine; return True if mon still alive */
boolean
hmon(struct monst *mon,
struct obj *obj,
int thrown, /* HMON_xxx (0 => hand-to-hand, other => ranged) */
int dieroll)
{
boolean result, anger_guards;
anger_guards = (mon->mpeaceful
&& (mon->ispriest || mon->isshk || is_watch(mon->data)));
result = hmon_hitmon(mon, obj, thrown, dieroll);
if (mon->ispriest && !rn2(2))
ghod_hitsu(mon);
if (anger_guards)
(void) angry_guards(!!Deaf);
return result;
}
/* hero hits monster bare handed */
static void
hmon_hitmon_barehands(struct _hitmon_data *hmd, struct monst *mon)
{
long spcdmgflg, silverhit = 0L; /* worn masks */
if (hmd->mdat == &mons[PM_SHADE]) {
hmd->dmg = 0;
} else {
/* note: 1..2 or 1..4 can be substantially increased by
strength bonus or skill bonus, usually both... */
hmd->dmg = rnd(!martial_bonus() ? 2 : 4);
hmd->use_weapon_skill = TRUE;
hmd->train_weapon_skill = (hmd->dmg > 1);
}
/* Blessed gloves give bonuses when fighting 'bare-handed'. So do
silver rings. Note: rings are worn under gloves, so you don't
get both bonuses, and two silver rings don't give double bonus.
When making only one hit, both rings are checked (backwards
compatibility => playability), but when making two hits, only the
ring on the hand making the attack is checked. */
spcdmgflg = uarmg ? W_ARMG
: (((hmd->twohits == 0 || hmd->twohits == 1) ? W_RINGR : 0L)
| ((hmd->twohits == 0 || hmd->twohits == 2) ? W_RINGL : 0L));
hmd->dmg += special_dmgval(&gy.youmonst, mon, spcdmgflg, &silverhit);
/* copy silverhit info back into struct _hitmon_data *hmd */
switch (hmd->twohits) {
case 0: /* only one hit being attempted; a silver ring on either hand
* applies but having silver rings on both is same as just one */
hmd->barehand_silver_rings = (silverhit & (W_RINGR | W_RINGL)) ? 1 : 0;
break;
case 1: /* first of two or more hit attempts; right ring applies */
hmd->barehand_silver_rings = (silverhit & W_RINGR) ? 1 : 0;
break;
case 2: /* second of two or more hit attempts; left ring applies */
hmd->barehand_silver_rings = (silverhit & W_RINGL) ? 1 : 0;
break;
default: /* third or later of more than two hit attempts (poly'd hero);
* rings were applied on first and second hits */
hmd->barehand_silver_rings = 0;
break;
}
if (hmd->barehand_silver_rings > 0)
hmd->silvermsg = TRUE;
}
static void
hmon_hitmon_weapon_ranged(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj is not NULL */
{
/* then do only 1-2 points of damage and don't use or
train weapon's skill */
if (hmd->mdat == &mons[PM_SHADE] && !shade_glare(obj))
hmd->dmg = 0;
else
hmd->dmg = rnd(2);
if (hmd->material == SILVER && mon_hates_silver(mon)) {
hmd->silvermsg = hmd->silverobj = TRUE;
/* if it will already inflict dmg, make it worse */
hmd->dmg += rnd((hmd->dmg) ? 20 : 10);
}
if (!hmd->thrown && obj == uwep && obj->otyp == BOOMERANG
&& rnl(4) == 4 - 1) {
boolean more_than_1 = (obj->quan > 1L);
pline("As you hit %s, %s%s breaks into splinters.",
mon_nam(mon), more_than_1 ? "one of " : "",
yname(obj));
if (!more_than_1)
uwepgone(); /* set gu.unweapon */
useup(obj);
if (!more_than_1)
obj = (struct obj *) 0;
hmd->hittxt = TRUE;
if (hmd->mdat != &mons[PM_SHADE])
hmd->dmg++;
}
}
static void
hmon_hitmon_weapon_melee(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj is not NULL */
{
int wtype;
struct obj *monwep;
/* "normal" weapon usage */
hmd->use_weapon_skill = TRUE;
hmd->dmg = dmgval(obj, mon);
/* a minimal hit doesn't exercise proficiency */
hmd->train_weapon_skill = (hmd->dmg > 1);
/* special attack actions */
if (!hmd->train_weapon_skill || mon == u.ustuck || u.twoweap
/* Cleaver can hit up to three targets at once so don't
let it also hit from behind or shatter foes' weapons */
|| (hmd->hand_to_hand && is_art(obj, ART_CLEAVER))) {
; /* no special bonuses */
} else if (mon->mflee && Role_if(PM_ROGUE) && !Upolyd
/* multi-shot throwing is too powerful here */
&& hmd->hand_to_hand) {
You("strike %s from behind!", mon_nam(mon));
hmd->dmg += rnd(u.ulevel);
hmd->hittxt = TRUE;
} else if (hmd->dieroll == 2 && obj == uwep
&& obj->oclass == WEAPON_CLASS
&& (bimanual(obj)
|| (Role_if(PM_SAMURAI) && obj->otyp == KATANA
&& !uarms))
&& ((wtype = uwep_skill_type()) != P_NONE
&& P_SKILL(wtype) >= P_SKILLED)
&& ((monwep = MON_WEP(mon)) != 0
&& !is_flimsy(monwep)
&& !obj_resists(monwep,
50 + 15 * (greatest_erosion(obj)
- greatest_erosion(monwep)),
100))) {
/*
* 2.5% chance of shattering defender's weapon when
* using a two-handed weapon; less if uwep is rusted.
* [dieroll == 2 is most successful non-beheading or
* -bisecting hit, in case of special artifact damage;
* the percentage chance is (1/20)*(50/100).]
* If attacker's weapon is rustier than defender's,
* the obj_resists chance is increased so the shatter
* chance is decreased; if less rusty, then vice versa.
*/
setmnotwielded(mon, monwep);
mon->weapon_check = NEED_WEAPON;
pline("%s from the force of your blow!",
Yobjnam2(monwep, "shatter"));
m_useupall(mon, monwep);
/* If someone just shattered MY weapon, I'd flee! */
if (rn2(4)) {
monflee(mon, d(2, 3), TRUE, TRUE);
}
hmd->hittxt = TRUE;
}
if (obj->oartifact
&& artifact_hit(&gy.youmonst, mon, obj, &hmd->dmg, hmd->dieroll)) {
/* artifact_hit updates 'tmp' but doesn't inflict any
damage; however, it might cause carried items to be
destroyed and they might do so */
if (DEADMONSTER(mon)) { /* artifact killed monster */
hmd->doreturn = TRUE;
hmd->retval = FALSE;
return;
/*return FALSE;*/
}
/* perhaps artifact tried to behead a headless monster */
if (hmd->dmg == 0) {
hmd->doreturn = TRUE;
hmd->retval = TRUE;
return;
/*return TRUE;*/
}
hmd->hittxt = TRUE;
}
if (hmd->material == SILVER && mon_hates_silver(mon)) {
hmd->silvermsg = hmd->silverobj = TRUE;
}
if (artifact_light(obj) && obj->lamplit
&& mon_hates_light(mon))
hmd->lightobj = TRUE;
if (u.usteed && !hmd->thrown && hmd->dmg > 0
&& weapon_type(obj) == P_LANCE && mon != u.ustuck) {
hmd->jousting = joust(mon, obj);
/* exercise skill even for minimal damage hits */
if (hmd->jousting)
hmd->train_weapon_skill = TRUE;
}
if (hmd->thrown == HMON_THROWN
&& (is_ammo(obj) || is_missile(obj))) {
if (ammo_and_launcher(obj, uwep)) {
/* elves and samurai do extra damage using their own
bows with own arrows; they're highly trained */
if (Role_if(PM_SAMURAI) && obj->otyp == YA
&& uwep->otyp == YUMI)
hmd->dmg++;
else if (Race_if(PM_ELF) && obj->otyp == ELVEN_ARROW
&& uwep->otyp == ELVEN_BOW)
hmd->dmg++;
hmd->train_weapon_skill = (hmd->dmg > 0);
}
if (obj->opoisoned && is_poisonable(obj))
hmd->ispoisoned = TRUE;
}
}
static void
hmon_hitmon_weapon(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj is not NULL */
{
/* is it not a melee weapon? */
if (/* if you strike with a bow... */
is_launcher(obj)
/* or strike with a missile in your hand... */
|| (!hmd->thrown && (is_missile(obj) || is_ammo(obj)))
/* or use a pole at short range and not mounted... */
|| (!hmd->thrown && !u.usteed && is_pole(obj))
/* or throw a missile without the proper bow... */
|| (is_ammo(obj) && (hmd->thrown != HMON_THROWN
|| !ammo_and_launcher(obj, uwep)))) {
hmon_hitmon_weapon_ranged(hmd, mon, obj);
} else {
hmon_hitmon_weapon_melee(hmd, mon, obj);
if (hmd->doreturn)
return;
}
}
static void
hmon_hitmon_potion(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj is not NULL */
{
if (obj->quan > 1L)
obj = splitobj(obj, 1L);
else
setuwep((struct obj *) 0);
freeinv(obj);
potionhit(mon, obj,
hmd->hand_to_hand ? POTHIT_HERO_BASH : POTHIT_HERO_THROW);
if (DEADMONSTER(mon)) {
hmd->doreturn = TRUE;
hmd->retval = FALSE; /* killed */
return;
}
hmd->hittxt = TRUE;
/* in case potion effect causes transformation */
hmd->mdat = mon->data;
hmd->dmg = (hmd->mdat == &mons[PM_SHADE]) ? 0 : 1;
}
static void
hmon_hitmon_misc_obj(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj is not NULL */
{
switch (obj->otyp) {
case BOULDER: /* 1d20 */
case HEAVY_IRON_BALL: /* 1d25 */
case IRON_CHAIN: /* 1d4+1 */
hmd->dmg = dmgval(obj, mon);
break;
case MIRROR:
if (breaktest(obj)) {
You("break %s. That's bad luck!", ysimple_name(obj));
change_luck(-2);
useup(obj);
obj = (struct obj *) 0;
hmd->unarmed = FALSE; /* avoid obj==0 confusion */
hmd->get_dmg_bonus = FALSE;
hmd->hittxt = TRUE;
}
hmd->dmg = 1;
break;
case EXPENSIVE_CAMERA:
You("succeed in destroying %s. Congratulations!",
ysimple_name(obj));
release_camera_demon(obj, u.ux, u.uy);
useup(obj);
hmd->doreturn = TRUE;
hmd->retval = TRUE;
return;
/*return TRUE;*/
case CORPSE: /* fixed by polder@cs.vu.nl */
if (touch_petrifies(&mons[obj->corpsenm])) {
hmd->dmg = 1;
hmd->hittxt = TRUE;
You("hit %s with %s.", mon_nam(mon),
corpse_xname(obj, (const char *) 0,
obj->dknown ? CXN_PFX_THE
: CXN_ARTICLE));
obj->dknown = 1;
if (!munstone(mon, TRUE))
minstapetrify(mon, TRUE);
if (resists_ston(mon))
break;
/* note: hp may be <= 0 even if munstoned==TRUE */
hmd->doreturn = TRUE;
hmd->retval = !DEADMONSTER(mon);
return;
/*return (boolean) !DEADMONSTER(mon);*/
#if 0
} else if (touch_petrifies(mdat)) {
; /* maybe turn the corpse into a statue? */
#endif
}
hmd->dmg = (ismnum(obj->corpsenm) ? mons[obj->corpsenm].msize
: 0) + 1;
break;
#define useup_eggs(o) \
do { \
if (hmd->thrown) \
obfree(o, (struct obj *) 0); \
else \
useupall(o); \
o = (struct obj *) 0; \
} while (0) /* now gone */
case EGG: {
long cnt = obj->quan;
hmd->dmg = 1; /* nominal physical damage */
hmd->get_dmg_bonus = FALSE;
hmd->hittxt = TRUE; /* message always given */
/* egg is always either used up or transformed, so next
hand-to-hand attack should yield a "bashing" mesg */
if (obj == uwep)
gu.unweapon = TRUE;
if (obj->spe && ismnum(obj->corpsenm)) {
if (obj->quan < 5L)
change_luck((schar) - (obj->quan));
else
change_luck(-5);
}
if (ismnum(obj->corpsenm)
&& touch_petrifies(&mons[obj->corpsenm])) {
/*learn_egg_type(obj->corpsenm);*/
pline("Splat! You hit %s with %s %s egg%s!",
mon_nam(mon),
obj->known ? "the" : cnt > 1L ? "some" : "a",
obj->known ? mons[obj->corpsenm].pmnames[NEUTRAL]
: "petrifying",
plur(cnt));
obj->known = 1; /* (not much point...) */
useup_eggs(obj);
if (!munstone(mon, TRUE))
minstapetrify(mon, TRUE);
if (resists_ston(mon))
break;
hmd->doreturn = TRUE;
hmd->retval = !DEADMONSTER(mon);
return;
/*return (boolean) (!DEADMONSTER(mon));*/
} else { /* ordinary egg(s) */
enum monnums mnum = obj->corpsenm;
const char *eggp =
(ismnum(mnum) && obj->known)
? the(mons[mnum].pmnames[NEUTRAL])
: (cnt > 1L) ? "some" : "an";
You("hit %s with %s egg%s.", mon_nam(mon), eggp,
plur(cnt));
if (touch_petrifies(hmd->mdat) && !stale_egg(obj)) {
pline_The("egg%s %s alive any more...", plur(cnt),
(cnt == 1L) ? "isn't" : "aren't");
if (obj->timed)
obj_stop_timers(obj);
obj->otyp = ROCK;
obj->oclass = GEM_CLASS;
obj->oartifact = 0;
obj->spe = 0;
obj->known = obj->dknown = obj->bknown = 0;
obj->owt = weight(obj);
if (hmd->thrown)
place_object(obj, mon->mx, mon->my);
} else {
pline("Splat!");
useup_eggs(obj);
exercise(A_WIS, FALSE);
}
}
break;
#undef useup_eggs
}
case CLOVE_OF_GARLIC: /* no effect against demons */
if (is_undead(hmd->mdat) || is_vampshifter(mon)) {
monflee(mon, d(2, 4), FALSE, TRUE);
}
hmd->dmg = 1;
break;
case CREAM_PIE:
case BLINDING_VENOM:
mon->msleeping = 0;
if (can_blnd(&gy.youmonst, mon,
(uchar) ((obj->otyp == BLINDING_VENOM)
? AT_SPIT
: AT_WEAP),
obj)) {
if (Blind) {
pline(obj->otyp == CREAM_PIE ? "Splat!"
: "Splash!");
} else if (obj->otyp == BLINDING_VENOM) {
pline_The("venom blinds %s%s!", mon_nam(mon),
mon->mcansee ? "" : " further");
} else {
char *whom = mon_nam(mon);
char *what = The(xname(obj));
if (!hmd->thrown && obj->quan > 1L)
what = An(singular(obj, xname));
/* note: s_suffix returns a modifiable buffer */
if (haseyes(hmd->mdat)
&& hmd->mdat != &mons[PM_FLOATING_EYE])
whom = strcat(strcat(s_suffix(whom), " "),
mbodypart(mon, FACE));
pline("%s %s over %s!", what,
vtense(what, "splash"), whom);
}
setmangry(mon, TRUE);
mon->mcansee = 0;
hmd->dmg = rn1(25, 21);
if (((int) mon->mblinded + hmd->dmg) > 127)
mon->mblinded = 127;
else
mon->mblinded += hmd->dmg;
} else {
pline(obj->otyp == CREAM_PIE ? "Splat!" : "Splash!");
setmangry(mon, TRUE);
}
{
boolean more_than_1 = (obj->quan > 1L);
if (hmd->thrown)
obfree(obj, (struct obj *) 0);
else
useup(obj);
if (!more_than_1)
obj = (struct obj *) 0;
}
hmd->hittxt = TRUE;
hmd->get_dmg_bonus = FALSE;
hmd->dmg = 0;
break;
case ACID_VENOM: /* thrown (or spit) */
if (resists_acid(mon)) {
Your("venom hits %s harmlessly.", mon_nam(mon));
hmd->dmg = 0;
} else {
Your("venom burns %s!", mon_nam(mon));
hmd->dmg = dmgval(obj, mon);
}
{
boolean more_than_1 = (obj->quan > 1L);
if (hmd->thrown)
obfree(obj, (struct obj *) 0);
else
useup(obj);
if (!more_than_1)
obj = (struct obj *) 0;
}
hmd->hittxt = TRUE;
hmd->get_dmg_bonus = FALSE;
break;
default:
/* non-weapons can damage because of their weight */
/* (but not too much) */
hmd->dmg = (obj->owt + 99) / 100;
hmd->dmg = (hmd->dmg <= 1) ? 1 : rnd(hmd->dmg);
if (hmd->dmg > 6)
hmd->dmg = 6;
/* wet towel has modest damage bonus beyond its weight,
based on its wetness */
if (is_wet_towel(obj)) {
boolean doubld = (mon->data == &mons[PM_IRON_GOLEM]);
/* wielded wet towel should probably use whip skill
(but not by setting objects[TOWEL].oc_skill==P_WHIP
because that would turn towel into a weptool);
due to low weight, tmp always starts at 1 here, and
due to wet towel's definition, obj->spe is 1..7 */
hmd->dmg += obj->spe * (doubld ? 2 : 1);
hmd->dmg = rnd(hmd->dmg); /* wet towel damage not capped at 6 */
/* usually lose some wetness but defer doing so
until after hit message */
hmd->dryit = (rn2(obj->spe + 1) > 0);
}
/* things like silver wands can arrive here so we
need another silver check; blessed check too */
if (hmd->material == SILVER && mon_hates_silver(mon)) {
hmd->dmg += rnd(20);
hmd->silvermsg = hmd->silverobj = TRUE;
}
if (obj->blessed && mon_hates_blessings(mon))
hmd->dmg += rnd(4);
}
}
/* do the actual hitting monster with obj/fists */
static void
hmon_hitmon_do_hit(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj can be NULL */
{
if (!obj) { /* attack with bare hands */
hmon_hitmon_barehands(hmd, mon);
} else {
/* obj is not NULL here because of the !obj check in this if block,
, so no guard is needed ahead of stone_missile(obj) */
/* stone missile does not hurt xorn or earth elemental, but doesn't
pass all the way through and continue on to some further target */
if ((hmd->thrown == HMON_THROWN
|| hmd->thrown == HMON_KICKED) /* not Applied */
&& stone_missile(obj) && passes_rocks(hmd->mdat)) {
hit(mshot_xname(obj), mon, " but does no harm.");
wakeup(mon, TRUE);
hmd->doreturn = TRUE;
hmd->retval = TRUE;
return;
}
/* remember obj's name since it might end up being destroyed and
we'll want to use it after that */
if (!(artifact_light(obj) && obj->lamplit))
Strcpy(hmd->saved_oname, cxname(obj));
else
Strcpy(hmd->saved_oname, bare_artifactname(obj));
if (obj->oclass == WEAPON_CLASS || is_weptool(obj)
|| obj->oclass == GEM_CLASS) {
hmon_hitmon_weapon(hmd, mon, obj);
if (hmd->doreturn)
return;
/* attacking with non-weapons */
} else if (obj->oclass == POTION_CLASS) {
hmon_hitmon_potion(hmd, mon, obj);
if (hmd->doreturn)
return;
} else {
if (hmd->mdat == &mons[PM_SHADE] && !shade_aware(obj)) {
hmd->dmg = 0;
} else {
hmon_hitmon_misc_obj(hmd, mon, obj);
}
}
}
}
static void
hmon_hitmon_dmg_recalc(struct _hitmon_data *hmd, struct obj *obj)
{
int dmgbonus = 0, strbonus, absbonus;
/*
* Potential bonus (or penalty) from worn ring of increase damage
* (or intrinsic bonus from eating same) or from strength. Strength
* bonus is increased for melee with two-handed weapons and decreased
* for dual attacks (but when both hit, the total for the two is more
* than the bonus for a regular single hit).
*/
if (hmd->get_dmg_bonus) {
/* for dual attacks, udaminc applies to both, and two-handed
weapons use it as-is */
dmgbonus = u.udaminc;
/* throwing using a propellor gets an increase-damage bonus
but not a strength one; other attacks get both;
for dual attacks, 3/4 of the strength bonus is used; when
both attacks hit, overall bonus is 3/2 rather than doubled;
melee hit with two-handed weapon uses 3/2 strength bonus to
approximately match double hit with two-weapon ('approximate'
because udaminc skews in favor of two-weapon); the 3/2 factor
for two-handed strength does not apply to polearms unless
hero is simply bashing with one of those and does not apply
to jousting because lances are one-handed */
if (hmd->thrown != HMON_THROWN
|| !obj || !uwep || !ammo_and_launcher(obj, uwep)) {
strbonus = dbon();
absbonus = abs(strbonus);
if (hmd->twohits)
strbonus = ((3 * absbonus + 2) / 4) * sgn(strbonus);
else if (hmd->thrown == HMON_MELEE && uwep && bimanual(uwep))
strbonus = ((3 * absbonus + 1) / 2) * sgn(strbonus);
dmgbonus += strbonus;
}
}
/*
* Potential bonus (or penalty) from weapon skill.
* 'use_weapon_skill' is True for hand-to-hand ordinary weapon,
* applied or jousting polearm or lance, thrown missile (dart,
* shuriken, boomerang), or shot ammo (arrow, bolt, rock/gem when
* wielding corresponding launcher).
* It is False for hand-to-hand or thrown non-weapon, hand-to-hand
* polearm or lance when not mounted, hand-to-hand missile or ammo
* or launcher, thrown non-missile, or thrown ammo (including rocks)
* when not wielding corresponding launcher.
*/
if (hmd->use_weapon_skill) {
struct obj *skillwep = obj;
if (PROJECTILE(obj) && ammo_and_launcher(obj, uwep))
skillwep = uwep;
dmgbonus += weapon_dam_bonus(skillwep);
/* hit for more than minimal damage (before being adjusted
for damage or skill bonus) trains the skill toward future
enhancement */
if (hmd->train_weapon_skill) {
/* [this assumes that `!thrown' implies wielded...] */
int wtype = hmd->thrown ? weapon_type(skillwep)
: uwep_skill_type();
use_skill(wtype, 1);
}
}
/* apply combined damage+strength and skill bonuses */
hmd->dmg += dmgbonus;
/* don't let penalty, if bonus is negative, turn a hit into a miss */
if (hmd->dmg < 1)
hmd->dmg = 1;
}
static void
hmon_hitmon_poison(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj is not NULL */
{
int nopoison = (10 - (obj->owt / 10));
if (nopoison < 2)
nopoison = 2;
if (Role_if(PM_SAMURAI)) {
You("dishonorably use a poisoned weapon!");
adjalign(-sgn(u.ualign.type));
} else if (u.ualign.type == A_LAWFUL && u.ualign.record > -10) {
You_feel("like an evil coward for using a poisoned weapon.");
adjalign(-1);
}
if (!rn2(nopoison)) {
/* remove poison now in case obj ends up in a bones file */
obj->opoisoned = FALSE;
/* defer "obj is no longer poisoned" until after hit message */
hmd->unpoisonmsg = TRUE;
}
if (resists_poison(mon))
hmd->needpoismsg = TRUE;
else if (rn2(10))
hmd->dmg += rnd(6);
else
hmd->poiskilled = TRUE;
}
static void
hmon_hitmon_jousting(
struct _hitmon_data *hmd,
struct monst *mon, /* target */
struct obj *obj) /* lance; obj is not NULL */
{
hmd->dmg += d(2, (obj == uwep) ? 10 : 2); /* [was in dmgval()] */
You("joust %s%s", mon_nam(mon), canseemon(mon) ? exclam(hmd->dmg) : ".");
/* if this hit just broke the never-hit-with-wielded-weapon conduct
(handled by caller...), give a livelog message for that now */
if (u.uconduct.weaphit <= 1)
first_weapon_hit(obj);
if (hmd->jousting < 0) {
pline("%s shatters on impact!", Yname2(obj));
/* (must be either primary or secondary weapon to get here) */
set_twoweap(FALSE); /* sets u.twoweap = FALSE;
* untwoweapon() is too verbose here */
if (obj == uwep)
uwepgone(); /* set gu.unweapon */
/* minor side-effect: broken lance won't split puddings */
useup(obj);
obj = (struct obj *) 0;
}
if (mhurtle_to_doom(mon, hmd->dmg, &hmd->mdat))
hmd->already_killed = TRUE;
hmd->hittxt = TRUE;
}
static void
hmon_hitmon_stagger(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj UNUSED)
{
/* VERY small chance of stunning opponent if unarmed. */
if (rnd(100) < P_SKILL(P_BARE_HANDED_COMBAT) && !bigmonst(hmd->mdat)
&& !thick_skinned(hmd->mdat)) {
if (canspotmon(mon))
pline("%s %s from your powerful strike!", Monnam(mon),
makeplural(stagger(mon->data, "stagger")));
if (mhurtle_to_doom(mon, hmd->dmg, &hmd->mdat))
hmd->already_killed = TRUE;
hmd->hittxt = TRUE;
}
}
static void
hmon_hitmon_pet(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj UNUSED)
{
if (mon->mtame && hmd->dmg > 0) {
/* do this even if the pet is being killed (affects revival) */
abuse_dog(mon); /* reduces tameness */
/* flee if still alive and still tame; if already suffering from
untimed fleeing, no effect, otherwise increases timed fleeing */
if (mon->mtame && !hmd->destroyed)
monflee(mon, 10 * rnd(hmd->dmg), FALSE, FALSE);
}
}
static void
hmon_hitmon_splitmon(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj can be NULL but guards are in place below */
{
if ((hmd->mdat == &mons[PM_BLACK_PUDDING]
|| hmd->mdat == &mons[PM_BROWN_PUDDING])
/* pudding is alive and healthy enough to split */
&& mon->mhp > 1 && !mon->mcan
/* iron weapon using melee or polearm hit [3.6.1: metal weapon too;
also allow either or both weapons to cause split when twoweap] */
&& obj && (obj == uwep || (u.twoweap && obj == uswapwep))
&& ((hmd->material == IRON
/* allow scalpel and tsurugi to split puddings */
|| hmd->material == METAL)
/* but not bashing with darts, arrows or ya */
&& !(is_ammo(obj) || is_missile(obj)))
&& hmd->hand_to_hand) {
struct monst *mclone;
char withwhat[BUFSZ];
if ((mclone = clone_mon(mon, 0, 0)) != 0) {
withwhat[0] = '\0';
if (u.twoweap && flags.verbose)
Sprintf(withwhat, " with %s", yname(obj));
pline("%s divides as you hit it%s!", Monnam(mon), withwhat);
hmd->hittxt = TRUE;
(void) mintrap(mclone, NO_TRAP_FLAGS);
}
}
}
static void
hmon_hitmon_msg_hit(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj) /* obj can be NULL for hand_to_hand; otherwise not */
{
if (!hmd->hittxt /*( thrown => obj exists )*/
&& (!hmd->destroyed
|| (hmd->thrown && gm.m_shot.n > 1
&& gm.m_shot.o == obj->otyp))) {
if (hmd->thrown)
hit(mshot_xname(obj), mon, exclam(hmd->dmg));
else if (!flags.verbose)
You("hit it.");
else /* hand_to_hand */
You("%s %s%s",
(obj && (is_shield(obj)
|| obj->otyp == HEAVY_IRON_BALL)) ? "bash"
: (obj && (objects[obj->otyp].oc_skill == P_WHIP
|| is_wet_towel(obj))) ? "lash"
: Role_if(PM_BARBARIAN) ? "smite"
: "hit",
mon_nam(mon), canseemon(mon) ? exclam(hmd->dmg) : ".");
}
}
static void
hmon_hitmon_msg_silver(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj UNUSED)
{
const char *fmt;
char *whom = mon_nam(mon);
char silverobjbuf[BUFSZ];
if (canspotmon(mon)) {
if (hmd->barehand_silver_rings == 1)
fmt = "Your silver ring sears %s!";
else if (hmd->barehand_silver_rings == 2)
fmt = "Your silver rings sear %s!";
else if (hmd->silverobj && hmd->saved_oname[0]) {
/* guard constructed format string against '%' in
saved_oname[] from xname(via cxname()) */
Snprintf(silverobjbuf, sizeof(silverobjbuf), "Your %s%s %s",
strstri(hmd->saved_oname, "silver") ? "" : "silver ",
hmd->saved_oname, vtense(hmd->saved_oname, "sear"));
(void) strNsubst(silverobjbuf, "%", "%%", 0);
strncat(silverobjbuf, " %s!",
sizeof(silverobjbuf) - (strlen(silverobjbuf) + 1));
fmt = silverobjbuf;
} else
fmt = "The silver sears %s!";
} else {
*whom = highc(*whom); /* "it" -> "It" */
fmt = "%s is seared!";
}
/* note: s_suffix returns a modifiable buffer */
if (!noncorporeal(hmd->mdat) && !amorphous(hmd->mdat))
whom = strcat(s_suffix(whom), " flesh");
DISABLE_WARNING_FORMAT_NONLITERAL
pline(fmt, whom);
RESTORE_WARNING_FORMAT_NONLITERAL
}
static void
hmon_hitmon_msg_lightobj(
struct _hitmon_data *hmd,
struct monst *mon,
struct obj *obj UNUSED)
{
const char *fmt;
char *whom = mon_nam(mon);
char emitlightobjbuf[BUFSZ];
if (canspotmon(mon)) {
if (hmd->saved_oname[0]) {
Sprintf(emitlightobjbuf,
"%s radiance penetrates deep into",
s_suffix(hmd->saved_oname));
Strcat(emitlightobjbuf, " %s!");
fmt = emitlightobjbuf;
} else
fmt = "The light sears %s!";
} else {
*whom = highc(*whom); /* "it" -> "It" */
fmt = "%s is seared!";
}
/* note: s_suffix returns a modifiable buffer */
if (!noncorporeal(hmd->mdat) && !amorphous(hmd->mdat))
whom = strcat(s_suffix(whom), " flesh");
DISABLE_WARNING_FORMAT_NONLITERAL
pline(fmt, whom);
RESTORE_WARNING_FORMAT_NONLITERAL
}
/*
* These will segfault if passed a NULL obj pointer:
* hmon_hitmon_weapon_ranged,
* hmon_hitmon_weapon_melee,
* hmon_hitmon_weapon,
* hmon_hitmon_potion,
* hmon_hitmon_misc_obj,
* hmon_hitmon_poison,
* hmon_hitmon_jousting,
*
* These are equipped to handle a NULL obj pointer:
* hmon_hitmon_stagger, - obj arg is unused
* hmon_hitmon_pet, - obj arg is unused
* hmon_hitmon_msg_silver, - obj arg is unused
* hmon_hitmon_msg_lightobj, - obj arg is unused
* hmon_hitmon_do_hit, - has obj and !obj code paths
* hmon_hitmon_splitmon, - has !obj guards
* hmon_hitmon_msg_hit, - has !obj guards exc. thrown which is ok
*/
/* guts of hmon(); returns True if 'mon' survives */
static boolean
hmon_hitmon(
struct monst *mon,
struct obj *obj,
int thrown, /* HMON_xxx (0 => hand-to-hand, other => ranged) */
int dieroll)
{
struct _hitmon_data hmd;
hmd.dmg = 0;
hmd.thrown = thrown;
hmd.twohits = thrown ? 0 : gt.twohits;
hmd.dieroll = dieroll;
hmd.mdat = mon->data;
hmd.use_weapon_skill = FALSE;
hmd.train_weapon_skill = FALSE;
hmd.barehand_silver_rings = 0;
hmd.silvermsg = FALSE;
hmd.silverobj = FALSE;
hmd.lightobj = FALSE;
hmd.material = obj ? objects[obj->otyp].oc_material
: 0; /* 0 == NO_MATERIAL */
hmd.jousting = 0;
hmd.hittxt = FALSE;
hmd.get_dmg_bonus = TRUE;
hmd.unarmed = !uwep && !uarm && !uarms;
hmd.hand_to_hand = (thrown == HMON_MELEE
/* not grapnels; applied implies uwep */
|| (thrown == HMON_APPLIED && is_pole(uwep)));
hmd.ispoisoned = FALSE;
hmd.unpoisonmsg = FALSE;
hmd.needpoismsg = FALSE;
hmd.poiskilled = FALSE;
hmd.already_killed = FALSE;
hmd.destroyed = FALSE;
hmd.dryit = FALSE;
hmd.doreturn = FALSE;
hmd.retval = FALSE;
hmd.saved_oname[0] = '\0';
hmon_hitmon_do_hit(&hmd, mon, obj);
if (hmd.doreturn)
return hmd.retval;
/*
***** NOTE: perhaps obj is undefined! (if !thrown && BOOMERANG)
* *OR* if attacking bare-handed!
* Note too: the cases where obj might get destroyed do not
* set 'use_weapon_skill', bare-handed does.
*/
if (hmd.dmg > 0)
hmon_hitmon_dmg_recalc(&hmd, obj);
if (hmd.ispoisoned)
hmon_hitmon_poison(&hmd, mon, obj);
if (hmd.dmg < 1) {
boolean mon_is_shade = (mon->data == &mons[PM_SHADE]);
/* make sure that negative damage adjustment can't result
in inadvertently boosting the victim's hit points */
hmd.dmg = (hmd.get_dmg_bonus && !mon_is_shade) ? 1 : 0;
if (mon_is_shade && !hmd.hittxt
&& thrown != HMON_THROWN && thrown != HMON_KICKED)
/* this gives "harmlessly passes through" feedback even when
hero doesn't see it happen; presumably sensed by touch? */
hmd.hittxt = shade_miss(&gy.youmonst, mon, obj, FALSE, TRUE);
}
if (hmd.jousting) {
hmon_hitmon_jousting(&hmd, mon, obj);
} else if (hmd.unarmed && hmd.dmg > 1 && !thrown && !obj && !Upolyd) {
hmon_hitmon_stagger(&hmd, mon, obj);
}
if (!hmd.already_killed) {
if (obj && (obj == uwep || (obj == uswapwep && u.twoweap))
/* known_hitum 'what counts as a weapon' criteria */
&& (obj->oclass == WEAPON_CLASS || is_weptool(obj))
&& (thrown == HMON_MELEE || thrown == HMON_APPLIED)
/* if jousting, the hit was already logged */
&& !hmd.jousting
/* note: caller has already incremented u.uconduct.weaphit
so we test for 1; 0 shouldn't be able to happen here... */
&& hmd.dmg > 0 && u.uconduct.weaphit <= 1)
first_weapon_hit(obj);
mon->mhp -= hmd.dmg;
}
/* adjustments might have made tmp become less than what
a level draining artifact has already done to max HP */
if (mon->mhp > mon->mhpmax)
mon->mhp = mon->mhpmax;
if (DEADMONSTER(mon))
hmd.destroyed = TRUE;
hmon_hitmon_pet(&hmd, mon, obj);
hmon_hitmon_splitmon(&hmd, mon, obj);
hmon_hitmon_msg_hit(&hmd, mon, obj);
if (hmd.dryit) /* dryit implies wet towel, so 'obj' is still intact */
dry_a_towel(obj, -1, TRUE);
if (hmd.silvermsg)
hmon_hitmon_msg_silver(&hmd, mon, obj);
if (hmd.lightobj)
hmon_hitmon_msg_lightobj(&hmd, mon, obj);
/* if a "no longer poisoned" message is coming, it will be last;
obj->opoisoned was cleared above and any message referring to
"poisoned <obj>" has now been given; we want just "<obj>" for
last message, so reformat while obj is still accessible */
if (hmd.unpoisonmsg)
Strcpy(hmd.saved_oname, cxname(obj));
/* [note: thrown obj might go away during killed()/xkilled() call
(via 'thrownobj'; if swallowed, it gets added to engulfer's
minvent and might merge with a stack that's already there)] */
/* already_killed and poiskilled won't apply for Trollsbane */
if (hmd.needpoismsg)
pline_The("poison doesn't seem to affect %s.", mon_nam(mon));
if (hmd.poiskilled) {
pline_The("poison was deadly...");
if (!hmd.already_killed)
xkilled(mon, XKILL_NOMSG);
hmd.destroyed = TRUE; /* return FALSE; */
} else if (hmd.destroyed) {
if (!hmd.already_killed) {
if (troll_baned(mon, obj))
gm.mkcorpstat_norevive = TRUE;
killed(mon); /* takes care of most messages */
gm.mkcorpstat_norevive = FALSE;
}
} else if (u.umconf && hmd.hand_to_hand) {
nohandglow(mon);
if (!mon->mconf && !resist(mon, SPBOOK_CLASS, 0, NOTELL)) {
mon->mconf = 1;
if (!mon->mstun && !helpless(mon) && canseemon(mon))
pline("%s appears confused.", Monnam(mon));
}
}
if (hmd.unpoisonmsg)
Your("%s %s no longer poisoned.", hmd.saved_oname,
vtense(hmd.saved_oname, "are"));
if (!hmd.destroyed)
wakeup(mon, TRUE);
return hmd.destroyed ? FALSE : TRUE;
}
/* joust or martial arts punch is knocking the target back; that might
kill 'mon' (via trap) before known_hitum() has a chance to do so;
return True if we kill mon, False otherwise */
static boolean
mhurtle_to_doom(
struct monst *mon, /* target monster */
int tmp, /* amount of pending damage */
struct permonst **mptr) /* caller's cached copy of mon->data */
{
/* only hurtle if pending physical damage (tmp) isn't going to kill mon */
if (tmp < mon->mhp) {
mhurtle(mon, u.dx, u.dy, 1);
/* update caller's cached mon->data in case mon was pushed into
a polymorph trap or is a vampshifter whose current form has
been killed by a trap so that it reverted to original form */
*mptr = mon->data;
if (DEADMONSTER(mon))
return TRUE;
}
return FALSE; /* mon isn't dead yet */
}
/* gamelog version of "you've broken never-hit-with-wielded-weapon conduct;
the conduct is tracked in known_hitum(); we're called by hmon_hitmon() */
static void
first_weapon_hit(struct obj *weapon)
{
char buf[BUFSZ];
/* avoid xname() since that includes "named <foo>" and we don't want
player-supplied <foo> in livelog */
buf[0] = '\0';
/* include "cursed" if known but don't bother with blessed */
if (weapon->cursed && weapon->bknown)
Strcat(buf, "cursed "); /* normally supplied by doname() */
if (obj_is_pname(weapon)) {
Strcat(buf, ONAME(weapon)); /* fully IDed artifact */
} else {
Strcat(buf, simpleonames(weapon));
if (weapon->oartifact && weapon->dknown)
Sprintf(eos(buf), " named %s", bare_artifactname(weapon));
}
/* when a hit breaks the never-hit-with-wielded-weapon conduct
(handled by caller) we need to log the message about that before
monster is possibly killed; otherwise getting log entry sequence
N : killed for the first time
N : hit with a wielded weapon for the first time
reported on the same turn (N) looks "suboptimal" */
livelog_printf(LL_CONDUCT,
"hit with a wielded weapon (%s) for the first time", buf);
}
static boolean
shade_aware(struct obj *obj)
{
if (!obj)
return FALSE;
/*
* The things in this list either
* 1) affect shades.
* OR
* 2) are dealt with properly by other routines
* when it comes to shades.
*/
if (obj->otyp == BOULDER
|| obj->otyp == HEAVY_IRON_BALL
|| obj->otyp == IRON_CHAIN /* dmgval handles those first three */
|| obj->otyp == MIRROR /* silver in the reflective surface */
|| obj->otyp == CLOVE_OF_GARLIC /* causes shades to flee */
|| objects[obj->otyp].oc_material == SILVER)
return TRUE;
return FALSE;
}
/* used for hero vs monster and monster vs monster; also handles
monster vs hero but that won't happen because hero can't be a shade */
boolean
shade_miss(
struct monst *magr,
struct monst *mdef,
struct obj *obj,
boolean thrown,
boolean verbose)
{
const char *what, *whose, *target;
boolean youagr = (magr == &gy.youmonst), youdef = (mdef == &gy.youmonst);
/* we're using dmgval() for zero/not-zero, not for actual damage amount */
if (mdef->data != &mons[PM_SHADE] || (obj && dmgval(obj, mdef)))
return FALSE;
if (verbose
&& ((youdef || cansee(mdef->mx, mdef->my) || sensemon(mdef))
|| (magr == &gy.youmonst && m_next2u(mdef)))) {
static const char harmlessly_thru[] = " harmlessly through ";
what = (!obj || shade_aware(obj)) ? "attack" : cxname(obj);
target = youdef ? "you" : mon_nam(mdef);
if (!thrown) {
whose = youagr ? "Your" : s_suffix(Monnam(magr));
pline("%s %s %s%s%s.", whose, what,
vtense(what, "pass"), harmlessly_thru, target);
} else {
pline("%s %s%s%s.", The(what), /* note: not pline_The() */
vtense(what, "pass"), harmlessly_thru, target);
}
if (!youdef && !canspotmon(mdef))
map_invisible(mdef->mx, mdef->my);
}
if (!youdef)
mdef->msleeping = 0;
return TRUE;
}
/* check whether slippery clothing protects from hug or wrap attack */
/* [currently assumes that you are the attacker] */
static boolean
m_slips_free(struct monst *mdef, struct attack *mattk)
{
struct obj *obj;
if (mattk->adtyp == AD_DRIN) {
/* intelligence drain attacks the head */
obj = which_armor(mdef, W_ARMH);
} else {
/* grabbing attacks the body */
obj = which_armor(mdef, W_ARMC); /* cloak */
if (!obj)
obj = which_armor(mdef, W_ARM); /* suit */
if (!obj)
obj = which_armor(mdef, W_ARMU); /* shirt */
}
/* if monster's cloak/armor is greased, your grab 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))) {
You("%s %s %s %s!",
(mattk->adtyp == AD_WRAP) ? "slip off of"
: "grab, but cannot hold onto",
s_suffix(mon_nam(mdef)), 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;
}
return TRUE;
}
return FALSE;
}
/* used when hitting a monster with a lance while mounted;
1: joust hit; 0: ordinary hit; -1: joust but break lance */
static int
joust(struct monst *mon, /* target */
struct obj *obj) /* weapon */
{
int skill_rating, joust_dieroll;
if (Fumbling || Stunned)
return 0;
/* sanity check; lance must be wielded in order to joust */
if (obj != uwep && (obj != uswapwep || !u.twoweap))
return 0;
/* if using two weapons, use worse of lance and two-weapon skills */
skill_rating = P_SKILL(weapon_type(obj)); /* lance skill */
if (u.twoweap && P_SKILL(P_TWO_WEAPON_COMBAT) < skill_rating)
skill_rating = P_SKILL(P_TWO_WEAPON_COMBAT);
if (skill_rating == P_ISRESTRICTED)
skill_rating = P_UNSKILLED; /* 0=>1 */
/* odds to joust are expert:80%, skilled:60%, basic:40%, unskilled:20% */
if ((joust_dieroll = rn2(5)) < skill_rating) {
if (joust_dieroll == 0 && rnl(50) == (50 - 1) && !unsolid(mon->data)
&& !obj_resists(obj, 0, 100))
return -1; /* hit that breaks lance */
return 1; /* successful joust */
}
return 0; /* no joust bonus; revert to ordinary attack */
}
/* send in a demon pet for the hero; exercise wisdom */
static void
demonpet(void)
{
int i;
struct permonst *pm;
struct monst *dtmp;
pline("Some hell-p has arrived!");
i = !rn2(6) ? ndemon(u.ualign.type) : NON_PM;
pm = i != NON_PM ? &mons[i] : gy.youmonst.data;
if ((dtmp = makemon(pm, u.ux, u.uy, NO_MM_FLAGS)) != 0)
(void) tamedog(dtmp, (struct obj *) 0);
exercise(A_WIS, TRUE);
}
static boolean
theft_petrifies(struct obj *otmp)
{
if (uarmg || otmp->otyp != CORPSE
|| !touch_petrifies(&mons[otmp->corpsenm]) || Stone_resistance)
return FALSE;
#if 0 /* no poly_when_stoned() critter has theft capability */
if (poly_when_stoned(gy.youmonst.data) && polymon(PM_STONE_GOLEM)) {
display_nhwindow(WIN_MESSAGE, FALSE); /* --More-- */
return TRUE;
}
#endif
/* stealing this corpse is fatal... */
instapetrify(corpse_xname(otmp, "stolen", CXN_ARTICLE));
/* apparently wasn't fatal after all... */
return TRUE;
}
/*
* Player uses theft attack against monster.
*
* If the target is wearing body armor, take all of its possessions;
* otherwise, take one object. [Is this really the behavior we want?]
*/
static void
steal_it(struct monst *mdef, struct attack *mattk)
{
struct obj *otmp, *gold = 0, *ustealo, **minvent_ptr;
long unwornmask;
otmp = mdef->minvent;
if (!otmp || (otmp->oclass == COIN_CLASS && !otmp->nobj))
return; /* nothing to take */
/* look for worn body armor */
ustealo = (struct obj *) 0;
if (could_seduce(&gy.youmonst, mdef, mattk) && mdef->mcanmove) {
/* find armor, and move it to end of inventory in the process */
minvent_ptr = &mdef->minvent;
while ((otmp = *minvent_ptr) != 0)
if (otmp->owornmask & W_ARM) {
if (ustealo)
panic("steal_it: multiple worn suits");
*minvent_ptr = otmp->nobj; /* take armor out of minvent */
ustealo = otmp;
ustealo->nobj = (struct obj *) 0;
} else {
minvent_ptr = &otmp->nobj;
}
*minvent_ptr = ustealo; /* put armor back into minvent */
}
gold = findgold(mdef->minvent);
if (ustealo) { /* we will be taking everything */
char heshe[20];
/* 3.7: this uses hero's base gender rather than nymph femininity
but was using hardcoded pronouns She/her for target monster;
switch to dynamic pronoun */
if (gender(mdef) == (int) u.mfemale
&& gy.youmonst.data->mlet == S_NYMPH)
You("charm %s. %s gladly hands over %s%s possessions.",
mon_nam(mdef), upstart(strcpy(heshe, mhe(mdef))),
!gold ? "" : "most of ", mhis(mdef));
else
You("seduce %s and %s starts to take off %s clothes.",
mon_nam(mdef), mhe(mdef), mhis(mdef));
}
/* prevent gold from being stolen so that steal-item isn't a superset
of steal-gold; shuffling it out of minvent before selecting next
item, and then back in case hero or monster dies (hero touching
stolen c'trice corpse or monster wielding one and having gloves
stolen) is less bookkeeping than skipping it within the loop or
taking it out once and then trying to figure out how to put it back */
if (gold)
obj_extract_self(gold);
while ((otmp = mdef->minvent) != 0) {
if (gold) /* put 'mdef's gold back after remembering mdef->minvent */
mpickobj(mdef, gold), gold = 0;
if (!Upolyd)
break; /* no longer have ability to steal */
unwornmask = otmp->owornmask;
/* this would take place when doname() formats the object for
the hold_another_object() call, but we want to do it before
otmp gets removed from mdef's inventory */
if (otmp->oartifact && !Blind)
find_artifact(otmp);
/* take the object away from the monster */
extract_from_minvent(mdef, otmp, TRUE, FALSE);
/* special message for final item; no need to check owornmask because
* ustealo is only set on objects with (owornmask & W_ARM) */
if (otmp == ustealo)
pline("%s finishes taking off %s suit.", Monnam(mdef),
mhis(mdef));
/* give the object to the character */
otmp = hold_another_object(otmp, "You snatched but dropped %s.",
doname(otmp), "You steal: ");
/* might have dropped otmp, and it might have broken or left level */
if (!otmp || otmp->where != OBJ_INVENT)
continue;
if (theft_petrifies(otmp))
break; /* stop thieving even though hero survived */
/* more take-away handling, after theft message */
if (unwornmask & W_WEP) { /* stole wielded weapon */
possibly_unwield(mdef, FALSE);
} else if (unwornmask & W_ARMG) { /* stole worn gloves */
mselftouch(mdef, (const char *) 0, TRUE);
if (DEADMONSTER(mdef)) /* it's now a statue */
break; /* can't continue stealing */
}
if (!ustealo)
break; /* only taking one item */
/* take gold out of minvent before making next selection; if it
is the only thing left, the loop will terminate and it will be
put back below */
if ((gold = findgold(mdef->minvent)) != 0)
obj_extract_self(gold);
}
/* put gold back; won't happen if either hero or 'mdef' dies because
gold will be back in monster's inventory at either of those times
(so will be present in mdef's minvent for bones, or in its statue
now if it has just been turned into one) */
if (gold)
mpickobj(mdef, gold);
}
void
mhitm_ad_rust(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
if (completelyrusts(pd)) { /* iron golem */
/* note: the life-saved case is hypothetical because
life-saving doesn't work for golems */
pline("%s %s to pieces!", Monnam(mdef),
!mlifesaver(mdef) ? "falls" : "starts to fall");
xkilled(mdef, XKILL_NOMSG);
mhm->hitflags |= M_ATTK_DEF_DIED;
}
erode_armor(mdef, ERODE_RUST);
mhm->damage = 0; /* damageum(), int tmp */
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (magr->mcan) {
return;
}
if (completelyrusts(pd)) {
You("rust!");
/* KMH -- this is okay with unchanging */
rehumanize();
return;
}
erode_armor(&gy.youmonst, ERODE_RUST);
} else {
/* mhitm */
if (magr->mcan)
return;
if (completelyrusts(pd)) { /* PM_IRON_GOLEM */
if (gv.vis && canseemon(mdef))
pline("%s %s to pieces!", Monnam(mdef),
!mlifesaver(mdef) ? "falls" : "starts to fall");
monkilled(mdef, (char *) 0, AD_RUST);
if (!DEADMONSTER(mdef)) {
mhm->hitflags = M_ATTK_MISS;
mhm->done = TRUE;
return;
}
mhm->hitflags = (M_ATTK_DEF_DIED | (grow_up(magr, mdef) ? 0
: M_ATTK_AGR_DIED));
mhm->done = TRUE;
return;
}
erode_armor(mdef, ERODE_RUST);
mdef->mstrategy &= ~STRAT_WAITFORU;
mhm->damage = 0; /* mdamagem(), int tmp */
}
}
void
mhitm_ad_corr(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
erode_armor(mdef, ERODE_CORRODE);
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (magr->mcan)
return;
erode_armor(mdef, ERODE_CORRODE);
} else {
/* mhitm */
if (magr->mcan)
return;
erode_armor(mdef, ERODE_CORRODE);
mdef->mstrategy &= ~STRAT_WAITFORU;
mhm->damage = 0;
}
}
void
mhitm_ad_dcay(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
if (completelyrots(pd)) { /* wood golem or leather golem */
pline("%s %s to pieces!", Monnam(mdef),
!mlifesaver(mdef) ? "falls" : "starts to fall");
xkilled(mdef, XKILL_NOMSG);
}
erode_armor(mdef, ERODE_ROT);
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (magr->mcan)
return;
if (completelyrots(pd)) {
You("rot!");
/* KMH -- this is okay with unchanging */
rehumanize();
return;
}
erode_armor(mdef, ERODE_ROT);
} else {
/* mhitm */
if (magr->mcan)
return;
if (completelyrots(pd)) { /* PM_WOOD_GOLEM || PM_LEATHER_GOLEM */
/* note: the life-saved case is hypothetical because
life-saving doesn't work for golems */
if (gv.vis && canseemon(mdef))
pline("%s %s to pieces!", Monnam(mdef),
!mlifesaver(mdef) ? "falls" : "starts to fall");
monkilled(mdef, (char *) 0, AD_DCAY);
if (!DEADMONSTER(mdef)) {
mhm->done = TRUE;
mhm->hitflags = M_ATTK_MISS;
return;
}
mhm->done = TRUE;
mhm->hitflags = (M_ATTK_DEF_DIED
| (grow_up(magr, mdef) ? 0 : M_ATTK_AGR_DIED));
return;
}
erode_armor(mdef, ERODE_ROT);
mdef->mstrategy &= ~STRAT_WAITFORU;
mhm->damage = 0;
}
}
void
mhitm_ad_dren(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE);
if (magr == &gy.youmonst) {
/* uhitm */
if (!negated && !rn2(4))
xdrainenergym(mdef, TRUE);
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!negated && !rn2(4)) /* 25% chance */
drain_en(mhm->damage, FALSE);
mhm->damage = 0;
} else {
/* mhitm */
if (!negated && !rn2(4))
xdrainenergym(mdef, (boolean) (gv.vis && canspotmon(mdef)
&& mattk->aatyp != AT_ENGL));
mhm->damage = 0;
}
}
void
mhitm_ad_drli(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
if (!rn2(3) && !(resists_drli(mdef) || defended(mdef, AD_DRLI))
&& !mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
mhm->damage = d(2, 6); /* Stormbringer uses monhp_per_lvl
* (usually 1d8) */
pline("%s becomes weaker!", Monnam(mdef));
if (mdef->mhpmax - mhm->damage > (int) mdef->m_lev) {
mdef->mhpmax -= mhm->damage;
} else {
/* limit floor of mhpmax reduction to current m_lev + 1;
avoid increasing it if somehow already less than that */
if (mdef->mhpmax > (int) mdef->m_lev)
mdef->mhpmax = (int) mdef->m_lev + 1;
}
mdef->mhp -= mhm->damage;
/* !m_lev: level 0 monster is killed regardless of hit points
rather than drop to level -1; note: some non-living creatures
(golems, vortices) are subject to life-drain */
if (DEADMONSTER(mdef) || !mdef->m_lev) {
pline("%s %s!", Monnam(mdef),
nonliving(mdef->data) ? "expires" : "dies");
xkilled(mdef, XKILL_NOMSG);
} else
mdef->m_lev--;
mhm->damage = 0; /* damage has already been inflicted */
/* unlike hitting with Stormbringer, wounded hero doesn't
heal any from the drained life */
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!rn2(3) && !Drain_resistance
&& !mhitm_mgc_atk_negated(magr, mdef, TRUE)){
losexp("life drainage");
/* unlike hitting with Stormbringer, wounded attacker doesn't
heal any from the drained life */
}
} else {
/* mhitm */
/* mhitm_ad_deth gets redirected here for Death's touch */
boolean is_death = (mattk->adtyp == AD_DETH);
if (is_death
|| (!rn2(3) && !(resists_drli(mdef) || defended(mdef, AD_DRLI))
&& !mhitm_mgc_atk_negated(magr, mdef, TRUE))) {
if (!is_death) /* Stormbringer uses monhp_per_lvl (1d8) */
mhm->damage = d(2, 6);
if (gv.vis && canspotmon(mdef))
pline("%s becomes weaker!", Monnam(mdef));
if (mdef->mhpmax - mhm->damage > (int) mdef->m_lev) {
mdef->mhpmax -= mhm->damage;
} else {
/* limit floor of mhpmax reduction to current m_lev + 1;
avoid increasing it if somehow already less than that */
if (mdef->mhpmax > (int) mdef->m_lev)
mdef->mhpmax = (int) mdef->m_lev + 1;
}
if (mdef->m_lev == 0) /* automatic kill if drained past level 0 */
mhm->damage = mdef->mhp;
else
mdef->m_lev--;
/* unlike hitting with Stormbringer, wounded attacker doesn't
heal any from the drained life */
}
}
}
void
mhitm_ad_fire(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
const int orig_dmg = mhm->damage; /* damage coming into the function */
if (magr == &gy.youmonst) {
/* uhitm */
if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
mhm->damage = 0;
return;
}
if (!Blind)
pline("%s is %s!", Monnam(mdef), on_fire(pd, mattk));
if (completelyburns(pd)) { /* paper golem or straw golem */
if (!Blind)
/* note: the life-saved case is hypothetical because
life-saving doesn't work for golems */
pline("%s %s!", Monnam(mdef),
!mlifesaver(mdef) ? "burns completely"
: "is totally engulfed in flames");
else
You("smell burning%s.",
(pd == &mons[PM_PAPER_GOLEM]) ? " paper"
: (pd == &mons[PM_STRAW_GOLEM]) ? " straw" : "");
xkilled(mdef, XKILL_NOMSG | XKILL_NOCORPSE);
mhm->damage = 0;
return;
/* Don't return yet; keep hp<1 and mhm.damage=0 for pet msg */
}
if (resists_fire(mdef) || defended(mdef, AD_FIRE)) {
if (!Blind)
pline_The("fire doesn't heat %s!", mon_nam(mdef));
golemeffects(mdef, AD_FIRE, mhm->damage);
shieldeff(mdef->mx, mdef->my);
mhm->damage = 0;
}
mhm->damage += destroy_items(mdef, AD_FIRE, orig_dmg);
ignite_items(mdef->minvent);
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
pline("You're %s!", on_fire(pd, mattk));
if (completelyburns(pd)) { /* paper or straw golem */
You("go up in flames!");
monstunseesu(M_SEEN_FIRE);
/* KMH -- this is okay with unchanging */
rehumanize();
return;
} else if (Fire_resistance) {
pline_The("fire doesn't feel hot!");
monstseesu(M_SEEN_FIRE);
mhm->damage = 0;
} else {
monstunseesu(M_SEEN_FIRE);
}
if ((int) magr->m_lev > rn2(20)) {
(void) destroy_items(&gy.youmonst, AD_FIRE, orig_dmg);
ignite_items(gi.invent);
}
burn_away_slime();
} else {
mhm->damage = 0;
}
} else {
/* mhitm */
if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
mhm->damage = 0;
return;
}
if (gv.vis && canseemon(mdef))
pline("%s is %s!", Monnam(mdef), on_fire(pd, mattk));
if (completelyburns(pd)) { /* paper golem or straw golem */
/* note: the life-saved case is hypothetical because
life-saving doesn't work for golems */
if (gv.vis && canseemon(mdef))
pline("%s %s!", Monnam(mdef),
!mlifesaver(mdef) ? "burns completely"
: "is totally engulfed in flames");
monkilled(mdef, (char *) 0, AD_FIRE);
if (!DEADMONSTER(mdef)) {
mhm->hitflags = M_ATTK_MISS;
mhm->done = TRUE;
return;
}
mhm->hitflags = (M_ATTK_DEF_DIED
| (grow_up(magr, mdef) ? 0 : M_ATTK_AGR_DIED));
mhm->done = TRUE;
return;
}
if (resists_fire(mdef) || defended(mdef, AD_FIRE)) {
if (gv.vis && canseemon(mdef))
pline_The("fire doesn't seem to burn %s!", mon_nam(mdef));
shieldeff(mdef->mx, mdef->my);
golemeffects(mdef, AD_FIRE, mhm->damage);
mhm->damage = 0;
}
mhm->damage += destroy_items(mdef, AD_FIRE, orig_dmg);
ignite_items(mdef->minvent);
}
}
void
mhitm_ad_cold(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
const int orig_dmg = mhm->damage;
if (magr == &gy.youmonst) {
/* uhitm */
if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
mhm->damage = 0;
return;
}
if (!Blind)
pline("%s is covered in frost!", Monnam(mdef));
if (resists_cold(mdef) || defended(mdef, AD_COLD)) {
shieldeff(mdef->mx, mdef->my);
if (!Blind)
pline_The("frost doesn't chill %s!", mon_nam(mdef));
golemeffects(mdef, AD_COLD, mhm->damage);
mhm->damage = 0;
}
mhm->damage += destroy_items(mdef, AD_COLD, orig_dmg);
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
pline("You're covered in frost!");
if (Cold_resistance) {
pline_The("frost doesn't seem cold!");
monstseesu(M_SEEN_COLD);
mhm->damage = 0;
} else {
monstunseesu(M_SEEN_COLD);
}
if ((int) magr->m_lev > rn2(20))
(void) destroy_items(&gy.youmonst, AD_COLD, orig_dmg);
} else
mhm->damage = 0;
} else {
/* mhitm */
if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
mhm->damage = 0;
return;
}
if (gv.vis && canseemon(mdef))
pline("%s is covered in frost!", Monnam(mdef));
if (resists_cold(mdef) || defended(mdef, AD_COLD)) {
if (gv.vis && canseemon(mdef))
pline_The("frost doesn't seem to chill %s!", mon_nam(mdef));
shieldeff(mdef->mx, mdef->my);
golemeffects(mdef, AD_COLD, mhm->damage);
mhm->damage = 0;
}
mhm->damage += destroy_items(mdef, AD_COLD, orig_dmg);
}
}
void
mhitm_ad_elec(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
const int orig_dmg = mhm->damage;
if (magr == &gy.youmonst) {
/* uhitm */
if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
mhm->damage = 0;
return;
}
if (!Blind)
pline("%s is zapped!", Monnam(mdef));
if (resists_elec(mdef) || defended(mdef, AD_ELEC)) {
if (!Blind)
pline_The("zap doesn't shock %s!", mon_nam(mdef));
golemeffects(mdef, AD_ELEC, mhm->damage);
shieldeff(mdef->mx, mdef->my);
mhm->damage = 0;
}
mhm->damage += destroy_items(mdef, AD_ELEC, orig_dmg);
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
You("get zapped!");
if (Shock_resistance) {
pline_The("zap doesn't shock you!");
monstseesu(M_SEEN_ELEC);
mhm->damage = 0;
} else {
monstunseesu(M_SEEN_ELEC);
}
if ((int) magr->m_lev > rn2(20))
(void) destroy_items(&gy.youmonst, AD_ELEC, orig_dmg);
} else
mhm->damage = 0;
} else {
/* mhitm */
if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
mhm->damage = 0;
return;
}
if (gv.vis && canseemon(mdef))
pline("%s gets zapped!", Monnam(mdef));
if (resists_elec(mdef) || defended(mdef, AD_ELEC)) {
if (gv.vis && canseemon(mdef))
pline_The("zap doesn't shock %s!", mon_nam(mdef));
shieldeff(mdef->mx, mdef->my);
golemeffects(mdef, AD_ELEC, mhm->damage);
mhm->damage = 0;
}
mhm->damage += destroy_items(mdef, AD_ELEC, orig_dmg);
}
}
void
mhitm_ad_acid(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
if (resists_acid(mdef) || defended(mdef, AD_ACID))
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!magr->mcan && !rn2(3))
if (Acid_resistance) {
pline("You're covered in %s, but it seems harmless.",
hliquid("acid"));
monstseesu(M_SEEN_ACID);
mhm->damage = 0;
} else {
pline("You're covered in %s! It burns!", hliquid("acid"));
exercise(A_STR, FALSE);
monstunseesu(M_SEEN_ACID);
}
else
mhm->damage = 0;
} else {
/* mhitm */
if (magr->mcan) {
mhm->damage = 0;
return;
}
if (resists_acid(mdef) || defended(mdef, AD_ACID)) {
if (gv.vis && canseemon(mdef))
pline("%s is covered in %s, but it seems harmless.",
Monnam(mdef), hliquid("acid"));
mhm->damage = 0;
} else if (gv.vis && canseemon(mdef)) {
pline("%s is covered in %s!", Monnam(mdef), hliquid("acid"));
pline("It burns %s!", mon_nam(mdef));
}
if (!rn2(30))
erode_armor(mdef, ERODE_CORRODE);
if (!rn2(6))
acid_damage(MON_WEP(mdef));
}
}
/* steal gold */
void
mhitm_ad_sgld(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pa = magr->data;
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
struct obj *mongold = findgold(mdef->minvent);
if (mongold) {
obj_extract_self(mongold);
if (merge_choice(gi.invent, mongold)
|| inv_cnt(FALSE) < invlet_basic) {
addinv(mongold);
Your("purse feels heavier.");
} else {
You("grab %s's gold, but find no room in your knapsack.",
mon_nam(mdef));
dropy(mongold);
}
}
exercise(A_DEX, TRUE);
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (pd->mlet == pa->mlet)
return;
if (!magr->mcan)
stealgold(magr);
} else {
/* mhitm */
char buf[BUFSZ];
mhm->damage = 0;
if (magr->mcan)
return;
/* technically incorrect; no check for stealing gold from
* between mdef's feet...
*/
{
struct obj *gold = findgold(mdef->minvent);
if (!gold)
return;
obj_extract_self(gold);
add_to_minv(magr, gold);
}
mdef->mstrategy &= ~STRAT_WAITFORU;
Strcpy(buf, Monnam(magr));
if (gv.vis && canseemon(mdef)) {
pline("%s steals some gold from %s.", buf, mon_nam(mdef));
}
if (!tele_restrict(magr)) {
boolean couldspot = canspotmon(magr);
mhm->hitflags = M_ATTK_AGR_DONE;
(void) rloc(magr, RLOC_NOMSG);
/* TODO: use RLOC_MSG instead? */
if (gv.vis && couldspot && !canspotmon(magr))
pline("%s suddenly disappears!", buf);
}
}
}
void
mhitm_ad_tlpt(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
if (mhm->damage <= 0)
mhm->damage = 1;
if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
pline("%s is not affected.", Monnam(mdef));
} else {
char nambuf[BUFSZ];
boolean u_saw_mon = (canseemon(mdef) || engulfing_u(mdef));
/* record the name before losing sight of monster */
Strcpy(nambuf, Monnam(mdef));
if (u_teleport_mon(mdef, FALSE) && u_saw_mon
&& !(canseemon(mdef) || engulfing_u(mdef)))
pline("%s suddenly disappears!", nambuf);
if (mhm->damage >= mdef->mhp) { /* see hitmu(mhitu.c) */
if (mdef->mhp == 1)
++mdef->mhp;
mhm->damage = mdef->mhp - 1;
}
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
int tmphp;
hitmsg(magr, mattk);
if (!mhitm_mgc_atk_negated(magr, mdef, FALSE)) {
You("are not affected.");
} else {
if (flags.verbose)
Your("position suddenly seems %suncertain!",
(Teleport_control && !Stunned && !unconscious()) ? ""
: "very ");
tele();
/* As of 3.6.2: make sure damage isn't fatal; previously, it
was possible to be teleported and then drop dead at
the destination when QM's 1d4 damage gets applied below;
even though that wasn't "wrong", it seemed strange,
particularly if the teleportation had been controlled
[applying the damage first and not teleporting if fatal
is another alternative but it has its own complications] */
if ((Half_physical_damage ? (mhm->damage - 1) / 2 : mhm->damage)
>= (tmphp = (Upolyd ? u.mh : u.uhp))) {
mhm->damage = tmphp - 1;
if (Half_physical_damage)
mhm->damage *= 2; /* doesn't actually increase damage;
* we only get here if half the
* original damage would have
* been fatal, so double reduced
* damage will be less than original */
if (mhm->damage < 1) { /* implies (tmphp <= 1) */
mhm->damage = 1;
/* this might increase current HP beyond maximum HP but it
will be immediately reduced by caller, so that should
be indistinguishable from zero damage; we don't drop
damage all the way to zero because that inhibits any
passive counterattack if poly'd hero has one */
if (Upolyd && u.mh == 1)
++u.mh;
else if (!Upolyd && u.uhp == 1)
++u.uhp;
/* [don't set context.botl here] */
}
}
}
} else {
/* mhitm */
if (magr->mcan || mhm->damage >= mdef->mhp || tele_restrict(mdef)) {
; /* no negation message */
} else if (mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
if (gv.vis)
pline("%s is not affected.", Monnam(mdef));
} else {
char mdef_Monnam[BUFSZ];
boolean wasseen = canspotmon(mdef);
/* save the name before monster teleports, otherwise
we'll get "it" in the suddenly disappears message */
if (gv.vis && wasseen)
Strcpy(mdef_Monnam, Monnam(mdef));
mdef->mstrategy &= ~STRAT_WAITFORU;
(void) rloc(mdef, RLOC_NOMSG);
/* TODO: use RLOC_MSG instead? */
if (gv.vis && wasseen && !canspotmon(mdef) && mdef != u.usteed)
pline("%s suddenly disappears!", mdef_Monnam);
if (mhm->damage >= mdef->mhp) { /* see hitmu(mhitu.c) */
if (mdef->mhp == 1)
++mdef->mhp;
mhm->damage = mdef->mhp - 1;
}
}
}
}
void
mhitm_ad_blnd(
struct monst *magr, /* attacker */
struct attack *mattk, /* magr's attack */
struct monst *mdef, /* defender */
struct mhitm_data *mhm) /* optional for monster vs monster */
{
if (magr == &gy.youmonst) {
/* uhitm */
if (can_blnd(magr, mdef, mattk->aatyp, (struct obj *) 0)) {
if (!Blind && mdef->mcansee)
pline("%s is blinded.", Monnam(mdef));
mdef->mcansee = 0;
mhm->damage += mdef->mblinded;
if (mhm->damage > 127)
mhm->damage = 127;
mdef->mblinded = mhm->damage;
}
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
if (can_blnd(magr, mdef, mattk->aatyp, (struct obj *) 0)) {
if (!Blind)
pline("%s blinds you!", Monnam(magr));
make_blinded(BlindedTimeout + (long) mhm->damage, FALSE);
if (!Blind) /* => Eyes of the Overworld */
Your1(vision_clears);
}
mhm->damage = 0;
} else {
/* mhitm */
if (can_blnd(magr, mdef, mattk->aatyp, (struct obj *) 0)) {
char buf[BUFSZ];
unsigned rnd_tmp;
if (gv.vis && mdef->mcansee && canspotmon(mdef)) {
/* feedback for becoming blinded is given if observed
telepathically (canspotmon suffices) but additional
info about archon's glow is only given if seen */
Snprintf(buf, sizeof buf, "%s is blinded", Monnam(mdef));
if (mdef->data == &mons[PM_ARCHON] && canseemon(mdef))
Snprintf(eos(buf), sizeof buf - strlen(buf),
" by %s radiance", s_suffix(mon_nam(magr)));
pline("%s.", buf);
}
rnd_tmp = d((int) mattk->damn, (int) mattk->damd);
if ((rnd_tmp += mdef->mblinded) > 127)
rnd_tmp = 127;
mdef->mblinded = rnd_tmp;
mdef->mcansee = 0;
mdef->mstrategy &= ~STRAT_WAITFORU;
}
if (mhm)
mhm->damage = 0;
}
}
void
mhitm_ad_curs(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pa = magr->data;
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
if (night() && !rn2(10) && !mdef->mcan) {
if (pd == &mons[PM_CLAY_GOLEM]) {
if (!Blind)
pline("Some writing vanishes from %s head!",
s_suffix(mon_nam(mdef)));
xkilled(mdef, XKILL_NOMSG);
/* Don't return yet; keep hp<1 and mhm.damage=0 for pet msg */
} else {
mdef->mcan = 1;
You("chuckle.");
}
}
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!night() && pa == &mons[PM_GREMLIN])
return;
if (!magr->mcan && !rn2(10)) {
if (!Deaf) {
Soundeffect(se_laughter, 40);
if (Blind) {
You_hear("laughter.");
} else {
pline("%s chuckles.", Monnam(magr));
}
}
if (u.umonnum == PM_CLAY_GOLEM) {
pline("Some writing vanishes from your head!");
/* KMH -- this is okay with unchanging */
rehumanize();
return;
}
mon_give_prop(magr, attrcurse());
}
} else {
/* mhitm */
if (!night() && (pa == &mons[PM_GREMLIN]))
return;
if (!magr->mcan && !rn2(10)) {
mdef->mcan = 1; /* cancelled regardless of lifesave */
mdef->mstrategy &= ~STRAT_WAITFORU;
if (is_were(pd) && pd->mlet != S_HUMAN)
were_change(mdef);
if (pd == &mons[PM_CLAY_GOLEM]) {
if (gv.vis && canseemon(mdef)) {
pline("Some writing vanishes from %s head!",
s_suffix(mon_nam(mdef)));
pline("%s is destroyed!", Monnam(mdef));
}
mondied(mdef);
if (!DEADMONSTER(mdef)) {
mhm->hitflags = M_ATTK_MISS;
mhm->done = TRUE;
return;
} else if (mdef->mtame && !gv.vis) {
You(brief_feeling, "strangely sad");
}
mhm->hitflags = (M_ATTK_DEF_DIED
| (grow_up(magr, mdef) ? 0
: M_ATTK_AGR_DIED));
mhm->done = TRUE;
return;
}
if (!Deaf) {
if (!gv.vis)
You_hear("laughter.");
else if (canseemon(magr))
pline("%s chuckles.", Monnam(magr));
}
}
}
}
void
mhitm_ad_drst(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE);
struct permonst *pa = magr->data;
if (magr == &gy.youmonst) {
/* uhitm */
if (!negated && !rn2(8)) {
Your("%s was poisoned!", mpoisons_subj(magr, mattk));
if (resists_poison(mdef)) {
pline_The("poison doesn't seem to affect %s.", mon_nam(mdef));
} else {
if (!rn2(10)) {
Your("poison was deadly...");
mhm->damage = mdef->mhp;
} else
mhm->damage += rn1(10, 6);
}
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
int ptmp = A_STR; /* A_STR == 0 */
char buf[BUFSZ];
switch (mattk->adtyp) {
case AD_DRST: ptmp = A_STR; break;
case AD_DRDX: ptmp = A_DEX; break;
case AD_DRCO: ptmp = A_CON; break;
}
hitmsg(magr, mattk);
if (!negated && !rn2(8)) {
Sprintf(buf, "%s %s", s_suffix(Monnam(magr)),
mpoisons_subj(magr, mattk));
poisoned(buf, ptmp, pmname(pa, Mgender(magr)), 30, FALSE);
}
} else {
/* mhitm */
if (!negated && !rn2(8)) {
if (gv.vis && canspotmon(magr))
pline("%s %s was poisoned!", s_suffix(Monnam(magr)),
mpoisons_subj(magr, mattk));
if (resists_poison(mdef)) {
if (gv.vis && canspotmon(mdef) && canspotmon(magr))
pline_The("poison doesn't seem to affect %s.",
mon_nam(mdef));
} else {
if (rn2(10)) {
mhm->damage += rn1(10, 6);
} else {
if (gv.vis && canspotmon(mdef))
pline_The("poison was deadly...");
mhm->damage = mdef->mhp;
}
}
}
}
}
void
mhitm_ad_drin(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
struct obj *amu;
boolean lifsav;
/*
* Mind flayers have multiple AD_DRIN attacks (3 for plain mind flayer,
* 5 for master mind flayer). If one of those kills the target, skip
* the others (for rest of attacker's current move). To check whether
* hero has been killed, we check mortality counter. For a monster,
* we check whether it was wearing an amulet of life-saving before the
* attack and no longer wearing any amulet after the attack.
*/
if (magr == &gy.youmonst) {
/* uhitm */
struct obj *helmet;
if (gn.notonhead || !has_head(pd)) {
pline("%s doesn't seem harmed.", Monnam(mdef));
/* hero should skip remaining AT_TENT+AD_DRIN attacks
because they'll be just as harmless as this one (and also
to reduce verbosity) */
gs.skipdrin = TRUE;
mhm->damage = 0;
if (!Unchanging && pd == &mons[PM_GREEN_SLIME]) {
if (!Slimed) {
You("suck in some slime and don't feel very well.");
make_slimed(10L, (char *) 0);
}
}
return;
}
if (m_slips_free(mdef, mattk))
return;
if ((helmet = which_armor(mdef, W_ARMH)) != 0 && rn2(8)) {
pline("%s %s blocks your attack to %s head.",
s_suffix(Monnam(mdef)), helm_simple_name(helmet),
mhis(mdef));
return;
}
amu = which_armor(mdef, W_AMUL);
lifsav = amu && amu->otyp == AMULET_OF_LIFE_SAVING;
(void) eat_brains(&gy.youmonst, mdef, TRUE, &mhm->damage);
/* skip further AD_DRIN if amulet of life-saving got used up */
if (lifsav && !which_armor(mdef, W_AMUL))
gs.skipdrin = TRUE;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (defends(AD_DRIN, uwep) || !has_head(pd)) {
You("don't seem harmed.");
/* attacker should skip remaining AT_TENT+AD_DRIN attacks */
gs.skipdrin = TRUE;
/* Not clear what to do for green slimes */
return;
}
if (u_slip_free(magr, mattk))
return;
if (uarmh && rn2(8)) {
/* not body_part(HEAD) */
Your("%s blocks the attack to your head.",
helm_simple_name(uarmh));
return;
}
/* negative armor class doesn't reduce this damage */
if (Half_physical_damage)
mhm->damage = (mhm->damage + 1) / 2;
mdamageu(magr, mhm->damage);
mhm->damage = 0; /* don't inflict a second dose below */
if (!uarmh || uarmh->otyp != DUNCE_CAP) {
int oldmort = u.umortality,
mhitu = eat_brains(magr, mdef, TRUE, (int *) 0);
/* skip further AD_DRIN if hero's number of deaths went up */
if (u.umortality > oldmort)
gs.skipdrin = TRUE;
/* eat_brains() will miss if target is mindless (won't
happen here--hero is considered to retain his mind
regardless of current shape) or is noncorporeal
(can't happen here--no one can poly into a ghost
or shade) so this check for missing is academic */
if (mhitu == M_ATTK_MISS)
return;
}
/* adjattrib gives dunce cap message when appropriate */
(void) adjattrib(A_INT, -rnd(2), FALSE);
} else {
/* mhitm */
char buf[BUFSZ];
if (gn.notonhead || !has_head(pd)) {
if (gv.vis && canspotmon(mdef))
pline("%s doesn't seem harmed.", Monnam(mdef));
/* Not clear what to do for green slimes */
mhm->damage = 0;
/* don't bother with additional DRIN attacks since they wouldn't
be able to hit target on head either */
gs.skipdrin = TRUE; /* affects mattackm()'s attack loop */
return;
}
if ((mdef->misc_worn_check & W_ARMH) && rn2(8)) {
if (gv.vis && canspotmon(magr) && canseemon(mdef)) {
Strcpy(buf, s_suffix(Monnam(mdef)));
pline("%s helmet blocks %s attack to %s head.", buf,
s_suffix(mon_nam(magr)), mhis(mdef));
}
return;
}
amu = which_armor(mdef, W_AMUL);
lifsav = amu && amu->otyp == AMULET_OF_LIFE_SAVING;
mhm->hitflags = eat_brains(magr, mdef, gv.vis, &mhm->damage);
/* skip further AD_DRIN if amulet of life-saving got used up */
if (lifsav && !which_armor(mdef, W_AMUL))
gs.skipdrin = TRUE;
}
}
void
mhitm_ad_stck(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE);
struct permonst *pd = mdef->data;
boolean barbs = (magr->data == &mons[PM_BARBED_DEVIL]);
if (magr == &gy.youmonst) {
/* uhitm */
if (!negated && !sticks(pd) && m_next2u(mdef)) {
set_ustuck(mdef); /* it's now stuck to you */
if (barbs)
Your("barbs stick to %s!", y_monnam(mdef));
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!negated && !u.ustuck && !sticks(pd)) {
set_ustuck(magr);
if (barbs)
pline("The barbs stick to you!");
}
} else {
/* mhitm */
if (negated)
mhm->damage = 0;
}
}
void
mhitm_ad_wrap(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data, *pa = magr->data;
boolean coil = slithy(pa) && (pa->mlet == S_SNAKE || pa->mlet == S_NAGA);
if (magr == &gy.youmonst) {
/* uhitm */
if (!sticks(pd)) {
boolean tailmiss = !gn.notonhead;
if (!u.ustuck && !tailmiss && !rn2(10)) {
if (m_slips_free(mdef, mattk)) {
mhm->damage = 0;
} else {
You("%s yourself around %s!",
coil ? "coil" : "swing", mon_nam(mdef));
set_ustuck(mdef);
}
} else if (u.ustuck == mdef && !tailmiss) {
/* Monsters don't wear amulets of magical breathing */
if (is_pool(u.ux, u.uy) && !cant_drown(pd)) {
You("drown %s...", mon_nam(mdef));
mhm->damage = mdef->mhp;
} else if (mattk->aatyp == AT_HUGS)
pline("%s is being crushed.", Monnam(mdef));
} else {
mhm->damage = 0;
if (flags.verbose) {
if (coil && !tailmiss)
You("brush against %s.", mon_nam(mdef));
else
You("brush against %s %s.", s_suffix(mon_nam(mdef)),
tailmiss ? "tail" : mbodypart(mdef, LEG));
}
}
} else
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
if ((!magr->mcan || u.ustuck == magr) && !sticks(pd)) {
if (!u.ustuck && !rn2(10)) {
if (u_slip_free(magr, mattk)) {
mhm->damage = 0;
} else {
set_ustuck(magr); /* before message, for botl update */
urgent_pline("%s %s itself around you!",
Some_Monnam(magr),
coil ? "coils" : "swings");
}
} else if (u.ustuck == magr) {
if (is_pool(magr->mx, magr->my) && !Swimming && !Amphibious
&& !Breathless) {
boolean moat = (levl[magr->mx][magr->my].typ != POOL)
&& !is_waterwall(magr->mx, magr->my)
&& !Is_medusa_level(&u.uz)
&& !Is_waterlevel(&u.uz);
urgent_pline("%s drowns you...", Monnam(magr));
gk.killer.format = KILLED_BY_AN;
Sprintf(gk.killer.name, "%s by %s",
moat ? "moat" : "pool of water",
an(pmname(magr->data, Mgender(magr))));
done(DROWNING);
} else if (mattk->aatyp == AT_HUGS) {
You("are being crushed.");
}
} else {
mhm->damage = 0;
if (flags.verbose) {
if (coil)
pline("%s brushes against you.", Monnam(magr));
else
pline("%s brushes against your %s.", Monnam(magr),
body_part(LEG));
}
}
} else
mhm->damage = 0;
} else {
/* mhitm */
if (magr->mcan)
mhm->damage = 0;
if (!mhm->damage && (canseemon(magr) || canseemon(mdef))) {
pline("%s brushes against %s.",
Some_Monnam(magr), some_mon_nam(mdef));
}
}
}
void
mhitm_ad_plys(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
if (!rn2(3) && mhm->damage < mdef->mhp
&& !mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
if (!Blind)
pline("%s is frozen by you!", Monnam(mdef));
paralyze_monst(mdef, rnd(10));
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (gm.multi >= 0 && !rn2(3)
&& !mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
if (Free_action) {
You("momentarily stiffen.");
} else {
if (Blind)
You("are frozen!");
else
You("are frozen by %s!", mon_nam(magr));
gn.nomovemsg = You_can_move_again;
nomul(-rnd(10));
/* set gm.multi_reason;
3.6.x used "paralyzed by a monster"; be more specific */
dynamic_multi_reason(magr, "paralyzed", FALSE);
exercise(A_DEX, FALSE);
}
}
} else {
/* mhitm */
if (mdef->mcanmove && !rn2(3)
&& !mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
if (gv.vis && canspotmon(mdef)) {
char buf[BUFSZ];
Strcpy(buf, Monnam(mdef));
pline("%s is frozen by %s.", buf, mon_nam(magr));
}
paralyze_monst(mdef, rnd(10));
}
}
}
void
mhitm_ad_slee(
struct monst *magr, struct attack *mattk,
struct monst *mdef,
struct mhitm_data *mhm UNUSED)
{
if (magr == &gy.youmonst) {
/* uhitm */
if (!mdef->msleeping && !mhitm_mgc_atk_negated(magr, mdef, FALSE)
&& sleep_monst(mdef, rnd(10), -1)) {
if (!Blind)
pline("%s is put to sleep by you!", Monnam(mdef));
slept_monst(mdef);
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (gm.multi >= 0 && !rn2(5)
&& !mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
if (Sleep_resistance) {
monstseesu(M_SEEN_SLEEP);
return;
}
monstunseesu(M_SEEN_SLEEP);
fall_asleep(-rnd(10), TRUE);
if (Blind)
You("are put to sleep!");
else
You("are put to sleep by %s!", mon_nam(magr));
}
} else {
/* mhitm */
if (!mdef->msleeping && sleep_monst(mdef, rnd(10), -1)
&& sleep_monst(mdef, rnd(10), -1)) {
if (gv.vis && canspotmon(mdef)) {
char buf[BUFSZ];
Strcpy(buf, Monnam(mdef));
pline("%s is put to sleep by %s.", buf, mon_nam(magr));
}
mdef->mstrategy &= ~STRAT_WAITFORU;
slept_monst(mdef);
}
}
}
/* slime */
void
mhitm_ad_slim(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE);
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
if (negated)
return; /* physical damage only */
if (!rn2(4) && !slimeproof(pd)) {
if (!munslime(mdef, TRUE) && !DEADMONSTER(mdef)) {
/* this assumes newcham() won't fail; since hero has
a slime attack, green slimes haven't been geno'd */
You("turn %s into slime.", mon_nam(mdef));
if (newcham(mdef, &mons[PM_GREEN_SLIME], NO_NC_FLAGS))
pd = mdef->data;
}
/* munslime attempt could have been fatal */
if (DEADMONSTER(mdef)) {
mhm->hitflags = M_ATTK_DEF_DIED; /* skip death message */
mhm->done = TRUE;
return;
}
mhm->damage = 0;
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (negated) {
if (!magr->mcan)
You("escape harm.");
return;
}
if (flaming(pd)) {
pline_The("slime burns away!");
mhm->damage = 0;
} else if (Unchanging || noncorporeal(pd)
|| pd == &mons[PM_GREEN_SLIME]) {
You("are unaffected.");
mhm->damage = 0;
} else if (!Slimed) {
You("don't feel very well.");
make_slimed(10L, (char *) 0);
delayed_killer(SLIMED, KILLED_BY_AN,
pmname(magr->data, Mgender(magr)));
} else
pline("Yuck!");
} else {
/* mhitm */
if (negated)
return; /* physical damage only */
if (!rn2(4) && !slimeproof(pd)) {
if (!munslime(mdef, FALSE) && !DEADMONSTER(mdef)) {
unsigned ncflags = NO_NC_FLAGS;
if (gv.vis && canseemon(mdef))
ncflags |= NC_SHOW_MSG;
if (newcham(mdef, &mons[PM_GREEN_SLIME], ncflags))
pd = mdef->data;
mdef->mstrategy &= ~STRAT_WAITFORU;
mhm->hitflags = M_ATTK_HIT;
}
/* munslime attempt could have been fatal,
potentially to multiple monsters (SCR_FIRE) */
if (DEADMONSTER(magr))
mhm->hitflags |= M_ATTK_AGR_DIED;
if (DEADMONSTER(mdef))
mhm->hitflags |= M_ATTK_DEF_DIED;
mhm->damage = 0;
}
}
}
void
mhitm_ad_ench(
struct monst *magr, struct attack *mattk,
struct monst *mdef,
struct mhitm_data *mhm UNUSED)
{
if (magr == &gy.youmonst) {
/* uhitm */
/* there's no msomearmor() function, so just do damage */
} else if (mdef == &gy.youmonst) {
/* mhitu */
boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE);
hitmsg(magr, mattk);
/* uncancelled is sufficient enough; please
don't make this attack less frequent */
if (!negated) {
struct obj *obj = some_armor(mdef);
if (!obj) {
/* some rings are susceptible;
amulets and blindfolds aren't (at present) */
switch (rn2(5)) {
case 0:
break;
case 1:
obj = uright;
break;
case 2:
obj = uleft;
break;
case 3:
obj = uamul;
break;
case 4:
obj = ublindf;
break;
}
}
if (drain_item(obj, FALSE)) {
pline("%s less effective.", Yobjnam2(obj, "seem"));
}
}
} else {
/* mhitm */
/* there's no msomearmor() function, so just do damage */
}
}
void
mhitm_ad_slow(
struct monst *magr, struct attack *mattk,
struct monst *mdef,
struct mhitm_data *mhm UNUSED)
{
boolean negated = mhitm_mgc_atk_negated(magr, mdef, FALSE);
if (defended(mdef, AD_SLOW))
return;
if (magr == &gy.youmonst) {
/* uhitm */
if (!negated && mdef->mspeed != MSLOW) {
unsigned int oldspeed = mdef->mspeed;
mon_adjust_speed(mdef, -1, (struct obj *) 0);
if (mdef->mspeed != oldspeed && canseemon(mdef))
pline("%s slows down.", Monnam(mdef));
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!negated && HFast && !rn2(4))
u_slow_down();
} else {
/* mhitm */
if (!negated && mdef->mspeed != MSLOW) {
unsigned int oldspeed = mdef->mspeed;
mon_adjust_speed(mdef, -1, (struct obj *) 0);
mdef->mstrategy &= ~STRAT_WAITFORU;
if (mdef->mspeed != oldspeed && gv.vis && canspotmon(mdef))
pline("%s slows down.", Monnam(mdef));
}
}
}
void
mhitm_ad_conf(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
if (!mdef->mconf) {
if (canseemon(mdef))
pline("%s looks confused.", Monnam(mdef));
mdef->mconf = 1;
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!magr->mcan && !rn2(4) && !magr->mspec_used) {
magr->mspec_used = magr->mspec_used + (mhm->damage + rn2(6));
if (Confusion)
You("are getting even more confused.");
else
You("are getting confused.");
make_confused(HConfusion + mhm->damage, FALSE);
}
mhm->damage = 0;
} else {
/* mhitm */
/* Since confusing another monster doesn't have a real time
* limit, setting spec_used would not really be right (though
* we still should check for it).
*/
if (!magr->mcan && !mdef->mconf && !magr->mspec_used) {
if (gv.vis && canseemon(mdef))
pline("%s looks confused.", Monnam(mdef));
mdef->mconf = 1;
mdef->mstrategy &= ~STRAT_WAITFORU;
}
}
}
void
mhitm_ad_poly(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
boolean negated = (mhitm_mgc_atk_negated(magr, mdef, FALSE)
|| magr->mspec_used);
if (magr == &gy.youmonst) {
/* uhitm */
/* require weaponless attack in order to honor AD_POLY */
if (!uwep && mhm->damage < mdef->mhp) {
if (negated) {
/* assume that you can tell by touch if blinded */
pline("%s is not transformed.", Monnam(mdef));
} else {
mhm->damage = mon_poly(&gy.youmonst, mdef, mhm->damage);
if (DEADMONSTER(mdef))
mhm->hitflags |= M_ATTK_DEF_DIED;
mhm->hitflags |= M_ATTK_HIT;
mhm->done = TRUE;
}
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (Maybe_Half_Phys(mhm->damage) < (Upolyd ? u.mh : u.uhp)) {
if (negated) {
if (magr->mcan)
You("aren't transformed.");
} else {
mhm->damage = mon_poly(magr, &gy.youmonst, mhm->damage);
mhm->hitflags |= M_ATTK_HIT;
mhm->done = TRUE;
}
}
} else {
/* mhitm */
if (mhm->damage < mdef->mhp && !negated) {
mhm->damage = mon_poly(magr, mdef, mhm->damage);
if (DEADMONSTER(mdef))
mhm->hitflags |= M_ATTK_DEF_DIED;
mhm->hitflags |= M_ATTK_HIT;
mhm->done = TRUE;
}
}
}
void
mhitm_ad_famn(
struct monst *magr,
struct attack *mattk UNUSED,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm; hero can't polymorph into anything with this attack
so this won't happen; if it could, it would be the same as
the mhitm case except for messaging */
goto mhitm_famn;
} else if (mdef == &gy.youmonst) {
/* mhitu */
pline("%s reaches out, and your body shrivels.", Monnam(magr));
exercise(A_CON, FALSE);
if (!is_fainted())
morehungry(rn1(40, 40));
/* plus the normal damage */
} else {
mhitm_famn:
/* mhitm; it's possible for Famine to hit another monster;
if target is something that doesn't eat, it won't be harmed;
otherwise, just inflict the normal damage */
if (!(carnivorous(pd) || herbivorous(pd) || metallivorous(pd)))
mhm->damage = 0;
}
}
void
mhitm_ad_pest(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct attack alt_attk;
struct permonst *pa = magr->data;
if (magr == &gy.youmonst) {
/* uhitm; hero can't polymorph into anything with this attack
so this won't happen; if it could, it would be the same as
the mhitm case except for messaging */
goto mhitm_pest;
} else if (mdef == &gy.youmonst) {
/* mhitu */
pline("%s reaches out, and you feel fever and chills.", Monnam(magr));
(void) diseasemu(pa);
/* plus the normal damage */
} else {
mhitm_pest:
/* mhitm; it's possible for Pestilence to hit another monster;
treat it the same as an attack for AD_DISE damage */
alt_attk = *mattk;
alt_attk.adtyp = AD_DISE;
mhitm_ad_dise(magr, &alt_attk, mdef, mhm);
}
}
void
mhitm_ad_deth(
struct monst *magr,
struct attack *mattk UNUSED,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm; hero can't polymorph into anything with this attack
so this won't happen; if it could, it would be the same as
the mhitm case except for messaging */
goto mhitm_deth;
} else if (mdef == &gy.youmonst) {
/* mhitu */
pline("%s reaches out with its deadly touch.", Monnam(magr));
if (is_undead(pd)) {
/* still does some damage */
mhm->damage = (mhm->damage + 1) / 2;
pline("Was that the touch of death?");
return;
}
switch (rn2(20)) {
case 19:
case 18:
case 17:
if (!Antimagic) {
touch_of_death(magr);
mhm->damage = 0;
return;
}
/*FALLTHRU*/
default: /* case 16: ... case 5: */
You_feel("your life force draining away...");
mhm->permdmg = 1; /* actual damage done by caller */
return;
case 4:
case 3:
case 2:
case 1:
case 0:
if (Antimagic)
shieldeff(u.ux, u.uy);
pline("Lucky for you, it didn't work!");
mhm->damage = 0;
return;
}
} else {
mhitm_deth:
/* mhitm; it's possible for Death to hit another monster;
if target is undead, it will take some damage but less than an
undead hero would; otherwise, just inflict the normal damage */
if (is_undead(pd) && mhm->damage > 1)
mhm->damage = rnd(mhm->damage / 2);
/* simulate Death's touch with drain life attack */
mhitm_ad_drli(magr, mattk, mdef, mhm);
}
}
void
mhitm_ad_halu(
struct monst *magr,
struct attack *mattk UNUSED,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
mhm->damage = 0;
} else {
/* mhitm */
if (!magr->mcan && haseyes(pd) && mdef->mcansee) {
if (gv.vis && canseemon(mdef))
pline("%s looks %sconfused.", Monnam(mdef),
mdef->mconf ? "more " : "");
mdef->mconf = 1;
mdef->mstrategy &= ~STRAT_WAITFORU;
}
mhm->damage = 0;
}
}
boolean
do_stone_u(struct monst *mtmp)
{
if (!Stoned && !Stone_resistance
&& !(poly_when_stoned(gy.youmonst.data)
&& polymon(PM_STONE_GOLEM))) {
int kformat = KILLED_BY_AN;
const char *kname = pmname(mtmp->data, Mgender(mtmp));
if (mtmp->data->geno & G_UNIQ) {
if (!type_is_pname(mtmp->data))
kname = the(kname);
kformat = KILLED_BY;
}
make_stoned(5L, (char *) 0, kformat, kname);
return 1;
/* done_in_by(mtmp, STONING); */
}
return 0;
}
void
do_stone_mon(
struct monst *magr,
struct attack *mattk UNUSED,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
/* may die from the acid if it eats a stone-curing corpse */
if (munstone(mdef, FALSE))
goto post_stone;
if (poly_when_stoned(pd)) {
mon_to_stone(mdef);
mhm->damage = 0;
return;
}
if (!resists_ston(mdef)) {
if (gv.vis && canseemon(mdef))
pline("%s turns to stone!", Monnam(mdef));
monstone(mdef);
post_stone:
if (!DEADMONSTER(mdef)) {
mhm->hitflags = M_ATTK_MISS;
mhm->done = TRUE;
return;
} else if (mdef->mtame && !gv.vis) {
You(brief_feeling, "peculiarly sad");
}
mhm->hitflags = (M_ATTK_DEF_DIED
| (grow_up(magr, mdef) ? 0 : M_ATTK_AGR_DIED));
mhm->done = TRUE;
return;
}
mhm->damage = (mattk->adtyp == AD_STON ? 0 : 1);
}
void
mhitm_ad_phys(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pa = magr->data;
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
if (pd == &mons[PM_SHADE]) {
mhm->damage = 0;
if (!mhm->specialdmg)
impossible("bad shade attack function flow?");
}
mhm->damage += mhm->specialdmg;
if (mattk->aatyp == AT_WEAP) {
/* hmonas() uses known_hitum() to deal physical damage,
then also damageum() for non-AD_PHYS; don't inflict
extra physical damage for unusual damage types */
mhm->damage = 0;
} else if (mattk->aatyp == AT_KICK
|| mattk->aatyp == AT_CLAW
|| mattk->aatyp == AT_TUCH
|| mattk->aatyp == AT_HUGS) {
if (thick_skinned(pd))
mhm->damage = (mattk->aatyp == AT_KICK) ? 0
: (mhm->damage + 1) / 2;
/* add ring(s) of increase damage */
if (u.udaminc > 0) {
/* applies even if damage was 0 */
mhm->damage += u.udaminc;
} else if (mhm->damage > 0) {
/* ring(s) might be negative; avoid converting
0 to non-0 or positive to non-positive */
mhm->damage += u.udaminc;
if (mhm->damage < 1)
mhm->damage = 1;
}
}
} else if (mdef == &gy.youmonst) {
/* mhitu */
if (mattk->aatyp == AT_HUGS && !sticks(pd)) {
if (!u.ustuck && rn2(2)) {
if (u_slip_free(magr, mattk)) {
mhm->damage = 0;
mhm->hitflags |= M_ATTK_MISS;
} else {
set_ustuck(magr);
pline("%s grabs you!", Monnam(magr));
mhm->hitflags |= M_ATTK_HIT;
}
} else if (u.ustuck == magr) {
exercise(A_STR, FALSE);
You("are being %s.",
(pa == &mons[PM_ROPE_GOLEM]) ? "choked" : "crushed");
}
} else { /* hand to hand weapon */
struct obj *otmp = MON_WEP(magr);
if (mattk->aatyp == AT_WEAP && otmp) {
struct obj *marmg;
int tmp;
if (otmp->otyp == CORPSE
&& touch_petrifies(&mons[otmp->corpsenm])) {
mhm->damage = 1;
pline("%s hits you with the %s corpse.", Monnam(magr),
mons[otmp->corpsenm].pmnames[NEUTRAL]);
if (!Stoned) {
if (do_stone_u(magr)) {
mhm->hitflags = M_ATTK_HIT;
mhm->done = 1;
return;
}
}
}
mhm->damage += dmgval(otmp, mdef);
if ((marmg = which_armor(magr, W_ARMG)) != 0
&& marmg->otyp == GAUNTLETS_OF_POWER)
mhm->damage += rn1(4, 3); /* 3..6 */
if (mhm->damage <= 0)
mhm->damage = 1;
if (!otmp->oartifact
|| !artifact_hit(magr, mdef, otmp, &mhm->damage,
gm.mhitu_dieroll)) {
hitmsg(magr, mattk);
mhm->hitflags |= M_ATTK_HIT;
}
if (!mhm->damage)
return;
if (objects[otmp->otyp].oc_material == SILVER
&& Hate_silver) {
pline_The("silver sears your flesh!");
exercise(A_CON, FALSE);
}
/* this redundancy necessary because you have
to take the damage _before_ being cloned;
need to have at least 2 hp left to split */
tmp = mhm->damage;
if (u.uac < 0)
tmp -= rnd(-u.uac);
if (tmp < 1)
tmp = 1;
if (u.mh - tmp > 1
&& (objects[otmp->otyp].oc_material == IRON
/* relevant 'metal' objects are scalpel and tsurugi */
|| objects[otmp->otyp].oc_material == METAL)
&& (u.umonnum == PM_BLACK_PUDDING
|| u.umonnum == PM_BROWN_PUDDING)) {
if (tmp > 1)
exercise(A_STR, FALSE);
/* inflict damage now; we know it can't be fatal */
u.mh -= tmp;
disp.botl = TRUE;
mhm->damage = 0; /* don't inflict more damage below */
if (cloneu())
You("divide as %s hits you!", mon_nam(magr));
}
rustm(&gy.youmonst, otmp);
} else if (mattk->aatyp != AT_TUCH || mhm->damage != 0
|| magr != u.ustuck) {
hitmsg(magr, mattk);
mhm->hitflags |= M_ATTK_HIT;
}
}
} else {
/* mhitm */
struct obj *mwep = MON_WEP(magr);
boolean vis = canseemon(magr) && canseemon(mdef);
if (mattk->aatyp != AT_WEAP && mattk->aatyp != AT_CLAW)
mwep = 0;
if (shade_miss(magr, mdef, mwep, FALSE, vis)) {
mhm->damage = 0;
} else if (mattk->aatyp == AT_KICK && thick_skinned(pd)) {
/* [no 'kicking boots' check needed; monsters with kick attacks
can't wear boots and monsters that wear boots don't kick] */
mhm->damage = 0;
} else if (mwep) { /* non-Null 'mwep' implies AT_WEAP || AT_CLAW */
struct obj *marmg;
if (mwep->otyp == CORPSE
&& touch_petrifies(&mons[mwep->corpsenm])) {
do_stone_mon(magr, mattk, mdef, mhm);
if (mhm->done)
return;
}
mhm->damage += dmgval(mwep, mdef);
if ((marmg = which_armor(magr, W_ARMG)) != 0
&& marmg->otyp == GAUNTLETS_OF_POWER)
mhm->damage += rn1(4, 3); /* 3..6 */
if (mhm->damage < 1) /* is this necessary? mhitu.c has it... */
mhm->damage = 1;
if (mwep->oartifact) {
/* when magr's weapon is an artifact, caller suppressed its
usual 'hit' message in case artifact_hit() delivers one;
now we'll know and might need to deliver skipped message
(note: if there's no message there'll be no auxiliary
damage so the message here isn't coming too late) */
if (!artifact_hit(magr, mdef, mwep, &mhm->damage,
mhm->dieroll)) {
if (gv.vis)
pline("%s hits %s.", Monnam(magr),
mon_nam_too(mdef, magr));
mhm->hitflags |= M_ATTK_HIT;
}
/* artifact_hit updates 'tmp' but doesn't inflict any
damage; however, it might cause carried items to be
destroyed and they might do so */
if (DEADMONSTER(mdef)) {
mhm->hitflags = (M_ATTK_DEF_DIED
| (grow_up(magr, mdef) ? 0
: M_ATTK_AGR_DIED));
mhm->done = TRUE;
return;
}
}
if (mhm->damage)
rustm(mdef, mwep);
} else if (pa == &mons[PM_PURPLE_WORM] && pd == &mons[PM_SHRIEKER]) {
/* hack to enhance mm_aggression(); we don't want purple
worm's bite attack to kill a shrieker because then it
won't swallow the corpse; but if the target survives,
the subsequent engulf attack should accomplish that */
if (mhm->damage >= mdef->mhp && mdef->mhp > 1)
mhm->damage = mdef->mhp - 1;
}
}
}
void
mhitm_ad_ston(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
if (!munstone(mdef, TRUE))
minstapetrify(mdef, TRUE);
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!rn2(3)) {
if (magr->mcan) {
if (!Deaf)
You_hear("a cough from %s!", mon_nam(magr));
} else {
if (Hallucination && !Blind) {
Soundeffect(se_cockatrice_hiss, 50);
You_hear("hissing."); /* You_hear() deals with Deaf */
pline("%s appears to be blowing you a kiss...",
Monnam(magr));
} else if (!Deaf) {
You_hear("%s hissing!", s_suffix(mon_nam(magr)));
} else if (!Blind) {
pline("%s seems to grimace.", Monnam(magr));
}
/*
* 3.7: New moon is no longer overridden by carrying a
* lizard corpse. Having the moon's impact on terrestrial
* activity be affected by carrying a dead critter felt
* silly.
*
* That behavior dated to when there were no corpse objects
* yet; "dead lizard" was a distinct item. With a lizard
* corpse, hero can eat it to survive petrification and
* probably retain a partly eaten corpse for future use.
*
* Maintaining foodless conduct during a new moon might
* become a little harder. Clearing out cockatrice nests
* during a new moon could become quite a bit harder.
*/
if (!rn2(10) || flags.moonphase == NEW_MOON) {
if (do_stone_u(magr)) {
mhm->hitflags = M_ATTK_HIT;
mhm->done = TRUE;
return;
}
}
}
}
} else {
/* mhitm */
if (magr->mcan)
return;
do_stone_mon(magr, mattk, mdef, mhm);
if (mhm->done)
return;
}
}
void
mhitm_ad_were(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pa = magr->data;
if (magr == &gy.youmonst) {
/* uhitm */
mhitm_ad_phys(magr, mattk, mdef, mhm);
if (mhm->done)
return;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!rn2(4) && u.ulycn == NON_PM
&& !Protection_from_shape_changers && !defends(AD_WERE, uwep)
&& !mhitm_mgc_atk_negated(magr, mdef, TRUE)) {
urgent_pline("You feel feverish.");
exercise(A_CON, FALSE);
set_ulycn(monsndx(pa));
retouch_equipment(2);
}
} else {
/* mhitm */
mhitm_ad_phys(magr, mattk, mdef, mhm);
if (mhm->done)
return;
}
}
void
mhitm_ad_heal(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
mhitm_ad_phys(magr, mattk, mdef, mhm);
if (mhm->done)
return;
} else if (mdef == &gy.youmonst) {
/* mhitu */
/* a cancelled nurse is just an ordinary monster,
* nurses don't heal those that cause petrification */
if (magr->mcan || (Upolyd && touch_petrifies(pd))) {
hitmsg(magr, mattk);
return;
}
/* weapon check should match the one in sounds.c for MS_NURSE */
if (!(uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep)))
&& !uarmu && !uarm && !uarmc
&& !uarms && !uarmg && !uarmf && !uarmh) {
boolean goaway = FALSE;
pline("%s hits! (I hope you don't mind.)", Monnam(magr));
if (Upolyd) {
u.mh += rnd(7);
if (!rn2(7)) {
/* no upper limit necessary; effect is temporary */
u.mhmax++;
if (!rn2(13))
goaway = TRUE;
}
if (u.mh > u.mhmax)
u.mh = u.mhmax;
} else {
u.uhp += rnd(7);
if (!rn2(7)) {
/* hard upper limit via nurse care: 25 * ulevel */
if (u.uhpmax < 5 * u.ulevel + d(2 * u.ulevel, 10)) {
u.uhpmax++;
if (u.uhpmax > u.uhppeak)
u.uhppeak = u.uhpmax;
}
if (!rn2(13))
goaway = TRUE;
}
if (u.uhp > u.uhpmax)
u.uhp = u.uhpmax;
}
if (!rn2(3))
exercise(A_STR, TRUE);
if (!rn2(3))
exercise(A_CON, TRUE);
if (Sick)
make_sick(0L, (char *) 0, FALSE, SICK_ALL);
disp.botl = TRUE;
if (goaway) {
mongone(magr);
mhm->done = TRUE;
mhm->hitflags = M_ATTK_DEF_DIED; /* return 2??? */
return;
} else if (!rn2(33)) {
if (!tele_restrict(magr))
(void) rloc(magr, RLOC_MSG);
monflee(magr, d(3, 6), TRUE, FALSE);
mhm->done = TRUE;
mhm->hitflags = M_ATTK_HIT | M_ATTK_DEF_DIED; /* return 3??? */
return;
}
mhm->damage = 0;
} else {
if (Role_if(PM_HEALER)) {
if (!Deaf && !(gm.moves % 5)) {
SetVoice(magr, 0, 80, 0);
verbalize("Doc, I can't help you unless you cooperate.");
}
mhm->damage = 0;
} else
hitmsg(magr, mattk);
}
} else {
/* mhitm */
mhitm_ad_phys(magr, mattk, mdef, mhm);
if (mhm->done)
return;
}
}
void
mhitm_ad_stun(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
if (!Blind)
pline("%s %s for a moment.", Monnam(mdef),
makeplural(stagger(pd, "stagger")));
mdef->mstun = 1;
mhitm_ad_phys(magr, mattk, mdef, mhm);
if (mhm->done)
return;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!magr->mcan && !rn2(4)) {
make_stunned((HStun & TIMEOUT) + (long) mhm->damage, TRUE);
mhm->damage /= 2;
}
} else {
/* mhitm */
if (magr->mcan)
return;
if (canseemon(mdef))
pline("%s %s for a moment.", Monnam(mdef),
makeplural(stagger(pd, "stagger")));
mdef->mstun = 1;
mhitm_ad_phys(magr, mattk, mdef, mhm);
if (mhm->done)
return;
}
}
void
mhitm_ad_legs(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
#if 0
if (u.ucancelled) {
mhm->damage = 0;
return;
}
#endif
mhitm_ad_phys(magr, mattk, mdef, mhm);
if (mhm->done)
return;
} else if (mdef == &gy.youmonst) {
/* mhitu */
long side = rn2(2) ? RIGHT_SIDE : LEFT_SIDE;
const char *sidestr = (side == RIGHT_SIDE) ? "right" : "left",
*Monst_name = Monnam(magr), *leg = body_part(LEG);
/* This case is too obvious to ignore, but Nethack is not in
* general very good at considering height--most short monsters
* still _can_ attack you when you're flying or mounted.
*/
if ((u.usteed || Levitation || Flying) && !is_flyer(magr->data)) {
pline("%s tries to reach your %s %s!", Monst_name, sidestr, leg);
mhm->damage = 0;
} else if (magr->mcan) {
pline("%s nuzzles against your %s %s!", Monnam(magr),
sidestr, leg);
mhm->damage = 0;
} else {
if (uarmf) {
if (rn2(2) && (uarmf->otyp == LOW_BOOTS
|| uarmf->otyp == IRON_SHOES)) {
pline("%s pricks the exposed part of your %s %s!",
Monst_name, sidestr, leg);
} else if (!rn2(5)) {
pline("%s pricks through your %s boot!", Monst_name,
sidestr);
} else {
pline("%s scratches your %s boot!", Monst_name,
sidestr);
mhm->damage = 0;
return;
}
} else
pline("%s pricks your %s %s!", Monst_name, sidestr, leg);
set_wounded_legs(side, rnd(60 - ACURR(A_DEX)));
exercise(A_STR, FALSE);
exercise(A_DEX, FALSE);
}
} else {
/* mhitm */
if (magr->mcan) {
mhm->damage = 0;
return;
}
mhitm_ad_phys(magr, mattk, mdef, mhm);
if (mhm->done)
return;
}
}
void
mhitm_ad_dgst(
struct monst *magr,
struct attack *mattk UNUSED,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm */
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
mhm->damage = 0;
} else {
/* mhitm */
int num;
struct obj *obj;
/* eating a Rider or its corpse is fatal */
if (is_rider(pd)) {
if (gv.vis && canseemon(magr))
pline("%s %s!", Monnam(magr),
(pd == &mons[PM_FAMINE])
? "belches feebly, shrivels up and dies"
: (pd == &mons[PM_PESTILENCE])
? "coughs spasmodically and collapses"
: "vomits violently and drops dead");
mondied(magr);
if (!DEADMONSTER(magr)) {
mhm->hitflags = M_ATTK_MISS; /* lifesaved */
mhm->done = TRUE;
return;
} else if (magr->mtame && !gv.vis)
You(brief_feeling, "queasy");
mhm->hitflags = M_ATTK_AGR_DIED;
mhm->done = TRUE;
return;
}
if (flags.verbose && !Deaf) {
/* Soundeffect? */
SetVoice(magr, 0, 80, 0);
verbalize("Burrrrp!");
}
wake_nearto(magr->mx, magr->my, 2 * 2); /* Burrrrp! */
mhm->damage = mdef->mhp;
/* Use up amulet of life saving */
if ((obj = mlifesaver(mdef)) != 0)
m_useup(mdef, obj);
/* Is a corpse for nutrition possible? It may kill magr */
if (!corpse_chance(mdef, magr, TRUE) || DEADMONSTER(magr))
return;
/* Pets get nutrition from swallowing monster whole.
* No nutrition from G_NOCORPSE monster, eg, undead.
* DGST monsters don't die from undead corpses
*/
num = monsndx(pd);
if (magr->mtame && !magr->isminion
&& !(gm.mvitals[num].mvflags & G_NOCORPSE)) {
struct obj *virtualcorpse = mksobj(CORPSE, FALSE, FALSE);
int nutrit;
set_corpsenm(virtualcorpse, num);
nutrit = dog_nutrition(magr, virtualcorpse);
dealloc_obj(virtualcorpse);
/* only 50% nutrition, 25% of normal eating time */
if (magr->meating > 1)
magr->meating = (magr->meating + 3) / 4;
if (nutrit > 1)
nutrit /= 2;
EDOG(magr)->hungrytime += nutrit;
}
}
}
void
mhitm_ad_samu(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
/* when the Wizard or quest nemesis hits, there's a 1/20 chance
to steal a quest artifact (any, not just the one for the hero's
own role) or the Amulet or one of the invocation tools */
if (!rn2(20))
stealamulet(magr);
} else {
/* mhitm */
mhm->damage = 0;
}
}
/* disease */
void
mhitm_ad_dise(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pa = magr->data, *pd = mdef->data;
if (magr == &gy.youmonst) {
/* uhitm; hero can't polymorph into anything with this attack so
this won't happen; if it could, it would be the same as the
mhitm case except for messaging */
goto mhitm_dise;
} else if (mdef == &gy.youmonst) {
/* mhitu */
hitmsg(magr, mattk);
if (!diseasemu(pa))
mhm->damage = 0;
} else {
mhitm_dise:
/* mhitm; protected monsters use the same criteria as for poly'd
hero gaining sick resistance combined with any hero wielding a
weapon or wearing dragon scales/mail that guards against disease */
if (pd->mlet == S_FUNGUS || pd == &mons[PM_GHOUL]
|| defended(mdef, AD_DISE))
mhm->damage = 0;
/* else does ordinary damage */
}
}
/* seduce and also steal item */
void
mhitm_ad_sedu(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
struct permonst *pa = magr->data;
if (magr == &gy.youmonst) {
/* uhitm */
steal_it(mdef, mattk);
mhm->damage = 0;
} else if (mdef == &gy.youmonst) {
/* mhitu */
char buf[BUFSZ];
if (is_animal(magr->data)) {
hitmsg(magr, mattk);
if (magr->mcan)
return;
/* Continue below */
} else if (dmgtype(gy.youmonst.data, AD_SEDU)
/* !SYSOPT_SEDUCE: when hero is attacking and AD_SSEX
is disabled, it would be changed to another damage
type, but when defending, it remains as-is */
|| dmgtype(gy.youmonst.data, AD_SSEX)) {
pline("%s %s.", Monnam(magr),
Deaf ? "says something but you can't hear it"
: magr->minvent
? "brags about the goods some dungeon explorer provided"
: "makes some remarks about how difficult theft is lately");
if (!tele_restrict(magr))
(void) rloc(magr, RLOC_MSG);
mhm->hitflags = M_ATTK_AGR_DONE; /* return 3??? */
mhm->done = TRUE;
return;
} else if (magr->mcan) {
if (!Blind)
pline("%s tries to %s you, but you seem %s.",
Adjmonnam(magr, "plain"),
flags.female ? "charm" : "seduce",
flags.female ? "unaffected" : "uninterested");
if (rn2(3)) {
if (!tele_restrict(magr))
(void) rloc(magr, RLOC_MSG);
mhm->hitflags = M_ATTK_AGR_DONE; /* return 3??? */
mhm->done = TRUE;
return;
}
return;
}
buf[0] = '\0';
switch (steal(magr, buf)) {
case -1:
mhm->hitflags = M_ATTK_AGR_DIED; /* return 2??? */
mhm->done = TRUE;
return;
case 0:
return;
default:
if (!is_animal(magr->data) && !tele_restrict(magr))
(void) rloc(magr, RLOC_MSG);
if (is_animal(magr->data) && *buf) {
if (canseemon(magr))
pline("%s tries to %s away with %s.", Monnam(magr),
locomotion(magr->data, "run"), buf);
}
monflee(magr, 0, FALSE, FALSE);
mhm->hitflags = M_ATTK_AGR_DONE; /* return 3??? */
mhm->done = TRUE;
return;
}
} else {
/* mhitm */
struct obj *obj;
if (magr->mcan)
return;
/* find an object to steal, non-cursed if magr is tame */
for (obj = mdef->minvent; obj; obj = obj->nobj)
if (!magr->mtame || !obj->cursed)
break;
if (obj) {
char buf[BUFSZ];
char onambuf[BUFSZ], mdefnambuf[BUFSZ];
/* make a special x_monnam() call that never omits
the saddle, and save it for later messages */
Strcpy(mdefnambuf,
x_monnam(mdef, ARTICLE_THE, (char *) 0, 0, FALSE));
if (u.usteed == mdef && obj == which_armor(mdef, W_SADDLE))
/* "You can no longer ride <steed>." */
dismount_steed(DISMOUNT_POLY);
extract_from_minvent(mdef, obj, TRUE, FALSE);
/* add_to_minv() might free 'obj' [if it merges] */
if (gv.vis)
Strcpy(onambuf, doname(obj));
(void) add_to_minv(magr, obj);
Strcpy(buf, Monnam(magr));
if (gv.vis && canseemon(mdef)) {
pline("%s steals %s from %s!", buf, onambuf, mdefnambuf);
}
possibly_unwield(mdef, FALSE);
mdef->mstrategy &= ~STRAT_WAITFORU;
mselftouch(mdef, (const char *) 0, FALSE);
if (DEADMONSTER(mdef)) {
mhm->hitflags = (M_ATTK_DEF_DIED
| (grow_up(magr, mdef) ? 0
: M_ATTK_AGR_DIED));
mhm->done = TRUE;
return;
}
if (pa->mlet == S_NYMPH && !tele_restrict(magr)) {
boolean couldspot = canspotmon(magr);
mhm->hitflags = M_ATTK_AGR_DONE;
(void) rloc(magr, RLOC_NOMSG);
/* TODO: use RLOC_MSG instead? */
if (gv.vis && couldspot && !canspotmon(magr))
pline("%s suddenly disappears!", buf);
}
}
mhm->damage = 0;
}
}
void
mhitm_ad_ssex(struct monst *magr, struct attack *mattk, struct monst *mdef,
struct mhitm_data *mhm)
{
if (magr == &gy.youmonst) {
/* uhitm */
mhitm_ad_sedu(magr, mattk, mdef, mhm);
if (mhm->done)
return;
} else if (mdef == &gy.youmonst) {
/* mhitu */
if (SYSOPT_SEDUCE) {
if (could_seduce(magr, mdef, mattk) == 1 && !magr->mcan)
if (doseduce(magr)) {
mhm->hitflags = M_ATTK_AGR_DONE;
mhm->done = TRUE;
return;
}
return;
}
mhitm_ad_sedu(magr, mattk, mdef, mhm);
if (mhm->done)
return;
} else {
/* mhitm */
mhitm_ad_sedu(magr, mattk, mdef, mhm);
if (mhm->done)
return;
}
}
void
mhitm_adtyping(
struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
switch (mattk->adtyp) {
case AD_STUN: mhitm_ad_stun(magr, mattk, mdef, mhm); break;
case AD_LEGS: mhitm_ad_legs(magr, mattk, mdef, mhm); break;
case AD_WERE: mhitm_ad_were(magr, mattk, mdef, mhm); break;
case AD_HEAL: mhitm_ad_heal(magr, mattk, mdef, mhm); break;
case AD_PHYS: mhitm_ad_phys(magr, mattk, mdef, mhm); break;
case AD_FIRE: mhitm_ad_fire(magr, mattk, mdef, mhm); break;
case AD_COLD: mhitm_ad_cold(magr, mattk, mdef, mhm); break;
case AD_ELEC: mhitm_ad_elec(magr, mattk, mdef, mhm); break;
case AD_ACID: mhitm_ad_acid(magr, mattk, mdef, mhm); break;
case AD_STON: mhitm_ad_ston(magr, mattk, mdef, mhm); break;
case AD_SSEX: mhitm_ad_ssex(magr, mattk, mdef, mhm); break;
case AD_SITM:
case AD_SEDU: mhitm_ad_sedu(magr, mattk, mdef, mhm); break;
case AD_SGLD: mhitm_ad_sgld(magr, mattk, mdef, mhm); break;
case AD_TLPT: mhitm_ad_tlpt(magr, mattk, mdef, mhm); break;
case AD_BLND: mhitm_ad_blnd(magr, mattk, mdef, mhm); break;
case AD_CURS: mhitm_ad_curs(magr, mattk, mdef, mhm); break;
case AD_DRLI: mhitm_ad_drli(magr, mattk, mdef, mhm); break;
case AD_RUST: mhitm_ad_rust(magr, mattk, mdef, mhm); break;
case AD_CORR: mhitm_ad_corr(magr, mattk, mdef, mhm); break;
case AD_DCAY: mhitm_ad_dcay(magr, mattk, mdef, mhm); break;
case AD_DREN: mhitm_ad_dren(magr, mattk, mdef, mhm); break;
case AD_DRST:
case AD_DRDX:
case AD_DRCO: mhitm_ad_drst(magr, mattk, mdef, mhm); break;
case AD_DRIN: mhitm_ad_drin(magr, mattk, mdef, mhm); break;
case AD_STCK: mhitm_ad_stck(magr, mattk, mdef, mhm); break;
case AD_WRAP: mhitm_ad_wrap(magr, mattk, mdef, mhm); break;
case AD_PLYS: mhitm_ad_plys(magr, mattk, mdef, mhm); break;
case AD_SLEE: mhitm_ad_slee(magr, mattk, mdef, mhm); break;
case AD_SLIM: mhitm_ad_slim(magr, mattk, mdef, mhm); break;
case AD_ENCH: mhitm_ad_ench(magr, mattk, mdef, mhm); break;
case AD_SLOW: mhitm_ad_slow(magr, mattk, mdef, mhm); break;
case AD_CONF: mhitm_ad_conf(magr, mattk, mdef, mhm); break;
case AD_POLY: mhitm_ad_poly(magr, mattk, mdef, mhm); break;
case AD_DISE: mhitm_ad_dise(magr, mattk, mdef, mhm); break;
case AD_SAMU: mhitm_ad_samu(magr, mattk, mdef, mhm); break;
case AD_DETH: mhitm_ad_deth(magr, mattk, mdef, mhm); break;
case AD_PEST: mhitm_ad_pest(magr, mattk, mdef, mhm); break;
case AD_FAMN: mhitm_ad_famn(magr, mattk, mdef, mhm); break;
case AD_DGST: mhitm_ad_dgst(magr, mattk, mdef, mhm); break;
case AD_HALU: mhitm_ad_halu(magr, mattk, mdef, mhm); break;
default:
mhm->damage = 0;
}
}
int
damageum(
struct monst *mdef, /* target */
struct attack *mattk, /* hero's attack */
int specialdmg) /* blessed and/or silver bonus against various things */
{
struct mhitm_data mhm;
mhm.damage = d((int) mattk->damn, (int) mattk->damd);
mhm.hitflags = M_ATTK_MISS;
mhm.permdmg = 0;
mhm.specialdmg = specialdmg;
mhm.done = FALSE;
if (is_demon(gy.youmonst.data) && !rn2(13) && !uwep
&& u.umonnum != PM_AMOROUS_DEMON && u.umonnum != PM_BALROG) {
demonpet();
return M_ATTK_MISS;
}
mhitm_adtyping(&gy.youmonst, mattk, mdef, &mhm);
if (mhm.done)
return mhm.hitflags;
mdef->mstrategy &= ~STRAT_WAITFORU; /* in case player is very fast */
mdef->mhp -= mhm.damage;
if (DEADMONSTER(mdef)) {
/* troll killed by Trollsbane won't auto-revive; FIXME? same when
Trollsbane is wielded as primary and two-weaponing kills with
secondary, which matches monster vs monster behavior but is
different from the non-poly'd hero vs monster behavior */
if (mattk->aatyp == AT_WEAP || mattk->aatyp == AT_CLAW)
gm.mkcorpstat_norevive = troll_baned(mdef, uwep) ? TRUE : FALSE;
/* (DEADMONSTER(mdef) and !mhm.damage => already killed) */
if (mdef->mtame && !cansee(mdef->mx, mdef->my)) {
You_feel("embarrassed for a moment.");
if (mhm.damage)
xkilled(mdef, XKILL_NOMSG);
} else if (!flags.verbose) {
You("destroy it!");
if (mhm.damage)
xkilled(mdef, XKILL_NOMSG);
} else if (mhm.damage) {
killed(mdef); /* regular "you kill <mdef>" message */
}
gm.mkcorpstat_norevive = FALSE;
return M_ATTK_DEF_DIED;
}
return M_ATTK_HIT;
}
/* Hero, as a monster which is capable of an exploding attack mattk, is
* exploding at a target monster mdef, or just exploding at nothing (e.g. with
* forcefight) if mdef is null.
*/
int
explum(struct monst *mdef, struct attack *mattk)
{
int tmp = d((int) mattk->damn, (int) mattk->damd);
switch (mattk->adtyp) {
case AD_BLND:
if (mdef && !resists_blnd(mdef)) {
pline("%s is blinded by your flash of light!", Monnam(mdef));
mdef->mblinded = min((int) mdef->mblinded + tmp, 127);
mdef->mcansee = 0;
}
break;
case AD_HALU:
if (mdef && haseyes(mdef->data) && mdef->mcansee) {
pline("%s is affected by your flash of light!", Monnam(mdef));
mdef->mconf = 1;
}
break;
case AD_COLD:
case AD_FIRE:
case AD_ELEC:
/* See comment in mon_explodes() and in zap.c for an explanation
of this math. Here, the player is causing the explosion, so it
should be in the +20 to +29 range instead of negative. */
explode(u.ux, u.uy, (mattk->adtyp - 1) + 20, tmp, MON_EXPLODE,
adtyp_to_expltype(mattk->adtyp));
if (mdef && DEADMONSTER(mdef)) {
/* Other monsters may have died too, but return this if the actual
target died. */
return M_ATTK_DEF_DIED;
}
break;
default:
break;
}
wake_nearto(u.ux, u.uy, 7 * 7); /* same radius as exploding monster */
return M_ATTK_HIT;
}
static void
start_engulf(struct monst *mdef)
{
boolean u_digest = digests(gy.youmonst.data),
u_enfold = enfolds(gy.youmonst.data);
if (!Invisible) {
map_location(u.ux, u.uy, TRUE);
tmp_at(DISP_ALWAYS, mon_to_glyph(&gy.youmonst, rn2_on_display_rng));
tmp_at(mdef->mx, mdef->my);
}
You("%s %s%s!",
u_digest ? "swallow" : u_enfold ? "enclose" : "engulf",
mon_nam(mdef), u_digest ? " whole" : "");
nh_delay_output();
nh_delay_output();
}
static void
end_engulf(void)
{
if (!Invisible) {
tmp_at(DISP_END, 0);
newsym(u.ux, u.uy);
}
}
static int
gulpum(struct monst *mdef, struct attack *mattk)
{
static char msgbuf[BUFSZ]; /* for gn.nomovemsg */
int tmp;
int dam = d((int) mattk->damn, (int) mattk->damd);
boolean fatal_gulp,
u_digest = digests(gy.youmonst.data),
u_enfold = enfolds(gy.youmonst.data);
struct obj *otmp;
struct permonst *pd = mdef->data;
const char *expel_verb = u_digest ? "regurgitate"
: u_enfold ? "release"
: "expel";
/* Not totally the same as for real monsters. Specifically, these
* don't take multiple moves. (It's just too hard, for too little
* result, to program monsters which attack from inside you, which
* would be necessary if done accurately.) Instead, we arbitrarily
* kill the monster immediately for AD_DGST and we regurgitate them
* after exactly 1 round of attack otherwise. -KAA
*/
if (!engulf_target(&gy.youmonst, mdef))
return M_ATTK_MISS;
if (!(u_digest && u.uhunger >= 1500) && !u.uswallow) {
if (!flaming(gy.youmonst.data)) {
for (otmp = mdef->minvent; otmp; otmp = otmp->nobj)
(void) snuff_lit(otmp);
}
/* force vampire in bat, cloud, or wolf form to revert back to
vampire form now instead of dealing with that when it dies */
if (is_vampshifter(mdef)
&& newcham(mdef, &mons[mdef->cham], NO_NC_FLAGS)) {
You("%s it, then %s it.",
u_digest ? "swallow" : u_enfold ? "enclose" : "engulf",
expel_verb);
if (canspotmon(mdef)) {
/* Avoiding a_monnam here: if the target is named, it gives us
a sequence like "You bite Dracula. You swallow it, then
regurgitate it. It turns into Dracula." */
pline("It turns into %s.",
x_monnam(mdef, ARTICLE_A, (char *) 0,
(SUPPRESS_NAME | SUPPRESS_IT
| SUPPRESS_INVISIBLE), FALSE));
} else {
map_invisible(mdef->mx, mdef->my);
}
return M_ATTK_HIT;
}
/* engulfing a cockatrice or digesting a Rider or Medusa */
fatal_gulp = (touch_petrifies(pd) && !Stone_resistance)
|| (mattk->adtyp == AD_DGST
&& (is_rider(pd) || (pd == &mons[PM_MEDUSA]
&& !Stone_resistance)));
if (mattk->adtyp == AD_DGST && (!Slow_digestion || fatal_gulp))
eating_conducts(pd);
if (fatal_gulp && !is_rider(pd)) { /* petrification */
char kbuf[BUFSZ];
const char *mnam = pmname(pd, Mgender(mdef));
if (!type_is_pname(pd))
mnam = an(mnam);
You("%s %s.", u_digest ? "englut" : "engulf", mon_nam(mdef));
Sprintf(kbuf, "%s %s%s",
u_digest ? "swallowing"
: u_enfold ? "enclosing"
: "engulfing",
mnam, u_digest ? " whole" : "");
instapetrify(kbuf);
} else {
start_engulf(mdef);
switch (mattk->adtyp) {
case AD_DGST:
/* eating a Rider or its corpse is fatal */
if (is_rider(pd)) {
pline("Unfortunately, digesting any of it is fatal.");
end_engulf();
Sprintf(gk.killer.name, "unwisely tried to eat %s",
pmname(pd, Mgender(mdef)));
gk.killer.format = NO_KILLER_PREFIX;
done(DIED);
return M_ATTK_MISS; /* lifesaved */
}
if (Slow_digestion) {
dam = 0;
break;
}
/* Use up amulet of life saving */
if ((otmp = mlifesaver(mdef)) != 0)
m_useup(mdef, otmp);
newuhs(FALSE);
/* start_engulf() issues "you engulf <mdef>" above; this
used to specify XKILL_NOMSG but we need "you kill <mdef>"
in case we're also going to get "welcome to level N+1";
"you totally digest <mdef>" will be coming soon (after
several turns) but the level-gain message seems out of
order if the kill message is left implicit */
xkilled(mdef, XKILL_GIVEMSG | XKILL_NOCORPSE);
if (!DEADMONSTER(mdef)) { /* monster lifesaved */
You("hurriedly regurgitate the sizzling in your %s.",
body_part(STOMACH));
} else {
tmp = 1 + (pd->cwt >> 8);
if (corpse_chance(mdef, &gy.youmonst, TRUE)
&& !(gm.mvitals[monsndx(pd)].mvflags & G_NOCORPSE)) {
/* nutrition only if there can be a corpse */
u.uhunger += (pd->cnutrit + 1) / 2;
} else {
tmp = 0;
}
Sprintf(msgbuf, "You totally digest %s.", mon_nam(mdef));
if (tmp != 0) {
/* setting afternmv = end_engulf is tempting,
* but will cause problems if the player is
* attacked (which uses his real location) or
* if his See_invisible wears off
*/
You("digest %s.", mon_nam(mdef));
if (Slow_digestion)
tmp *= 2;
nomul(-tmp);
gm.multi_reason = "digesting something";
gn.nomovemsg = msgbuf;
/* possible intrinsic once totally digested */
gc.corpsenm_digested = monsndx(pd);
ga.afternmv = Finish_digestion;
} else
pline1(msgbuf);
if (pd == &mons[PM_GREEN_SLIME]) {
Sprintf(msgbuf, "%s isn't sitting well with you.",
The(pmname(pd, Mgender(mdef))));
if (!Unchanging) {
make_slimed(5L, (char *) 0);
}
} else
exercise(A_CON, TRUE);
}
end_engulf();
return M_ATTK_DEF_DIED;
case AD_PHYS:
if (gy.youmonst.data == &mons[PM_FOG_CLOUD]) {
pline("%s is laden with your moisture.", Monnam(mdef));
if ((breathless(pd) || amphibious(pd)) && !flaming(pd)) {
dam = 0;
pline("%s seems unharmed.", Monnam(mdef));
}
} else {
pline("%s is %s!", Monnam(mdef),
enfolds(gy.youmonst.data) ? "being squashed"
: "pummeled with your debris");
}
break;
case AD_ACID:
pline("%s is covered with your goo!", Monnam(mdef));
if (resists_acid(mdef)) {
pline("It seems harmless to %s.", mon_nam(mdef));
dam = 0;
}
break;
case AD_BLND:
if (can_blnd(&gy.youmonst, mdef, mattk->aatyp,
(struct obj *) 0)) {
if (mdef->mcansee)
pline("%s can't see in there!", Monnam(mdef));
mdef->mcansee = 0;
dam += mdef->mblinded;
if (dam > 127)
dam = 127;
mdef->mblinded = dam;
}
dam = 0;
break;
case AD_ELEC:
if (rn2(2)) {
pline_The("air around %s crackles with electricity.",
mon_nam(mdef));
if (resists_elec(mdef)) {
pline("%s seems unhurt.", Monnam(mdef));
dam = 0;
}
golemeffects(mdef, (int) mattk->adtyp, dam);
} else
dam = 0;
break;
case AD_COLD:
if (rn2(2)) {
if (resists_cold(mdef)) {
pline("%s seems mildly chilly.", Monnam(mdef));
dam = 0;
} else
pline("%s is freezing to death!", Monnam(mdef));
golemeffects(mdef, (int) mattk->adtyp, dam);
} else
dam = 0;
break;
case AD_FIRE:
if (rn2(2)) {
if (resists_fire(mdef)) {
pline("%s seems mildly hot.", Monnam(mdef));
dam = 0;
} else
pline("%s is burning to a crisp!", Monnam(mdef));
golemeffects(mdef, (int) mattk->adtyp, dam);
} else
dam = 0;
break;
case AD_DREN:
if (!rn2(4))
xdrainenergym(mdef, TRUE);
dam = 0;
break;
}
end_engulf();
mdef->mhp -= dam;
if (DEADMONSTER(mdef)) {
killed(mdef);
if (DEADMONSTER(mdef)) /* not lifesaved */
return M_ATTK_DEF_DIED;
}
You("%s %s!", expel_verb, mon_nam(mdef));
if ((Slow_digestion || is_animal(gy.youmonst.data)) && u_digest) {
pline("Obviously, you didn't like %s taste.",
s_suffix(mon_nam(mdef)));
}
}
}
return M_ATTK_MISS;
}
void
missum(
struct monst *mdef,
struct attack *mattk,
boolean wouldhavehit)
{
if (wouldhavehit) /* monk is missing due to penalty for wearing suit */
Your("armor is rather cumbersome...");
if (could_seduce(&gy.youmonst, mdef, mattk))
You("pretend to be friendly to %s.", mon_nam(mdef));
else if (canspotmon(mdef) && flags.verbose)
You("miss %s.", mon_nam(mdef));
else
You("miss it.");
if (!helpless(mdef))
wakeup(mdef, TRUE);
}
/* check whether equipment protects against knockback */
boolean
m_is_steadfast(struct monst *mtmp)
{
boolean is_u = (mtmp == &gy.youmonst);
struct obj *otmp = is_u ? uwep : MON_WEP(mtmp);
/* must be on the ground (or in water) */
if ((is_u ? (Flying || Levitation)
: (is_flyer(mtmp->data) || is_floater(mtmp->data)))
|| Is_airlevel(&u.uz) /* air or cloud */
|| (Is_waterlevel(&u.uz) && !is_pool(u.ux, u.uy))) /* air bubble */
return FALSE;
if (is_art(otmp, ART_GIANTSLAYER))
return TRUE;
/* steadfast if carrying any loadstone (and not floating or flying);
'is_u' test not needed here; m_carrying() is 'youmonst' aware */
if (m_carrying(mtmp, LOADSTONE))
return TRUE;
/* when mounted and steed is target of knockback, check the rider for
a loadstone too (Giantslayer's protection doesn't extend to steed) */
if (u.usteed && mtmp == u.usteed && carrying(LOADSTONE))
return TRUE;
return FALSE;
}
/* monster hits another monster hard enough to knock it back? */
boolean
mhitm_knockback(
struct monst *magr, /* attacker; might be hero */
struct monst *mdef, /* defender; might be hero (only if magr isn't) */
struct attack *mattk, /* attack type and damage info */
int *hitflags, /* modified if magr or mdef dies */
boolean weapon_used) /* True: via weapon hit */
{
char magrbuf[BUFSZ], mdefbuf[BUFSZ];
struct obj *otmp;
const char *knockedhow;
coordxy dx, dy, defx, defy;
int knockdistance = rn2(3) ? 1 : 2; /* 67%: 1 step, 33%: 2 steps */
boolean u_agr = (magr == &gy.youmonst);
boolean u_def = (mdef == &gy.youmonst);
boolean was_u = FALSE, dismount = FALSE;
/* 1/6 chance of attack knocking back a monster */
if (rn2(6))
return FALSE;
/* if hero is stuck to a cursed saddle, knock the steed back */
if (u_def && u.usteed) {
if ((otmp = which_armor(u.usteed, W_SADDLE)) != 0 && otmp->cursed) {
mdef = u.usteed;
was_u = TRUE;
u_def = FALSE;
} else {
dismount = TRUE; /* saddle is not cursed; knock hero out of it */
}
}
/* monsters must be alive */
if ((!u_agr && DEADMONSTER(magr))
|| (!u_def && DEADMONSTER(mdef)))
return FALSE;
/* attacker must be much larger than defender */
if (!(magr->data->msize > (mdef->data->msize + 1)))
return FALSE;
/* only certain attacks qualify for knockback */
if (!((mattk->adtyp == AD_PHYS)
&& (mattk->aatyp == AT_CLAW
|| mattk->aatyp == AT_KICK
|| mattk->aatyp == AT_BUTT
|| (mattk->aatyp == AT_WEAP && !weapon_used))))
return FALSE;
/* needs a solid physical hit */
if (unsolid(magr->data))
return FALSE;
/* the attack must have hit */
/* mon-vs-mon code path doesn't set up hitflags */
if ((u_agr || u_def) && !(*hitflags & M_ATTK_HIT))
return FALSE;
/* steadfast defender cannot be pushed around */
if (m_is_steadfast(mdef)) {
if (u_def || (u.usteed && mdef == u.usteed)) {
mdefbuf[0] = '\0';
if (u.usteed)
Snprintf(mdefbuf, sizeof mdefbuf, "and %s ",
y_monnam(u.usteed));
You("%sdon't budge.", mdefbuf);
} else if (canseemon(mdef)) {
pline("%s doesn't budge.", Monnam(mdef));
}
return FALSE;
}
/* decide where the first step will place the target; not accurate
for being knocked out of saddle but doesn't need to be; used for
message before actual hurtle */
defx = u_def ? u.ux : mdef->mx;
defy = u_def ? u.uy : mdef->my;
dx = sgn(defx - (u_agr ? u.ux : magr->mx));
dy = sgn(defy - (u_agr ? u.uy : magr->my));
/* subtly vary the message text if monster won't actually move */
knockedhow = dismount ? "out of your saddle"
: will_hurtle(mdef, defx + dx, defy + dy) ? "backward"
: "back";
/* give the message */
if (u_def || canseemon(mdef)) {
Strcpy(magrbuf, u_agr ? "You" : Monnam(magr));
Strcpy(mdefbuf, (u_def || was_u) ? "you" : y_monnam(mdef));
if (was_u)
Snprintf(eos(mdefbuf), sizeof mdefbuf - strlen(mdefbuf),
" and %s", y_monnam(u.usteed));
/*
* uhitm: You knock the gnome back with a powerful blow!
* mhitu: The red dragon knocks you back with a forceful blow!
* mhitm: The fire giant knocks the gnome back with a forceful strike!
*/
pline("%s %s %s %s with a %s %s!",
magrbuf, vtense(magrbuf, "knock"), mdefbuf, knockedhow,
rn2(2) ? "forceful" : "powerful", rn2(2) ? "blow" : "strike");
} else if (u_agr) {
/* hero knocks unseen foe back; noticed by touch */
You_feel("%s be knocked %s!", some_mon_nam(mdef), knockedhow);
}
if (u.ustuck && (u_def || u_agr))
unstuck(u.ustuck);
/* do the actual knockback effect */
if (u_def) {
if (dismount) {
/* normally u.dx,u.dy indicates the direction hero is throwing,
zapping, &c but here it is used to pass preferred direction
for dismount to dismount_steed (for DISMOUNT_KNOCKED only) */
u.dx = dx;
u.dy = dy;
dismount_steed(DISMOUNT_KNOCKED);
} else {
hurtle(dx, dy, knockdistance, FALSE);
*hitflags |= M_ATTK_HIT;
}
set_apparxy(magr); /* update magr's idea of where you are */
if (!Stunned && !rn2(4))
make_stunned((long) (knockdistance + 1), TRUE); /* 2 or 3 */
} else {
mhurtle(mdef, dx, dy, knockdistance);
if (!u_agr)
*hitflags |= M_ATTK_HIT;
if (DEADMONSTER(mdef)) {
if (!was_u)
*hitflags |= M_ATTK_DEF_DIED;
} else if (!rn2(4)) {
mdef->mstun = 1;
/* if steed and hero were knocked back, update attacker's idea
of where hero is */
if (mdef == u.usteed)
set_apparxy(magr);
}
}
if (!u_agr) {
if (DEADMONSTER(magr))
*hitflags |= M_ATTK_AGR_DIED;
}
return TRUE;
}
/* attack monster as a monster; returns True if mon survives */
static boolean
hmonas(struct monst *mon)
{
struct attack *mattk, alt_attk;
struct obj *weapon, **originalweapon;
boolean altwep = FALSE, weapon_used = FALSE, odd_claw = TRUE;
int i, tmp, dieroll, armorpenalty, sum[NATTK],
dhit = 0, attknum = 0, multi_claw = 0, multi_weap = 0;
boolean monster_survived;
/* not used here but umpteen mhitm_ad_xxxx() need this */
gv.vis = (canseemon(mon) || m_next2u(mon));
/* with just one touch/claw/weapon attack, both rings matter;
with more than one, alternate right and left when checking
whether silver ring causes successful hit */
for (i = 0; i < NATTK; i++) {
sum[i] = M_ATTK_MISS;
mattk = getmattk(&gy.youmonst, mon, i, sum, &alt_attk);
if (mattk->aatyp == AT_WEAP)
++multi_weap;
if (mattk->aatyp == AT_WEAP
|| mattk->aatyp == AT_CLAW || mattk->aatyp == AT_TUCH)
++multi_claw;
}
multi_claw = (multi_claw > 1); /* switch from count to yes/no */
gt.twohits = 0;
gs.skipdrin = FALSE; /* [see mattackm(mhitm.c)] */
for (i = 0; i < NATTK; i++) {
/* sum[i] = M_ATTK_MISS; -- now done above */
mattk = getmattk(&gy.youmonst, mon, i, sum, &alt_attk);
if (gs.skipdrin && mattk->aatyp == AT_TENT && mattk->adtyp == AD_DRIN)
continue;
weapon = 0;
switch (mattk->aatyp) {
case AT_WEAP:
/* if (!uwep) goto weaponless; */
use_weapon:
odd_claw = !odd_claw; /* see case AT_CLAW,AT_TUCH below */
/* if we've already hit with a two-handed weapon, we don't
get to make another weapon attack (note: monsters who
use weapons do not have this restriction, but they also
never have the opportunity to use two weapons) */
if (weapon_used && (sum[i - 1] > M_ATTK_MISS)
&& uwep && bimanual(uwep))
continue;
/* Certain monsters don't use weapons when encountered as enemies,
* but players who polymorph into them have hands or claws and
* thus should be able to use weapons. This shouldn't prohibit
* the use of most special abilities, either.
* If monster has multiple claw attacks, only one can use weapon.
*/
weapon_used = TRUE;
/* Potential problem: if the monster gets multiple weapon attacks,
* we currently allow the player to get each of these as a weapon
* attack. Is this really desirable?
*/
/* approximate two-weapon mode; known_hitum() -> hmon() -> &c
might destroy the weapon argument, but it might also already
be Null, and we want to track that for passive() */
originalweapon = (altwep && uswapwep) ? &uswapwep : &uwep;
if (uswapwep /* set up 'altwep' flag for next iteration */
/* only consider secondary when wielding one-handed primary */
&& uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep))
&& !bimanual(uwep)
/* only switch if not wearing shield and not at artifact;
shield limitation is iffy since still get extra swings
if polyform has them, but it matches twoweap behavior;
twoweap also only allows primary to be an artifact, so
if alternate weapon is one, don't use it */
&& !uarms && !uswapwep->oartifact
/* only switch to uswapwep if it's a weapon */
&& (uswapwep->oclass == WEAPON_CLASS || is_weptool(uswapwep))
/* only switch if uswapwep is not bow, arrows, or darts */
&& !(is_launcher(uswapwep) || is_ammo(uswapwep)
|| is_missile(uswapwep)) /* dart, shuriken, boomerang */
/* and not two-handed and not incapable of being wielded */
&& !bimanual(uswapwep)
&& !(objects[uswapwep->otyp].oc_material == SILVER
&& Hate_silver))
altwep = !altwep; /* toggle for next attack */
weapon = *originalweapon;
if (!weapon) /* no need to go beyond no-gloves to rings; not ...*/
originalweapon = &uarmg; /*... subject to erosion damage */
tmp = find_roll_to_hit(mon, AT_WEAP, weapon, &attknum,
&armorpenalty);
mon_maybe_unparalyze(mon);
dieroll = rnd(20);
dhit = (tmp > dieroll || u.uswallow);
if (multi_weap > 1)
++gt.twohits;
/* caller must set gb.bhitpos */
monster_survived = known_hitum(mon, weapon, &dhit, tmp,
armorpenalty, mattk, dieroll);
/* originalweapon points to an equipment slot which might
now be empty if the weapon was destroyed during the hit;
passive(,weapon,...) won't call passive_obj() in that case */
weapon = *originalweapon; /* might receive passive erosion */
if (!monster_survived) {
/* enemy dead, before any special abilities used */
sum[i] = M_ATTK_DEF_DIED;
break;
} else
sum[i] = dhit ? M_ATTK_HIT : M_ATTK_MISS;
/* might be a worm that gets cut in half; if so, early return */
if (m_at(u.ux + u.dx, u.uy + u.dy) != mon) {
i = NATTK; /* skip additional attacks */
/* proceed with uswapwep->cursed check, then exit loop */
goto passivedone;
}
/* Do not print "You hit" message; known_hitum already did it. */
if (dhit && mattk->adtyp != AD_SPEL && mattk->adtyp != AD_PHYS)
sum[i] = damageum(mon, mattk, 0);
break;
case AT_CLAW:
if (uwep && !cantwield(gy.youmonst.data) && !weapon_used)
goto use_weapon;
/*FALLTHRU*/
case AT_TUCH:
if (uwep && gy.youmonst.data->mlet == S_LICH && !weapon_used)
goto use_weapon;
/*FALLTHRU*/
case AT_KICK:
case AT_BITE:
case AT_STNG:
case AT_BUTT:
case AT_TENT:
/*weaponless:*/
tmp = find_roll_to_hit(mon, mattk->aatyp, (struct obj *) 0,
&attknum, &armorpenalty);
mon_maybe_unparalyze(mon);
dieroll = rnd(20);
dhit = (tmp > dieroll || u.uswallow);
if (dhit) {
int compat, specialdmg;
long silverhit = 0L;
const char *verb = 0; /* verb or body part */
if (!u.uswallow
&& (compat = could_seduce(&gy.youmonst, mon, mattk))
!= 0) {
You("%s %s %s.",
(mon->mcansee && haseyes(mon->data)) ? "smile at"
: "talk to",
mon_nam(mon),
(compat == 2) ? "engagingly" : "seductively");
/* doesn't anger it; no wakeup() */
sum[i] = damageum(mon, mattk, 0);
break;
}
wakeup(mon, TRUE);
specialdmg = 0; /* blessed and/or silver bonus */
switch (mattk->aatyp) {
case AT_CLAW:
case AT_TUCH:
/* verb=="claws" may be overridden below */
verb = (mattk->aatyp == AT_TUCH) ? "touch" : "claws";
/* decide if silver-hater will be hit by silver ring(s);
for 'multi_claw' where attacks alternate right/left,
assume 'even' claw or touch attacks use dominant hand
or paw, 'odd' ones use non-dominant hand for ring
interaction; even vs odd is based on actual attacks
rather than on index into mon->dat->mattk[] so that
{bite,claw,claw} instead of {claw,claw,bite} doesn't
make poly'd hero mysteriously switch handedness */
odd_claw = !odd_claw;
specialdmg = special_dmgval(&gy.youmonst, mon,
W_ARMG
| ((odd_claw || !multi_claw)
? W_RINGL : 0L)
| ((!odd_claw || !multi_claw)
? W_RINGR : 0L),
&silverhit);
break;
case AT_TENT:
/* assumes mind flayer's tentacles-on-head rather
than sea monster's tentacle-as-arm */
verb = "tentacles";
break;
case AT_KICK:
verb = "kick";
specialdmg = special_dmgval(&gy.youmonst, mon, W_ARMF,
&silverhit);
break;
case AT_BUTT:
verb = "head butt"; /* mbodypart(mon,HEAD)=="head" */
/* hypothetical; if any form with a head-butt attack
could wear a helmet, it would hit shades when
wearing a blessed (or silver) one */
specialdmg = special_dmgval(&gy.youmonst, mon, W_ARMH,
&silverhit);
break;
case AT_BITE:
verb = "bite";
break;
case AT_STNG:
verb = "sting";
break;
default:
verb = "hit";
break;
}
if (mon->data == &mons[PM_SHADE] && !specialdmg) {
if (!strcmp(verb, "hit")
|| (mattk->aatyp == AT_CLAW && humanoid(mon->data)))
verb = "attack";
Your("%s %s harmlessly through %s.",
verb, vtense(verb, "pass"), mon_nam(mon));
} else {
/* either not a shade or no special silver/blessed damage,
other unsolid monsters are immune to AT_TUCH+AD_WRAP */
if (failed_grab(&gy.youmonst, mon, mattk))
break; /* miss; message already given */
if (mattk->aatyp == AT_TENT) {
Your("tentacles suck %s.", mon_nam(mon));
} else {
if (mattk->aatyp == AT_CLAW)
verb = "hit"; /* not "claws" */
You("%s %s.", verb, mon_nam(mon));
if (silverhit && flags.verbose)
silver_sears(&gy.youmonst, mon, silverhit);
}
sum[i] = damageum(mon, mattk, specialdmg);
}
} else { /* !dhit */
missum(mon, mattk, (tmp + armorpenalty > dieroll));
}
break;
case AT_HUGS: {
int specialdmg;
long silverhit = 0L;
boolean byhand = hug_throttles(&mons[u.umonnum]), /* rope golem */
unconcerned = (byhand && !can_be_strangled(mon));
if (sticks(mon->data) || u.uswallow || gn.notonhead
|| (byhand && (uwep || !has_head(mon->data)))) {
/* can't hold a holder due to subsequent ambiguity over
who is holding whom; can't hug engulfer from inside;
can't hug a worm tail (would immobilize entire worm!);
byhand: can't choke something that lacks a head;
not allowed to make a choking hug if wielding a weapon
(but might have grabbed w/o weapon, then wielded one,
and may even be attacking a different monster now) */
if (byhand && uwep && u.ustuck
&& !(sticks(u.ustuck->data) || u.uswallow))
uunstick();
continue; /* not 'break'; bypass passive counter-attack */
}
/* automatic if prev two attacks succeed, or if
already grabbed in a previous attack */
dhit = 1;
wakeup(mon, TRUE);
/* choking hug/throttling grab uses hands (gloves or rings);
normal hug uses outermost of cloak/suit/shirt */
specialdmg = special_dmgval(&gy.youmonst, mon,
byhand ? (W_ARMG | W_RINGL | W_RINGR)
: (W_ARMC | W_ARM | W_ARMU),
&silverhit);
if (unconcerned) {
/* strangling something which can't be strangled */
if (mattk != &alt_attk) {
alt_attk = *mattk;
mattk = &alt_attk;
}
/* change damage to 1d1; not strangling but still
doing [minimal] physical damage to victim's body */
mattk->damn = mattk->damd = 1;
/* don't give 'unconcerned' feedback if there is extra damage
or if it is nearly destroyed or if creature doesn't have
the mental ability to be concerned in the first place */
if (specialdmg || mindless(mon->data)
|| mon->mhp <= 1 + max(u.udaminc, 1))
unconcerned = FALSE;
}
if (mon->data == &mons[PM_SHADE]) {
const char *verb = byhand ? "grasp" : "hug";
/* hugging a shade; successful if blessed outermost armor
for normal hug, or blessed gloves or silver ring(s) for
choking hug; deals damage but never grabs hold */
if (specialdmg) {
You("%s %s%s", verb, mon_nam(mon), exclam(specialdmg));
if (silverhit && flags.verbose)
silver_sears(&gy.youmonst, mon, silverhit);
sum[i] = damageum(mon, mattk, specialdmg);
} else {
Your("%s passes harmlessly through %s.",
verb, mon_nam(mon));
}
break;
}
/* can't grab unsolid creatures (checked after shade handling) */
if (failed_grab(&gy.youmonst, mon, mattk))
break;
/* hug attack against ordinary foe */
if (mon == u.ustuck) {
pline("%s is being %s%s.", Monnam(mon),
byhand ? "throttled" : "crushed",
/* extra feedback for non-breather being choked */
unconcerned ? " but doesn't seem concerned" : "");
if (silverhit && flags.verbose)
silver_sears(&gy.youmonst, mon, silverhit);
sum[i] = damageum(mon, mattk, specialdmg);
} else if (i >= 2 && (sum[i - 1] > M_ATTK_MISS)
&& (sum[i - 2] > M_ATTK_MISS)) {
/* in case we're hugging a new target while already
holding something else; yields feedback
"<u.ustuck> is no longer in your clutches" */
if (u.ustuck && u.ustuck != mon)
uunstick();
You("grab %s!", mon_nam(mon));
set_ustuck(mon);
if (silverhit && flags.verbose)
silver_sears(&gy.youmonst, mon, silverhit);
sum[i] = damageum(mon, mattk, specialdmg);
}
break; /* AT_HUGS */
}
case AT_EXPL: /* automatic hit if next to */
dhit = -1;
wakeup(mon, TRUE);
You("explode!");
sum[i] = explum(mon, mattk);
break;
case AT_ENGL:
tmp = find_roll_to_hit(mon, mattk->aatyp, (struct obj *) 0,
&attknum, &armorpenalty);
mon_maybe_unparalyze(mon);
if ((dhit = (tmp > rnd(20 + i)))) {
wakeup(mon, TRUE);
/* can't engulf unsolid creatures */
if (mon->data == &mons[PM_SHADE]) {
/* no specialdmg check needed */
Your("attempt to surround %s is harmless.", mon_nam(mon));
} else if (failed_grab(&gy.youmonst, mon, mattk)) {
; /* non-shade miss; message already given */
} else {
sum[i] = gulpum(mon, mattk);
if (sum[i] == M_ATTK_DEF_DIED
&& (mon->data->mlet == S_ZOMBIE
|| mon->data->mlet == S_MUMMY)
&& rn2(5) && !Sick_resistance) {
You_feel("%ssick.", (Sick) ? "very " : "");
mdamageu(mon, rnd(8));
}
}
} else {
missum(mon, mattk, FALSE);
}
break;
case AT_MAGC:
/* No check for uwep; if wielding nothing we want to
* do the normal 1-2 points bare hand damage...
*/
if ((gy.youmonst.data->mlet == S_KOBOLD
|| gy.youmonst.data->mlet == S_ORC
|| gy.youmonst.data->mlet == S_GNOME) && !weapon_used)
goto use_weapon;
/*FALLTHRU*/
case AT_NONE:
case AT_BOOM:
continue;
/* Not break--avoid passive attacks from enemy */
case AT_BREA:
case AT_SPIT:
case AT_GAZE: /* all done using #monster command */
dhit = 0;
break;
default: /* Strange... */
impossible("strange attack of yours (%d)", mattk->aatyp);
}
if (dhit == -1) {
u.mh = -1; /* dead in the current form */
rehumanize();
}
if (sum[i] == M_ATTK_DEF_DIED) {
/* defender dead */
(void) passive(mon, weapon, 1, 0, mattk->aatyp, FALSE);
} else {
(void) passive(mon, weapon, (sum[i] != M_ATTK_MISS), 1,
mattk->aatyp, FALSE);
}
if (mhitm_knockback(&gy.youmonst, mon, mattk, &sum[i], weapon_used))
break;
/* don't use sum[i] beyond this point;
'i' will be out of bounds if we get here via 'goto' */
passivedone:
/* when using dual weapons, cursed secondary weapon doesn't weld,
it gets dropped; do the same when multiple AT_WEAP attacks
simulate twoweap */
if (uswapwep && weapon == uswapwep && weapon->cursed) {
drop_uswapwep();
break; /* don't proceed with additional attacks */
}
/* stop attacking if defender has died;
needed to defer this until after uswapwep->cursed check */
if (DEADMONSTER(mon))
break;
if (!Upolyd)
break; /* No extra attacks if no longer a monster */
if (gm.multi < 0)
break; /* If paralyzed while attacking, i.e. floating eye */
}
gv.vis = FALSE; /* reset */
gt.twohits = 0;
/* return value isn't used, but make it match hitum()'s */
return !DEADMONSTER(mon);
}
/* Special (passive) attacks on you by monsters done here.
*/
int
passive(
struct monst *mon,
struct obj *weapon, /* uwep or uswapwep or uarmg or uarmf or Null */
boolean mhitb,
boolean maliveb,
uchar aatyp,
boolean wep_was_destroyed)
{
struct permonst *ptr = mon->data;
int i, tmp;
int mhit = mhitb ? M_ATTK_HIT : M_ATTK_MISS;
int malive = maliveb ? M_ATTK_HIT : M_ATTK_MISS;
for (i = 0;; i++) {
if (i >= NATTK)
return (malive | mhit); /* no passive attacks */
if (ptr->mattk[i].aatyp == AT_NONE)
break; /* try this one */
}
/* Note: tmp not always used */
if (ptr->mattk[i].damn)
tmp = d((int) ptr->mattk[i].damn, (int) ptr->mattk[i].damd);
else if (ptr->mattk[i].damd)
tmp = d((int) mon->m_lev + 1, (int) ptr->mattk[i].damd);
else
tmp = 0;
/* These affect you even if they just died.
*/
switch (ptr->mattk[i].adtyp) {
case AD_FIRE:
if (mhitb && !mon->mcan && weapon) {
if (aatyp == AT_KICK) {
if (uarmf && !rn2(6))
(void) erode_obj(uarmf, xname(uarmf), ERODE_BURN,
EF_GREASE | EF_VERBOSE);
} else if (aatyp == AT_WEAP || aatyp == AT_CLAW
|| aatyp == AT_MAGC || aatyp == AT_TUCH)
passive_obj(mon, weapon, &(ptr->mattk[i]));
}
break;
case AD_ACID:
if (mhitb && rn2(2)) {
if (Blind || !flags.verbose)
You("are splashed!");
else
You("are splashed by %s %s!", s_suffix(mon_nam(mon)),
hliquid("acid"));
if (!Acid_resistance) {
mdamageu(mon, tmp);
monstunseesu(M_SEEN_ACID);
} else {
monstseesu(M_SEEN_ACID);
}
if (!rn2(30))
erode_armor(&gy.youmonst, ERODE_CORRODE);
}
if (mhitb && weapon) {
if (aatyp == AT_KICK) {
if (uarmf && !rn2(6))
(void) erode_obj(uarmf, xname(uarmf), ERODE_CORRODE,
EF_GREASE | EF_VERBOSE);
} else if (aatyp == AT_WEAP || aatyp == AT_CLAW
|| aatyp == AT_MAGC || aatyp == AT_TUCH)
passive_obj(mon, weapon, &(ptr->mattk[i]));
}
exercise(A_STR, FALSE);
break;
case AD_STON:
if (mhitb) { /* successful attack */
long protector = attk_protection((int) aatyp);
/* hero using monsters' AT_MAGC attack is hitting hand to
hand rather than casting a spell */
if (aatyp == AT_MAGC)
protector = W_ARMG;
if (protector == 0L /* no protection */
|| (protector == W_ARMG && !uarmg
&& !uwep && !wep_was_destroyed)
|| (protector == W_ARMF && !uarmf)
|| (protector == W_ARMH && !uarmh)
|| (protector == (W_ARMC | W_ARMG) && (!uarmc || !uarmg))) {
if (!Stone_resistance
&& !(poly_when_stoned(gy.youmonst.data)
&& polymon(PM_STONE_GOLEM))) {
done_in_by(mon, STONING); /* "You turn to stone..." */
return M_ATTK_DEF_DIED;
}
}
}
break;
case AD_RUST:
if (mhitb && !mon->mcan && weapon) {
if (aatyp == AT_KICK) {
if (uarmf)
(void) erode_obj(uarmf, xname(uarmf), ERODE_RUST,
EF_GREASE | EF_VERBOSE);
} else if (aatyp == AT_WEAP || aatyp == AT_CLAW
|| aatyp == AT_MAGC || aatyp == AT_TUCH)
passive_obj(mon, weapon, &(ptr->mattk[i]));
}
break;
case AD_CORR:
if (mhitb && !mon->mcan && weapon) {
if (aatyp == AT_KICK) {
if (uarmf)
(void) erode_obj(uarmf, xname(uarmf), ERODE_CORRODE,
EF_GREASE | EF_VERBOSE);
} else if (aatyp == AT_WEAP || aatyp == AT_CLAW
|| aatyp == AT_MAGC || aatyp == AT_TUCH)
passive_obj(mon, weapon, &(ptr->mattk[i]));
}
break;
case AD_MAGM:
/* wrath of gods for attacking Oracle */
if (Antimagic) {
shieldeff(u.ux, u.uy);
monstseesu(M_SEEN_MAGR);
pline("A hail of magic missiles narrowly misses you!");
} else {
You("are hit by magic missiles appearing from thin air!");
mdamageu(mon, tmp);
monstunseesu(M_SEEN_MAGR);
}
break;
case AD_ENCH: /* KMH -- remove enchantment (disenchanter) */
if (mhitb) {
if (aatyp == AT_KICK) {
if (!weapon)
break;
} else if (aatyp == AT_BITE || aatyp == AT_BUTT
|| (aatyp >= AT_STNG && aatyp < AT_WEAP)) {
break; /* no object involved */
} else {
/*
* TODO: #H2668 - if hitting with a ring that has a
* positive enchantment, it ought to be subject to
* having that enchantment reduced. But we don't have
* sufficient information here to know which hand/ring
* has delivered a weaponless blow.
*/
;
}
passive_obj(mon, weapon, &(ptr->mattk[i]));
}
break;
default:
break;
}
/* These only affect you if they still live.
*/
if (malive && !mon->mcan && rn2(3)) {
switch (ptr->mattk[i].adtyp) {
case AD_PLYS:
if (ptr == &mons[PM_FLOATING_EYE]) {
if (!canseemon(mon)) {
break;
}
if (mon->mcansee) {
if (ureflects("%s gaze is reflected by your %s.",
s_suffix(Monnam(mon)))) {
;
} else if (Hallucination && rn2(4)) {
/* [it's the hero who should be getting paralyzed
and isn't; this message describes the monster's
reaction rather than the hero's escape] */
pline("%s looks %s%s.", Monnam(mon),
!rn2(2) ? "" : "rather ",
!rn2(2) ? "numb" : "stupefied");
} else if (Free_action) {
You("momentarily stiffen under %s gaze!",
s_suffix(mon_nam(mon)));
} else {
You("are frozen by %s gaze!", s_suffix(mon_nam(mon)));
nomul((ACURR(A_WIS) > 12 || rn2(4)) ? -tmp : -127);
/* set gm.multi_reason;
3.6.x used "frozen by a monster's gaze" */
dynamic_multi_reason(mon, "frozen", TRUE);
gn.nomovemsg = 0;
}
} else {
pline("%s cannot defend itself.",
Adjmonnam(mon, "blind"));
if (!rn2(500))
change_luck(-1);
}
} else if (Free_action) {
You("momentarily stiffen.");
} else { /* gelatinous cube */
You("are frozen by %s!", mon_nam(mon));
gn.nomovemsg = You_can_move_again;
nomul(-tmp);
/* set gm.multi_reason;
3.6.x used "frozen by a monster"; be more specific */
dynamic_multi_reason(mon, "frozen", FALSE);
exercise(A_DEX, FALSE);
}
break;
case AD_COLD: /* brown mold or blue jelly */
if (monnear(mon, u.ux, u.uy)) {
if (Cold_resistance) {
shieldeff(u.ux, u.uy);
You_feel("a mild chill.");
monstseesu(M_SEEN_COLD);
ugolemeffects(AD_COLD, tmp);
break;
}
monstunseesu(M_SEEN_COLD);
You("are suddenly very cold!");
mdamageu(mon, tmp);
/* monster gets stronger with your heat! */
mon->mhp += tmp / 2;
if (mon->mhpmax < mon->mhp)
mon->mhpmax = mon->mhp;
/* at a certain point, the monster will reproduce! */
if (mon->mhpmax > ((int) (mon->m_lev + 1) * 8))
(void) split_mon(mon, &gy.youmonst);
}
break;
case AD_STUN: /* specifically yellow mold */
if (!Stunned)
make_stunned((long) tmp, TRUE);
break;
case AD_FIRE:
if (monnear(mon, u.ux, u.uy)) {
if (Fire_resistance) {
shieldeff(u.ux, u.uy);
You_feel("mildly warm.");
monstseesu(M_SEEN_FIRE);
ugolemeffects(AD_FIRE, tmp);
break;
}
monstunseesu(M_SEEN_FIRE);
You("are suddenly very hot!");
mdamageu(mon, tmp); /* fire damage */
}
break;
case AD_ELEC:
if (Shock_resistance) {
shieldeff(u.ux, u.uy);
You_feel("a mild tingle.");
monstseesu(M_SEEN_ELEC);
ugolemeffects(AD_ELEC, tmp);
break;
}
monstunseesu(M_SEEN_ELEC);
You("are jolted with electricity!");
mdamageu(mon, tmp);
break;
default:
break;
}
}
return (malive | mhit);
}
/*
* Special (passive) attacks on an attacking object by monsters done here.
* Assumes the attack was successful.
*/
void
passive_obj(
struct monst *mon,
struct obj *obj, /* null means pick uwep, uswapwep or uarmg */
struct attack *mattk) /* null means we find one internally */
{
struct permonst *ptr = mon->data;
int i;
/* [this first bit is obsolete; we're not called with Null anymore] */
/* if caller hasn't specified an object, use uwep, uswapwep or uarmg */
if (!obj) {
obj = (u.twoweap && uswapwep && !rn2(2)) ? uswapwep : uwep;
if (!obj && mattk->adtyp == AD_ENCH)
obj = uarmg; /* no weapon? then must be gloves */
if (!obj)
return; /* no object to affect */
}
/* if caller hasn't specified an attack, find one */
if (!mattk) {
for (i = 0;; i++) {
if (i >= NATTK)
return; /* no passive attacks */
if (ptr->mattk[i].aatyp == AT_NONE)
break; /* try this one */
}
mattk = &(ptr->mattk[i]);
}
switch (mattk->adtyp) {
case AD_FIRE:
if (!rn2(6) && !mon->mcan
/* steam vortex: fire resist applies, fire damage doesn't */
&& mon->data != &mons[PM_STEAM_VORTEX]) {
(void) erode_obj(obj, NULL, ERODE_BURN, EF_NONE);
}
break;
case AD_ACID:
if (!rn2(6)) {
(void) erode_obj(obj, NULL, ERODE_CORRODE, EF_GREASE);
}
break;
case AD_RUST:
if (!mon->mcan) {
(void) erode_obj(obj, (char *) 0, ERODE_RUST, EF_GREASE);
}
break;
case AD_CORR:
if (!mon->mcan) {
(void) erode_obj(obj, (char *) 0, ERODE_CORRODE, EF_GREASE);
}
break;
case AD_ENCH:
if (!mon->mcan) {
if (drain_item(obj, TRUE) && carried(obj)
&& (obj->known || obj->oclass == ARMOR_CLASS)) {
pline("%s less effective.", Yobjnam2(obj, "seem"));
}
break;
}
default:
break;
}
if (carried(obj))
update_inventory();
}
DISABLE_WARNING_FORMAT_NONLITERAL
/* Note: caller must ascertain mtmp is mimicking... */
void
stumble_onto_mimic(struct monst *mtmp)
{
const char *fmt = "Wait! That's %s!", *generic = "a monster", *what = 0;
if (!u.ustuck && !mtmp->mflee && dmgtype(mtmp->data, AD_STCK)
/* must be adjacent; attack via polearm could be from farther away */
&& m_next2u(mtmp))
set_ustuck(mtmp);
if (Blind) {
if (!Blind_telepat)
what = generic; /* with default fmt */
else if (M_AP_TYPE(mtmp) == M_AP_MONSTER)
what = a_monnam(mtmp); /* differs from what was sensed */
} else {
int glyph = levl[u.ux + u.dx][u.uy + u.dy].glyph;
if (glyph_is_cmap(glyph) && (glyph_to_cmap(glyph) == S_hcdoor
|| glyph_to_cmap(glyph) == S_vcdoor))
fmt = "The door actually was %s!";
else if (glyph_is_object(glyph) && glyph_to_obj(glyph) == GOLD_PIECE)
fmt = "That gold was %s!";
/* cloned Wiz starts out mimicking some other monster and
might make himself invisible before being revealed */
if (mtmp->minvis && !See_invisible)
what = generic;
else
what = a_monnam(mtmp);
}
if (what)
pline(fmt, what);
wakeup(mtmp, FALSE); /* clears mimicking */
/* if hero is blind, wakeup() won't display the monster even though
it's no longer concealed */
if (!canspotmon(mtmp)
&& !glyph_is_invisible(levl[mtmp->mx][mtmp->my].glyph))
map_invisible(mtmp->mx, mtmp->my);
}
RESTORE_WARNING_FORMAT_NONLITERAL
static void
nohandglow(struct monst *mon)
{
char *hands;
boolean altfeedback;
if (!u.umconf || mon->mconf)
return;
hands = makeplural(body_part(HAND));
altfeedback = (Blind || Invisible); /* Invisible == Invis && !See_invis */
if (u.umconf == 1) {
if (altfeedback)
Your("%s stop tingling.", hands);
else
Your("%s stop glowing %s.", hands, hcolor(NH_RED));
} else {
if (altfeedback)
pline_The("tingling in your %s lessens.", hands);
else
Your("%s no longer glow so brightly %s.", hands, hcolor(NH_RED));
}
u.umconf--;
}
/* returns 1 if light flash has noticeable effect on 'mtmp', 0 otherwise */
int
flash_hits_mon(struct monst *mtmp,
struct obj *otmp) /* source of flash */
{
struct rm *lev;
int tmp, amt, useeit, res = 0;
if (gn.notonhead)
return 0;
lev = &levl[mtmp->mx][mtmp->my];
useeit = canseemon(mtmp);
if (mtmp->msleeping && haseyes(mtmp->data)) {
mtmp->msleeping = 0;
if (useeit) {
pline_The("flash awakens %s.", mon_nam(mtmp));
res = 1;
}
} else if (mtmp->data->mlet != S_LIGHT) {
if (!resists_blnd(mtmp)) {
tmp = dist2(otmp->ox, otmp->oy, mtmp->mx, mtmp->my);
if (useeit) {
pline("%s is blinded by the flash!", Monnam(mtmp));
res = 1;
}
if (mtmp->data == &mons[PM_GREMLIN]) {
/* Rule #1: Keep them out of the light. */
amt = otmp->otyp == WAN_LIGHT ? d(1 + otmp->spe, 4)
: rn2(min(mtmp->mhp, 4));
light_hits_gremlin(mtmp, amt);
}
if (!DEADMONSTER(mtmp)) {
if (!gc.context.mon_moving)
setmangry(mtmp, TRUE);
if (tmp < 9 && !mtmp->isshk && rn2(4))
monflee(mtmp, rn2(4) ? rnd(100) : 0, FALSE, TRUE);
mtmp->mcansee = 0;
mtmp->mblinded = (tmp < 3) ? 0 : rnd(1 + 50 / tmp);
}
} else if (flags.verbose && useeit) {
if (lev->lit)
pline("The flash of light shines on %s.", mon_nam(mtmp));
else
pline("%s is illuminated.", Monnam(mtmp));
res = 2; /* 'message has been given' temporary value */
}
}
if (res) {
if (!lev->lit)
display_nhwindow(WIN_MESSAGE, TRUE);
res &= 1; /* change temporary 2 back to 0 */
}
return res;
}
void
light_hits_gremlin(struct monst *mon, int dmg)
{
pline("%s %s!", Monnam(mon),
(dmg > mon->mhp / 2) ? "wails in agony" : "cries out in pain");
mon->mhp -= dmg;
wake_nearto(mon->mx, mon->my, 30);
if (DEADMONSTER(mon)) {
if (gc.context.mon_moving)
monkilled(mon, (char *) 0, AD_BLND);
else
killed(mon);
} else if (cansee(mon->mx, mon->my) && !canspotmon(mon)) {
map_invisible(mon->mx, mon->my);
}
}
/*uhitm.c*/