Files
nethack/src/steal.c
Alex Smith fce66245ca Don't attempt to cache encumber_msg result
There was only one point in the code at which this caching was
being done, and it was incorrect: it's possible for the result of
near_capacity to change during a monster turn because monster
actions can change either inventory weight or carry capacity.

The bug was particularly relevant in cases where a character
polymorphed into a slow weak monster gets attacked by a monster
that moves at normal speed: due to the polyform being slow, the
normal-speed monster gets in a lot of attacks and causes a
rehumanization, but due to the polyform being weak, it was
burdened at the start of the monster turn, and so when that
penalty is (due to the bug) applied to the next turn it can
mean that the character misses the next turn too, and may end up
dying as a result.
2025-11-24 02:07:23 +00:00

901 lines
34 KiB
C

/* NetHack 3.7 steal.c $NHDT-Date: 1720895742 2024/07/13 18:35:42 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.132 $ */
/* 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"
staticfn int unstolenarm(void);
staticfn int stealarm(void);
staticfn void worn_item_removal(struct monst *, struct obj *);
/* proportional subset of gold; return value actually fits in an int */
long
somegold(long lmoney)
{
int igold = (lmoney >= (long) LARGEST_INT) ? LARGEST_INT : (int) lmoney;
if (igold < 50)
; /* all gold */
else if (igold < 100)
igold = rn1(igold - 25 + 1, 25);
else if (igold < 500)
igold = rn1(igold - 50 + 1, 50);
else if (igold < 1000)
igold = rn1(igold - 100 + 1, 100);
else if (igold < 5000)
igold = rn1(igold - 500 + 1, 500);
else if (igold < 10000)
igold = rn1(igold - 1000 + 1, 1000);
else
igold = rn1(igold - 5000 + 1, 5000);
return (long) igold;
}
/*
* Find the first (and hopefully only) gold object in a chain.
* Used when leprechaun (or you as leprechaun) looks for
* someone else's gold. Returns a pointer so the gold may
* be seized without further searching.
* May search containers too.
* Deals in gold only, as leprechauns don't care for lesser coins.
*/
struct obj *
findgold(struct obj *argchain)
{
struct obj *chain = argchain; /* allow arg to be nonnull */
while (chain && chain->otyp != GOLD_PIECE)
chain = chain->nobj;
return chain;
}
/*
* Steal gold coins only. Leprechauns don't care for lesser coins.
*/
void
stealgold(struct monst *mtmp)
{
struct obj *fgold = g_at(u.ux, u.uy);
struct obj *ygold;
long tmp;
struct monst *who;
const char *whose, *what;
/* skip lesser coins on the floor */
while (fgold && fgold->otyp != GOLD_PIECE)
fgold = fgold->nexthere;
/* Do you have real gold? */
ygold = findgold(gi.invent);
if (fgold && (!ygold || fgold->quan > ygold->quan || !rn2(5))) {
obj_extract_self(fgold);
add_to_minv(mtmp, fgold);
newsym(u.ux, u.uy);
if (u.usteed) {
who = u.usteed;
whose = s_suffix(y_monnam(who));
what = makeplural(mbodypart(who, FOOT));
} else {
who = &gy.youmonst;
whose = "your";
what = makeplural(body_part(FOOT));
}
/* [ avoid "between your rear regions" :-] */
if (slithy(who->data))
what = "coils";
/* reduce "rear hooves/claws" to "hooves/claws" */
if (!strncmp(what, "rear ", 5))
what += 5;
pline("%s quickly snatches some gold from %s %s %s!", Monnam(mtmp),
(Levitation || Flying) ? "beneath" : "between", whose, what);
if (!ygold || !rn2(5)) {
if (!tele_restrict(mtmp))
(void) rloc(mtmp, RLOC_MSG);
monflee(mtmp, 0, FALSE, FALSE);
}
} else if (ygold) {
const int gold_price = objects[GOLD_PIECE].oc_cost;
tmp = (somegold(money_cnt(gi.invent)) + gold_price - 1) / gold_price;
tmp = min(tmp, ygold->quan);
if (tmp < ygold->quan)
ygold = splitobj(ygold, tmp);
else
setnotworn(ygold);
freeinv(ygold);
add_to_minv(mtmp, ygold);
Your("purse feels lighter.");
if (!tele_restrict(mtmp))
(void) rloc(mtmp, RLOC_MSG);
monflee(mtmp, 0, FALSE, FALSE);
disp.botl = TRUE;
}
}
/* monster who was stealing from hero has just died */
void
thiefdead(void)
{
/* hero is busy taking off an item of armor which takes multiple turns */
gs.stealmid = 0;
if (ga.afternmv == stealarm) {
ga.afternmv = unstolenarm;
gn.nomovemsg = (char *) 0;
}
}
/* checks whether hero can be responsive to seduction attempts; similar to
Unaware but also includes paralysis */
boolean
unresponsive(void)
{
if (gm.multi >= 0)
return FALSE;
return (unconscious() || is_fainted()
|| (gm.multi_reason
&& (!strncmp(gm.multi_reason, "frozen", 6)
|| !strncmp(gm.multi_reason, "paralyzed", 9))));
}
/* called via (*ga.afternmv)() when hero finishes taking off armor that
was slated to be stolen but the thief died in the interim */
staticfn int
unstolenarm(void)
{
struct obj *obj;
/* find the object before clearing stealoid; it has already become
not-worn and is still in hero's inventory */
for (obj = gi.invent; obj; obj = obj->nobj)
if (obj->o_id == gs.stealoid)
break;
gs.stealoid = 0;
if (obj) {
You("finish taking off your %s.", armor_simple_name(obj));
}
return 0;
}
/* finish stealing an item of armor which takes multiple turns to take off */
staticfn int
stealarm(void)
{
struct monst *mtmp;
struct obj *otmp, *nextobj;
if (!gs.stealoid || !gs.stealmid)
goto botm;
for (otmp = gi.invent; otmp; otmp = nextobj) {
nextobj = otmp->nobj;
if (otmp->o_id == gs.stealoid) {
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
if (mtmp->m_id == gs.stealmid) {
if (DEADMONSTER(mtmp)) {
impossible("stealarm(): dead monster stealing");
goto botm; /* (could just use 'break' here) */
}
/* maybe the thief polymorphed into something without a
steal attack, or perhaps while stealing hero's suit
the thief took away other items causing hero to fall
into water or lava and then teleport to safety */
if (!dmgtype(mtmp->data, AD_SITM)
|| distu(mtmp->mx, mtmp->my) > 2)
goto botm; /* (could just use 'break' here) */
if (otmp->unpaid)
subfrombill(otmp, shop_keeper(*u.ushops));
freeinv(otmp);
pline("%s steals %s!", Monnam(mtmp), doname(otmp));
(void) mpickobj(mtmp, otmp); /* may free otmp */
/* Implies seduction, "you gladly hand over ..."
so we don't set mavenge bit here. */
monflee(mtmp, 0, FALSE, FALSE);
if (!tele_restrict(mtmp))
(void) rloc(mtmp, RLOC_MSG);
break;
}
}
break;
}
}
botm:
gs.stealoid = gs.stealmid = 0; /* in case only one has been reset so far */
return 0;
}
/* An object you're wearing has been taken off by a monster (theft or
seduction). Also used if a worn item gets transformed (stone to flesh). */
void
remove_worn_item(
struct obj *obj,
boolean unchain_ball) /* whether to unpunish or just unwield */
{
unsigned oldinuse;
if (donning(obj))
cancel_don();
if (!obj->owornmask)
return;
/*
* Losing worn gear might drop hero into water or lava or onto a
* location-changing trap or take away the ability to breathe in water.
* Marking it 'in_use' prevents emergency_disrobe() from dropping it
* and lava_effects() from destroying it; other cases impacting object
* location (or destruction) might still have issues.
*
* Note: if a hangup save occurs when 'in_use' is set, the item will
* be destroyed via useup() during restore. Maybe remove_worn_item()
* and emergency_disrobe() should switch to using obj->bypass instead
* but that would need a lot more cooperation by callers. It's a
* tradeoff between protecting the player against unintentional hangup
* and defending the game against deliberate hangup when player sees a
* message about something undesirable followed by --More--.
*/
oldinuse = obj->in_use;
obj->in_use = 1;
if (obj->owornmask & W_ARMOR) {
if (obj == uskin) {
impossible("Removing embedded scales?");
skinback(TRUE); /* uarm = uskin; uskin = 0; */
}
if (obj == uarm)
(void) Armor_off();
else if (obj == uarmc)
(void) Cloak_off();
else if (obj == uarmf)
(void) Boots_off();
else if (obj == uarmg)
(void) Gloves_off();
else if (obj == uarmh)
(void) Helmet_off();
else if (obj == uarms)
(void) Shield_off();
else if (obj == uarmu)
(void) Shirt_off();
/* catchall -- should never happen */
else
setworn((struct obj *) 0, obj->owornmask & W_ARMOR);
} else if (obj->owornmask & W_AMUL) {
Amulet_off();
} else if (obj->owornmask & W_RING) {
Ring_gone(obj);
} else if (obj->owornmask & W_TOOL) {
Blindf_off(obj);
} else if (obj->owornmask & W_WEAPONS) {
if (obj == uwep)
uwepgone();
if (obj == uswapwep)
uswapwepgone();
if (obj == uquiver)
uqwepgone();
}
if (obj->owornmask & (W_BALL | W_CHAIN)) {
if (unchain_ball)
unpunish();
} else if (obj->owornmask) {
/* catchall */
setnotworn(obj);
}
if (obj->where == OBJ_DELETED)
debugpline1("remove_worn_item() \"%s\" deleted!", simpleonames(obj));
obj->in_use = oldinuse;
}
/* during theft of a worn item: remove_worn_item(), prefaced by a message */
staticfn void
worn_item_removal(
struct monst *mon,
struct obj *obj)
{
char objbuf[BUFSZ], article[20], *p;
const char *verb;
int strip_art;
Strcpy(objbuf, doname(obj));
/* massage the object description */
strip_art = !strncmp(objbuf, "the ", 4) ? 4
: !strncmp(objbuf, "an ", 3) ? 3
: !strncmp(objbuf, "a ", 2) ? 2
: 0;
if (strip_art) { /* convert "a/an/the <object>" to "your object" */
copynchars(article, objbuf, strip_art);
/* when removing attached iron ball, caller passes 'uchain';
when formatted, it will be "an iron chain (attached to you)";
change "an" to "the" rather than to "your" in that situation */
(void) strsubst(objbuf, article, (obj == uchain) ? "the " : "your ");
}
/* these ought to be guarded against matching user-supplied name */
(void) strsubst(objbuf, " (being worn)", "");
(void) strsubst(objbuf, " (alternate weapon; not wielded)", "");
/* convert "ring (on left hand)" to "ring (from left hand)" */
if ((p = strstri(objbuf, " (on "))
&& (!strncmp(p + 5, "left ", 5) || !strncmp(p + 5, "right ", 6)))
(void) strsubst(p + 2, "on", "from");
/* slightly iffy for alternate weapon that isn't actively dual-wielded,
but it's better to alert the player to the change in equipment than
to suppress the message for that case */
verb = ((obj->owornmask & W_WEAPONS) != 0L) ? "disarms"
: ((obj->owornmask & W_ACCESSORY) != 0L) ? "removes"
: "takes off";
pline("%s %s %s.", Some_Monnam(mon), verb, objbuf);
iflags.last_msg = PLNMSG_MON_TAKES_OFF_ITEM;
/* removal might trigger more messages (due to loss of Lev|Fly;
descending happens before the theft in progress finishes) */
remove_worn_item(obj, TRUE);
}
/* Returns 1 when something was stolen (or at least, when N should flee now),
* returns -1 if the monster died in the attempt.
* Avoid stealing the object 'stealoid'.
* Nymphs and monkeys won't steal coins (so that their "steal item" attack
* doesn't become a superset of leprechaun's "steal gold" attack).
*/
int
steal(struct monst *mtmp, char *objnambuf)
{
struct obj *otmp;
char Monnambuf[BUFSZ];
int tmp, could_petrify, armordelay, olddelay, icnt,
named = 0, retrycnt = 0;
boolean monkey_business = is_animal(mtmp->data),
seen = canspotmon(mtmp),
was_doffing, was_punished = Punished;
if (objnambuf)
*objnambuf = '\0';
/* the following is true if successful on first of two attacks. */
if (!monnear(mtmp, u.ux, u.uy))
return 0;
/* stealing a worn item might drop hero into water or lava where
teleporting to safety could result in a previously visible thief
no longer being visible; it could also be a case of a blinded
hero being able to see via wearing the Eyes of the Overworld and
having those stolen; remember the name as it is now; if unseen,
nymphs will be "Someone" and monkeys will be "Something" */
Strcpy(Monnambuf, Some_Monnam(mtmp));
/* food being eaten might already be used up but will not have
been removed from inventory yet; we don't want to steal that,
so this will cause it to be removed now */
if (go.occupation)
(void) maybe_finished_meal(FALSE);
icnt = inv_cnt(FALSE); /* don't include gold */
if (!icnt || (icnt == 1 && uskin)) {
/* Not even a thousand men in armor can strip a naked man. */
nothing_to_steal:
/* nymphs might target uchain if invent is empty; monkeys won't;
hero becomes unpunished but nymph ends up empty handed */
if (Punished && !monkey_business && rn2(4)) {
/* uball is not carried (uchain never is) */
assert(uball != NULL && uball->where == OBJ_FLOOR);
worn_item_removal(mtmp, uchain);
} else if (u.utrap && u.utraptype == TT_BURIEDBALL
&& !monkey_business && !rn2(4)) {
boolean dummy;
/* buried ball is not tracked via 'uball' and there is no chain
at all (hence no uchain to take off) */
pline("%s takes off your unseen chain.", Monnambuf);
(void) openholdingtrap(&gy.youmonst, &dummy);
} else if (Blind) {
pline("Somebody tries to rob you, but finds nothing to steal.");
} else if (inv_cnt(TRUE) > inv_cnt(FALSE)) {
pline("%s tries to rob you, but isn't interested in gold.",
Monnambuf);
} else {
pline("%s tries to rob you, but there is nothing to steal!",
Monnambuf);
}
return 1; /* let her flee */
}
if (monkey_business || uarmg) {
; /* skip ring special cases */
} else if (Adornment & LEFT_RING) {
otmp = uleft;
goto gotobj;
} else if (Adornment & RIGHT_RING) {
otmp = uright;
goto gotobj;
}
retry:
tmp = 0;
for (otmp = gi.invent; otmp; otmp = otmp->nobj)
if ((!uarm || otmp != uarmc) && otmp != uskin
&& otmp->oclass != COIN_CLASS)
tmp += (otmp->owornmask & (W_ARMOR | W_ACCESSORY)) ? 5 : 1;
if (!tmp)
goto nothing_to_steal;
tmp = rn2(tmp);
for (otmp = gi.invent; otmp; otmp = otmp->nobj)
if ((!uarm || otmp != uarmc) && otmp != uskin
&& otmp->oclass != COIN_CLASS) {
tmp -= (otmp->owornmask & (W_ARMOR | W_ACCESSORY)) ? 5 : 1;
if (tmp < 0)
break;
}
if (!otmp) {
impossible("Steal fails!");
return 0;
}
/* can't steal ring(s) while wearing gloves */
if ((otmp == uleft || otmp == uright) && uarmg)
otmp = uarmg;
/* can't steal gloves while wielding - so steal the wielded item. */
if (otmp == uarmg && uwep)
otmp = uwep;
/* can't steal armor while wearing cloak - so steal the cloak. */
else if (otmp == uarm && uarmc)
otmp = uarmc;
/* can't steal shirt while wearing cloak or suit */
else if (otmp == uarmu && uarmc)
otmp = uarmc;
else if (otmp == uarmu && uarm)
otmp = uarm;
gotobj:
if (otmp->o_id == gs.stealoid)
return 0;
if (otmp->otyp == BOULDER && !throws_rocks(mtmp->data)) {
if (!retrycnt++)
goto retry;
goto cant_take;
}
/* animals can't overcome curse stickiness nor unlock chains */
if (monkey_business) {
boolean ostuck;
/* is the player prevented from voluntarily giving up this item?
(ignores loadstones; the !can_carry() check will catch those) */
if (otmp == uball)
ostuck = TRUE; /* effectively worn; curse is implicit */
else if (otmp == uquiver || (otmp == uswapwep && !u.twoweap))
ostuck = FALSE; /* not really worn; curse doesn't matter */
else
ostuck = ((otmp->cursed && otmp->owornmask)
/* nymphs can steal rings from under
cursed weapon but animals can't */
|| (otmp == RING_ON_PRIMARY && welded(uwep))
|| (otmp == RING_ON_SECONDARY && welded(uwep)
&& bimanual(uwep)));
if (ostuck || can_carry(mtmp, otmp) == 0) {
static const char *const how[] = {
"steal", "snatch", "grab", "take"
};
cant_take:
pline("%s tries to %s %s%s but gives up.", Monnambuf,
ROLL_FROM(how),
(otmp->owornmask & W_ARMOR) ? "your " : "",
(otmp->owornmask & W_ARMOR) ? armor_simple_name(otmp)
: yname(otmp));
/* the fewer items you have, the less likely the thief
is going to stick around to try again (0) instead of
running away (1) */
return !rn2(inv_cnt(FALSE) / 5 + 2);
}
}
if (otmp->otyp == LEASH && otmp->leashmon) {
if (monkey_business && otmp->cursed)
goto cant_take;
o_unleash(otmp);
}
was_doffing = doffing(otmp);
/* stop donning/doffing now so that afternmv won't be clobbered
below; stop_occupation doesn't handle donning/doffing */
olddelay = stop_donning(otmp);
/* you're going to notice the theft... */
stop_occupation();
if (otmp->owornmask & (W_ARMOR | W_ACCESSORY)) {
switch (otmp->oclass) {
case TOOL_CLASS:
case AMULET_CLASS:
case RING_CLASS:
case FOOD_CLASS: /* meat ring */
worn_item_removal(mtmp, otmp);
break;
case ARMOR_CLASS:
armordelay = objects[otmp->otyp].oc_delay;
if (olddelay > 0 && olddelay < armordelay)
armordelay = olddelay;
if (monkey_business || unresponsive()) {
/* animals usually don't have enough patience to take off
items which require extra time; unconscious or paralyzed
hero can't be charmed into taking off his own armor */
if (armordelay >= 1 && !olddelay && rn2(10))
goto cant_take;
worn_item_removal(mtmp, otmp);
break;
} else {
int curssv = otmp->cursed;
int slowly;
otmp->cursed = 0;
slowly = (armordelay >= 1 || gm.multi < 0);
if (flags.female)
urgent_pline("%s charms you. You gladly %s your %s.",
!seen ? "She" : Monnambuf,
curssv ? "let her take"
: !slowly ? "hand over"
: was_doffing ? "continue removing"
: "start removing",
armor_simple_name(otmp));
else
urgent_pline("%s seduces you and %s off your %s.",
!seen ? "She" : Adjmonnam(mtmp, "beautiful"),
curssv ? "helps you to take"
: !slowly ? "you take"
: was_doffing ? "you continue taking"
: "you start taking",
armor_simple_name(otmp));
named++;
/* the following is to set multi for later on */
nomul(-armordelay);
gm.multi_reason = "taking off clothes";
gn.nomovemsg = 0;
remove_worn_item(otmp, TRUE);
otmp->cursed = curssv;
if (gm.multi < 0) {
gs.stealoid = otmp->o_id;
gs.stealmid = mtmp->m_id;
ga.afternmv = stealarm;
return 0;
}
}
break;
default:
impossible("Tried to steal a strange worn thing. [%d]",
otmp->oclass);
}
/* hero's blindfold might have just been stolen; if so, replace
cached "Someone" or "Something" with Monnam */
if (!seen && canspotmon(mtmp))
Strcpy(Monnambuf, Monnam(mtmp));
} else if (otmp->owornmask) { /* weapon or ball&chain */
struct obj *item = otmp;
if (otmp == uball) /* non-Null uball implies non-Null uchain */
item = uchain; /* yields a more accurate 'takes off' message */
worn_item_removal(mtmp, item);
/* if we switched from uball to uchain for the preface message,
then unpunish() took place and both those pointers are now Null,
with 'item' a stale pointer to freed chain; the ball is still
present though and 'otmp' is still valid; if uball was also
wielded or quivered, the corresponding weapon pointer hasn't
been cleared yet; do that, with no preface message this time */
if ((otmp->owornmask & W_WEAPONS) != 0L)
remove_worn_item(otmp, FALSE);
}
/* do this before removing it from inventory */
if (objnambuf)
Strcpy(objnambuf, yname(otmp));
/* usually set mavenge bit so knights won't suffer an alignment penalty
during retaliation; not applicable for removing attached iron ball */
if (!Conflict && !(was_punished && !Punished))
mtmp->mavenge = 1;
if (otmp->unpaid)
subfrombill(otmp, shop_keeper(*u.ushops));
freeinv(otmp);
/* if we just gave a message about removing a worn item and there have
been no intervening messages, shorten '<mon> stole <item>' message */
if (iflags.last_msg == PLNMSG_MON_TAKES_OFF_ITEM
&& mtmp->data->mlet == S_NYMPH)
++named;
urgent_pline("%s stole %s.", named ? "She" : Monnambuf, doname(otmp));
encumber_msg();
could_petrify = (otmp->otyp == CORPSE
&& touch_petrifies(&mons[otmp->corpsenm]));
otmp->how_lost = LOST_STOLEN;
(void) mpickobj(mtmp, otmp); /* may free otmp */
if (could_petrify && !(mtmp->misc_worn_check & W_ARMG)) {
minstapetrify(mtmp, TRUE);
return -1;
}
return (gm.multi < 0) ? 0 : 1;
}
/* Returns 1 if otmp is free'd, 0 otherwise. */
int
mpickobj(struct monst *mtmp, struct obj *otmp)
{
int freed_otmp;
boolean snuff_otmp = FALSE;
if (!otmp) {
impossible("monster (%s) taking or picking up nothing?",
pmname(mtmp->data, Mgender(mtmp)));
return 1;
} else if (otmp == uball || otmp == uchain) {
impossible("monster (%s) taking or picking up attached %s (%s)?",
pmname(mtmp->data, Mgender(mtmp)),
(otmp == uchain) ? "chain" : "ball", simpleonames(otmp));
return 0;
}
/* if monster is acquiring a thrown or kicked object, the throwing
or kicking code shouldn't continue to track and place it */
if (otmp == gt.thrownobj)
gt.thrownobj = 0;
else if (otmp == gk.kickedobj)
gk.kickedobj = 0;
/* an unpaid item can be on the floor; if a monster picks it up, take
it off the shop bill */
if (otmp->unpaid || (Has_contents(otmp) && count_unpaid(otmp->cobj))) {
subfrombill(otmp, find_objowner(otmp, otmp->ox, otmp->oy));
}
/* don't want hidden light source inside the monster; assumes that
engulfers won't have external inventories; whirly monsters cause
the light to be extinguished rather than letting it shine thru */
if (obj_sheds_light(otmp) && attacktype(mtmp->data, AT_ENGL)) {
/* this is probably a burning object that you dropped or threw */
if (engulfing_u(mtmp) && !Blind)
pline("%s out.", Tobjnam(otmp, "go"));
snuff_otmp = TRUE;
}
/* for hero owned object on shop floor, mtmp is taking possession
and if it's eventually dropped in a shop, shk will claim it */
otmp->no_charge = 0;
/* some object handling is only done if mtmp isn't a pet */
if (!mtmp->mtame) {
/* if monst is unseen, some info hero knows about this object becomes
lost; continual pickup and drop by pets makes this too annoying if
it is applied to them; when engulfed (where monster can't be seen
because vision is disabled), or when held (or poly'd and holding)
while blind, behave as if the monster can be 'seen' by touch */
if (!canseemon(mtmp) && mtmp != u.ustuck)
unknow_object(otmp);
/* if otmp has flags set for how it left hero's inventory, change
those flags; if thrown, now stolen and autopickup might override
pickup_types and autopickup exceptions based on 'pickup_stolen'
rather than 'pickup_thrown'; if previously stolen, stays stolen;
if previously dropped, now forgotten and autopickup will operate
normally regardless of the setting for 'dropped_nopick' */
if (otmp->how_lost == LOST_THROWN)
otmp->how_lost = LOST_STOLEN;
else if (otmp->how_lost == LOST_DROPPED)
otmp->how_lost = LOST_NONE;
}
/* Must do carrying effects on object prior to add_to_minv() */
carry_obj_effects(otmp);
/* add_to_minv() might free otmp [if merged with something else],
so we have to call it after doing the object checks */
freed_otmp = add_to_minv(mtmp, otmp);
/* and we had to defer this until object is in mtmp's inventory */
if (snuff_otmp)
snuff_light_source(mtmp->mx, mtmp->my);
return freed_otmp;
}
/* called for AD_SAMU (the Wizard and quest nemeses) */
void
stealamulet(struct monst *mtmp)
{
char buf[BUFSZ];
struct obj *otmp = 0, *obj = 0;
int real = 0, fake = 0, n;
/* target every quest artifact, not just current role's;
if hero has more than one, choose randomly so that player
can't use inventory ordering to influence the theft */
for (n = 0, obj = gi.invent; obj; obj = obj->nobj)
if (any_quest_artifact(obj))
++n, otmp = obj;
if (n > 1) {
n = rnd(n);
for (otmp = gi.invent; otmp; otmp = otmp->nobj)
if (any_quest_artifact(otmp) && !--n)
break;
}
if (!otmp) {
/* if we didn't find any quest artifact, find another valuable item */
if (u.uhave.amulet) {
real = AMULET_OF_YENDOR;
fake = FAKE_AMULET_OF_YENDOR;
} else if (u.uhave.bell) {
real = BELL_OF_OPENING;
fake = BELL;
} else if (u.uhave.book) {
real = SPE_BOOK_OF_THE_DEAD;
} else if (u.uhave.menorah) {
real = CANDELABRUM_OF_INVOCATION;
} else
return; /* you have nothing of special interest */
/* If we get here, real and fake have been set up. */
for (n = 0, obj = gi.invent; obj; obj = obj->nobj)
if (obj->otyp == real || (obj->otyp == fake && !mtmp->iswiz))
++n, otmp = obj;
if (n > 1) {
n = rnd(n);
for (otmp = gi.invent; otmp; otmp = otmp->nobj)
if ((otmp->otyp == real
|| (otmp->otyp == fake && !mtmp->iswiz)) && !--n)
break;
}
}
if (otmp) { /* we have something to snatch */
/* take off outer gear if we're targeting [hypothetical]
quest artifact suit, shirt, gloves, or rings */
if ((otmp == uarm || otmp == uarmu) && uarmc)
worn_item_removal(mtmp, uarmc);
if (otmp == uarmu && uarm)
worn_item_removal(mtmp, uarm);
if ((otmp == uarmg || ((otmp == uright || otmp == uleft) && uarmg))
&& uwep) {
/* gloves are about to be unworn; unwield weapon(s) first */
if (u.twoweap) /* remove_worn_item(uswapwep) indirectly */
worn_item_removal(mtmp, uswapwep); /* clears u.twoweap */
worn_item_removal(mtmp, uwep);
}
if ((otmp == uright || otmp == uleft) && uarmg)
/* calls Gloves_off() to handle wielded cockatrice corpse */
worn_item_removal(mtmp, uarmg);
/* finally, steal the target item */
if (otmp->owornmask)
worn_item_removal(mtmp, otmp);
if (otmp->unpaid)
subfrombill(otmp, shop_keeper(*u.ushops));
freeinv(otmp);
Strcpy(buf, doname(otmp));
(void) mpickobj(mtmp, otmp); /* could merge and free otmp but won't */
pline("%s steals %s!", Some_Monnam(mtmp), buf);
if (can_teleport(mtmp->data) && !tele_restrict(mtmp))
(void) rloc(mtmp, RLOC_MSG);
encumber_msg();
}
}
/* when a mimic gets poked with something, it might take that thing
(at present, only implemented for when the hero does the poking) */
void
maybe_absorb_item(
struct monst *mon,
struct obj *obj,
int ochance, int achance) /* percent chance for ordinary item, artifact */
{
if (obj == uball || obj == uchain || obj->oclass == ROCK_CLASS
|| obj_resists(obj, 100 - ochance, 100 - achance)
|| !touch_artifact(obj, mon))
return;
if (carried(obj)) {
if (obj->owornmask)
remove_worn_item(obj, TRUE);
if (obj->unpaid)
subfrombill(obj, shop_keeper(*u.ushops));
if (cansee(mon->mx, mon->my)) {
/* Some_Monnam() avoids "It pulls ... and absorbs it!"
if hero can see the location but not the monster */
pline("%s pulls %s away from you and absorbs %s!",
Some_Monnam(mon), /* Monnam() or "Something" */
yname(obj), (obj->quan > 1L) ? "them" : "it");
} else {
const char *hand_s = body_part(HAND);
if (bimanual(obj))
hand_s = makeplural(hand_s);
pline("%s %s pulled from your %s!", upstart(yname(obj)),
otense(obj, "are"), hand_s);
}
freeinv(obj);
encumber_msg();
} else {
/* not carried; presumably thrown or kicked */
if (canspotmon(mon))
pline("%s absorbs %s!", Monnam(mon), yname(obj));
}
/* add to mon's inventory */
(void) mpickobj(mon, obj);
}
/* drop one object taken from a (possibly dead) monster's inventory */
void
mdrop_obj(
struct monst *mon,
struct obj *obj,
boolean verbosely)
{
coordxy omx = mon->mx, omy = mon->my;
long unwornmask = obj->owornmask;
/* call distant_name() for its possible side-effects even if the result
might not be printed, and do it before extracting obj from minvent */
char *obj_name = distant_name(obj, doname);
extract_from_minvent(mon, obj, FALSE, TRUE);
/* don't charge for an owned saddle on dead steed (provided
that the hero is within the same shop at the time) */
if (unwornmask && mon->mtame && (unwornmask & W_SADDLE) != 0L
&& !obj->unpaid && costly_spot(omx, omy)
/* being at costly_spot guarantees lev->roomno is not 0 */
&& strchr(in_rooms(u.ux, u.uy, SHOPBASE), levl[omx][omy].roomno)) {
obj->no_charge = 1;
}
/* obj_no_longer_held(obj); -- done by place_object */
if (verbosely && cansee(omx, omy))
pline_mon(mon, "%s drops %s.", Monnam(mon), obj_name);
if (!flooreffects(obj, omx, omy, "fall")) {
place_object(obj, omx, omy);
stackobj(obj);
}
/* do this last, after placing obj on floor; removing steed's saddle
throws rider, possibly inflicting fatal damage and producing bones; this
is why we had to call extract_from_minvent() with do_intrinsics=FALSE */
if (!DEADMONSTER(mon) && unwornmask)
update_mon_extrinsics(mon, obj, FALSE, TRUE);
}
/* some monsters bypass the normal rules for moving between levels or
even leaving the game entirely; when that happens, prevent them from
taking the Amulet, invocation items, or quest artifact with them */
void
mdrop_special_objs(struct monst *mon)
{
struct obj *obj, *otmp;
for (obj = mon->minvent; obj; obj = otmp) {
otmp = obj->nobj;
/* the Amulet, invocation tools, and Rider corpses resist even when
artifacts and ordinary objects are given 0% resistance chance;
current role's quest artifact is rescued too--quest artifacts
for the other roles are not */
if (obj_resists(obj, 0, 0) || is_quest_artifact(obj)) {
if (mon->mx) {
mdrop_obj(mon, obj, FALSE);
} else { /* migrating monster not on map */
extract_from_minvent(mon, obj, TRUE, TRUE);
rloco(obj);
}
}
}
}
/* release the objects the creature is carrying */
void
relobj(
struct monst *mtmp,
int show,
boolean is_pet) /* If true, pet should keep wielded/worn items */
{
struct obj *otmp;
int omx = mtmp->mx, omy = mtmp->my;
/* vault guard's gold goes away rather than be dropped... */
if (mtmp->isgd && (otmp = findgold(mtmp->minvent)) != 0) {
if (canspotmon(mtmp))
pline("%s gold %s.", s_suffix(Monnam(mtmp)),
canseemon(mtmp) ? "vanishes" : "seems to vanish");
obj_extract_self(otmp);
obfree(otmp, (struct obj *) 0);
} /* isgd && has gold */
while ((otmp = (is_pet ? droppables(mtmp) : mtmp->minvent)) != 0) {
mdrop_obj(mtmp, otmp, is_pet && flags.verbose);
}
if (show && cansee(omx, omy))
newsym(omx, omy);
}
/*steal.c*/