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
avoid a panic splitbill when shopkeeper is trapped by the door
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

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 struct monst *FDECL(get_container_location, (struct obj *obj, int *, int *));
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 void FDECL(cancel_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. */
/* 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 */
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) {
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. */
/* NetHack may be freely redistributed. See license for details. */
@@ -403,7 +403,7 @@ int mm_flags;
if (enexto(&cc, mm->x, mm->y, mdat) &&
(!revive_corpses ||
!(otmp = sobj_at(CORPSE, cc.x, cc.y)) ||
!revive(otmp)))
!revive(otmp, FALSE)))
(void) makemon(mdat, cc.x, cc.y, mm_flags);
}
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. */
/* 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
* 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 *
animate_statue(statue, x, y, cause, fail_reason)
@@ -392,15 +404,16 @@ int *fail_reason;
{
int mnum = statue->corpsenm;
struct permonst *mptr = &mons[mnum];
struct monst *mon = 0;
struct monst *mon = 0, *shkp;
struct obj *item;
coord cc;
boolean historic = (Role_if(PM_ARCHEOLOGIST) && !context.mon_moving &&
(statue->spe & STATUE_HISTORIC)),
boolean historic = (Role_if(PM_ARCHEOLOGIST) &&
(statue->spe & STATUE_HISTORIC) != 0),
use_saved_traits;
char statuename[BUFSZ];
Strcpy(statuename,the(xname(statue)));
const char *comes_to_life;
char statuename[BUFSZ], tmpbuf[BUFSZ];
static const char historic_statue_is_gone[] =
"that the historic statue is now gone";
if (cant_revive(&mnum, TRUE, statue)) {
/* mnum has changed; we won't be animating this statue as itself */
@@ -449,10 +462,7 @@ int *fail_reason;
return (struct monst *)0;
}
/* in case statue is wielded and hero zaps stone-to-flesh at self */
if (statue->owornmask) remove_worn_item(statue, TRUE);
/* allow statues to be of a specific gender */
/* a non-montraits() statue might specify gender */
if (statue->spe & STATUE_MALE)
mon->female = FALSE;
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->onamelth)
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 */
while ((item = statue->cobj) != 0) {
obj_extract_self(item);
(void) add_to_minv(mon, item);
}
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);
/* 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 */
if (x == u.ux && y == u.uy &&
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. */
/* 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.
*/
struct monst *
revive(obj)
register struct obj *obj;
revive(corpse, by_hero)
struct obj *corpse;
boolean by_hero;
{
register struct monst *mtmp = (struct monst *)0;
struct permonst *mptr;
struct obj *container = (struct obj *)0;
int container_nesting = 0;
schar savetame = 0;
boolean recorporealization = FALSE;
boolean in_container = FALSE;
struct monst *mtmp = 0;
struct permonst *mptr;
struct obj *container;
coord xy;
xchar x, y;
int montype, container_nesting = 0;
if(obj->otyp == CORPSE) {
int montype = obj->corpsenm;
xchar x, y;
if (corpse->otyp != CORPSE) {
impossible("Attempting to revive %s?", xname(corpse));
return (struct monst *)0;
}
if (obj->where == OBJ_CONTAINED) {
/* deal with corpses in [possibly nested] containers */
struct monst *carrier;
int holder = 0;
x = y = 0;
if (corpse->where != OBJ_CONTAINED) {
/* only for invent, minvent, or floor */
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;
carrier = get_container_location(container, &holder,
&container_nesting);
switch(holder) {
case OBJ_MINVENT:
x = carrier->mx; y = carrier->my;
in_container = TRUE;
break;
case OBJ_INVENT:
x = u.ux; y = u.uy;
in_container = TRUE;
break;
case OBJ_FLOOR:
if (!get_obj_location(obj, &x, &y, CONTAINED_TOO))
return (struct monst *) 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");
}
}
container = corpse->ocontainer;
carrier = get_container_location(container, &holder,
&container_nesting);
switch (holder) {
case OBJ_MINVENT:
x = carrier->mx, y = carrier->my;
break;
case OBJ_INVENT:
x = u.ux, y = u.uy;
break;
case OBJ_FLOOR:
(void) get_obj_location(corpse, &x, &y, CONTAINED_TOO);
break;
default:
break; /* x,y are 0 */
}
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
@@ -762,7 +788,7 @@ struct monst *mon;
if (youseeit) Strcpy(corpse, corpse_xname(otmp, TRUE));
/* 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;
if (youseeit) {
if (!once++) Strcpy(owner,
@@ -1577,29 +1603,30 @@ struct obj *obj, *otmp;
break;
case WAN_UNDEAD_TURNING:
case SPE_TURN_UNDEAD:
if (obj->otyp == EGG)
revive_egg(obj);
else {
int corpsenm = (obj->otyp == CORPSE) ?
corpse_revive_type(obj) : 0;
res = !!revive(obj);
if (res && corpsenm && Role_if(PM_HEALER)) {
boolean u_noticed = FALSE;
if (Hallucination && !Deaf) {
You_hear("the sound of a defibrillator.");
u_noticed = TRUE;
} else if (!Blind) {
You("observe %s %s change dramatically.",
s_suffix(an(mons[corpsenm].mname)),
nonliving(&mons[corpsenm]) ?
if (obj->otyp == EGG) {
revive_egg(obj);
} else if (obj->otyp == CORPSE) {
int corpsenm = corpse_revive_type(obj);
res = !!revive(obj, TRUE);
if (res && Role_if(PM_HEALER)) {
boolean u_noticed = FALSE;
if (Hallucination && !Deaf) {
You_hear("the sound of a defibrillator.");
u_noticed = TRUE;
} else if (!Blind) {
You("observe %s %s change dramatically.",
s_suffix(an(mons[corpsenm].mname)),
nonliving(&mons[corpsenm]) ?
"motility" : "health");
u_noticed = TRUE;
}
if (u_noticed) {
makeknown(otmp->otyp);
exercise(A_WIS, TRUE);
}
u_noticed = TRUE;
}
if (u_noticed) {
makeknown(otmp->otyp);
exercise(A_WIS, TRUE);
}
}
}
break;
case WAN_OPENING: