From d8a0f734815d6d0d81151c7222e63efbfe3f7cd4 Mon Sep 17 00:00:00 2001 From: "nethack.rankin" Date: Sun, 1 Apr 2007 03:32:53 +0000 Subject: [PATCH] stone-to-flesh vs golem statues and figurines (trunk only) Back in in #U1216 suggested that statues of stone golems which are hit by stone-to-flesh spell should leave flesh golem corpses instead of meatballs. But they should actually have revived as flesh golems. Stone-to-flesh revival of golem statues and golem figurines didn't work as intended because golems other than flesh or leather were considered to be vegan/vegatarian, which caused them to produce meat rather than living monsters. Also, S-to-F of a leather golem statue worked as intended and created a flesh golem, but S-to-F of a leather golem firugine created a leather golem out of stone object. This fixes it so that any type of golem statue or golem figurine revives as a flesh golem. Two other bugs noticed and fixed: (1) S-to-F cast on self while a figurine of a non-veggy monster was "worn" in one of the three weapon slots triggered an "extract_nobj: object lost" panic similar to several similar cases which were recently fixed (that was 3.4.3; for development code, it gave "obfree: deleting worn obj" warning instead). (2) S-to-F activating a shop-owned figurine didn't charge for it. --- doc/fixes35.0 | 2 + src/trap.c | 8 +- src/zap.c | 220 ++++++++++++++++++++++++++++---------------------- 3 files changed, 129 insertions(+), 101 deletions(-) diff --git a/doc/fixes35.0 b/doc/fixes35.0 index 14bb9fe1a0..815628b62a 100644 --- a/doc/fixes35.0 +++ b/doc/fixes35.0 @@ -207,6 +207,8 @@ give better feedback when thrown shop-owned food gets used up taming a monster effect of negative AC on damage received was calculated differently than normal when deciding whether hero poly'd into pudding would split unicorn horn produced by revived monster will polymorph as if non-magic +stone-to-flesh on any golem statue or golem figurine creates flesh golem +stone-to-flesh which activates shop-owned figurine entails shop charges Platform- and/or Interface-Specific Fixes diff --git a/src/trap.c b/src/trap.c index a3aa0b372c..aa81fd7a87 100644 --- a/src/trap.c +++ b/src/trap.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)trap.c 3.5 2007/02/10 */ +/* SCCS Id: @(#)trap.c 3.5 2007/03/30 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -458,7 +458,7 @@ int *fail_reason; coord cc; boolean historic = (Role_if(PM_ARCHEOLOGIST) && (statue->spe & STATUE_HISTORIC) != 0), - use_saved_traits; + golem_xform = FALSE, use_saved_traits; const char *comes_to_life; char statuename[BUFSZ], tmpbuf[BUFSZ]; static const char historic_statue_is_gone[] = @@ -470,9 +470,10 @@ int *fail_reason; use_saved_traits = FALSE; } else if (is_golem(mptr) && cause == ANIMATE_SPELL) { /* statue of any golem hit by stone-to-flesh becomes flesh golem */ + golem_xform = (mptr != &mons[PM_FLESH_GOLEM]); mnum = PM_FLESH_GOLEM; mptr = &mons[PM_FLESH_GOLEM]; - use_saved_traits = FALSE; + use_saved_traits = (has_omonst(statue) && !golem_xform); } else { use_saved_traits = has_omonst(statue); } @@ -530,6 +531,7 @@ int *fail_reason; } comes_to_life = !canspotmon(mon) ? "disappears" : + golem_xform ? "turns into flesh" : (nonliving(mon->data) || is_vampshifter(mon)) ? "moves" : "comes to life"; if ((x == u.ux && y == u.uy) || cause == ANIMATE_SPELL) { diff --git a/src/zap.c b/src/zap.c index 1d9684fa1a..f701cbf832 100644 --- a/src/zap.c +++ b/src/zap.c @@ -21,6 +21,7 @@ extern boolean m_using; STATIC_DCL void FDECL(polyuse, (struct obj *,int,int)); STATIC_DCL void FDECL(create_polymon, (struct obj *,int)); +STATIC_DCL int FDECL(stone_to_flesh_obj, (struct obj *)); STATIC_DCL boolean FDECL(zap_updown, (struct obj *)); STATIC_DCL int FDECL(zhitm, (struct monst *,int,int,struct obj **)); STATIC_DCL void FDECL(zhitu, (int,int,const char *,XCHAR_P,XCHAR_P)); @@ -1475,7 +1476,6 @@ poly_obj(obj, id) /* ** we are now done adjusting the object ** */ - /* swap otmp for obj */ replace_object(obj, otmp); if (obj_location == OBJ_INVENT) { @@ -1524,6 +1524,121 @@ poly_obj(obj, id) return otmp; } +/* stone-to-flesh spell hits and maybe transforms or animates obj */ +STATIC_OVL int +stone_to_flesh_obj(obj) +struct obj *obj; +{ + int res = 1; /* affected object by default */ + struct permonst *ptr; + struct monst *mon; + struct obj *item; + xchar oox, ooy; + boolean smell = FALSE, golem_xform = FALSE; + + if (objects[obj->otyp].oc_material != MINERAL && + objects[obj->otyp].oc_material != GEMSTONE) return 0; + + (void) get_obj_location(obj, &oox, &ooy, 0); + /* add more if stone objects are added.. */ + switch (objects[obj->otyp].oc_class) { + case ROCK_CLASS: /* boulders and statues */ + case TOOL_CLASS: /* figurines */ + if (obj->otyp == BOULDER) { + obj = poly_obj(obj, HUGE_CHUNK_OF_MEAT); + smell = TRUE; + } else if (obj->otyp == STATUE || obj->otyp == FIGURINE) { + ptr = &mons[obj->corpsenm]; + if (is_golem(ptr)) { + golem_xform = (ptr != &mons[PM_FLESH_GOLEM]); + } else if (vegetarian(ptr)) { + /* Don't animate monsters that aren't flesh */ + obj = poly_obj(obj, MEATBALL); + smell = TRUE; + break; + } + if (obj->otyp == STATUE) { + /* animate_statue() forces all golems to become flesh golems */ + mon = animate_statue(obj, oox, ooy, ANIMATE_SPELL, (int *)0); + } else { /* (obj->otyp == FIGURINE) */ + if (golem_xform) ptr = &mons[PM_FLESH_GOLEM]; + mon = makemon(ptr, oox, ooy, NO_MINVENT); + if (mon) { + if (costly_spot(oox, ooy) && !obj->no_charge) { + if (costly_spot(u.ux, u.uy)) + addtobill(obj, carried(obj), FALSE, FALSE); + else + stolen_value(obj, oox, ooy, TRUE, FALSE); + } + if (obj->timed) obj_stop_timers(obj); + if (carried(obj)) + useup(obj); + else + delobj(obj); + if (cansee(mon->mx, mon->my)) + pline_The("figurine %sanimates!", + golem_xform ? "turns to flesh and " : ""); + } + } + if (mon) { + ptr = mon->data; + /* this golem handling is redundant... */ + if (is_golem(ptr) && ptr != &mons[PM_FLESH_GOLEM]) + (void)newcham(mon, &mons[PM_FLESH_GOLEM], TRUE, FALSE); + } else if ((ptr->geno & (G_NOCORPSE|G_UNIQ)) != 0) { + /* didn't revive but can't leave corpse either */ + res = 0; + } else { + /* unlikely to get here since genociding monsters also + sets the G_NOCORPSE flag; drop statue's contents */ + while ((item = obj->cobj) != 0) { + bypass_obj(item); /* make stone-to-flesh miss it */ + obj_extract_self(item); + place_object(item, oox, ooy); + } + obj = poly_obj(obj, CORPSE); + } + } else { /* miscellaneous tool or unexpected rock... */ + res = 0; + } + break; + /* maybe add weird things to become? */ + case RING_CLASS: /* some of the rings are stone */ + obj = poly_obj(obj, MEAT_RING); + smell = TRUE; + break; + case WAND_CLASS: /* marble wand */ + obj = poly_obj(obj, MEAT_STICK); + smell = TRUE; + break; + case GEM_CLASS: /* stones & gems */ + obj = poly_obj(obj, MEATBALL); + smell = TRUE; + break; + case WEAPON_CLASS: /* crysknife */ + /* fall through */ + default: + res = 0; + break; + } + + if (smell) { + /* non-meat eaters smell meat, meat eaters smell its flavor; + monks are considered non-meat eaters regardless of behavior; + other roles are non-meat eaters if they haven't broken + vegetarian conduct yet (or if poly'd into non-carnivorous/ + non-omnivorous form, regardless of whether it's herbivorous, + non-eating, or something stranger) */ + if (Role_if(PM_MONK) || !u.uconduct.unvegetarian || + !carnivorous(youmonst.data)) + Norep("You smell the odor of meat."); + else + Norep("You smell a delicious smell."); + } + newsym(oox, ooy); + return res; +} + /* * Object obj was hit by the effect of the wand/spell otmp. Return * non-zero if the wand/spell had any effect. @@ -1534,7 +1649,6 @@ struct obj *obj, *otmp; { int res = 1; /* affected object by default */ boolean learn_it = FALSE, maybelearnit; - xchar refresh_x, refresh_y; /* fundamental: a wand effect hitting itself doesn't do anything; otherwise we need to guard against accessing otmp after something @@ -1558,6 +1672,11 @@ struct obj *obj, *otmp; * UNDEAD_TURNING - When an undead creature gets killed via * undead turning, prevent its corpse from being * immediately revived by the same effect. + * STONE_TO_FLESH - If a statue can't be revived, its + * contents get dropped before turning it into + * meat; prevent those contents from being hit. + * retouch_equipment() - bypass flag is used to track which + * items have been handled (bhito isn't involved). * * The bypass bit on all objects is reset each turn, whenever * context.bypasses is set. @@ -1736,102 +1855,7 @@ struct obj *obj, *otmp; res = 0; break; case SPE_STONE_TO_FLESH: - refresh_x = obj->ox; refresh_y = obj->oy; - if (objects[obj->otyp].oc_material != MINERAL && - objects[obj->otyp].oc_material != GEMSTONE) { - res = 0; - break; - } - /* add more if stone objects are added.. */ - switch (objects[obj->otyp].oc_class) { - case ROCK_CLASS: /* boulders and statues */ - if (obj->otyp == BOULDER) { - obj = poly_obj(obj, HUGE_CHUNK_OF_MEAT); - goto smell; - } else if (obj->otyp == STATUE) { - xchar oox, ooy; - - (void) get_obj_location(obj, &oox, &ooy, 0); - refresh_x = oox; refresh_y = ooy; - if (vegetarian(&mons[obj->corpsenm])) { - /* Don't animate monsters that aren't flesh */ - obj = poly_obj(obj, MEATBALL); - goto smell; - } - if (!animate_statue(obj, oox, ooy, - ANIMATE_SPELL, (int *)0)) { - struct obj *item; -makecorpse: if (mons[obj->corpsenm].geno & - (G_NOCORPSE|G_UNIQ)) { - res = 0; - break; - } - /* Unlikely to get here since genociding - * monsters also sets the G_NOCORPSE flag. - * Drop the contents, poly_obj looses them. - */ - while ((item = obj->cobj) != 0) { - obj_extract_self(item); - place_object(item, oox, ooy); - } - obj = poly_obj(obj, CORPSE); - break; - } - } else { /* new rock class object... */ - /* impossible? */ - res = 0; - } - break; - case TOOL_CLASS: /* figurine */ - { - struct monst *mon; - xchar oox, ooy; - - if (obj->otyp != FIGURINE) { - res = 0; - break; - } - if (vegetarian(&mons[obj->corpsenm])) { - /* Don't animate monsters that aren't flesh */ - obj = poly_obj(obj, MEATBALL); - goto smell; - } - (void) get_obj_location(obj, &oox, &ooy, 0); - refresh_x = oox; refresh_y = ooy; - mon = makemon(&mons[obj->corpsenm], - oox, ooy, NO_MM_FLAGS); - if (mon) { - delobj(obj); - if (cansee(mon->mx, mon->my)) - pline_The("figurine animates!"); - break; - } - goto makecorpse; - } - /* maybe add weird things to become? */ - case RING_CLASS: /* some of the rings are stone */ - obj = poly_obj(obj, MEAT_RING); - goto smell; - case WAND_CLASS: /* marble wand */ - obj = poly_obj(obj, MEAT_STICK); - goto smell; - case GEM_CLASS: /* rocks & gems */ - obj = poly_obj(obj, MEATBALL); -smell: - if (herbivorous(youmonst.data) && - (!carnivorous(youmonst.data) || - Role_if(PM_MONK) || !u.uconduct.unvegetarian)) - Norep("You smell the odor of meat."); - else - Norep("You smell a delicious smell."); - break; - case WEAPON_CLASS: /* crysknife */ - /* fall through */ - default: - res = 0; - break; - } - newsym(refresh_x, refresh_y); + res = stone_to_flesh_obj(obj); break; default: impossible("What an interesting effect (%d)", otmp->otyp);