diff --git a/include/extern.h b/include/extern.h index 3df221569..14fc2d176 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1699,7 +1699,7 @@ E void FDECL(litroom, (BOOLEAN_P,struct obj *)); E void FDECL(do_genocide, (int)); E void FDECL(punish, (struct obj *)); E void NDECL(unpunish); -E boolean FDECL(cant_create, (int *, BOOLEAN_P)); +E boolean FDECL(cant_revive, (int *,BOOLEAN_P,struct obj *)); #ifdef WIZARD E boolean NDECL(create_particular); #endif diff --git a/include/mondata.h b/include/mondata.h index 8c37db245..dd37dc872 100644 --- a/include/mondata.h +++ b/include/mondata.h @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)mondata.h 3.4 2003/01/08 */ +/* SCCS Id: @(#)mondata.h 3.4 2003/11/29 */ /* Copyright (c) 1989 Mike Threepoint */ /* NetHack may be freely redistributed. See license for details. */ @@ -143,6 +143,10 @@ (ptr) == &mons[PM_HUMAN]) /* return TRUE if the monster tends to revive */ #define is_reviver(ptr) (is_rider(ptr) || (ptr)->mlet == S_TROLL) +/* monsters whose corpses and statues need special handling; + note that high priests and the Wizard of Yendor are flagged + as unique even though they really aren't; that's ok here */ +#define unique_corpstat(ptr) (((ptr)->geno & G_UNIQ) != 0) /* this returns the light's range, or 0 if none; if we add more light emitting monsters, we'll likely have to add a new light range field to mons[] */ diff --git a/src/bones.c b/src/bones.c index 585200a6a..f64a383ce 100644 --- a/src/bones.c +++ b/src/bones.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)bones.c 3.4 2003/09/06 */ +/* SCCS Id: @(#)bones.c 3.4 2003/11/29 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985,1993. */ /* NetHack may be freely redistributed. See license for details. */ @@ -64,17 +64,21 @@ boolean restore; if (otmp->cobj) resetobjs(otmp->cobj,restore); - if (((otmp->otyp != CORPSE || otmp->corpsenm < SPECIAL_PM) - && otmp->otyp != STATUE) - && (!otmp->oartifact || - (restore && (exist_artifact(otmp->otyp, ONAME(otmp)) - || is_quest_artifact(otmp))))) { - otmp->oartifact = 0; - otmp->onamelth = 0; - *ONAME(otmp) = '\0'; - } else if (otmp->oartifact && restore) - artifact_exists(otmp,ONAME(otmp),TRUE); - if (!restore) { + if (restore) { + /* artifact bookeeping needs to be done during + restore; other fixups are done while saving */ + if (otmp->oartifact) { + if (exist_artifact(otmp->otyp, ONAME(otmp)) || + is_quest_artifact(otmp)) { + /* prevent duplicate--revert to ordinary obj */ + otmp->oartifact = 0; + otmp->onamelth = 0; + *ONAME(otmp) = '\0'; + } else { + artifact_exists(otmp, ONAME(otmp), TRUE); + } + } + } else { /* saving */ /* do not zero out o_ids for ghost levels anymore */ if(objects[otmp->otyp].oc_uses_known) otmp->known = 0; @@ -83,6 +87,23 @@ boolean restore; otmp->invlet = 0; otmp->no_charge = 0; + /* strip user-supplied names */ + /* Statue and some corpse names are left intact, + presumeably in case they came from score file. + [TODO: this ought to be done differently--names + which came from such a source or came from any + stoned or killed monster should be flagged in + some manner; then we could just check the flag + here and keep "real" names (dead pets, &c) while + discarding player notes attached to statues.] */ + if (otmp->onamelth && + !(otmp->oartifact || otmp->otyp == STATUE || + (otmp->otyp == CORPSE && + otmp->corpsenm >= SPECIAL_PM))) { + otmp->onamelth = 0; + *ONAME(otmp) = '\0'; + } + if (otmp->otyp == SLIME_MOLD) goodfruit(otmp->spe); #ifdef MAIL else if (otmp->otyp == SCR_MAIL) otmp->spe = 1; @@ -91,8 +112,21 @@ boolean restore; else if (otmp->otyp == TIN) { /* make tins of unique monster's meat be empty */ if (otmp->corpsenm >= LOW_PM && - (mons[otmp->corpsenm].geno & G_UNIQ)) + unique_corpstat(&mons[otmp->corpsenm])) otmp->corpsenm = NON_PM; + } else if (otmp->otyp == CORPSE || + otmp->otyp == STATUE) { + int mnum = otmp->corpsenm; + + /* Discard incarnation details of unique + monsters (by passing null instead of otmp + for object), shopkeepers (by passing false + for revival flag), temple priests, and + vault guards in order to prevent corpse + revival or statue reanimation. */ + if (otmp->oattached == OATTACHED_MONST && + cant_revive(&mnum, FALSE, (struct obj *)0)) + otmp->oattached = OATTACHED_NOTHING; } else if (otmp->otyp == AMULET_OF_YENDOR) { /* no longer the real Amulet */ otmp->otyp = FAKE_AMULET_OF_YENDOR; diff --git a/src/makemon.c b/src/makemon.c index f4337b48a..f76099c9f 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)makemon.c 3.4 2003/09/06 */ +/* SCCS Id: @(#)makemon.c 3.4 2003/11/30 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -898,7 +898,8 @@ register int mmflags; mtmp->m_id = context.ident++; if (!mtmp->m_id) mtmp->m_id = context.ident++; /* ident overflowed */ set_mon_data(mtmp, ptr, 0); - if (mtmp->data->msound == MS_LEADER) + if (mtmp->data->msound == MS_LEADER && + quest_info(MS_LEADER) == mndx) quest_status.leader_m_id = mtmp->m_id; mtmp->mxlth = xlth; mtmp->mnum = mndx; @@ -987,13 +988,16 @@ register int mmflags; LS_MONSTER, (genericptr_t)mtmp); mitem = 0; /* extra inventory item for this monster */ + mtmp->cham = CHAM_ORDINARY; /* default is "not a shapechanger" */ if ((mcham = pm_to_cham(mndx)) != CHAM_ORDINARY) { - /* If you're protected with a ring, don't create - * any shape-changing chameleons -dgk - */ - if (Protection_from_shape_changers) - mtmp->cham = CHAM_ORDINARY; - else { + /* this is a shapechanger after all */ + if (Protection_from_shape_changers) { + ; /* stuck in its natural form (CHAM_ORDINARY) */ + } else { + /* new shapechanger starts out with random form + (this explicitly picks something from the normal + selection for current difficulty level rather + than from among shapechanger's preferred forms) */ mtmp->cham = mcham; (void) newcham(mtmp, rndmonst(), FALSE, FALSE); } diff --git a/src/mon.c b/src/mon.c index e3b2f4563..f679c11e2 100644 --- a/src/mon.c +++ b/src/mon.c @@ -145,9 +145,16 @@ STATIC_VAR short cham_to_pm[] = { PM_SANDESTIN, }; -#define KEEPTRAITS(mon) (mon->isshk || mon->mtame || \ - (mon->data->geno & G_UNIQ) || is_reviver(mon->data) || \ - (mon->m_id == quest_status.leader_m_id)) +/* for deciding whether corpse will carry along full monster data */ +#define KEEPTRAITS(mon) ((mon)->isshk || (mon)->mtame || \ + unique_corpstat(mon->data) || \ + is_reviver((mon)->data) || \ + /* normally leader the will be unique, */ \ + /* but he might have been polymorphed */ \ + (mon)->m_id == quest_status.leader_m_id || \ + /* special cancellation handling for these */ \ + (dmgtype((mon)->data, AD_SEDU) || \ + dmgtype((mon)->data, AD_SSEX))) /* Creates a monster corpse, a "special" corpse, or nothing if it doesn't * leave corpses. Monsters which leave "special" corpses should have diff --git a/src/read.c b/src/read.c index 4db3d8ff2..9b0fbae50 100644 --- a/src/read.c +++ b/src/read.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)read.c 3.4 2003/10/22 */ +/* SCCS Id: @(#)read.c 3.4 2003/11/29 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1813,9 +1813,10 @@ unpunish() * one, the disoriented creature becomes a zombie */ boolean -cant_create(mtype, revival) +cant_revive(mtype, revival, from_obj) int *mtype; boolean revival; +struct obj *from_obj; { /* SHOPKEEPERS can be revived now */ @@ -1826,6 +1827,12 @@ boolean revival; } else if (*mtype==PM_LONG_WORM_TAIL) { /* for create_particular() */ *mtype = PM_LONG_WORM; return TRUE; + } else if (from_obj && unique_corpstat(&mons[*mtype]) && + from_obj->oattached != OATTACHED_MONST) { + /* unique corpses (from bones or wizard mode wish) or + statues (bones or any wish) end up as shapechangers */ + *mtype = PM_DOPPELGANGER; + return TRUE; } return FALSE; } @@ -1890,7 +1897,7 @@ create_particular() pline(thats_enough_tries); } else { if (!randmonst) { - (void) cant_create(&which, FALSE); + (void) cant_revive(&which, FALSE, (struct obj *)0); whichpm = &mons[which]; } for (i = 0; i <= multi; i++) { diff --git a/src/trap.c b/src/trap.c index 909ebebee..dc9f7b850 100644 --- a/src/trap.c +++ b/src/trap.c @@ -389,60 +389,69 @@ xchar x, y; int cause; int *fail_reason; { - struct permonst *mptr; + int mnum = statue->corpsenm; + struct permonst *mptr = &mons[mnum]; struct monst *mon = 0; struct obj *item; coord cc; - boolean historic = (Role_if(PM_ARCHEOLOGIST) && !context.mon_moving && (statue->spe & STATUE_HISTORIC)); + boolean historic = (Role_if(PM_ARCHEOLOGIST) && !context.mon_moving && + (statue->spe & STATUE_HISTORIC)), + use_saved_traits; char statuename[BUFSZ]; Strcpy(statuename,the(xname(statue))); - if (statue->oxlth && statue->oattached == OATTACHED_MONST) { + if (cant_revive(&mnum, TRUE, statue)) { + /* mnum has changed; we won't be animating this statue as itself */ + if (mnum != PM_DOPPELGANGER) mptr = &mons[mnum]; + use_saved_traits = FALSE; + } else if (is_golem(mptr) && cause == ANIMATE_SPELL) { + /* statue of any golem hit by stone-to-flesh becomes flesh golem */ + mnum = PM_FLESH_GOLEM; + mptr = &mons[PM_FLESH_GOLEM]; + use_saved_traits = FALSE; + } else { + use_saved_traits = (statue->oxlth && + statue->oattached == OATTACHED_MONST); + } + + if (use_saved_traits) { + /* restore a petrified monster */ cc.x = x, cc.y = y; mon = montraits(statue, &cc); - if (mon && mon->mtame && !mon->isminion) - wary_dog(mon, TRUE); - } else { - /* statue of any golem hit with stone-to-flesh becomes flesh golem */ - if (is_golem(&mons[statue->corpsenm]) && cause == ANIMATE_SPELL) - mptr = &mons[PM_FLESH_GOLEM]; - else - mptr = &mons[statue->corpsenm]; - /* - * Guard against someone wishing for a statue of a unique monster - * (which is allowed in normal play) and then tossing it onto the - * [detected or guessed] location of a statue trap. Normally the - * uppermost statue is the one which would be activated. - */ - if ((mptr->geno & G_UNIQ) && cause != ANIMATE_SPELL) { - if (fail_reason) *fail_reason = AS_MON_IS_UNIQUE; - return (struct monst *)0; + if (mon) { + if (mon->mtame && !mon->isminion) + wary_dog(mon, TRUE); + /* might be bringing quest leader back to life */ + if (quest_status.leader_is_dead && + /* leader_is_dead implies that leader_m_id is valid */ + mon->m_id == quest_status.leader_m_id) + quest_status.leader_is_dead = FALSE; } - if (cause == ANIMATE_SPELL && - ((mptr->geno & G_UNIQ) || mptr->msound == MS_GUARDIAN)) { - /* Statues of quest guardians or unique monsters - * will not stone-to-flesh as the real thing. - */ + } else { + /* statues of unique monsters from bones or wishing end + up here (cant_revive() sets mnum to be doppelganger; + mptr reflects the original form for use by newcham()) */ + if ((mnum == PM_DOPPELGANGER && + mptr != &mons[PM_DOPPELGANGER]) || + /* block quest guards from other roles */ + (mptr->msound == MS_GUARDIAN && + quest_info(MS_GUARDIAN) != mnum)) { mon = makemon(&mons[PM_DOPPELGANGER], x, y, - NO_MINVENT|MM_NOCOUNTBIRTH|MM_ADJACENTOK); - if (mon) { - /* makemon() will set mon->cham to - * CHAM_ORDINARY if hero is wearing - * ring of protection from shape changers - * when makemon() is called, so we have to - * check the field before calling newcham(). - */ - if (mon->cham == CHAM_DOPPELGANGER) - (void) newcham(mon, mptr, FALSE, FALSE); - } + NO_MINVENT | MM_NOCOUNTBIRTH | MM_ADJACENTOK); + /* if hero has protection from shape changers, cham field will + be CHAM_ORDINARY; otherwise, set form to match the statue */ + if (mon && mon->cham != CHAM_ORDINARY) + (void) newcham(mon, mptr, FALSE, FALSE); } else mon = makemon(mptr, x, y, (cause == ANIMATE_SPELL) ? - (NO_MINVENT | MM_ADJACENTOK) : NO_MINVENT); + (NO_MINVENT | MM_ADJACENTOK) : NO_MINVENT); } if (!mon) { - if (fail_reason) *fail_reason = AS_NO_MON; + if (fail_reason) + *fail_reason = unique_corpstat(&mons[statue->corpsenm]) ? + AS_MON_IS_UNIQUE : AS_NO_MON; return (struct monst *)0; } @@ -468,6 +477,10 @@ int *fail_reason; /* mimic statue becomes seen mimic; other hiders won't be hidden */ if (mon->m_ap_type) seemimic(mon); else mon->mundetected = FALSE; + /* when reanimating a stoned monster, protection from shape changers + might be different now than it was when the monster was petrified */ + if (use_saved_traits) restore_cham(mon); + if ((x == u.ux && y == u.uy) || cause == ANIMATE_SPELL) { const char *comes_to_life = nonliving(mon->data) ? "moves" : "comes to life"; diff --git a/src/zap.c b/src/zap.c index 85b82ef8d..1c5a38278 100644 --- a/src/zap.c +++ b/src/zap.c @@ -552,11 +552,13 @@ revive(obj) register struct obj *obj; { 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; @@ -611,23 +613,37 @@ register struct obj *obj; x = new_xy.x, y = new_xy.y; } - if(cant_create(&montype, TRUE)) { - /* make a zombie or worm instead */ - mtmp = makemon(&mons[montype], x, y, - NO_MINVENT|MM_NOWAIT); - if (mtmp) { - mtmp->mhp = mtmp->mhpmax = 100; - mon_adjust_speed(mtmp, 2, (struct obj *)0); /* MFAST */ + 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 == CHAM_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) + coord xy; + + xy.x = x; xy.y = y; + mtmp = montraits(obj, &xy); + if (mtmp) { + if (mtmp->mtame && !mtmp->isminion) wary_dog(mtmp, TRUE); + /* might be reviving quest leader */ + if (quest_status.leader_is_dead && + /* _is_dead implies that _m_id is valid */ + mtmp->m_id == quest_status.leader_m_id) + quest_status.leader_is_dead = FALSE; + } } else - mtmp = makemon(&mons[montype], x, y, + mtmp = makemon(&mons[montype], x, y, NO_MINVENT|MM_NOWAIT|MM_NOCOUNTBIRTH); if (mtmp) { if (obj->oxlth && (obj->oattached == OATTACHED_M_ID)) { @@ -654,10 +670,6 @@ register struct obj *obj; /* Monster retains its name */ if (obj->onamelth) mtmp = christen_monst(mtmp, ONAME(obj)); - /* flag the quest leader as alive. */ - if (mtmp->data->msound == MS_LEADER || mtmp->m_id == - quest_status.leader_m_id) - quest_status.leader_is_dead = FALSE; } } if (mtmp) { @@ -665,6 +677,12 @@ register struct obj *obj; mtmp->mhp = eaten_stat(mtmp->mhp, obj); /* track that this monster was revived at least once */ mtmp->mrevived = 1; + /* in case this was a shapechanger corpse and + Protection_from_shape_changers happens to be + different now than it was when the monster + was killed (probably a no-op here since + KEEPTRAITS() doesn't include shapechangers) */ + restore_cham(mtmp); if (recorporealization) { /* If mtmp is revivification of former tame ghost*/