fix #U782 - undead turning in a shop [trunk only]

Reported last December by <email deleted>.  Using
a wand or spell of undead turning inside a shop used up corpses without
checking whether they were owned by the shop.  Although the report didn't
mention it, using stone-to-flesh on statues had the same problem.

     I'm not completely satisfied by some aspects of this code, but if I
don't commit what I've got now I probably never would.  My original notes
are lost; I thought that there were some additional fixes present, but
looking at these diffs I don't see anything else significant enough to
warrant mention in the fixes file.
This commit is contained in:
nethack.rankin
2004-10-22 01:04:34 +00:00
parent 467d35d9e4
commit eaae10c837
6 changed files with 285 additions and 222 deletions

View File

@@ -56,6 +56,7 @@ destroying a worn item via dipping in burning oil would not unwear/unwield
the item properly, possibly leading to various strange behaviors the item properly, possibly leading to various strange behaviors
avoid a panic splitbill when shopkeeper is trapped by the door avoid a panic splitbill when shopkeeper is trapped by the door
grammar tidbit for message given when eating tainted meat is also cannibalism grammar tidbit for message given when eating tainted meat is also cannibalism
charge for reviving a shop owned corpse or reanimating a shop owned statue
Platform- and/or Interface-Specific Fixes Platform- and/or Interface-Specific Fixes

View File

@@ -2422,7 +2422,7 @@ E boolean FDECL(get_obj_location, (struct obj *,xchar *,xchar *,int));
E boolean FDECL(get_mon_location, (struct monst *,xchar *,xchar *,int)); E boolean FDECL(get_mon_location, (struct monst *,xchar *,xchar *,int));
E struct monst *FDECL(get_container_location, (struct obj *obj, int *, int *)); E struct monst *FDECL(get_container_location, (struct obj *obj, int *, int *));
E struct monst *FDECL(montraits, (struct obj *,coord *)); E struct monst *FDECL(montraits, (struct obj *,coord *));
E struct monst *FDECL(revive, (struct obj *)); E struct monst *FDECL(revive, (struct obj *,BOOLEAN_P));
E int FDECL(unturn_dead, (struct monst *)); E int FDECL(unturn_dead, (struct monst *));
E void FDECL(cancel_item, (struct obj *)); E void FDECL(cancel_item, (struct obj *));
E boolean FDECL(drain_item, (struct obj *)); E boolean FDECL(drain_item, (struct obj *));

View File

@@ -1,4 +1,4 @@
/* SCCS Id: @(#)do.c 3.4 2003/12/17 */ /* SCCS Id: @(#)do.c 3.4 2004/09/10 */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */ /* NetHack may be freely redistributed. See license for details. */
@@ -1511,7 +1511,7 @@ struct obj *corpse;
/* container_where is the outermost container's location even if nested */ /* container_where is the outermost container's location even if nested */
if (container_where == OBJ_MINVENT && mtmp2) mcarry = mtmp2; if (container_where == OBJ_MINVENT && mtmp2) mcarry = mtmp2;
} }
mtmp = revive(corpse); /* corpse is gone if successful */ mtmp = revive(corpse, FALSE); /* corpse is gone if successful */
if (mtmp) { if (mtmp) {
chewed = (mtmp->mhp < mtmp->mhpmax); chewed = (mtmp->mhp < mtmp->mhpmax);

View File

@@ -1,4 +1,4 @@
/* SCCS Id: @(#)mkroom.c 3.4 2001/09/06 */ /* SCCS Id: @(#)mkroom.c 3.4 2004/06/10 */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */ /* NetHack may be freely redistributed. See license for details. */
@@ -403,7 +403,7 @@ int mm_flags;
if (enexto(&cc, mm->x, mm->y, mdat) && if (enexto(&cc, mm->x, mm->y, mdat) &&
(!revive_corpses || (!revive_corpses ||
!(otmp = sobj_at(CORPSE, cc.x, cc.y)) || !(otmp = sobj_at(CORPSE, cc.x, cc.y)) ||
!revive(otmp))) !revive(otmp, FALSE)))
(void) makemon(mdat, cc.x, cc.y, mm_flags); (void) makemon(mdat, cc.x, cc.y, mm_flags);
} }
level.flags.graveyard = TRUE; /* reduced chance for undead corpse */ level.flags.graveyard = TRUE; /* reduced chance for undead corpse */

View File

@@ -1,4 +1,4 @@
/* SCCS Id: @(#)trap.c 3.4 2004/08/23 */ /* SCCS Id: @(#)trap.c 3.4 2004/09/10 */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */ /* NetHack may be freely redistributed. See license for details. */
@@ -382,6 +382,18 @@ boolean td; /* td == TRUE : trap door or hole */
* *
* Perhaps x, y is not needed if we can use get_obj_location() to find * Perhaps x, y is not needed if we can use get_obj_location() to find
* the statue's location... ??? * the statue's location... ???
*
* Sequencing matters:
* create monster; if it fails, give up with statue intact;
* give "statue comes to life" message;
* if statue belongs to shop, have shk give "you owe" message;
* transfer statue contents to monster (after stolen_value());
* delete statue.
* [This ordering means that if the statue ends up wearing a cloak of
* invisibility or a mummy wrapping, the visibility checks might be
* wrong, but to avoid that we'd have to clone the statue contents
* first in order to give them to the monster before checking their
* shop status--it's not worth the hassle.]
*/ */
struct monst * struct monst *
animate_statue(statue, x, y, cause, fail_reason) animate_statue(statue, x, y, cause, fail_reason)
@@ -392,15 +404,16 @@ int *fail_reason;
{ {
int mnum = statue->corpsenm; int mnum = statue->corpsenm;
struct permonst *mptr = &mons[mnum]; struct permonst *mptr = &mons[mnum];
struct monst *mon = 0; struct monst *mon = 0, *shkp;
struct obj *item; struct obj *item;
coord cc; coord cc;
boolean historic = (Role_if(PM_ARCHEOLOGIST) && !context.mon_moving && boolean historic = (Role_if(PM_ARCHEOLOGIST) &&
(statue->spe & STATUE_HISTORIC)), (statue->spe & STATUE_HISTORIC) != 0),
use_saved_traits; use_saved_traits;
char statuename[BUFSZ]; const char *comes_to_life;
char statuename[BUFSZ], tmpbuf[BUFSZ];
Strcpy(statuename,the(xname(statue))); static const char historic_statue_is_gone[] =
"that the historic statue is now gone";
if (cant_revive(&mnum, TRUE, statue)) { if (cant_revive(&mnum, TRUE, statue)) {
/* mnum has changed; we won't be animating this statue as itself */ /* mnum has changed; we won't be animating this statue as itself */
@@ -449,10 +462,7 @@ int *fail_reason;
return (struct monst *)0; return (struct monst *)0;
} }
/* in case statue is wielded and hero zaps stone-to-flesh at self */ /* a non-montraits() statue might specify gender */
if (statue->owornmask) remove_worn_item(statue, TRUE);
/* allow statues to be of a specific gender */
if (statue->spe & STATUE_MALE) if (statue->spe & STATUE_MALE)
mon->female = FALSE; mon->female = FALSE;
else if (statue->spe & STATUE_FEMALE) else if (statue->spe & STATUE_FEMALE)
@@ -460,40 +470,65 @@ int *fail_reason;
/* if statue has been named, give same name to the monster */ /* if statue has been named, give same name to the monster */
if (statue->onamelth) if (statue->onamelth)
mon = christen_monst(mon, ONAME(statue)); mon = christen_monst(mon, ONAME(statue));
/* mimic statue becomes seen mimic; other hiders won't be hidden */
if (mon->m_ap_type) seemimic(mon);
else mon->mundetected = FALSE;
comes_to_life = !canspotmon(mon) ? "disappears" :
(nonliving(mon->data) || is_vampshifter(mon)) ?
"moves" : "comes to life";
if ((x == u.ux && y == u.uy) || cause == ANIMATE_SPELL) {
/* "the|your|Manlobbi's statue [of a wombat]" */
Sprintf(statuename, "%s%s", shk_your(tmpbuf, statue),
(cause == ANIMATE_SPELL) ? xname(statue) : "statue");
pline("%s %s!", upstart(statuename), comes_to_life);
} else if (cause == ANIMATE_SHATTER) {
if (cansee(x, y))
Sprintf(statuename, "%s%s", shk_your(tmpbuf, statue),
xname(statue));
else
Strcpy(statuename, "a statue");
pline("Instead of shattering, %s suddenly %s!",
statuename, comes_to_life);
} else { /* cause == ANIMATE_NORMAL */
You("find %s posing as a statue.",
canspotmon(mon) ? a_monnam(mon) : something);
stop_occupation();
}
/* if this isn't caused by a monster using a wand of striking,
there might be consequences for the hero */
if (!context.mon_moving) {
/* if statue is owned by a shop, hero will have to pay for it;
stolen_value gives a message (about debt or use of credit)
which refers to "it" so needs to follow a message describing
the object ("the statue comes to life" one above) */
if (cause != ANIMATE_NORMAL && costly_spot(x, y) &&
(shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) != 0)
(void) stolen_value(statue, x, y,
(boolean)shkp->mpeaceful, FALSE);
if (historic) {
You_feel("guilty %s.", historic_statue_is_gone);
adjalign(-1);
}
} else {
if (historic && cansee(x, y))
You_feel("regret %s.", historic_statue_is_gone);
/* no alignment penalty */
}
/* transfer any statue contents to monster's inventory */ /* transfer any statue contents to monster's inventory */
while ((item = statue->cobj) != 0) { while ((item = statue->cobj) != 0) {
obj_extract_self(item); obj_extract_self(item);
(void) add_to_minv(mon, item); (void) add_to_minv(mon, item);
} }
m_dowear(mon, TRUE); m_dowear(mon, TRUE);
/* in case statue is wielded and hero zaps stone-to-flesh at self */
if (statue->owornmask) remove_worn_item(statue, TRUE);
/* statue no longer exists */
delobj(statue); delobj(statue);
/* mimic statue becomes seen mimic; other hiders won't be hidden */
if (mon->m_ap_type) seemimic(mon);
else mon->mundetected = FALSE;
if ((x == u.ux && y == u.uy) || cause == ANIMATE_SPELL) {
const char *comes_to_life = (nonliving(mon->data) ||
is_vampshifter(mon)) ?
"moves" : "comes to life";
if (cause == ANIMATE_SPELL)
pline("%s %s!", upstart(statuename),
canspotmon(mon) ? comes_to_life : "disappears");
else
pline_The("statue %s!",
canspotmon(mon) ? comes_to_life : "disappears");
if (historic) {
You_feel("guilty that the historic statue is now gone.");
adjalign(-1);
}
} else if (cause == ANIMATE_SHATTER)
pline("Instead of shattering, the statue suddenly %s!",
canspotmon(mon) ? "comes to life" : "disappears");
else { /* cause == ANIMATE_NORMAL */
You("find %s posing as a statue.",
canspotmon(mon) ? a_monnam(mon) : something);
stop_occupation();
}
/* avoid hiding under nothing */ /* avoid hiding under nothing */
if (x == u.ux && y == u.uy && if (x == u.ux && y == u.uy &&
Upolyd && hides_under(youmonst.data) && !OBJ_AT(x, y)) Upolyd && hides_under(youmonst.data) && !OBJ_AT(x, y))

387
src/zap.c
View File

@@ -1,4 +1,4 @@
/* SCCS Id: @(#)zap.c 3.4 2004/08/02 */ /* SCCS Id: @(#)zap.c 3.4 2004/09/10 */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */ /* NetHack may be freely redistributed. See license for details. */
@@ -563,168 +563,194 @@ int *container_nesting;
* successful. Note: this does NOT use up the corpse if it fails. * successful. Note: this does NOT use up the corpse if it fails.
*/ */
struct monst * struct monst *
revive(obj) revive(corpse, by_hero)
register struct obj *obj; struct obj *corpse;
boolean by_hero;
{ {
register struct monst *mtmp = (struct monst *)0; struct monst *mtmp = 0;
struct permonst *mptr; struct permonst *mptr;
struct obj *container = (struct obj *)0; struct obj *container;
int container_nesting = 0; coord xy;
schar savetame = 0; xchar x, y;
boolean recorporealization = FALSE; int montype, container_nesting = 0;
boolean in_container = FALSE;
if(obj->otyp == CORPSE) { if (corpse->otyp != CORPSE) {
int montype = obj->corpsenm; impossible("Attempting to revive %s?", xname(corpse));
xchar x, y; return (struct monst *)0;
}
if (obj->where == OBJ_CONTAINED) { x = y = 0;
/* deal with corpses in [possibly nested] containers */ if (corpse->where != OBJ_CONTAINED) {
struct monst *carrier; /* only for invent, minvent, or floor */
int holder = 0; container = 0;
(void) get_obj_location(corpse, &x, &y, 0);
} else {
/* deal with corpses in [possibly nested] containers */
struct monst *carrier;
int holder = OBJ_FREE;
container = obj->ocontainer; container = corpse->ocontainer;
carrier = get_container_location(container, &holder, carrier = get_container_location(container, &holder,
&container_nesting); &container_nesting);
switch(holder) { switch (holder) {
case OBJ_MINVENT: case OBJ_MINVENT:
x = carrier->mx; y = carrier->my; x = carrier->mx, y = carrier->my;
in_container = TRUE; break;
break; case OBJ_INVENT:
case OBJ_INVENT: x = u.ux, y = u.uy;
x = u.ux; y = u.uy; break;
in_container = TRUE; case OBJ_FLOOR:
break; (void) get_obj_location(corpse, &x, &y, CONTAINED_TOO);
case OBJ_FLOOR: break;
if (!get_obj_location(obj, &x, &y, CONTAINED_TOO)) default:
return (struct monst *) 0; break; /* x,y are 0 */
in_container = TRUE;
break;
default:
return (struct monst *)0;
}
} else {
/* only for invent, minvent, or floor */
if (!get_obj_location(obj, &x, &y, 0))
return (struct monst *) 0;
}
if (in_container) {
/* Rules for revival from containers:
- the container cannot be locked
- the container cannot be heavily nested (>2 is arbitrary)
- the container cannot be a statue or bag of holding
(except in very rare cases for the latter)
*/
if (!x || !y || container->olocked || container_nesting > 2 ||
container->otyp == STATUE ||
(container->otyp == BAG_OF_HOLDING && rn2(40)))
return (struct monst *)0;
}
if (MON_AT(x,y)) {
coord new_xy;
if (enexto(&new_xy, x, y, &mons[montype]))
x = new_xy.x, y = new_xy.y;
}
mptr = &mons[montype];
if (cant_revive(&montype, TRUE, obj)) {
/* make a zombie or doppelganger instead */
mtmp = makemon(&mons[montype], x, y,
NO_MINVENT | MM_NOWAIT);
if (mtmp) {
if (mtmp->cham == PM_DOPPELGANGER) {
/* change shape to match the corpse */
(void) newcham(mtmp, mptr, FALSE, FALSE);
} else if (mtmp->data->mlet == S_ZOMBIE) {
mtmp->mhp = mtmp->mhpmax = 100;
mon_adjust_speed(mtmp, 2, (struct obj *)0); /* MFAST */
}
}
} else {
if (obj->oxlth && (obj->oattached == OATTACHED_MONST)) {
coord xy;
xy.x = x; xy.y = y;
mtmp = montraits(obj, &xy);
if (mtmp && mtmp->mtame && !mtmp->isminion)
wary_dog(mtmp, TRUE);
} else
mtmp = makemon(&mons[montype], x, y,
NO_MINVENT|MM_NOWAIT|MM_NOCOUNTBIRTH);
if (mtmp) {
if (obj->oxlth && (obj->oattached == OATTACHED_M_ID)) {
unsigned m_id;
struct monst *ghost;
(void) memcpy((genericptr_t)&m_id,
(genericptr_t)obj->oextra, sizeof(m_id));
ghost = find_mid(m_id, FM_FMON);
if (ghost && ghost->data == &mons[PM_GHOST]) {
int x2, y2;
x2 = ghost->mx; y2 = ghost->my;
if (ghost->mtame)
savetame = ghost->mtame;
if (canseemon(ghost))
pline("%s is suddenly drawn into its former body!",
Monnam(ghost));
mondead(ghost);
recorporealization = TRUE;
newsym(x2, y2);
}
/* don't mess with obj->oxlth here */
obj->oattached = OATTACHED_NOTHING;
}
/* Monster retains its name */
if (obj->onamelth)
mtmp = christen_monst(mtmp, ONAME(obj));
}
}
if (mtmp) {
if (obj->oeaten)
mtmp->mhp = eaten_stat(mtmp->mhp, obj);
/* track that this monster was revived at least once */
mtmp->mrevived = 1;
if (recorporealization) {
/* If mtmp is revivification of former tame ghost*/
if (savetame) {
struct monst *mtmp2 = tamedog(mtmp, (struct obj *)0);
if (mtmp2) {
mtmp2->mtame = savetame;
mtmp = mtmp2;
}
}
/* was ghost, now alive, it's all very confusing */
mtmp->mconf = 1;
}
switch (obj->where) {
case OBJ_INVENT:
useup(obj);
break;
case OBJ_FLOOR:
/* in case MON_AT+enexto for invisible mon */
x = obj->ox, y = obj->oy;
/* not useupf(), which charges */
if (obj->quan > 1L)
obj = splitobj(obj, 1L);
delobj(obj);
newsym(x, y);
break;
case OBJ_MINVENT:
m_useup(obj->ocarry, obj);
break;
case OBJ_CONTAINED:
obj_extract_self(obj);
obfree(obj, (struct obj *) 0);
break;
default:
panic("revive");
}
}
} }
return mtmp; }
if (!x || !y ||
/* Rules for revival from containers:
- the container cannot be locked
- the container cannot be heavily nested (>2 is arbitrary)
- the container cannot be a statue or bag of holding
(except in very rare cases for the latter)
*/
(container &&
(container->olocked || container_nesting > 2 ||
container->otyp == STATUE ||
(container->otyp == BAG_OF_HOLDING && rn2(40)))))
return (struct monst *)0;
/* record the object's location now that we're sure where it is */
corpse->ox = x, corpse->oy = y;
/* prepare for the monster */
montype = corpse->corpsenm;
mptr = &mons[montype];
/* [should probably handle recorporealization first; if corpse and
ghost are at same location, revived creature shouldn't be bumped
to an adjacent spot by ghost which joins with it] */
if (MON_AT(x,y)) {
if (enexto(&xy, x, y, mptr))
x = xy.x, y = xy.y;
}
if (cant_revive(&montype, TRUE, corpse)) {
/* make a zombie or doppelganger instead */
/* note: montype has changed; mptr keeps old value for newcham() */
mtmp = makemon(&mons[montype], x, y, NO_MINVENT|MM_NOWAIT);
if (mtmp) {
corpse->oattached = OATTACHED_NOTHING; /* skip ghost handling */
if (mtmp->cham == PM_DOPPELGANGER) {
/* change shape to match the corpse */
(void) newcham(mtmp, mptr, FALSE, FALSE);
} else if (mtmp->data->mlet == S_ZOMBIE) {
mtmp->mhp = mtmp->mhpmax = 100;
mon_adjust_speed(mtmp, 2, (struct obj *)0); /* MFAST */
}
}
} else if (corpse->oxlth && corpse->oattached == OATTACHED_MONST) {
/* use saved traits */
xy.x = x, xy.y = y;
mtmp = montraits(corpse, &xy);
if (mtmp && mtmp->mtame && !mtmp->isminion)
wary_dog(mtmp, TRUE);
} else {
/* make a new monster */
mtmp = makemon(mptr, x, y, NO_MINVENT|MM_NOWAIT|MM_NOCOUNTBIRTH);
}
if (!mtmp) return (struct monst *)0;
/* if this is caused by the hero there might be a shop charge */
if (by_hero) {
struct monst *shkp = 0;
x = corpse->ox, y = corpse->oy;
if (costly_spot(x, y))
shkp = shop_keeper(*in_rooms(x, y, SHOPBASE));
if (cansee(x, y))
pline_The("%s glows iridescently.", cxname(corpse));
else if (shkp)
/* need some prior description of the corpse since
stolen_value() will refer to the object as "it" */
pline("A corpse is resuscitated.");
if (shkp)
(void) stolen_value(corpse, x, y, (boolean)shkp->mpeaceful, FALSE);
/* [we don't give any comparable message about the corpse for
the !by_hero case because caller might have already done so] */
}
/* handle recorporealization of an active ghost */
if (corpse->oxlth && corpse->oattached == OATTACHED_M_ID) {
unsigned m_id;
struct monst *ghost, *mtmp2;
struct obj *otmp;
(void) memcpy((genericptr_t)&m_id,
(genericptr_t)corpse->oextra, sizeof m_id);
ghost = find_mid(m_id, FM_FMON);
if (ghost && ghost->data == &mons[PM_GHOST]) {
if (canseemon(ghost))
pline("%s is suddenly drawn into its former body!",
Monnam(ghost));
/* transfer the ghost's inventory along with it */
while ((otmp = ghost->minvent) != 0) {
obj_extract_self(otmp);
add_to_minv(mtmp, otmp);
}
/* tame the revived monster if its ghost was tame */
if (ghost->mtame && !mtmp->mtame) {
mtmp2 = tamedog(mtmp, (struct obj *)0);
if (mtmp2) {
/* ghost's edog data is ignored */
mtmp2->mtame = ghost->mtame;
mtmp = mtmp2;
}
}
/* was ghost, now alive, it's all very confusing */
mtmp->mconf = 1;
/* separate ghost monster no longer exists */
mongone(ghost);
}
corpse->oattached = OATTACHED_NOTHING;
}
/* monster retains its name */
if (corpse->onamelth)
mtmp = christen_monst(mtmp, ONAME(corpse));
/* partially eaten corpse yields wounded monster */
if (corpse->oeaten)
mtmp->mhp = eaten_stat(mtmp->mhp, corpse);
/* track that this monster was revived at least once */
mtmp->mrevived = 1;
/* finally, get rid of the corpse--it's gone now */
switch (corpse->where) {
case OBJ_INVENT:
useup(corpse);
break;
case OBJ_FLOOR:
/* in case MON_AT+enexto for invisible mon */
x = corpse->ox, y = corpse->oy;
/* not useupf(), which charges */
if (corpse->quan > 1L)
corpse = splitobj(corpse, 1L);
delobj(corpse);
newsym(x, y);
break;
case OBJ_MINVENT:
m_useup(corpse->ocarry, corpse);
break;
case OBJ_CONTAINED:
obj_extract_self(corpse);
obfree(corpse, (struct obj *)0);
break;
default:
panic("revive");
}
return mtmp;
} }
STATIC_OVL void STATIC_OVL void
@@ -762,7 +788,7 @@ struct monst *mon;
if (youseeit) Strcpy(corpse, corpse_xname(otmp, TRUE)); if (youseeit) Strcpy(corpse, corpse_xname(otmp, TRUE));
/* for a merged group, only one is revived; should this be fixed? */ /* for a merged group, only one is revived; should this be fixed? */
if ((mtmp2 = revive(otmp)) != 0) { if ((mtmp2 = revive(otmp, !context.mon_moving)) != 0) {
++res; ++res;
if (youseeit) { if (youseeit) {
if (!once++) Strcpy(owner, if (!once++) Strcpy(owner,
@@ -1577,29 +1603,30 @@ struct obj *obj, *otmp;
break; break;
case WAN_UNDEAD_TURNING: case WAN_UNDEAD_TURNING:
case SPE_TURN_UNDEAD: case SPE_TURN_UNDEAD:
if (obj->otyp == EGG) if (obj->otyp == EGG) {
revive_egg(obj); revive_egg(obj);
else { } else if (obj->otyp == CORPSE) {
int corpsenm = (obj->otyp == CORPSE) ? int corpsenm = corpse_revive_type(obj);
corpse_revive_type(obj) : 0;
res = !!revive(obj); res = !!revive(obj, TRUE);
if (res && corpsenm && Role_if(PM_HEALER)) { if (res && Role_if(PM_HEALER)) {
boolean u_noticed = FALSE; boolean u_noticed = FALSE;
if (Hallucination && !Deaf) {
You_hear("the sound of a defibrillator."); if (Hallucination && !Deaf) {
u_noticed = TRUE; You_hear("the sound of a defibrillator.");
} else if (!Blind) { u_noticed = TRUE;
You("observe %s %s change dramatically.", } else if (!Blind) {
s_suffix(an(mons[corpsenm].mname)), You("observe %s %s change dramatically.",
nonliving(&mons[corpsenm]) ? s_suffix(an(mons[corpsenm].mname)),
nonliving(&mons[corpsenm]) ?
"motility" : "health"); "motility" : "health");
u_noticed = TRUE; u_noticed = TRUE;
}
if (u_noticed) {
makeknown(otmp->otyp);
exercise(A_WIS, TRUE);
}
} }
if (u_noticed) {
makeknown(otmp->otyp);
exercise(A_WIS, TRUE);
}
}
} }
break; break;
case WAN_OPENING: case WAN_OPENING: