From: nethack.rankin Date: Tue, 5 Apr 2005 05:24:04 +0000 (+0000) Subject: alteration of shop-owned objects (trunk only) X-Git-Tag: MOVE2GIT~1292 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c7099cd772a7b326308b2693204e742f3d4432c8;p=nethack alteration of shop-owned objects (trunk only) [This ought to be suitable for the branch version too but I'm not going to spend the effort to migrate it there.] Recently From a bug report, reducing the value of a shop object via cursed enchantment was ignored by shopkeeper. This replaces the existing costly_cancel() routine with costly_alteration() which performs a similar task: bill for any item whose value has been made less. The hero owns the resulting object but must pay for the original one before being allowed to leave the shop. This covers the majority of cases where bill_dummy_object() was already being used: cancelling a charged or enchanted item, casting drain life at same, diluting potions or blanking scrolls or books by dipping them into a potion of water, dulling a weapon by engraving with it, eating unpaid food or opening unpaid tins, applying a cream pie to hit yourself with it in the face, applying a wand to break it, burning something by dipping it into lit potion of oil, and clearing potions by dipping a unicorn horn into them. The shop billing behavior for those actions hasn't been changed, just consolidated into one place which delivers a common message for them. This also covers many cases which weren't being handled: stripping wand or magic tool charges via cursed scroll of charging, reducing a charged ring's enchantment via same, reducing weapon or armor enchantment via cursed scroll of enchant weapon or armor, stripping an item's rustproofing via confused enchantment, making a crysknife revert to a worm tooth, unblessing potions of holy water or uncursing potions of unholy water. (That last one won't be billed if it's the result of prayer rather scroll, spell, or #dip.) And this tries to handle the reverse situation more thoroughly too: many actions which improve the value of an unpaid item now also cause the shop bill to be updated to reflect its new higher price. Aside from the basic enchanting and charging magic, it covers converting dragon scales into dragon scale mail and worm tooth into crysknife. Some things which might be expected to inflate shop prices, like rustproofing or increasing the number of charges in a wand, don't actually affect the price. And there are bound to be cases where the price is affected but I've overlooked. --- diff --git a/doc/fixes35.0 b/doc/fixes35.0 index 4c1bbed2e..15d49459c 100644 --- a/doc/fixes35.0 +++ b/doc/fixes35.0 @@ -80,6 +80,9 @@ allow use of the < command to try to exit a pit clean up messages when you stop levitation while riding a flying steed account for all attacks when determining max_passive_dmg dipping in acid can erode the dipped object +various actions--such as enchanting--performed on an unpaid shop object + either force the hero to buy the item (when its value is lowered) or + increase the current bill (when its value is raised) Platform- and/or Interface-Specific Fixes diff --git a/include/extern.h b/include/extern.h index 8e456d04d..b41f0f2e5 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1084,6 +1084,7 @@ E int NDECL(rndmonnum); E struct obj *FDECL(splitobj, (struct obj *,long)); E void FDECL(replace_object, (struct obj *,struct obj *)); E void FDECL(bill_dummy_object, (struct obj *)); +E void FDECL(costly_alteration, (struct obj *,int)); E struct obj *FDECL(mksobj, (int,BOOLEAN_P,BOOLEAN_P)); E int FDECL(bcsign, (struct obj *)); E int FDECL(weight, (struct obj *)); @@ -1878,6 +1879,7 @@ E struct obj *FDECL(find_oid, (unsigned)); E long FDECL(contained_cost, (struct obj *,struct monst *,long,BOOLEAN_P, BOOLEAN_P)); E long FDECL(contained_gold, (struct obj *)); E void FDECL(picked_container, (struct obj *)); +E void FDECL(alter_cost, (struct obj *,long)); E long FDECL(unpaid_cost, (struct obj *)); E boolean FDECL(billable, (struct monst **,struct obj *,CHAR_P,BOOLEAN_P)); E void FDECL(addtobill, (struct obj *,BOOLEAN_P,BOOLEAN_P,BOOLEAN_P)); diff --git a/include/hack.h b/include/hack.h index 1846e92a1..c9ae85eb8 100644 --- a/include/hack.h +++ b/include/hack.h @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)hack.h 3.5 2005/03/07 */ +/* SCCS Id: @(#)hack.h 3.5 2005/03/28 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -59,6 +59,23 @@ #define SELL_DELIBERATE (1) #define SELL_DONTSELL (2) +/* alteration types--keep in synch with costly_alteration(mkobj.c) */ +#define COST_CANCEL 0 /* standard cancellation */ +#define COST_DRAIN 1 /* drain life upon an object */ +#define COST_UNCHRG 2 /* cursed charging */ +#define COST_UNBLSS 3 /* unbless (devalues holy water) */ +#define COST_UNHOLY 4 /* uncurse (devalues unholy water) */ +#define COST_DECHNT 5 /* disenchant weapons or armor */ +#define COST_DEGRD 6 /* removal of rustproofing, dulling via engraving */ +#define COST_DILUTE 7 /* potion dilution */ +#define COST_ERASE 8 /* scroll or spellbook blanking */ +#define COST_BURN 9 /* dipped into flaming oil */ +#define COST_NUTRLZ 10 /* neutralized via unicorn horn */ +#define COST_DSTROY 11 /* wand breaking (bill first, useup later) */ +#define COST_SPLAT 12 /* cream pie to own face (ditto) */ +#define COST_BITE 13 /* start eating food */ +#define COST_OPEN 14 /* open tin */ + /* * This is the way the game ends. If these are rearranged, the arrays * in end.c and topten.c will need to be changed. Some parts of the diff --git a/src/apply.c b/src/apply.c index 5beb35615..f93e487b0 100644 --- a/src/apply.c +++ b/src/apply.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)apply.c 3.5 2005/01/05 */ +/* SCCS Id: @(#)apply.c 3.5 2005/03/28 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1132,7 +1132,7 @@ struct obj *obj; if (obj->where == OBJ_MINVENT ? cansee(x,y) : !Blind) pline("%s %s light!", Yname2(obj), otense(obj, "catch")); if (obj->otyp == POT_OIL) makeknown(obj->otyp); - if (obj->unpaid && costly_spot(u.ux, u.uy) && (obj->where == OBJ_INVENT)) { + if (carried(obj) && obj->unpaid && costly_spot(u.ux, u.uy)) { /* if it catches while you have it, then it's your tough luck */ check_unpaid(obj); verbalize("That's in addition to the cost of %s %s, of course.", @@ -1187,7 +1187,8 @@ struct obj *obj; Blind ? "." : " brightly!"); if (obj->unpaid && costly_spot(u.ux, u.uy) && obj->age == 20L * (long)objects[obj->otyp].oc_cost) { - const char *ithem = obj->quan > 1L ? "them" : "it"; + const char *ithem = (obj->quan > 1L) ? "them" : "it"; + verbalize("You burn %s, you bought %s!", ithem, ithem); bill_dummy_object(obj); } @@ -2582,10 +2583,8 @@ struct obj *obj; You_cant("see through all the sticky goop on your %s.", body_part(FACE)); } - if (obj->unpaid) { - verbalize("You used it, you bought it!"); - bill_dummy_object(obj); - } + /* useup() is appropriate, but we want costly_alteration()'s message */ + costly_alteration(obj, COST_SPLAT); obj_extract_self(obj); delobj(obj); return(0); @@ -2753,7 +2752,7 @@ do_break_wand(obj) */ if (obj->unpaid) { check_unpaid(obj); /* Extra charge for use */ - bill_dummy_object(obj); + costly_alteration(obj, COST_DSTROY); } current_wand = obj; /* destroy_item might reset this */ diff --git a/src/do.c b/src/do.c index b3c4693ae..d125bd455 100644 --- a/src/do.c +++ b/src/do.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)do.c 3.5 2004/09/10 */ +/* SCCS Id: @(#)do.c 3.5 2005/03/28 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -589,16 +589,20 @@ struct obj *obj; { if (!obj) { return; - } else if ((Is_container(obj) || obj->otyp == STATUE) && obj->cobj) { + } else if (Has_contents(obj)) { struct obj *contents; - for(contents=obj->cobj; contents; contents=contents->nobj) + + for (contents = obj->cobj; contents; contents = contents->nobj) obj_no_longer_held(contents); } - switch(obj->otyp) { + switch (obj->otyp) { case CRYSKNIFE: /* KMH -- Fixed crysknives have only 10% chance of reverting */ /* only changes when not held by player or monster */ if (!obj->oerodeproof || !rn2(10)) { + /* if monsters aren't moving, assume player is responsible */ + if (!context.mon_moving && !program_state.gameover) + costly_alteration(obj, COST_DEGRD); obj->otyp = WORM_TOOTH; obj->oerodeproof = 0; } diff --git a/src/eat.c b/src/eat.c index 5368ceb9a..495b4d4ea 100644 --- a/src/eat.c +++ b/src/eat.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)eat.c 3.5 2005/03/09 */ +/* SCCS Id: @(#)eat.c 3.5 2005/03/28 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -15,7 +15,7 @@ STATIC_PTR int NDECL(eatmdone); STATIC_PTR int NDECL(eatfood); -STATIC_PTR void FDECL(costly_tin, (const char*)); +STATIC_PTR void FDECL(costly_tin, (int)); STATIC_PTR int NDECL(opentin); STATIC_PTR int NDECL(unfaint); @@ -293,13 +293,7 @@ register struct obj *otmp; } if (!otmp->oeaten) { - if(((!carried(otmp) && costly_spot(otmp->ox, otmp->oy) && - !otmp->no_charge) - || otmp->unpaid)) { - /* create a dummy duplicate to put on bill */ - verbalize("You bit it, you bought it!"); - bill_dummy_object(otmp); - } + costly_alteration(otmp, COST_BITE); otmp->oeaten = (otmp->otyp == CORPSE ? mons[otmp->corpsenm].cnutrit : objects[otmp->otyp].oc_nutrition); @@ -992,21 +986,19 @@ violated_vegetarian() * will split() context.tin.tin if necessary */ STATIC_PTR void -costly_tin(verb) - const char* verb; /* if 0, the verb is "open" */ +costly_tin(alter_type) +int alter_type; /* COST_xxx */ { - if(((!carried(context.tin.tin) && - costly_spot(context.tin.tin->ox, context.tin.tin->oy) && - !context.tin.tin->no_charge) - || context.tin.tin->unpaid)) { - verbalize("You %s it, you bought it!", verb ? verb : "open"); - if(context.tin.tin->quan > 1L) { - context.tin.tin = splitobj(context.tin.tin, 1L); - if (context.tin.tin) - context.tin.o_id = context.tin.tin->o_id; - } - bill_dummy_object(context.tin.tin); + struct obj *tin = context.tin.tin; + + if (carried(tin) ? tin->unpaid : + (costly_spot(tin->ox, tin->oy) && !tin->no_charge)) { + if (tin->quan > 1L) { + tin = context.tin.tin = splitobj(tin, 1L); + context.tin.o_id = tin->o_id; } + costly_alteration(tin, alter_type); + } } int @@ -1106,7 +1098,7 @@ opentin() /* called during each move whilst opening a tin */ if(context.tin.tin->otrapped || (context.tin.tin->cursed && context.tin.tin->spe != -1 && !rn2(8))) { b_trapped("tin", 0); - costly_tin("destroyed"); + costly_tin(COST_DSTROY); goto use_me; } @@ -1116,7 +1108,7 @@ opentin() /* called during each move whilst opening a tin */ if (mnum == NON_PM) { pline("It turns out to be empty."); context.tin.tin->dknown = context.tin.tin->known = TRUE; - costly_tin((const char*)0); + costly_tin(COST_OPEN); goto use_me; } r = tin_variety(context.tin.tin); @@ -1139,7 +1131,7 @@ opentin() /* called during each move whilst opening a tin */ if (!Hallucination) context.tin.tin->dknown = context.tin.tin->known = TRUE; if (flags.verbose) You("discard the open tin."); - costly_tin((const char*)0); + costly_tin(COST_OPEN); goto use_me; } /* in case stop_occupation() was called on previous meal */ @@ -1162,7 +1154,7 @@ opentin() /* called during each move whilst opening a tin */ cpostfx(mnum); /* charge for one at pre-eating cost */ - costly_tin((const char*)0); + costly_tin(COST_OPEN); /* check for vomiting added by GAN 01/16/87 */ if(tintxts[r].nut < 0) make_vomiting((long)rn1(15,10), FALSE); @@ -1186,12 +1178,12 @@ opentin() /* called during each move whilst opening a tin */ context.tin.tin->dknown = context.tin.tin->known = TRUE; if (flags.verbose) You("discard the open tin."); - costly_tin((const char*)0); + costly_tin(COST_OPEN); goto use_me; } context.tin.tin->dknown = context.tin.tin->known = TRUE; - costly_tin((const char*)0); + costly_tin(COST_OPEN); if (!context.tin.tin->cursed) pline("This makes you feel like %s!", diff --git a/src/engrave.c b/src/engrave.c index 3d0a38ddc..30cfc23a7 100644 --- a/src/engrave.c +++ b/src/engrave.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)engrave.c 3.5 2004/01/03 */ +/* SCCS Id: @(#)engrave.c 3.5 2005/03/28 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1031,13 +1031,7 @@ doengrave() * "ere", then "th". */ pline("%s dull.", Yobjnam2(otmp, "get")); - if (otmp->unpaid) { - struct monst *shkp = shop_keeper(*u.ushops); - if (shkp) { - You("damage it, you pay for it!"); - bill_dummy_object(otmp); - } - } + costly_alteration(otmp, COST_DEGRD); if (len > maxelen) { multi = -maxelen; otmp->spe = -3; diff --git a/src/mkobj.c b/src/mkobj.c index 35fd7bccb..d84ff79da 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -315,6 +315,11 @@ struct obj *otmp; * an object which is different from what it started out as; the "I x" * command needs to display the original object. * + * [BUG: The cost might end up being different if item originally had a + * surcharge but doesn't get one now or vice versa. Having the dummy keep + * the same o_id value as the original would avoid this; is that viable? + * (Mustn't give the original itself a new o_id, so can't just swap them.)] + * * The caller is responsible for checking otmp->unpaid and * costly_spot(u.ux, u.uy). This function will make otmp no charge. * @@ -349,6 +354,74 @@ register struct obj *otmp; return; } +/* alteration types; must match COST_xxx macros in hack.h */ +static const char * const alteration_verbs[] = { + "cancel", "drain", "uncharge", "unbless", "uncurse", + "disenchant", "degrade", "dilute", "erase", "burn", + "neutralize", "destroy", "splatter", "bite", "open", +}; + +/* possibly bill for an object which the player has just modified */ +void +costly_alteration(obj, alter_type) +struct obj *obj; +int alter_type; +{ + xchar ox, oy; + char objroom; + const char *those, *them, *what; + struct monst *shkp = 0; + + if (alter_type < 0 || alter_type >= SIZE(alteration_verbs)) { + impossible("invalid alteration type (%d)", alter_type); + alter_type = 0; + } + + ox = oy = 0; /* lint suppression */ + objroom = '\0'; /* ditto */ + if (carried(obj) || obj->where == OBJ_FREE) { + /* OBJ_FREE catches obj_no_longer_held()'s transformation + of crysknife back into worm tooth; the object has been + removed from inventory but not necessarily placed at + its new location yet--the unpaid flag will still be set + if this item is owned by a shop */ + if (!obj->unpaid) return; + } else { + /* this get_obj_location shouldn't fail, but if it does, + use hero's location */ + if (!get_obj_location(obj, &ox, &oy, CONTAINED_TOO)) + ox = u.ux, oy = u.uy; + objroom = *in_rooms(ox, oy, SHOPBASE); + /* if no shop cares about it, we're done */ + if (!billable(&shkp, obj, objroom, FALSE)) return; + } + + if (obj->quan == 1L) + those = "that", them = "it"; + else + those = "those", them = "them"; + + switch (obj->where) { + case OBJ_FREE: /* obj_no_longer_held() */ + case OBJ_INVENT: + what = simple_typename(obj->otyp); + if (obj->quan != 1L) what = makeplural(what); + verbalize("You %s %s %s, you pay for %s!", + alteration_verbs[alter_type], those, what, them); + bill_dummy_object(obj); + break; + case OBJ_FLOOR: + if (costly_spot(u.ux, u.uy) && objroom == *u.ushops) { + verbalize("You %s %s, you pay for %s!", + alteration_verbs[alter_type], those, them); + bill_dummy_object(obj); + } else { + (void) stolen_value(obj, ox, oy, FALSE, FALSE); + } + break; + } +} + static const char dknowns[] = { WAND_CLASS, RING_CLASS, POTION_CLASS, SCROLL_CLASS, GEM_CLASS, SPBOOK_CLASS, WEAPON_CLASS, TOOL_CLASS, 0 diff --git a/src/potion.c b/src/potion.c index e269bdb78..1c5310497 100644 --- a/src/potion.c +++ b/src/potion.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)potion.c 3.5 2004/12/21 */ +/* SCCS Id: @(#)potion.c 3.5 2005/03/26 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1571,20 +1571,14 @@ register struct obj *obj; } pline("%s%s.", Yobjnam2(obj,"dilute"), obj->odiluted ? " further" : ""); - if(obj->unpaid && costly_spot(u.ux, u.uy)) { - You("dilute it, you pay for it."); - bill_dummy_object(obj); - } + costly_alteration(obj, COST_DILUTE); if (obj->odiluted) { obj->odiluted = 0; -#ifdef UNIXPC obj->blessed = FALSE; obj->cursed = FALSE; -#else - obj->blessed = obj->cursed = FALSE; -#endif obj->otyp = POT_WATER; - } else obj->odiluted++; + } else + obj->odiluted++; update_inventory(); return TRUE; case SCROLL_CLASS: @@ -1593,15 +1587,10 @@ register struct obj *obj; && obj->otyp != SCR_MAIL #endif ) { - if (!Blind) { - boolean oq1 = obj->quan == 1L; - pline_The("scroll%s %s.", - oq1 ? "" : "s", otense(obj, "fade")); - } - if(obj->unpaid && costly_spot(u.ux, u.uy)) { - You("erase it, you pay for it."); - bill_dummy_object(obj); - } + if (!Blind) + pline_The("scroll%s %s.", + plur(obj->quan), otense(obj, "fade")); + costly_alteration(obj, COST_ERASE); obj->otyp = SCR_BLANK_PAPER; obj->spe = 0; update_inventory(); @@ -1611,18 +1600,15 @@ register struct obj *obj; if (obj->otyp != SPE_BLANK_PAPER) { if (obj->otyp == SPE_BOOK_OF_THE_DEAD) { - pline("%s suddenly heats up; steam rises and it remains dry.", - The(xname(obj))); + pline( + "%s suddenly heats up; steam rises and it remains dry.", + The(xname(obj))); } else { - if (!Blind) { - boolean oq1 = obj->quan == 1L; - pline_The("spellbook%s %s.", - oq1 ? "" : "s", otense(obj, "fade")); - } - if(obj->unpaid && costly_spot(u.ux, u.uy)) { - You("erase it, you pay for it."); - bill_dummy_object(obj); - } + if (!Blind) + pline_The("spellbook%s %s.", + plur(obj->quan), + otense(obj, "fade")); + costly_alteration(obj, COST_ERASE); obj->otyp = SPE_BLANK_PAPER; update_inventory(); } @@ -1698,14 +1684,17 @@ dodip() potion->in_use = TRUE; /* assume it will be used up */ if(potion->otyp == POT_WATER) { boolean useeit = !Blind || (obj == ublindf && Blindfolded_only); + if (potion->blessed) { if (obj->cursed) { if (useeit) pline("%s %s.", Yobjnam2(obj, "softly glow"), hcolor(NH_AMBER)); + obj->bknown = 1; + if (obj->otyp == POT_WATER && obj->unpaid) + costly_alteration(obj, COST_UNHOLY); uncurse(obj); - obj->bknown=1; poof: if(!(objects[potion->otyp].oc_name_known) && !(objects[potion->otyp].oc_uname)) @@ -1719,8 +1708,10 @@ dodip() Yobjnam2(obj, "softly glow"), index(vowels, *tmp) ? "n" : "", tmp); } + obj->bknown = 1; bless(obj); - obj->bknown=1; + if (obj->otyp == POT_WATER && obj->unpaid) + alter_cost(obj, 0L); goto poof; } } else if (potion->cursed) { @@ -1729,8 +1720,10 @@ dodip() pline("%s %s.", Yobjnam2(obj, "glow"), hcolor((const char *)"brown")); + obj->bknown = 1; + if (obj->otyp == POT_WATER && obj->unpaid) + costly_alteration(obj, COST_UNBLSS); unbless(obj); - obj->bknown=1; goto poof; } else if(!obj->cursed) { if (useeit) { @@ -1739,8 +1732,10 @@ dodip() Yobjnam2(obj, "glow"), index(vowels, *tmp) ? "n" : "", tmp); } + obj->bknown = 1; curse(obj); - obj->bknown=1; + if (obj->otyp == POT_WATER && obj->unpaid) + alter_cost(obj, 0L); goto poof; } } else @@ -1902,8 +1897,8 @@ dodip() /* catch_lit does all the work if true */ } else if (obj->oerodeproof || obj_resists(obj, 5, 95) || !is_flammable(obj) || obj->oclass == FOOD_CLASS) { - pline("%s %s to burn for a moment.", - Yname2(obj), otense(obj, "seem")); + pline("%s %s to burn for a moment but %s unharmed.", + Yname2(obj), otense(obj, "seem"), otense(obj, "are")); } else { if ((omat == PLASTIC || omat == PAPER) && !obj->oartifact) obj->oeroded = MAX_ERODE; @@ -1911,18 +1906,13 @@ dodip() obj->oeroded == MAX_ERODE ? "destroys" : "damages", yname(obj), obj->oeroded == MAX_ERODE ? '!' : '.'); + costly_alteration(obj, COST_BURN); if (obj->oeroded == MAX_ERODE) { if (obj->owornmask) remove_worn_item(obj, TRUE); obj_extract_self(obj); obfree(obj, (struct obj *)0); obj = (struct obj *) 0; } else { - /* we know it's carried */ - if (obj->unpaid) { - /* create a dummy duplicate to put on bill */ - verbalize("You burnt it, you bought it!"); - bill_dummy_object(obj); - } obj->oeroded++; } } @@ -2009,11 +1999,8 @@ dodip() if (potion->quan > 1L) { singlepotion = splitobj(potion, 1L); } else singlepotion = potion; - - if(singlepotion->unpaid && costly_spot(u.ux, u.uy)) { - You("use it, you pay for it."); - bill_dummy_object(singlepotion); - } + + costly_alteration(singlepotion, COST_NUTRLZ); singlepotion->otyp = mixture; singlepotion->blessed = 0; if (mixture == POT_WATER) diff --git a/src/read.c b/src/read.c index 25e73c129..4a572e1c3 100644 --- a/src/read.c +++ b/src/read.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)read.c 3.5 2004/05/07 */ +/* SCCS Id: @(#)read.c 3.5 2005/03/28 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -150,14 +150,15 @@ STATIC_OVL void stripspe(obj) register struct obj *obj; { - if (obj->blessed) pline(nothing_happens); - else { - if (obj->spe > 0) { - obj->spe = 0; - if (obj->otyp == OIL_LAMP || obj->otyp == BRASS_LANTERN) - obj->age = 0; - pline("%s briefly.", Yobjnam2(obj, "vibrate")); - } else pline(nothing_happens); + if (obj->blessed || obj->spe <= 0) { + pline(nothing_happens); + } else { + /* order matters: message, shop handling, actual transformation */ + pline("%s briefly.", Yobjnam2(obj, "vibrate")); + costly_alteration(obj, COST_UNCHRG); + obj->spe = 0; + if (obj->otyp == OIL_LAMP || obj->otyp == BRASS_LANTERN) + obj->age = 0; } } @@ -257,6 +258,10 @@ int curse_bless; } if (obj->spe >= lim) p_glow2(obj, NH_BLUE); else p_glow1(obj); +#if 0 /*[shop price doesn't vary by charge count]*/ + /* update shop bill to reflect new higher price */ + if (obj->unpaid) alter_cost(obj, 0L); +#endif } } else if (obj->oclass == RING_CLASS && @@ -278,12 +283,15 @@ int curse_bless; RIGHT_RING) : 0L; pline("%s spins %sclockwise for a moment.", Yname2(obj), s < 0 ? "counter" : ""); + if (s < 0) costly_alteration(obj, COST_DECHNT); /* cause attributes and/or properties to be updated */ if (is_on) Ring_off(obj); obj->spe += s; /* update the ring while it's off */ if (is_on) setworn(obj, mask), Ring_on(obj); /* oartifact: if a touch-sensitive artifact ring is ever created the above will need to be revised */ + /* update shop bill to reflect new higher price */ + if (s > 0 && obj->unpaid) alter_cost(obj, 0L); } } else if (obj->oclass == TOOL_CLASS) { @@ -637,11 +645,12 @@ struct obj *sobj; int seffects(sobj) -register struct obj *sobj; +struct obj *sobj; { - register int cval; - register boolean confused = (Confusion != 0); - register struct obj *otmp; + int cval; + boolean confused = (Confusion != 0), + old_erodeproof, new_erodeproof; + struct obj *otmp; if (objects[sobj->otyp].oc_magic) exercise(A_WIS, TRUE); /* just for trying */ @@ -650,7 +659,8 @@ register struct obj *sobj; case SCR_MAIL: known = TRUE; if (sobj->spe) - pline("This seems to be junk mail addressed to the finder of the Eye of Larn."); + pline( + "This seems to be junk mail addressed to the finder of the Eye of Larn."); /* note to the puzzled: the game Larn actually sends you junk * mail if you win! */ @@ -673,7 +683,9 @@ register struct obj *sobj; return(1); } if(confused) { - otmp->oerodeproof = !(sobj->cursed); + old_erodeproof = (otmp->oerodeproof != 0); + new_erodeproof = !sobj->cursed; + otmp->oerodeproof = 0; /* for messages */ if(Blind) { otmp->rknown = FALSE; pline("%s warm for a moment.", @@ -683,16 +695,22 @@ register struct obj *sobj; pline("%s covered by a %s %s %s!", Yobjnam2(otmp, "are"), sobj->cursed ? "mottled" : "shimmering", - hcolor(sobj->cursed ? NH_BLACK : NH_GOLDEN), + hcolor(sobj->cursed ? NH_BLACK : NH_GOLDEN), sobj->cursed ? "glow" : (is_shield(otmp) ? "layer" : "shield")); } - if (otmp->oerodeproof && + if (new_erodeproof && (otmp->oeroded || otmp->oeroded2)) { otmp->oeroded = otmp->oeroded2 = 0; pline("%s as good as new!", Yobjnam2(otmp, Blind ? "feel" : "look")); } + if (old_erodeproof && !new_erodeproof) { + /* restore old_erodeproof before shop charges */ + otmp->oerodeproof = 1; + costly_alteration(otmp, COST_DEGRD); + } + otmp->oerodeproof = new_erodeproof ? 1 : 0; break; } /* elven armor vibrates warningly when enchanted beyond a limit */ @@ -739,29 +757,35 @@ register struct obj *sobj; /* assumes same order */ otmp->otyp = GRAY_DRAGON_SCALE_MAIL + otmp->otyp - GRAY_DRAGON_SCALES; - otmp->cursed = 0; if (sobj->blessed) { - otmp->spe++; - otmp->blessed = 1; - } + otmp->spe++; + if (!otmp->blessed) bless(otmp); + } else if (otmp->cursed) + uncurse(otmp); otmp->known = 1; setworn(otmp, W_ARM); + if (otmp->unpaid) alter_cost(otmp, 0L); /* shop bill */ break; } pline("%s %s%s%s%s for a %s.", - Yname2(otmp), - s == 0 ? "violently " : nul, - otense(otmp, Blind ? "vibrate" : "glow"), - (!Blind && !same_color) ? " " : nul, - (Blind || same_color) ? nul : hcolor(sobj->cursed ? NH_BLACK : NH_SILVER), - (s*s>1) ? "while" : "moment"); - otmp->cursed = sobj->cursed; - if (!otmp->blessed || sobj->cursed) - otmp->blessed = sobj->blessed; + Yname2(otmp), + s == 0 ? "violently " : nul, + otense(otmp, Blind ? "vibrate" : "glow"), + (!Blind && !same_color) ? " " : nul, + (Blind || same_color) ? nul : + hcolor(sobj->cursed ? NH_BLACK : NH_SILVER), + (s * s > 1) ? "while" : "moment"); + /* [this cost handling will need updating if shop pricing is + ever changed to care about curse/bless status of armor] */ + if (s < 0) costly_alteration(otmp, COST_DECHNT); + if (sobj->cursed && !otmp->cursed) curse(otmp); + else if (sobj->blessed && !otmp->blessed) bless(otmp); if (s) { otmp->spe += s; adj_abon(otmp, s); known = otmp->known; + /* update shop bill to reflect new higher price */ + if (s > 0 && otmp->unpaid) alter_cost(otmp, 0L); } if ((otmp->spe > (special_armor ? 5 : 3)) && @@ -781,8 +805,16 @@ register struct obj *sobj; exercise(A_CON, FALSE); return(1); } - otmp->oerodeproof = sobj->cursed; + old_erodeproof = (otmp->oerodeproof != 0); + new_erodeproof = (sobj->cursed != 0); + otmp->oerodeproof = 0; /* for messages */ p_glow2(otmp, NH_PURPLE); + if (old_erodeproof && !new_erodeproof) { + /* restore old_erodeproof before shop charges */ + otmp->oerodeproof = 1; + costly_alteration(otmp, COST_DEGRD); + } + otmp->oerodeproof = new_erodeproof ? 1 : 0; break; } if(!sobj->cursed || !otmp || !otmp->cursed) { @@ -850,7 +882,8 @@ register struct obj *sobj; break; case SCR_SCARE_MONSTER: case SPE_CAUSE_FEAR: - { register int ct = 0; + { + register int ct = 0; register struct monst *mtmp; for(mtmp = fmon; mtmp; mtmp = mtmp->nmon) { @@ -884,17 +917,16 @@ register struct obj *sobj; break; case SCR_REMOVE_CURSE: case SPE_REMOVE_CURSE: - { register struct obj *obj; - if(confused) - if (Hallucination) - You_feel("the power of the Force against you!"); - else - You_feel("like you need some help."); - else - if (Hallucination) - You_feel("in touch with the Universal Oneness."); - else - You_feel("like someone is helping you."); + { + register struct obj *obj; + + You_feel(!Hallucination ? + (!confused ? + "like someone is helping you." : + "like you need some help.") : + (!confused ? + "in touch with the Universal Oneness." : + "the power of the Force against you!")); if (sobj->cursed) { pline_The("scroll disintegrates."); @@ -932,8 +964,39 @@ register struct obj *sobj; if (sobj->blessed || wornmask || obj->otyp == LOADSTONE || (obj->otyp == LEASH && obj->leashmon)) { + unsigned save_bknown, save_cursed, save_blessed; + boolean was_cursed = !!obj->cursed, + was_blessed = !!obj->blessed, + was_normal = !(was_cursed || was_blessed); + if(confused) blessorcurse(obj, 2); else uncurse(obj); + /* water price varies by curse/bless status */ + if (obj->unpaid && obj->otyp == POT_WATER) { + if ((was_cursed && !obj->cursed) || + (was_blessed && !obj->blessed)) { + /* make `Ix' more specific for this item */ + save_bknown = obj->bknown; + obj->bknown = 1; + /* temporarily restore curse/bless to + obtain the right shop price (if potion + went from cursed directly to blessed + or vice versa its price didn't change + but hero will have to buy it anyway) */ + save_cursed = obj->cursed; + obj->cursed = was_cursed ? 1 : 0; + save_blessed = obj->blessed; + obj->blessed = was_blessed ? 1 : 0; + costly_alteration(obj, was_cursed ? + COST_UNHOLY : COST_UNBLSS); + obj->bknown = save_bknown; + obj->cursed = save_cursed; + obj->blessed = save_blessed; + } else if (was_normal && + (obj->blessed || obj->cursed)) { + alter_cost(obj, 0L); + } + } /* unpaid water */ } } } @@ -957,10 +1020,11 @@ register struct obj *sobj; */ break; case SCR_ENCHANT_WEAPON: - if(uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep)) - && confused) { - /* oclass check added 10/25/86 GAN */ - uwep->oerodeproof = !(sobj->cursed); + if (confused && uwep && + (uwep->oclass == WEAPON_CLASS || is_weptool(uwep))) { + old_erodeproof = (uwep->oerodeproof != 0); + new_erodeproof = !sobj->cursed; + uwep->oerodeproof = 0; /* for messages */ if (Blind) { uwep->rknown = FALSE; Your("weapon feels warm for a moment."); @@ -977,12 +1041,19 @@ register struct obj *sobj; pline("%s as good as new!", Yobjnam2(uwep, Blind ? "feel" : "look")); } - } else return !chwepon(sobj, - sobj->cursed ? -1 : - !uwep ? 1 : - uwep->spe >= 9 ? (rn2(uwep->spe) == 0) : - sobj->blessed ? rnd(3-uwep->spe/3) : 1); - break; + if (old_erodeproof && !new_erodeproof) { + /* restore old_erodeproof before shop charges */ + uwep->oerodeproof = 1; + costly_alteration(uwep, COST_DEGRD); + } + uwep->oerodeproof = new_erodeproof ? 1 : 0; + break; + } + return !chwepon(sobj, + sobj->cursed ? -1 : + !uwep ? 1 : + uwep->spe >= 9 ? (rn2(uwep->spe) == 0) : + sobj->blessed ? rnd(3 - uwep->spe / 3) : 1); case SCR_TAMING: case SPE_CHARM_MONSTER: if (u.uswallow) { @@ -1280,8 +1351,9 @@ register struct obj *sobj; } punish(sobj); break; - case SCR_STINKING_CLOUD: { - coord cc; + case SCR_STINKING_CLOUD: + { + coord cc; You("have found a scroll of stinking cloud!"); known = TRUE; diff --git a/src/shk.c b/src/shk.c index ec04143fc..11cb465a7 100644 --- a/src/shk.c +++ b/src/shk.c @@ -2005,6 +2005,31 @@ register struct monst *shkp; return tmp; } +/* called when an item's value has been enhanced; if it happens to be + on any shop bill, update that bill to reflect the new higher price + [if the new price drops for some reason, keep the old one in place] */ +void +alter_cost(obj, amt) +struct obj *obj; +long amt; /* if 0, use regular shop pricing, otherwise force amount; + if negative, use abs(amt) even if it's less than old cost */ +{ + struct bill_x *bp = 0; + struct monst *shkp; + long new_price; + + for (shkp = next_shkp(fmon, TRUE); shkp; shkp = next_shkp(shkp, TRUE)) + if ((bp = onbill(obj, shkp, TRUE)) != 0) { + new_price = !amt ? get_cost(obj, shkp) : (amt < 0L) ? -amt : amt; + if (new_price > bp->price || amt < 0L) { + bp->price = new_price; + update_inventory(); + } + break; /* done */ + } + return; +} + /* called from doinv(invent.c) for inventory of unpaid objects */ long unpaid_cost(unp_obj) diff --git a/src/wield.c b/src/wield.c index 45fa37145..66a1b0d69 100644 --- a/src/wield.c +++ b/src/wield.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)wield.c 3.5 2003/01/29 */ +/* SCCS Id: @(#)wield.c 3.5 2005/03/28 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -694,21 +694,24 @@ register int amount; if (otmp && otmp->oclass == SCROLL_CLASS) otyp = otmp->otyp; - if(uwep->otyp == WORM_TOOTH && amount >= 0) { + if (uwep->otyp == WORM_TOOTH && amount >= 0) { + /* order: message, transformation, shop handling */ + Your("%s is much sharper now.", simple_typename(WORM_TOOTH)); uwep->otyp = CRYSKNIFE; uwep->oerodeproof = 0; - Your("weapon seems sharper now."); - uwep->cursed = 0; + if (uwep->cursed) uncurse(uwep); + /* update shop bill to reflect new higher value */ + if (uwep->unpaid) alter_cost(uwep, 0L); if (otyp != STRANGE_OBJECT) makeknown(otyp); - return(1); - } - - if(uwep->otyp == CRYSKNIFE && amount < 0) { + return 1; + } else if (uwep->otyp == CRYSKNIFE && amount < 0) { + /* order matters: message, shop handling, transformation */ + Your("%s is much duller now.", simple_typename(CRYSKNIFE)); + costly_alteration(uwep, COST_DEGRD); /* DECHNT? other? */ uwep->otyp = WORM_TOOTH; uwep->oerodeproof = 0; - Your("weapon seems duller now."); if (otyp != STRANGE_OBJECT && otmp->bknown) makeknown(otyp); - return(1); + return 1; } if (amount < 0 && uwep->oartifact && restrict_name(uwep, ONAME(uwep))) { @@ -738,8 +741,13 @@ register int amount; (amount > 0 || (amount < 0 && otmp->bknown))) makeknown(otyp); } + if (amount < 0) costly_alteration(uwep, COST_DECHNT); uwep->spe += amount; - if(amount > 0) uwep->cursed = 0; + if (amount > 0) { + if (uwep->cursed) uncurse(uwep); + /* update shop bill to reflect new higher price */ + if (uwep->unpaid) alter_cost(uwep, 0L); + } /* * Enchantment, which normally improves a weapon, has an diff --git a/src/zap.c b/src/zap.c index 9b64da537..61b9e49b6 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)zap.c 3.5 2005/03/18 */ +/* SCCS Id: @(#)zap.c 3.5 2005/03/28 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -19,7 +19,6 @@ extern boolean notonhead; /* for long worms */ /* kludge to use mondied instead of killed */ extern boolean m_using; -STATIC_DCL void FDECL(costly_cancel, (struct obj *)); STATIC_DCL void FDECL(polyuse, (struct obj*, int, int)); STATIC_DCL void FDECL(create_polymon, (struct obj *, int)); STATIC_DCL boolean FDECL(zap_updown, (struct obj *)); @@ -802,48 +801,15 @@ struct monst *mon; return res; } -static const char charged_objs[] = { WAND_CLASS, WEAPON_CLASS, ARMOR_CLASS, 0 }; - -STATIC_OVL void -costly_cancel(obj) -register struct obj *obj; -{ - char objroom; - struct monst *shkp = (struct monst *)0; - - if (obj->no_charge) return; - - switch (obj->where) { - case OBJ_INVENT: - if (obj->unpaid) { - shkp = shop_keeper(*u.ushops); - if (!shkp) return; - Norep("You cancel an unpaid object, you pay for it!"); - bill_dummy_object(obj); - } - break; - case OBJ_FLOOR: - objroom = *in_rooms(obj->ox, obj->oy, SHOPBASE); - shkp = shop_keeper(objroom); - if (!shkp || !inhishop(shkp)) return; - if (costly_spot(u.ux, u.uy) && objroom == *u.ushops) { - Norep("You cancel it, you pay for it!"); - bill_dummy_object(obj); - } else - (void) stolen_value(obj, obj->ox, obj->oy, FALSE, FALSE); - break; - } -} - /* cancel obj, possibly carried by you or a monster */ void cancel_item(obj) register struct obj *obj; { - boolean u_ring = (obj == uleft) || (obj == uright); - register boolean holy = (obj->otyp == POT_WATER && obj->blessed); + boolean u_ring = (obj == uleft || obj == uright); + int otyp = obj->otyp; - switch(obj->otyp) { + switch (otyp) { case RIN_GAIN_STRENGTH: if ((obj->owornmask & W_RING) && u_ring) { ABON(A_STR) -= obj->spe; @@ -885,51 +851,50 @@ register struct obj *obj; break; /* case RIN_PROTECTION: not needed */ } - if (objects[obj->otyp].oc_magic - || (obj->spe && (obj->oclass == ARMOR_CLASS || - obj->oclass == WEAPON_CLASS || is_weptool(obj))) - || obj->otyp == POT_ACID || obj->otyp == POT_SICKNESS) { + if (objects[otyp].oc_magic || + (obj->spe && (obj->oclass == ARMOR_CLASS || + obj->oclass == WEAPON_CLASS || is_weptool(obj))) || + otyp == POT_ACID || otyp == POT_SICKNESS || + (otyp == WATER && (obj->blessed || obj->cursed))) { if (obj->spe != ((obj->oclass == WAND_CLASS) ? -1 : 0) && - obj->otyp != WAN_CANCELLATION && - /* can't cancel cancellation */ - obj->otyp != MAGIC_LAMP && - obj->otyp != CANDELABRUM_OF_INVOCATION) { - costly_cancel(obj); + otyp != WAN_CANCELLATION && /* can't cancel cancellation */ + otyp != MAGIC_LAMP && /* cancelling doesn't remove djini */ + otyp != CANDELABRUM_OF_INVOCATION) { + costly_alteration(obj, COST_CANCEL); obj->spe = (obj->oclass == WAND_CLASS) ? -1 : 0; } switch (obj->oclass) { case SCROLL_CLASS: - costly_cancel(obj); + costly_alteration(obj, COST_CANCEL); obj->otyp = SCR_BLANK_PAPER; obj->spe = 0; break; case SPBOOK_CLASS: - if (obj->otyp != SPE_CANCELLATION && - obj->otyp != SPE_BOOK_OF_THE_DEAD) { - costly_cancel(obj); + if (otyp != SPE_CANCELLATION && otyp != SPE_BOOK_OF_THE_DEAD) { + costly_alteration(obj, COST_CANCEL); obj->otyp = SPE_BLANK_PAPER; } break; case POTION_CLASS: - costly_cancel(obj); - if (obj->otyp == POT_SICKNESS || - obj->otyp == POT_SEE_INVISIBLE) { - /* sickness is "biologically contaminated" fruit juice; cancel it - * and it just becomes fruit juice... whereas see invisible - * tastes like "enchanted" fruit juice, it similarly cancels. - */ + costly_alteration(obj, (otyp == WATER && obj->cursed) ? + COST_UNHOLY : COST_CANCEL); + if (otyp == POT_SICKNESS || otyp == POT_SEE_INVISIBLE) { + /* sickness is "biologically contaminated" fruit juice; + cancel it and it just becomes fruit juice... + whereas see invisible tastes like "enchanted" fruit + juice, it similarly cancels */ obj->otyp = POT_FRUIT_JUICE; } else { - obj->otyp = POT_WATER; + obj->otyp = POT_WATER; obj->odiluted = 0; /* same as any other water */ } break; } } - if (holy) costly_cancel(obj); unbless(obj); uncurse(obj); #ifdef INVISIBLE_OBJECTS + /*[this will be insufficient if it ever reduces obj's shop value]*/ if (obj->oinvis) obj->oinvis = 0; #endif return; @@ -954,7 +919,7 @@ register struct obj *obj; return (FALSE); /* Charge for the cost of the object */ - costly_cancel(obj); /* The term "cancel" is okay for now */ + costly_alteration(obj, COST_DRAIN); /* Drain the object and any implied effects */ obj->spe--; @@ -1213,6 +1178,11 @@ struct obj *obj; delobj(obj); } +/* classes of items whose current charge count carries over across polymorph */ +static const char charged_objs[] = { + WAND_CLASS, WEAPON_CLASS, ARMOR_CLASS, '\0' +}; + /* * Polymorph the object to the given object ID. If the ID is STRANGE_OBJECT * then pick random object from the source's class (this is the standard