From: PatR Date: Wed, 17 Mar 2021 17:36:42 +0000 (-0700) Subject: multiple gold stacks in invent X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a06f5ec4942fc873daf96503be942a1e348f0f3e;p=nethack multiple gold stacks in invent 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. --- diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 1dfbf18c2..126fba511 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -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 ------------------------------------------------------------------ diff --git a/include/extern.h b/include/extern.h index a13c05a63..419f25f82 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 *); diff --git a/src/cmd.c b/src/cmd.c index 6277dec28..266b83afe 100644 --- 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(); diff --git a/src/invent.c b/src/invent.c index 81754ac5d..0ee550c6f 100644 --- a/src/invent.c +++ b/src/invent.c @@ -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)