From eaae10c837dc07a4712a2175cc35c01a873c70bf Mon Sep 17 00:00:00 2001 From: "nethack.rankin" Date: Fri, 22 Oct 2004 01:04:34 +0000 Subject: [PATCH] fix #U782 - undead turning in a shop [trunk only] Reported last December by . 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. --- doc/fixes34.4 | 1 + include/extern.h | 2 +- src/do.c | 4 +- src/mkroom.c | 4 +- src/trap.c | 105 ++++++++----- src/zap.c | 381 +++++++++++++++++++++++++---------------------- 6 files changed, 280 insertions(+), 217 deletions(-) diff --git a/doc/fixes34.4 b/doc/fixes34.4 index ace4cfb25..5314a755b 100644 --- a/doc/fixes34.4 +++ b/doc/fixes34.4 @@ -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 diff --git a/include/extern.h b/include/extern.h index d88ffcd09..7c53e4ab0 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 *)); diff --git a/src/do.c b/src/do.c index 927c3f7b0..0e230ff36 100644 --- a/src/do.c +++ b/src/do.c @@ -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); diff --git a/src/mkroom.c b/src/mkroom.c index 3e0f78827..419eae089 100644 --- a/src/mkroom.c +++ b/src/mkroom.c @@ -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 */ diff --git a/src/trap.c b/src/trap.c index 950b4c793..1ea19350b 100644 --- a/src/trap.c +++ b/src/trap.c @@ -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)); - /* 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); - delobj(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) { - 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"); + /* "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 - 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 */ + 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); + /* avoid hiding under nothing */ if (x == u.ux && y == u.uy && Upolyd && hides_under(youmonst.data) && !OBJ_AT(x, y)) diff --git a/src/zap.c b/src/zap.c index f5f56dba8..4cac707a3 100644 --- a/src/zap.c +++ b/src/zap.c @@ -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; - - if(obj->otyp == CORPSE) { - int montype = obj->corpsenm; - xchar x, y; - - if (obj->where == OBJ_CONTAINED) { - /* deal with corpses in [possibly nested] containers */ - struct monst *carrier; - int holder = 0; - - 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; - } + struct monst *mtmp = 0; + struct permonst *mptr; + struct obj *container; + coord xy; + xchar x, y; + int montype, container_nesting = 0; + + if (corpse->otyp != CORPSE) { + impossible("Attempting to revive %s?", xname(corpse)); + return (struct monst *)0; + } - if (MON_AT(x,y)) { - coord new_xy; + 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 = 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 */ + } + } + 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; - if (enexto(&new_xy, x, y, &mons[montype])) - x = new_xy.x, y = new_xy.y; - } + /* 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; + } - 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; + 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; - 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; - } + /* if this is caused by the hero there might be a shop charge */ + if (by_hero) { + struct monst *shkp = 0; - 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"); - } + 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); } - return mtmp; + 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: -- 2.40.0