]> granicus.if.org Git - nethack/commitdiff
multiple gold stacks in invent
authorPatR <rankin@nethack.org>
Wed, 17 Mar 2021 17:36:42 +0000 (10:36 -0700)
committerPatR <rankin@nethack.org>
Wed, 17 Mar 2021 17:36:42 +0000 (10:36 -0700)
The pull request that fixed a couple of instances where it was
possible to have multiple entries for gold in inventory indirectly
pointed out that the error checking was clumsy.  If you executed
the #adjust command while having two '$' items in inventory, you
were told twice that you had multiple stacks of gold in inventory.
Change how that's handled so that the warning appears at most once
for any given #adjust command.  Also avoids having #adjust's use
of getobj() re-scan entire invent for every item in invent.

Also, if player did manage to get two or more '$' entries, #adjust
would allow moving any but the last to a letter entry.  Once in a
letter, further #adjust with count specified could split the letter
gold entries into even more gold entries.  Now, if the player picks
gold as the #adjust 'from' item (which is only possible when there
are wrong letter gold entries or multiple ones or both) then #adjust
will now force 'to' slot to be '$' (without asking player to pick).

Lastly, the inventory check for multiple and/or wrong slot gold is
now performed by wizard mode sanity_check() in addition to #adjust.

doc/fixes37.0
include/extern.h
src/cmd.c
src/invent.c

index 1dfbf18c279a6326358079149c006e9f6e58a0ab..126fba511bf5d964ac1a1bf2b456a54d1a4854b3 100644 (file)
@@ -417,6 +417,10 @@ attempting to throw a partial stack of gold at self was prevented but left
        the partial stack in an extra $ inventory slot
 quivering a partial stack of gold succeeded and put the partial stack in an
        extra $ inventory slot
+if player managed to get multiple $ items, all but the last could be moved to
+       normal letter slots via #adjust and then subsequent #adjust with a
+       count could split them into even more slots
+
 
 Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
 ------------------------------------------------------------------
index a13c05a63bb19b2af51ceb8dcdb3c2ba075a28ce..419f25f823f4d898502ecdf24ae95cf9624c2611 100644 (file)
@@ -1046,6 +1046,7 @@ extern void useupf(struct obj *, long);
 extern char *let_to_name(char, boolean, boolean);
 extern void free_invbuf(void);
 extern void reassign(void);
+extern boolean check_invent_gold(const char *);
 extern int doorganize(void);
 extern void free_pickinv_cache(void);
 extern int count_unpaid(struct obj *);
index 6277dec280263b523f8512c9e259b5d7a643e59b..266b83afeed5b0fd89b759b0e963eb661c04d4c4 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -2906,6 +2906,7 @@ RESTORE_WARNING_FORMAT_NONLITERAL
 void
 sanity_check(void)
 {
+    (void) check_invent_gold("invent");
     obj_sanity_check();
     timer_sanity_check();
     mon_sanity_check();
index 81754ac5dc12a96f8dce2e6e84f54cec2f8109a6..0ee550c6f16f55168b843367bbd2e3b19bc182d4 100644 (file)
@@ -33,6 +33,7 @@ static struct obj *find_unpaid(struct obj *, struct obj **);
 static void menu_identify(int);
 static boolean tool_in_use(struct obj *);
 static int adjust_ok(struct obj *);
+static int adjust_gold_ok(struct obj *);
 static char obj_to_let(struct obj *);
 static void mime_action(const char *);
 
@@ -3944,37 +3945,51 @@ reassign(void)
     g.lastinvnr = i;
 }
 
-/* getobj callback for item to #adjust */
+/* invent gold sanity check; used by doorganize() to control how getobj()
+   deals with gold and also by wizard mode sanity_check() */
+boolean
+check_invent_gold(const char *why) /* 'why' == caller in case of warning */
+{
+    struct obj *otmp;
+    int goldstacks = 0, wrongslot = 0;
+
+    /* there should be at most one stack of gold in invent, in slot '$' */
+    for (otmp = g.invent; otmp; otmp = otmp->nobj)
+        if (otmp->oclass == COIN_CLASS) {
+            ++goldstacks;
+            if (otmp->invlet != GOLD_SYM)
+                ++wrongslot;
+        }
+
+    if (goldstacks > 1 || wrongslot > 0) {
+        impossible("%s: %s%s%s", why,
+                   (wrongslot > 1) ? "gold in wrong slots"
+                      : (wrongslot > 0) ? "gold in wrong slot"
+                           : "",
+                   (wrongslot > 0 && goldstacks > 1) ? " and " : "",
+                   (goldstacks > 1) ? "multiple gold stacks" : "");
+        return TRUE; /* gold can be #adjusted */
+    }
+
+    return FALSE; /* gold can't be #adjusted */
+}
+
+/* normal getobj callback for item to #adjust; excludes gold */
 int
 adjust_ok(struct obj *obj)
 {
-    if (!obj)
+    if (!obj || obj->oclass == COIN_CLASS)
         return GETOBJ_EXCLUDE;
 
-    /* gold should never end up in a letter slot, nor should two '$' slots
-     * occur, but if they ever do, allow #adjust to handle them (in the
-     * past, things like this have happened, usually due to bknown being
-     * erroneously set on one stack, clear on another; object merger isn't
-     * fooled by that anymore) */
-    if (obj->oclass == COIN_CLASS) {
-        int goldstacks = 0;
-        struct obj *otmp;
-        if (obj->invlet != GOLD_SYM)
-            return GETOBJ_SUGGEST;
-        for (otmp = g.invent; otmp; otmp = otmp->nobj) {
-            if (otmp->oclass == COIN_CLASS) {
-                goldstacks++;
-            }
-        }
+    return GETOBJ_SUGGEST;
+}
 
-        if (goldstacks > 1) {
-            impossible("getobj: multiple gold stacks in inventory");
-            return GETOBJ_SUGGEST;
-        }
-        /* assuming this impossible case doesn't happen, gold should be
-         * outright ignored as far as #adjust is concerned */
+/* getobj callback for item to #adjust if gold is wonky; allows gold */
+int
+adjust_gold_ok(struct obj *obj)
+{
+    if (!obj)
         return GETOBJ_EXCLUDE;
-    }
 
     return GETOBJ_SUGGEST;
 }
@@ -4019,6 +4034,10 @@ adjust_ok(struct obj *obj)
  *      However, when splitting results in a merger, the name of the
  *      destination overrides that of the source, even if destination
  *      is unnamed and source is named.
+ *
+ *      Gold is only a candidate to adjust if we've somehow managed
+ *      to get multiple stacks and/or it is in a slot other than '$'.
+ *      Specifying a count to split it into two stacks is not allowed.
  */
 int
 doorganize(void) /* inventory organizer by Del Lamb */
@@ -4033,7 +4052,8 @@ doorganize(void) /* inventory organizer by Del Lamb */
     char qbuf[QBUFSZ];
     char *objname, *otmpname;
     const char *adj_type;
-    boolean ever_mind = FALSE, collect;
+    int (*adjust_filter)(struct obj *);
+    boolean ever_mind = FALSE, collect, isgold;
 
     /* when no invent, or just gold in '$' slot, there's nothing to adjust */
     if (!g.invent || (g.invent->oclass == COIN_CLASS
@@ -4045,10 +4065,19 @@ doorganize(void) /* inventory organizer by Del Lamb */
 
     if (!flags.invlet_constant)
         reassign();
+
+    /* filter passed to getobj() depends upon gold sanity */
+    adjust_filter = check_invent_gold("adjust") ? adjust_gold_ok : adjust_ok;
+
     /* get object the user wants to organize (the 'from' slot) */
-    obj = getobj("adjust", adjust_ok, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
+    obj = getobj("adjust", adjust_filter, GETOBJ_PROMPT | GETOBJ_ALLOWCNT);
     if (!obj)
         return 0;
+    /* can only be gold if check_invent_gold() found a problem:  multiple '$'
+       stacks and/or gold in some other slot, otherwise (*adjust_filter)()
+       won't allow gold to be picked; if player has picked any stack of gold
+       as #adjust 'from' slot, we'll force the 'to' slot to be '$' below */
+    isgold = (obj->oclass == COIN_CLASS);
 
     /* figure out whether user gave a split count to getobj() */
     splitting = bumped = 0;
@@ -4099,7 +4128,7 @@ doorganize(void) /* inventory organizer by Del Lamb */
     Sprintf(qbuf, "Adjust letter to what [%s]%s?", lets,
             g.invent ? " (? see used letters)" : "");
     for (trycnt = 1; ; ++trycnt) {
-        let = yn_function(qbuf, (char *) 0, '\0');
+        let = !isgold ? yn_function(qbuf, (char *) 0, '\0') : GOLD_SYM;
         if (let == '?' || let == '*') {
             let = display_used_invlets(splitting ? obj->invlet : 0);
             if (!let)