]> granicus.if.org Git - nethack/commitdiff
alteration of shop-owned objects (trunk only)
authornethack.rankin <nethack.rankin>
Tue, 5 Apr 2005 05:24:04 +0000 (05:24 +0000)
committernethack.rankin <nethack.rankin>
Tue, 5 Apr 2005 05:24:04 +0000 (05:24 +0000)
[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.

13 files changed:
doc/fixes35.0
include/extern.h
include/hack.h
src/apply.c
src/do.c
src/eat.c
src/engrave.c
src/mkobj.c
src/potion.c
src/read.c
src/shk.c
src/wield.c
src/zap.c

index 4c1bbed2e7ffc6c1f3a1983f764969b74ead9273..15d49459cb3e0760365f6cc651b130319a152eb3 100644 (file)
@@ -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
index 8e456d04d736290b5d17367fbd2ec29001712d89..b41f0f2e55f4fcced9d39bbdbeb8bbeb3f900e3f 100644 (file)
@@ -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));
index 1846e92a1d50c9b26902e0d81d19a7d25ba55d07..c9ae85eb81eec55ad79751fdf9e506e90e2b8508 100644 (file)
@@ -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. */
 
 #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
index 5beb356154f86f04b00436aee719913a406de096..f93e487b0fd37e8e6535b19ce240d0a268c82e28 100644 (file)
@@ -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 */
index b3c4693aed82d86c162721e29ee16371c830f066..d125bd4556660de4f3d723460e032358ca72bf06 100644 (file)
--- 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;
            }
index 5368ceb9a9529ad64346ab75ecf2219e00fa1567..495b4d4eab1c88f943f4ae900ea73039ccc870b9 100644 (file)
--- 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!",
index 3d0a38ddc03faf84249cf40e86c0571d2e9ee545..30cfc23a77d6328ea0b3444b85c459921cdcd7e5 100644 (file)
@@ -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;
index 35fd7bccb2767b88c585d51ffa13fc6e028f145b..d84ff79daee72eaba03e933bc8be4ef2e7a97586 100644 (file)
@@ -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
index e269bdb78557171b870c7144ec1db0d2ced46d49..1c531049738993d585ef7d7ef12ab89aab8ec6c6 100644 (file)
@@ -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)
index 25e73c1296911d65a0a11a4fc94513270266427a..4a572e1c30da33bf48a967ac530487e8311542d9 100644 (file)
@@ -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;
index ec04143fc32b7e61e18f747d7f30bf92451eba98..11cb465a744cf41c43960ace63c8bc01445d9a46 100644 (file)
--- 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)
index 45fa37145978b5dfc0db80ddbfc60916103795a5..66a1b0d69ca8b7996fa6ea3a5f0c9ae51f308a1a 100644 (file)
@@ -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
index 9b64da537cadd8429f43bd2acd428618b7075e40..61b9e49b6a713364b91839ec257d1045b9a5626b 100644 (file)
--- 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