Teach obj_sanity_check() and clear_bypasses() about the new obj list. It should always be empty when sanity checks are performed. That might not be the case when obj bypasses are cleared, although failing to clear bypasses for deleted objects wouldn't make any difference, so this is mainly cosmetic.
1327 lines
46 KiB
C
1327 lines
46 KiB
C
/* NetHack 3.7 worn.c $NHDT-Date: 1715109581 2024/05/07 19:19:41 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.109 $ */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/*-Copyright (c) Robert Patrick Rankin, 2013. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
staticfn void m_lose_armor(struct monst *, struct obj *, boolean) NONNULLPTRS;
|
|
staticfn void clear_bypass(struct obj *) NO_NNARGS;
|
|
staticfn void m_dowear_type(struct monst *, long, boolean, boolean) NONNULLARG1;
|
|
staticfn int extra_pref(struct monst *, struct obj *) NONNULLARG1;
|
|
|
|
static const struct worn {
|
|
long w_mask;
|
|
struct obj **w_obj;
|
|
const char *w_what; /* for failing sanity check's feedback */
|
|
} worn[] = { { W_ARM, &uarm, "suit" },
|
|
{ W_ARMC, &uarmc, "cloak" },
|
|
{ W_ARMH, &uarmh, "helmet" },
|
|
{ W_ARMS, &uarms, "shield" },
|
|
{ W_ARMG, &uarmg, "gloves" },
|
|
{ W_ARMF, &uarmf, "boots" },
|
|
{ W_ARMU, &uarmu, "shirt" },
|
|
{ W_RINGL, &uleft, "left ring" },
|
|
{ W_RINGR, &uright, "right ring" },
|
|
{ W_WEP, &uwep, "weapon" },
|
|
{ W_SWAPWEP, &uswapwep, "alternate weapon" },
|
|
{ W_QUIVER, &uquiver, "quiver" },
|
|
{ W_AMUL, &uamul, "amulet" },
|
|
{ W_TOOL, &ublindf, "facewear" }, /* blindfold|towel|lenses */
|
|
{ W_BALL, &uball, "chained ball" },
|
|
{ W_CHAIN, &uchain, "attached chain" },
|
|
{ 0, 0, (char *) 0 }
|
|
};
|
|
|
|
/* This only allows for one blocking item per property */
|
|
#define w_blocks(o, m) \
|
|
((o->otyp == MUMMY_WRAPPING && ((m) & W_ARMC) != 0L) ? INVIS \
|
|
: (o->otyp == CORNUTHAUM && ((m) & W_ARMH) != 0L \
|
|
&& !Role_if(PM_WIZARD)) ? CLAIRVOYANT \
|
|
: (is_art(o, ART_EYES_OF_THE_OVERWORLD) \
|
|
&& ((m) & W_TOOL) != 0L) ? BLINDED \
|
|
: 0)
|
|
/* note: monsters don't have clairvoyance, so dependency on hero's role here
|
|
has no significant effect on their use of w_blocks() */
|
|
|
|
/* calc the range of hero's unblind telepathy */
|
|
void
|
|
recalc_telepat_range(void)
|
|
{
|
|
const struct worn *wp;
|
|
int nobjs = 0;
|
|
|
|
for (wp = worn; wp->w_mask; wp++) {
|
|
struct obj *oobj = *(wp->w_obj);
|
|
|
|
if (oobj && objects[oobj->otyp].oc_oprop == TELEPAT)
|
|
nobjs++;
|
|
}
|
|
/* count all artifacts with SPFX_ESP as one */
|
|
if (ETelepat & W_ART)
|
|
nobjs++;
|
|
|
|
if (nobjs)
|
|
u.unblind_telepat_range = (BOLT_LIM * BOLT_LIM) * nobjs;
|
|
else
|
|
u.unblind_telepat_range = -1;
|
|
}
|
|
|
|
/* Updated to use the extrinsic and blocked fields. */
|
|
void
|
|
setworn(struct obj *obj, long mask)
|
|
{
|
|
const struct worn *wp;
|
|
struct obj *oobj;
|
|
int p;
|
|
|
|
if ((mask & (W_ARM | I_SPECIAL)) == (W_ARM | I_SPECIAL)) {
|
|
/* restoring saved game; no properties are conferred via skin */
|
|
uskin = obj;
|
|
/* assert( !uarm ); */
|
|
} else {
|
|
for (wp = worn; wp->w_mask; wp++) {
|
|
if (wp->w_mask & mask) {
|
|
oobj = *(wp->w_obj);
|
|
if (oobj && !(oobj->owornmask & wp->w_mask))
|
|
impossible("Setworn: mask=0x%08lx.", wp->w_mask);
|
|
if (oobj) {
|
|
if (u.twoweap && (oobj->owornmask & (W_WEP | W_SWAPWEP)))
|
|
set_twoweap(FALSE); /* u.twoweap = FALSE */
|
|
oobj->owornmask &= ~wp->w_mask;
|
|
if (wp->w_mask & ~(W_SWAPWEP | W_QUIVER)) {
|
|
/* leave as "x = x <op> y", here and below, for broken
|
|
* compilers */
|
|
p = objects[oobj->otyp].oc_oprop;
|
|
u.uprops[p].extrinsic =
|
|
u.uprops[p].extrinsic & ~wp->w_mask;
|
|
/* if the hero removed an extrinsic-granting item,
|
|
nearby monsters will notice and attempt attacks of
|
|
that type again */
|
|
monstunseesu_prop(p);
|
|
if ((p = w_blocks(oobj, mask)) != 0)
|
|
u.uprops[p].blocked &= ~wp->w_mask;
|
|
if (oobj->oartifact)
|
|
set_artifact_intrinsic(oobj, 0, mask);
|
|
}
|
|
/* in case wearing or removal is in progress or removal
|
|
is pending (via 'A' command for multiple items) */
|
|
cancel_doff(oobj, wp->w_mask);
|
|
}
|
|
*(wp->w_obj) = obj;
|
|
if (obj) {
|
|
obj->owornmask |= wp->w_mask;
|
|
/* Prevent getting/blocking intrinsics from wielding
|
|
* potions, through the quiver, etc.
|
|
* Allow weapon-tools, too.
|
|
* wp_mask should be same as mask at this point.
|
|
*/
|
|
if (wp->w_mask & ~(W_SWAPWEP | W_QUIVER)) {
|
|
if (obj->oclass == WEAPON_CLASS || is_weptool(obj)
|
|
|| mask != W_WEP) {
|
|
p = objects[obj->otyp].oc_oprop;
|
|
u.uprops[p].extrinsic =
|
|
u.uprops[p].extrinsic | wp->w_mask;
|
|
if ((p = w_blocks(obj, mask)) != 0)
|
|
u.uprops[p].blocked |= wp->w_mask;
|
|
}
|
|
if (obj->oartifact)
|
|
set_artifact_intrinsic(obj, 1, mask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (obj && (obj->owornmask & W_ARMOR) != 0L)
|
|
u.uroleplay.nudist = FALSE;
|
|
/* tux -> tuxedo -> "monkey suit" -> monk's suit */
|
|
iflags.tux_penalty = (uarm && Role_if(PM_MONK) && gu.urole.spelarmr);
|
|
}
|
|
update_inventory();
|
|
recalc_telepat_range();
|
|
}
|
|
|
|
/* called e.g. when obj is destroyed */
|
|
/* Updated to use the extrinsic and blocked fields. */
|
|
void
|
|
setnotworn(struct obj *obj)
|
|
{
|
|
const struct worn *wp;
|
|
int p;
|
|
|
|
if (!obj)
|
|
return;
|
|
if (u.twoweap && (obj == uwep || obj == uswapwep))
|
|
set_twoweap(FALSE); /* u.twoweap = FALSE */
|
|
for (wp = worn; wp->w_mask; wp++)
|
|
if (obj == *(wp->w_obj)) {
|
|
/* in case wearing or removal is in progress or removal
|
|
is pending (via 'A' command for multiple items) */
|
|
cancel_doff(obj, wp->w_mask);
|
|
|
|
*(wp->w_obj) = (struct obj *) 0;
|
|
p = objects[obj->otyp].oc_oprop;
|
|
u.uprops[p].extrinsic = u.uprops[p].extrinsic & ~wp->w_mask;
|
|
monstunseesu_prop(p); /* remove this extrinsic from seenres */
|
|
obj->owornmask &= ~wp->w_mask;
|
|
if (obj->oartifact)
|
|
set_artifact_intrinsic(obj, 0, wp->w_mask);
|
|
if ((p = w_blocks(obj, wp->w_mask)) != 0)
|
|
u.uprops[p].blocked &= ~wp->w_mask;
|
|
}
|
|
if (!uarm)
|
|
iflags.tux_penalty = FALSE;
|
|
update_inventory();
|
|
recalc_telepat_range();
|
|
}
|
|
|
|
/* called when saving with FREEING flag set has just discarded inventory */
|
|
void
|
|
allunworn(void)
|
|
{
|
|
const struct worn *wp;
|
|
|
|
u.twoweap = 0; /* uwep and uswapwep are going away */
|
|
/* remove stale pointers; called after the objects have been freed
|
|
(without first being unworn) while saving invent during game save;
|
|
note: uball and uchain might not be freed yet but we clear them
|
|
here anyway (savegamestate() and its callers deal with them) */
|
|
for (wp = worn; wp->w_mask; wp++) {
|
|
/* object is already gone so we don't/can't update is owornmask */
|
|
*(wp->w_obj) = (struct obj *) 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* return item worn in slot indicated by wornmask; needed by poly_obj() */
|
|
struct obj *
|
|
wearmask_to_obj(long wornmask)
|
|
{
|
|
const struct worn *wp;
|
|
|
|
for (wp = worn; wp->w_mask; wp++)
|
|
if (wp->w_mask & wornmask)
|
|
return *wp->w_obj;
|
|
return (struct obj *) 0;
|
|
}
|
|
|
|
/* return a bitmask of the equipment slot(s) a given item might be worn in */
|
|
long
|
|
wearslot(struct obj *obj)
|
|
{
|
|
int otyp = obj->otyp;
|
|
/* practically any item can be wielded or quivered; it's up to
|
|
our caller to handle such things--we assume "normal" usage */
|
|
long res = 0L; /* default: can't be worn anywhere */
|
|
|
|
switch (obj->oclass) {
|
|
case AMULET_CLASS:
|
|
res = W_AMUL; /* WORN_AMUL */
|
|
break;
|
|
case RING_CLASS:
|
|
res = W_RINGL | W_RINGR; /* W_RING, BOTH_SIDES */
|
|
break;
|
|
case ARMOR_CLASS:
|
|
switch (objects[otyp].oc_armcat) {
|
|
case ARM_SUIT:
|
|
res = W_ARM;
|
|
break; /* WORN_ARMOR */
|
|
case ARM_SHIELD:
|
|
res = W_ARMS;
|
|
break; /* WORN_SHIELD */
|
|
case ARM_HELM:
|
|
res = W_ARMH;
|
|
break; /* WORN_HELMET */
|
|
case ARM_GLOVES:
|
|
res = W_ARMG;
|
|
break; /* WORN_GLOVES */
|
|
case ARM_BOOTS:
|
|
res = W_ARMF;
|
|
break; /* WORN_BOOTS */
|
|
case ARM_CLOAK:
|
|
res = W_ARMC;
|
|
break; /* WORN_CLOAK */
|
|
case ARM_SHIRT:
|
|
res = W_ARMU;
|
|
break; /* WORN_SHIRT */
|
|
}
|
|
break;
|
|
case WEAPON_CLASS:
|
|
res = W_WEP | W_SWAPWEP;
|
|
if (objects[otyp].oc_merge)
|
|
res |= W_QUIVER;
|
|
break;
|
|
case TOOL_CLASS:
|
|
if (otyp == BLINDFOLD || otyp == TOWEL || otyp == LENSES)
|
|
res = W_TOOL; /* WORN_BLINDF */
|
|
else if (is_weptool(obj) || otyp == TIN_OPENER)
|
|
res = W_WEP | W_SWAPWEP;
|
|
else if (otyp == SADDLE)
|
|
res = W_SADDLE;
|
|
break;
|
|
case FOOD_CLASS:
|
|
if (obj->otyp == MEAT_RING)
|
|
res = W_RINGL | W_RINGR;
|
|
break;
|
|
case GEM_CLASS:
|
|
res = W_QUIVER;
|
|
break;
|
|
case BALL_CLASS:
|
|
res = W_BALL;
|
|
break;
|
|
case CHAIN_CLASS:
|
|
res = W_CHAIN;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* for 'sanity_check' option, called by you_sanity_check() */
|
|
void
|
|
check_wornmask_slots(void)
|
|
{
|
|
/* we'll skip ball and chain here--they warrant separate sanity check */
|
|
#define IGNORE_SLOTS (W_ART | W_ARTI | W_SADDLE | W_BALL| W_CHAIN)
|
|
char whybuf[BUFSZ];
|
|
const struct worn *wp;
|
|
struct obj *o, *otmp;
|
|
long m;
|
|
|
|
for (wp = worn; wp->w_mask; wp++) {
|
|
m = wp->w_mask;
|
|
if ((m & IGNORE_SLOTS) != 0L && (m & ~IGNORE_SLOTS) == 0L)
|
|
continue;
|
|
if ((o = *wp->w_obj) != 0) {
|
|
whybuf[0] = '\0';
|
|
/* slot pointer (uarm, uwep, &c) is populated; check that object
|
|
is in inventory and has the relevant owornmask bit set */
|
|
for (otmp = gi.invent; otmp; otmp = otmp->nobj)
|
|
if (otmp == o)
|
|
break;
|
|
if (!otmp)
|
|
Sprintf(whybuf, "%s (%s) not found in invent",
|
|
wp->w_what, fmt_ptr(o));
|
|
else if ((o->owornmask & m) == 0L)
|
|
Sprintf(whybuf, "%s bit not set in owornmask [0x%08lx]",
|
|
wp->w_what, o->owornmask);
|
|
else if ((o->owornmask & ~(m | IGNORE_SLOTS)) != 0L)
|
|
Sprintf(whybuf, "%s wrong bit set in owornmask [0x%08lx]",
|
|
wp->w_what, o->owornmask);
|
|
if (whybuf[0])
|
|
impossible("Worn-slot insanity: %s.", whybuf);
|
|
} /* o != NULL */
|
|
|
|
/* check whether any item other than the one in the slot pointer
|
|
claims to be worn/wielded in this slot; make this test whether
|
|
'o' is Null or not; [sanity_check_worn(mkobj.c) for object by
|
|
object checking will most likely have already caught this] */
|
|
for (otmp = gi.invent; otmp; otmp = otmp->nobj) {
|
|
if (otmp != o && (otmp->owornmask & m) != 0L
|
|
/* embedded scales owornmask is W_ARM|I_SPECIAL so would
|
|
give a false complaint about item other than uarm having
|
|
W_ARM bit set if we didn't screen it out here */
|
|
&& (m != W_ARM || otmp != uskin
|
|
|| (otmp->owornmask & I_SPECIAL) == 0L)) {
|
|
Sprintf(whybuf, "%s [0x%08lx] has %s mask 0x%08lx bit set",
|
|
simpleonames(otmp), otmp->owornmask, wp->w_what, m);
|
|
impossible("Worn-slot insanity: %s.", whybuf);
|
|
}
|
|
}
|
|
} /* for wp in worn[] */
|
|
|
|
#ifdef EXTRA_SANITY_CHECKS
|
|
if (uskin) {
|
|
const char *what = "embedded scales";
|
|
|
|
o = uskin;
|
|
m = W_ARM | I_SPECIAL;
|
|
whybuf[0] = '\0';
|
|
for (otmp = gi.invent; otmp; otmp = otmp->nobj)
|
|
if (otmp == o)
|
|
break;
|
|
if (!otmp)
|
|
Sprintf(whybuf, "%s (%s) not found in invent",
|
|
what, fmt_ptr(o));
|
|
else if ((o->owornmask & m) != m)
|
|
Sprintf(whybuf, "%s bits not set in owornmask [0x%08lx]",
|
|
what, o->owornmask);
|
|
else if ((o->owornmask & ~(m | IGNORE_SLOTS)) != 0L)
|
|
Sprintf(whybuf, "%s wrong bit set in owornmask [0x%08lx]",
|
|
what, o->owornmask);
|
|
else if (!Is_dragon_scales(o))
|
|
Sprintf(whybuf, "%s (%s) %s not dragon scales",
|
|
what, simpleonames(o), otense(o, "are"));
|
|
else if (Dragon_scales_to_pm(o) != &mons[u.umonnum])
|
|
Sprintf(whybuf, "%s, hero is not %s",
|
|
what, an(mons[u.umonnum].pmnames[NEUTRAL]));
|
|
if (whybuf[0])
|
|
impossible("Worn-slot insanity: %s.", whybuf);
|
|
} /* uskin */
|
|
#endif /* EXTRA_SANITY_CHECKS */
|
|
|
|
#ifdef EXTRA_SANITY_CHECKS
|
|
/* dual wielding: not a slot but lots of things to verify */
|
|
if (u.twoweap) {
|
|
const char *why = NULL;
|
|
|
|
if (!uwep || !uswapwep) {
|
|
Sprintf(whybuf, "without %s%s%s",
|
|
!uwep ? "uwep" : "",
|
|
(!uwep && !uswapwep) ? " and without " : "",
|
|
!uswapwep ? "uswapwep" : "");
|
|
why = whybuf;
|
|
} else if (uarms)
|
|
why = "while wearing shield";
|
|
else if (uwep->oclass != WEAPON_CLASS && !is_weptool(uwep))
|
|
why = "uwep is not a weapon";
|
|
else if (is_launcher(uwep) || is_ammo(uwep) || is_missile(uwep))
|
|
why = "uwep is not a melee weapon";
|
|
else if (bimanual(uwep))
|
|
why = "uwep is two-handed";
|
|
else if (uswapwep->oclass != WEAPON_CLASS && !is_weptool(uswapwep))
|
|
why = "uswapwep is not a weapon";
|
|
else if (is_launcher(uswapwep) || is_ammo(uswapwep)
|
|
|| is_missile(uswapwep))
|
|
why = "uswapwep is not a melee weapon";
|
|
else if (bimanual(uswapwep))
|
|
why = "uswapwep is two-handed";
|
|
else if (!could_twoweap(gy.youmonst.data))
|
|
why = "without two weapon attacks";
|
|
|
|
if (why)
|
|
impossible("Two-weapon insanity: %s.", why);
|
|
}
|
|
#endif /* EXTRA_SANITY_CHECKS */
|
|
return;
|
|
#undef IGNORE_SLOTS
|
|
} /* check_wornmask_slots() */
|
|
|
|
void
|
|
mon_set_minvis(struct monst *mon)
|
|
{
|
|
mon->perminvis = 1;
|
|
if (!mon->invis_blkd) {
|
|
mon->minvis = 1;
|
|
newsym(mon->mx, mon->my); /* make it disappear */
|
|
if (mon->wormno)
|
|
see_wsegs(mon); /* and any tail too */
|
|
}
|
|
}
|
|
|
|
void
|
|
mon_adjust_speed(
|
|
struct monst *mon,
|
|
int adjust, /* positive => increase speed, negative => decrease */
|
|
struct obj *obj) /* item to make known if effect can be seen */
|
|
{
|
|
struct obj *otmp;
|
|
boolean give_msg = !gi.in_mklev, petrify = FALSE;
|
|
unsigned int oldspeed = mon->mspeed;
|
|
|
|
switch (adjust) {
|
|
case 2:
|
|
mon->permspeed = MFAST;
|
|
give_msg = FALSE; /* special case monster creation */
|
|
break;
|
|
case 1:
|
|
if (mon->permspeed == MSLOW)
|
|
mon->permspeed = 0;
|
|
else
|
|
mon->permspeed = MFAST;
|
|
break;
|
|
case 0: /* just check for worn speed boots */
|
|
break;
|
|
case -1:
|
|
if (mon->permspeed == MFAST)
|
|
mon->permspeed = 0;
|
|
else
|
|
mon->permspeed = MSLOW;
|
|
break;
|
|
case -2:
|
|
mon->permspeed = MSLOW;
|
|
give_msg = FALSE; /* (not currently used) */
|
|
break;
|
|
case -3: /* petrification */
|
|
/* take away intrinsic speed but don't reduce normal speed */
|
|
if (mon->permspeed == MFAST)
|
|
mon->permspeed = 0;
|
|
petrify = TRUE;
|
|
break;
|
|
case -4: /* green slime */
|
|
if (mon->permspeed == MFAST)
|
|
mon->permspeed = 0;
|
|
give_msg = FALSE;
|
|
break;
|
|
}
|
|
|
|
for (otmp = mon->minvent; otmp; otmp = otmp->nobj)
|
|
if (otmp->owornmask && objects[otmp->otyp].oc_oprop == FAST)
|
|
break;
|
|
if (otmp) /* speed boots */
|
|
mon->mspeed = MFAST;
|
|
else
|
|
mon->mspeed = mon->permspeed;
|
|
|
|
/* no message if monster is immobile (temp or perm) or unseen */
|
|
if (give_msg && (mon->mspeed != oldspeed || petrify) && mon->data->mmove
|
|
&& !(mon->mfrozen || mon->msleeping) && canseemon(mon)) {
|
|
/* fast to slow (skipping intermediate state) or vice versa */
|
|
const char *howmuch =
|
|
(mon->mspeed + oldspeed == MFAST + MSLOW) ? "much " : "";
|
|
|
|
if (petrify) {
|
|
/* mimic the player's petrification countdown; "slowing down"
|
|
even if fast movement rate retained via worn speed boots */
|
|
if (flags.verbose)
|
|
pline("%s is slowing down.", Monnam(mon));
|
|
} else if (adjust > 0 || mon->mspeed == MFAST)
|
|
pline("%s is suddenly moving %sfaster.", Monnam(mon), howmuch);
|
|
else
|
|
pline("%s seems to be moving %sslower.", Monnam(mon), howmuch);
|
|
|
|
/* might discover an object if we see the speed change happen */
|
|
if (obj != 0)
|
|
learnwand(obj);
|
|
}
|
|
}
|
|
|
|
/* alchemy smock confers two properties, poison and acid resistance
|
|
but objects[ALCHEMY_SMOCK].oc_oprop can only describe one of them;
|
|
if it is poison resistance, alternate property is acid resistance;
|
|
if someone changes it to acid resistance, alt becomes poison resist;
|
|
if someone changes it to hallucination resistance, all bets are off
|
|
[TODO: handle alternate properties conferred by dragon scales/mail] */
|
|
#define altprop(o) \
|
|
(((o)->otyp == ALCHEMY_SMOCK) \
|
|
? (POISON_RES + ACID_RES - objects[(o)->otyp].oc_oprop) \
|
|
: 0)
|
|
|
|
/* armor put on or taken off; might be magical variety */
|
|
void
|
|
update_mon_extrinsics(
|
|
struct monst *mon,
|
|
struct obj *obj, /* armor being worn or taken off */
|
|
boolean on,
|
|
boolean silently)
|
|
{
|
|
int unseen;
|
|
uchar mask;
|
|
struct obj *otmp;
|
|
int which = (int) objects[obj->otyp].oc_oprop,
|
|
altwhich = altprop(obj);
|
|
|
|
unseen = !canseemon(mon);
|
|
if (!which && !altwhich)
|
|
goto maybe_blocks;
|
|
|
|
again:
|
|
if (on) {
|
|
switch (which) {
|
|
case INVIS:
|
|
mon->minvis = !mon->invis_blkd;
|
|
break;
|
|
case FAST: {
|
|
boolean save_in_mklev = gi.in_mklev;
|
|
if (silently)
|
|
gi.in_mklev = TRUE;
|
|
mon_adjust_speed(mon, 0, obj);
|
|
gi.in_mklev = save_in_mklev;
|
|
break;
|
|
}
|
|
/* properties handled elsewhere */
|
|
case ANTIMAGIC:
|
|
case REFLECTING:
|
|
case PROTECTION:
|
|
break;
|
|
/* properties which have no effect for monsters */
|
|
case CLAIRVOYANT:
|
|
case STEALTH:
|
|
case TELEPAT:
|
|
break;
|
|
/* properties which should have an effect but aren't implemented */
|
|
case LEVITATION:
|
|
case FLYING:
|
|
case WWALKING:
|
|
break;
|
|
/* properties which maybe should have an effect but don't */
|
|
case DISPLACED:
|
|
case FUMBLING:
|
|
case JUMPING:
|
|
break;
|
|
default:
|
|
/* 1 through 8 correspond to MR_xxx mask values */
|
|
if (which >= 1 && which <= 8) {
|
|
/* FIRE,COLD,SLEEP,DISINT,SHOCK,POISON,ACID,STONE */
|
|
mask = (uchar) (1 << (which - 1));
|
|
mon->mextrinsics |= (unsigned short) mask;
|
|
}
|
|
break;
|
|
}
|
|
} else { /* off */
|
|
switch (which) {
|
|
case INVIS:
|
|
mon->minvis = mon->perminvis;
|
|
break;
|
|
case FAST: {
|
|
boolean save_in_mklev = gi.in_mklev;
|
|
if (silently)
|
|
gi.in_mklev = TRUE;
|
|
mon_adjust_speed(mon, 0, obj);
|
|
gi.in_mklev = save_in_mklev;
|
|
break;
|
|
}
|
|
case FIRE_RES:
|
|
case COLD_RES:
|
|
case SLEEP_RES:
|
|
case DISINT_RES:
|
|
case SHOCK_RES:
|
|
case POISON_RES:
|
|
case ACID_RES:
|
|
case STONE_RES:
|
|
/*
|
|
* Update monster's extrinsics (for worn objects only;
|
|
* 'obj' itself might still be worn or already unworn).
|
|
*
|
|
* If an alchemy smock is being taken off, this code will
|
|
* be run twice (via 'goto again') and other worn gear
|
|
* gets tested for conferring poison resistance on the
|
|
* first pass and acid resistance on the second.
|
|
*
|
|
* If some other item is being taken off, there will be
|
|
* only one pass but a worn alchemy smock will be an
|
|
* alternate source for either of those two resistances.
|
|
*/
|
|
mask = (uchar) (1 << (which - 1));
|
|
for (otmp = mon->minvent; otmp; otmp = otmp->nobj) {
|
|
if (otmp == obj || !otmp->owornmask)
|
|
continue;
|
|
if ((int) objects[otmp->otyp].oc_oprop == which)
|
|
break;
|
|
/* check whether 'otmp' confers target property as an extra
|
|
one rather than as the one specified for it in objects[] */
|
|
if (altprop(otmp) == which)
|
|
break;
|
|
}
|
|
if (!otmp)
|
|
mon->mextrinsics &= ~((unsigned short) mask);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* worn alchemy smock/apron confers both poison resistance and acid
|
|
resistance to the hero so do likewise for monster who wears one */
|
|
if (altwhich && which != altwhich) {
|
|
which = altwhich;
|
|
goto again;
|
|
}
|
|
|
|
maybe_blocks:
|
|
/* obj->owornmask has been cleared by this point, so we can't use it.
|
|
However, since monsters don't wield armor, we don't have to guard
|
|
against that and can get away with a blanket worn-mask value. */
|
|
switch (w_blocks(obj, ~0L)) {
|
|
case INVIS:
|
|
mon->invis_blkd = on ? 1 : 0;
|
|
mon->minvis = on ? 0 : mon->perminvis;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!on && mon == u.usteed && obj->otyp == SADDLE)
|
|
dismount_steed(DISMOUNT_FELL);
|
|
|
|
/* if couldn't see it but now can, or vice versa, update display */
|
|
if (!silently && (unseen ^ !canseemon(mon)))
|
|
newsym(mon->mx, mon->my);
|
|
}
|
|
|
|
#undef altprop
|
|
|
|
int
|
|
find_mac(struct monst *mon)
|
|
{
|
|
struct obj *obj;
|
|
int base = mon->data->ac;
|
|
long mwflags = mon->misc_worn_check;
|
|
|
|
for (obj = mon->minvent; obj; obj = obj->nobj) {
|
|
if (obj->owornmask & mwflags) {
|
|
if (obj->otyp == AMULET_OF_GUARDING)
|
|
base -= 2; /* fixed amount, not impacted by erosion */
|
|
else
|
|
base -= ARM_BONUS(obj);
|
|
/* since ARM_BONUS is positive, subtracting it increases AC */
|
|
}
|
|
}
|
|
/* same cap as for hero [find_ac(do_wear.c)] */
|
|
if (abs(base) > AC_MAX)
|
|
base = sgn(base) * AC_MAX;
|
|
return base;
|
|
}
|
|
|
|
/*
|
|
* weapons are handled separately;
|
|
* rings and eyewear aren't used by monsters
|
|
*/
|
|
|
|
/* Wear the best object of each type that the monster has. During creation,
|
|
* the monster can put everything on at once; otherwise, wearing takes time.
|
|
* This doesn't affect monster searching for objects--a monster may very well
|
|
* search for objects it would not want to wear, because we don't want to
|
|
* check which_armor() each round.
|
|
*
|
|
* We'll let monsters put on shirts and/or suits under worn cloaks, but
|
|
* not shirts under worn suits. This is somewhat arbitrary, but it's
|
|
* too tedious to have them remove and later replace outer garments,
|
|
* and preventing suits under cloaks makes it a little bit too easy for
|
|
* players to influence what gets worn. Putting on a shirt underneath
|
|
* already worn body armor is too obviously buggy...
|
|
*/
|
|
void
|
|
m_dowear(struct monst *mon, boolean creation)
|
|
{
|
|
boolean can_wear_armor;
|
|
|
|
#define RACE_EXCEPTION TRUE
|
|
/* Note the restrictions here are the same as in dowear in do_wear.c
|
|
* except for the additional restriction on intelligence. (Players
|
|
* are always intelligent, even if polymorphed).
|
|
*/
|
|
if (verysmall(mon->data) || nohands(mon->data) || is_animal(mon->data))
|
|
return;
|
|
/* give mummies a chance to wear their wrappings
|
|
* and let skeletons wear their initial armor */
|
|
if (mindless(mon->data)
|
|
&& (!creation || (mon->data->mlet != S_MUMMY
|
|
&& mon->data != &mons[PM_SKELETON])))
|
|
return;
|
|
|
|
m_dowear_type(mon, W_AMUL, creation, FALSE);
|
|
can_wear_armor = !cantweararm(mon->data); /* for suit, cloak, shirt */
|
|
/* can't put on shirt if already wearing suit */
|
|
if (can_wear_armor && !(mon->misc_worn_check & W_ARM))
|
|
m_dowear_type(mon, W_ARMU, creation, FALSE);
|
|
/* WrappingAllowed() makes any size between small and huge eligible;
|
|
treating small as a special case allows hobbits, gnomes, and
|
|
kobolds to wear all cloaks; large and huge allows giants and such
|
|
to wear mummy wrappings but not other cloaks */
|
|
if (can_wear_armor || WrappingAllowed(mon->data))
|
|
m_dowear_type(mon, W_ARMC, creation, FALSE);
|
|
m_dowear_type(mon, W_ARMH, creation, FALSE);
|
|
if (!MON_WEP(mon) || !bimanual(MON_WEP(mon)))
|
|
m_dowear_type(mon, W_ARMS, creation, FALSE);
|
|
m_dowear_type(mon, W_ARMG, creation, FALSE);
|
|
if (!slithy(mon->data) && mon->data->mlet != S_CENTAUR)
|
|
m_dowear_type(mon, W_ARMF, creation, FALSE);
|
|
if (can_wear_armor)
|
|
m_dowear_type(mon, W_ARM, creation, FALSE);
|
|
else
|
|
m_dowear_type(mon, W_ARM, creation, RACE_EXCEPTION);
|
|
}
|
|
|
|
staticfn void
|
|
m_dowear_type(
|
|
struct monst *mon,
|
|
long flag, /* wornmask value */
|
|
boolean creation,
|
|
boolean racialexception) /* small monsters that are allowed for player
|
|
* races (gnomes) can wear suits */
|
|
{
|
|
struct obj *old, *best, *obj;
|
|
long oldmask = 0L;
|
|
int m_delay = 0;
|
|
int sawmon = canseemon(mon), sawloc = cansee(mon->mx, mon->my);
|
|
boolean autocurse;
|
|
char nambuf[BUFSZ];
|
|
|
|
if (mon->mfrozen)
|
|
return; /* probably putting previous item on */
|
|
|
|
/* Get a copy of monster's name before altering its visibility */
|
|
Strcpy(nambuf, See_invisible ? Monnam(mon) : mon_nam(mon));
|
|
|
|
old = which_armor(mon, flag);
|
|
if (old && old->cursed)
|
|
return;
|
|
if (old && flag == W_AMUL && old->otyp != AMULET_OF_GUARDING)
|
|
return; /* no amulet better than life-saving or reflection */
|
|
best = old;
|
|
|
|
for (obj = mon->minvent; obj; obj = obj->nobj) {
|
|
switch (flag) {
|
|
case W_AMUL:
|
|
if (obj->oclass != AMULET_CLASS
|
|
|| (obj->otyp != AMULET_OF_LIFE_SAVING
|
|
&& obj->otyp != AMULET_OF_REFLECTION
|
|
&& obj->otyp != AMULET_OF_GUARDING))
|
|
continue;
|
|
/* for 'best' to be non-Null, it must be an amulet of guarding;
|
|
life-saving and reflection don't get here due to early return
|
|
and other amulets of guarding can't be any better */
|
|
if (!best || obj->otyp != AMULET_OF_GUARDING) {
|
|
best = obj;
|
|
if (best->otyp != AMULET_OF_GUARDING)
|
|
goto outer_break; /* life-saving or reflection; use it */
|
|
}
|
|
continue; /* skip post-switch armor handling */
|
|
case W_ARMU:
|
|
if (!is_shirt(obj))
|
|
continue;
|
|
break;
|
|
case W_ARMC:
|
|
if (!is_cloak(obj))
|
|
continue;
|
|
/* mummy wrapping is only cloak allowed when bigger than human */
|
|
if (mon->data->msize > MZ_HUMAN && obj->otyp != MUMMY_WRAPPING)
|
|
continue;
|
|
/* avoid mummy wrapping if it will allow hero to see mon (unless
|
|
this is a new mummy; an invisible one is feasible via ^G) */
|
|
if (mon->minvis && w_blocks(obj, W_ARMC) == INVIS
|
|
&& !See_invisible && !creation)
|
|
continue;
|
|
break;
|
|
case W_ARMH:
|
|
if (!is_helmet(obj))
|
|
continue;
|
|
/* changing alignment is not implemented for monsters;
|
|
priests and minions could change alignment but wouldn't
|
|
want to, so they reject helms of opposite alignment */
|
|
if (obj->otyp == HELM_OF_OPPOSITE_ALIGNMENT
|
|
&& (mon->ispriest || mon->isminion))
|
|
continue;
|
|
/* (flimsy exception matches polyself handling) */
|
|
if (has_horns(mon->data) && !is_flimsy(obj))
|
|
continue;
|
|
break;
|
|
case W_ARMS:
|
|
if (!is_shield(obj))
|
|
continue;
|
|
break;
|
|
case W_ARMG:
|
|
if (!is_gloves(obj))
|
|
continue;
|
|
break;
|
|
case W_ARMF:
|
|
if (!is_boots(obj))
|
|
continue;
|
|
break;
|
|
case W_ARM:
|
|
if (!is_suit(obj))
|
|
continue;
|
|
if (racialexception && (racial_exception(mon, obj) < 1))
|
|
continue;
|
|
break;
|
|
}
|
|
if (obj->owornmask)
|
|
continue;
|
|
/* I'd like to define a VISIBLE_ARM_BONUS which doesn't assume the
|
|
* monster knows obj->spe, but if I did that, a monster would keep
|
|
* switching forever between two -2 caps since when it took off one
|
|
* it would forget spe and once again think the object is better
|
|
* than what it already has.
|
|
*/
|
|
if (best && (ARM_BONUS(best) + extra_pref(mon, best)
|
|
>= ARM_BONUS(obj) + extra_pref(mon, obj)))
|
|
continue;
|
|
best = obj;
|
|
}
|
|
outer_break:
|
|
if (!best || best == old)
|
|
return;
|
|
|
|
/* same auto-cursing behavior as for hero */
|
|
autocurse = ((best->otyp == HELM_OF_OPPOSITE_ALIGNMENT
|
|
|| best->otyp == DUNCE_CAP) && !best->cursed);
|
|
/* if wearing a cloak, account for the time spent removing
|
|
and re-wearing it when putting on a suit or shirt */
|
|
if ((flag == W_ARM || flag == W_ARMU) && (mon->misc_worn_check & W_ARMC))
|
|
m_delay += 2;
|
|
/* when upgrading a piece of armor, account for time spent
|
|
taking off current one */
|
|
if (old) {
|
|
m_delay += objects[old->otyp].oc_delay;
|
|
|
|
oldmask = old->owornmask; /* needed later by artifact_light() */
|
|
old->owornmask = 0L; /* avoid doname() showing "(being worn)" */
|
|
}
|
|
|
|
if (!creation) {
|
|
if (sawmon) {
|
|
char buf[BUFSZ];
|
|
|
|
if (old)
|
|
Sprintf(buf, " removes %s and", distant_name(old, doname));
|
|
else
|
|
buf[0] = '\0';
|
|
pline("%s%s puts on %s.", Monnam(mon), buf,
|
|
distant_name(best, doname));
|
|
if (autocurse)
|
|
pline("%s %s %s %s for a moment.", s_suffix(Monnam(mon)),
|
|
simpleonames(best), otense(best, "glow"),
|
|
hcolor(NH_BLACK));
|
|
} /* can see it */
|
|
m_delay += objects[best->otyp].oc_delay;
|
|
mon->mfrozen = m_delay;
|
|
if (mon->mfrozen)
|
|
mon->mcanmove = 0;
|
|
}
|
|
if (old) {
|
|
update_mon_extrinsics(mon, old, FALSE, creation);
|
|
|
|
/* owornmask was cleared above but artifact_light() expects it */
|
|
old->owornmask = oldmask;
|
|
if (old->lamplit && artifact_light(old))
|
|
end_burn(old, FALSE);
|
|
old->owornmask = 0L;
|
|
}
|
|
mon->misc_worn_check |= flag;
|
|
best->owornmask |= flag;
|
|
if (autocurse)
|
|
curse(best);
|
|
if (artifact_light(best) && !best->lamplit) {
|
|
begin_burn(best, FALSE);
|
|
vision_recalc(1);
|
|
if (!creation && best->lamplit && cansee(mon->mx, mon->my)) {
|
|
const char *adesc = arti_light_description(best);
|
|
|
|
if (sawmon) /* could already see monster */
|
|
pline("%s %s to shine %s.", Yname2(best),
|
|
otense(best, "begin"), adesc);
|
|
else if (canseemon(mon)) /* didn't see it until new light */
|
|
pline("%s %s shining %s.", Yname2(best),
|
|
otense(best, "are"), adesc);
|
|
else if (sawloc) /* saw location but not invisible monster */
|
|
pline("%s begins to shine %s.", Something, adesc);
|
|
else /* didn't see location until new light */
|
|
pline("%s is shining %s.", Something, adesc);
|
|
}
|
|
}
|
|
update_mon_extrinsics(mon, best, TRUE, creation);
|
|
/* if couldn't see it but now can, or vice versa, */
|
|
if (!creation && (sawmon ^ canseemon(mon))) {
|
|
if (mon->minvis && !See_invisible) {
|
|
pline("Suddenly you cannot see %s.", nambuf);
|
|
makeknown(best->otyp);
|
|
/* } else if (!mon->minvis) {
|
|
* pline("%s suddenly appears!", Amonnam(mon)); */
|
|
}
|
|
}
|
|
}
|
|
#undef RACE_EXCEPTION
|
|
|
|
struct obj *
|
|
which_armor(struct monst *mon, long flag)
|
|
{
|
|
if (mon == &gy.youmonst) {
|
|
switch (flag) {
|
|
case W_ARM:
|
|
return uarm;
|
|
case W_ARMC:
|
|
return uarmc;
|
|
case W_ARMH:
|
|
return uarmh;
|
|
case W_ARMS:
|
|
return uarms;
|
|
case W_ARMG:
|
|
return uarmg;
|
|
case W_ARMF:
|
|
return uarmf;
|
|
case W_ARMU:
|
|
return uarmu;
|
|
default:
|
|
impossible("bad flag in which_armor");
|
|
return 0;
|
|
}
|
|
} else {
|
|
struct obj *obj;
|
|
|
|
for (obj = mon->minvent; obj; obj = obj->nobj)
|
|
if (obj->owornmask & flag)
|
|
return obj;
|
|
return (struct obj *) 0;
|
|
}
|
|
}
|
|
|
|
/* remove an item of armor and then drop it */
|
|
staticfn void
|
|
m_lose_armor(
|
|
struct monst *mon,
|
|
struct obj *obj,
|
|
boolean polyspot)
|
|
{
|
|
extract_from_minvent(mon, obj, TRUE, FALSE);
|
|
place_object(obj, mon->mx, mon->my);
|
|
if (polyspot)
|
|
bypass_obj(obj);
|
|
/* call stackobj() if we ever drop anything that can merge */
|
|
newsym(mon->mx, mon->my);
|
|
}
|
|
|
|
/* clear bypass bits for an object chain, plus contents if applicable */
|
|
staticfn void
|
|
clear_bypass(struct obj *objchn)
|
|
{
|
|
struct obj *o;
|
|
|
|
for (o = objchn; o; o = o->nobj) {
|
|
o->bypass = 0;
|
|
if (Has_contents(o))
|
|
clear_bypass(o->cobj);
|
|
}
|
|
}
|
|
|
|
/* all objects with their bypass bit set should now be reset to normal;
|
|
this can be a relatively expensive operation so is only called if
|
|
gc.context.bypasses is set */
|
|
void
|
|
clear_bypasses(void)
|
|
{
|
|
struct monst *mtmp;
|
|
|
|
/*
|
|
* 'Object' bypass is also used for one monster function:
|
|
* polymorph control of long worms. Activated via setting
|
|
* gc.context.bypasses even if no specific object has been
|
|
* bypassed.
|
|
*/
|
|
|
|
clear_bypass(fobj);
|
|
clear_bypass(gi.invent);
|
|
clear_bypass(gm.migrating_objs);
|
|
clear_bypass(gl.level.buriedobjlist);
|
|
clear_bypass(gb.billobjs);
|
|
clear_bypass(go.objs_deleted);
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
clear_bypass(mtmp->minvent);
|
|
/* long worm created by polymorph has mon->mextra->mcorpsenm set
|
|
to PM_LONG_WORM to flag it as not being subject to further
|
|
polymorph (so polymorph zap won't hit monster to transform it
|
|
into a long worm, then hit that worm's tail and transform it
|
|
again on same zap); clearing mcorpsenm reverts worm to normal */
|
|
if (mtmp->data == &mons[PM_LONG_WORM] && has_mcorpsenm(mtmp))
|
|
MCORPSENM(mtmp) = NON_PM;
|
|
}
|
|
for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) {
|
|
clear_bypass(mtmp->minvent);
|
|
/* no MCORPSENM(mtmp)==PM_LONG_WORM check here; long worms can't
|
|
be just created by polymorph and migrating at the same time */
|
|
}
|
|
/* this is a no-op since mydogs is only non-Null during level change or
|
|
final ascension and we aren't called at those times, but be thorough */
|
|
for (mtmp = gm.mydogs; mtmp; mtmp = mtmp->nmon)
|
|
clear_bypass(mtmp->minvent);
|
|
/* ball and chain can be "floating", not on any object chain (when
|
|
hero is swallowed by an engulfing monster, for instance) */
|
|
if (uball)
|
|
uball->bypass = 0;
|
|
if (uchain)
|
|
uchain->bypass = 0;
|
|
|
|
gc.context.bypasses = FALSE;
|
|
}
|
|
|
|
void
|
|
bypass_obj(struct obj *obj)
|
|
{
|
|
obj->bypass = 1;
|
|
gc.context.bypasses = TRUE;
|
|
}
|
|
|
|
/* set or clear the bypass bit in a list of objects */
|
|
void
|
|
bypass_objlist(
|
|
struct obj *objchain,
|
|
boolean on) /* TRUE => set, FALSE => clear */
|
|
{
|
|
if (on && objchain)
|
|
gc.context.bypasses = TRUE;
|
|
while (objchain) {
|
|
objchain->bypass = on ? 1 : 0;
|
|
objchain = objchain->nobj;
|
|
}
|
|
}
|
|
|
|
/* return the first object without its bypass bit set; set that bit
|
|
before returning so that successive calls will find further objects */
|
|
struct obj *
|
|
nxt_unbypassed_obj(struct obj *objchain)
|
|
{
|
|
while (objchain) {
|
|
if (!objchain->bypass) {
|
|
bypass_obj(objchain);
|
|
break;
|
|
}
|
|
objchain = objchain->nobj;
|
|
}
|
|
return objchain;
|
|
}
|
|
|
|
/* like nxt_unbypassed_obj() but operates on sortloot_item array rather
|
|
than an object linked list; the array contains obj==Null terminator;
|
|
there's an added complication that the array may have stale pointers
|
|
for deleted objects (see Multiple-Drop case in askchain(invent.c)) */
|
|
struct obj *
|
|
nxt_unbypassed_loot(Loot *lootarray, struct obj *listhead)
|
|
{
|
|
struct obj *o, *obj;
|
|
|
|
while ((obj = lootarray->obj) != 0) {
|
|
for (o = listhead; o; o = o->nobj)
|
|
if (o == obj)
|
|
break;
|
|
if (o && !obj->bypass) {
|
|
bypass_obj(obj);
|
|
break;
|
|
}
|
|
++lootarray;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
void
|
|
mon_break_armor(struct monst *mon, boolean polyspot)
|
|
{
|
|
struct obj *otmp;
|
|
struct permonst *mdat = mon->data;
|
|
boolean vis = cansee(mon->mx, mon->my),
|
|
handless_or_tiny = (nohands(mdat) || verysmall(mdat)),
|
|
noride = FALSE;
|
|
const char *pronoun = mhim(mon), *ppronoun = mhis(mon);
|
|
|
|
if (breakarm(mdat)) {
|
|
if ((otmp = which_armor(mon, W_ARM)) != 0) {
|
|
if ((Is_dragon_scales(otmp) && mdat == Dragon_scales_to_pm(otmp))
|
|
|| (Is_dragon_mail(otmp) && mdat == Dragon_mail_to_pm(otmp))) {
|
|
; /* no message here;
|
|
"the dragon merges with his scaly armor" is odd
|
|
and the monster's previous form is already gone */
|
|
} else {
|
|
Soundeffect(se_cracking_sound, 100);
|
|
if (vis)
|
|
pline("%s breaks out of %s armor!", Monnam(mon), ppronoun);
|
|
else
|
|
You_hear("a cracking sound.");
|
|
}
|
|
m_useup(mon, otmp);
|
|
}
|
|
if ((otmp = which_armor(mon, W_ARMC)) != 0
|
|
/* mummy wrapping adapts to small and very big sizes */
|
|
&& (otmp->otyp != MUMMY_WRAPPING || !WrappingAllowed(mdat))) {
|
|
if (otmp->oartifact) {
|
|
if (vis)
|
|
pline("%s %s falls off!", s_suffix(Monnam(mon)),
|
|
cloak_simple_name(otmp));
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
} else {
|
|
Soundeffect(se_ripping_sound, 100);
|
|
if (vis)
|
|
pline("%s %s tears apart!", s_suffix(Monnam(mon)),
|
|
cloak_simple_name(otmp));
|
|
else
|
|
You_hear("a ripping sound.");
|
|
m_useup(mon, otmp);
|
|
}
|
|
}
|
|
if ((otmp = which_armor(mon, W_ARMU)) != 0) {
|
|
if (vis)
|
|
pline("%s shirt rips to shreds!", s_suffix(Monnam(mon)));
|
|
else
|
|
You_hear("a ripping sound.");
|
|
m_useup(mon, otmp);
|
|
}
|
|
} else if (sliparm(mdat)) {
|
|
/* sliparm checks whirly, noncorporeal, and small or under */
|
|
boolean passes_thru_clothes = !(mdat->msize <= MZ_SMALL);
|
|
|
|
if ((otmp = which_armor(mon, W_ARM)) != 0) {
|
|
Soundeffect(se_thud, 50);
|
|
if (vis)
|
|
pline("%s armor falls around %s!", s_suffix(Monnam(mon)),
|
|
pronoun);
|
|
else
|
|
You_hear("a thud.");
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
}
|
|
if ((otmp = which_armor(mon, W_ARMC)) != 0
|
|
/* mummy wrapping adapts to small and very big sizes */
|
|
&& (otmp->otyp != MUMMY_WRAPPING || !WrappingAllowed(mdat))) {
|
|
if (vis) {
|
|
if (is_whirly(mon->data))
|
|
pline("%s %s falls, unsupported!", s_suffix(Monnam(mon)),
|
|
cloak_simple_name(otmp));
|
|
else
|
|
pline("%s shrinks out of %s %s!", Monnam(mon), ppronoun,
|
|
cloak_simple_name(otmp));
|
|
}
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
}
|
|
if ((otmp = which_armor(mon, W_ARMU)) != 0) {
|
|
if (vis) {
|
|
if (passes_thru_clothes)
|
|
pline("%s seeps right through %s shirt!", Monnam(mon),
|
|
ppronoun);
|
|
else
|
|
pline("%s becomes much too small for %s shirt!",
|
|
Monnam(mon), ppronoun);
|
|
}
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
}
|
|
}
|
|
if (handless_or_tiny) {
|
|
/* [caller needs to handle weapon checks] */
|
|
if ((otmp = which_armor(mon, W_ARMG)) != 0) {
|
|
if (vis)
|
|
pline("%s drops %s gloves%s!", Monnam(mon), ppronoun,
|
|
MON_WEP(mon) ? " and weapon" : "");
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
}
|
|
if ((otmp = which_armor(mon, W_ARMS)) != 0) {
|
|
Soundeffect(se_clank, 50);
|
|
if (vis)
|
|
pline("%s can no longer hold %s shield!", Monnam(mon),
|
|
ppronoun);
|
|
else
|
|
You_hear("a clank.");
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
}
|
|
}
|
|
if (handless_or_tiny || has_horns(mdat)) {
|
|
if ((otmp = which_armor(mon, W_ARMH)) != 0
|
|
/* flimsy test for horns matches polyself handling */
|
|
&& (handless_or_tiny || !is_flimsy(otmp))) {
|
|
if (vis)
|
|
pline("%s helmet falls to the %s!", s_suffix(Monnam(mon)),
|
|
surface(mon->mx, mon->my));
|
|
else
|
|
You_hear("a clank.");
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
}
|
|
}
|
|
if (handless_or_tiny || slithy(mdat) || mdat->mlet == S_CENTAUR) {
|
|
if ((otmp = which_armor(mon, W_ARMF)) != 0) {
|
|
if (vis) {
|
|
if (is_whirly(mon->data))
|
|
pline("%s boots fall away!", s_suffix(Monnam(mon)));
|
|
else
|
|
pline("%s boots %s off %s feet!", s_suffix(Monnam(mon)),
|
|
verysmall(mdat) ? "slide" : "are pushed", ppronoun);
|
|
}
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
}
|
|
}
|
|
if (!can_saddle(mon)) {
|
|
if ((otmp = which_armor(mon, W_SADDLE)) != 0) {
|
|
m_lose_armor(mon, otmp, polyspot);
|
|
if (vis)
|
|
pline("%s saddle falls off.", s_suffix(Monnam(mon)));
|
|
}
|
|
if (mon == u.usteed)
|
|
noride = TRUE;
|
|
}
|
|
if (noride || (mon == u.usteed && !can_ride(mon))) {
|
|
You("can no longer ride %s.", mon_nam(mon));
|
|
if (touch_petrifies(u.usteed->data) && !Stone_resistance && rnl(3)) {
|
|
char buf[BUFSZ];
|
|
|
|
You("touch %s.", mon_nam(u.usteed));
|
|
Sprintf(buf, "falling off %s",
|
|
an(pmname(u.usteed->data, Mgender(u.usteed))));
|
|
instapetrify(buf);
|
|
}
|
|
dismount_steed(DISMOUNT_FELL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* bias a monster's preferences towards armor that has special benefits. */
|
|
staticfn int
|
|
extra_pref(struct monst *mon, struct obj *obj)
|
|
{
|
|
/* currently only does speed boots, but might be expanded if monsters
|
|
* get to use more armor abilities
|
|
*/
|
|
if (obj) {
|
|
if (obj->otyp == SPEED_BOOTS && mon->permspeed != MFAST)
|
|
return 20;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Exceptions to things based on race.
|
|
* Correctly checks polymorphed player race.
|
|
* Returns:
|
|
* 0 No exception, normal rules apply.
|
|
* 1 If the race/object combination is acceptable.
|
|
* -1 If the race/object combination is unacceptable.
|
|
*/
|
|
int
|
|
racial_exception(struct monst *mon, struct obj *obj)
|
|
{
|
|
const struct permonst *ptr = raceptr(mon);
|
|
|
|
/* Acceptable Exceptions: */
|
|
/* Allow hobbits to wear elven armor - LoTR */
|
|
if (ptr == &mons[PM_HOBBIT] && is_elven_armor(obj))
|
|
return 1;
|
|
/* Unacceptable Exceptions: */
|
|
/* Checks for object that certain races should never use go here */
|
|
/* return -1; */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Remove an object from a monster's inventory. */
|
|
void
|
|
extract_from_minvent(
|
|
struct monst *mon,
|
|
struct obj *obj,
|
|
boolean do_extrinsics, /* whether to call update_mon_extrinsics */
|
|
boolean silently) /* doesn't affect all possible messages,
|
|
* just update_mon_extrinsics's */
|
|
{
|
|
long unwornmask = obj->owornmask;
|
|
|
|
/*
|
|
* At its core this is just obj_extract_self(), but it also handles
|
|
* any updates that need to happen if the gear is equipped or in
|
|
* some other sort of state that needs handling.
|
|
* Note that like obj_extract_self(), this leaves obj free.
|
|
*/
|
|
|
|
if (obj->where != OBJ_MINVENT) {
|
|
impossible("extract_from_minvent called on object not in minvent");
|
|
return;
|
|
}
|
|
/* handle gold dragon scales/scale-mail (lit when worn) before clearing
|
|
obj->owornmask because artifact_light() expects that to be W_ARM */
|
|
if ((unwornmask & W_ARM) != 0 && obj->lamplit && artifact_light(obj))
|
|
end_burn(obj, FALSE);
|
|
|
|
obj_extract_self(obj);
|
|
obj->owornmask = 0L;
|
|
if (unwornmask) {
|
|
if (!DEADMONSTER(mon) && do_extrinsics) {
|
|
update_mon_extrinsics(mon, obj, FALSE, silently);
|
|
}
|
|
mon->misc_worn_check &= ~unwornmask;
|
|
/* give monster a chance to wear other equipment on its next
|
|
move instead of waiting until it picks something up */
|
|
check_gear_next_turn(mon);
|
|
}
|
|
obj_no_longer_held(obj);
|
|
if (unwornmask & W_WEP) {
|
|
mwepgone(mon); /* unwields and sets weapon_check to NEED_WEAPON */
|
|
}
|
|
}
|
|
|
|
#undef w_blocks
|
|
|
|
/*worn.c*/
|