From: PatR Date: Wed, 20 Apr 2022 20:38:09 +0000 (-0700) Subject: item-action 'I' - use #adjust to split a stack X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a9a9d190387caa493daa0b223688ac4a315ea34a;p=nethack item-action 'I' - use #adjust to split a stack More context-sensitive inventory support. While examining inventory, if you pick an item other than gold and it has a quantity of more than 1, "I - Adjust inventory by splitting this stack" will be one of the menu choices. Breaking doorganize() into two parts was much easier than expected, but the new internal command added to be an alternate for the first part had more niggling details than anticipated. Message history only shows the first digit with "Split off how many?" if the player enters more than that. --- diff --git a/include/extern.h b/include/extern.h index d6a4ca252..c5cd0e839 100644 --- a/include/extern.h +++ b/include/extern.h @@ -286,7 +286,7 @@ extern const char *directionname(int); extern int isok(int, int); extern int get_adjacent_loc(const char *, const char *, xchar, xchar, coord *); extern const char *click_to_cmd(int, int, int); -extern char get_count(char *, char, long, cmdcount_nht *, boolean); +extern char get_count(const char *, char, long, cmdcount_nht *, unsigned); #ifdef HANGUPHANDLING extern void hangup(int); extern void end_of_input(void); @@ -1148,6 +1148,7 @@ extern void free_invbuf(void); extern void reassign(void); extern boolean check_invent_gold(const char *); extern int doorganize(void); +extern int adjust_split(void); extern void free_pickinv_cache(void); extern int count_unpaid(struct obj *); extern int count_buc(struct obj *, int, boolean(*)(struct obj *)); diff --git a/include/hack.h b/include/hack.h index 95edc50ee..2f54b327c 100644 --- a/include/hack.h +++ b/include/hack.h @@ -476,6 +476,11 @@ typedef uint32_t mmflags_nht; /* makemon MM_ flags */ #define SUPPRESS_HISTORY 4 #define URGENT_MESSAGE 8 +/* get_count flags */ +#define GC_NOFLAGS 0 +#define GC_SAVEHIST 1 /* save "Count: 123" in message history */ +#define GC_ECHOFIRST 2 /* echo "Count: 1" even when there's only one digit */ + /* rloc() flags */ #define RLOC_NONE 0x00 #define RLOC_ERR 0x01 /* allow impossible() if no rloc */ diff --git a/src/cmd.c b/src/cmd.c index 957b874f0..3050db6a0 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -2605,6 +2605,7 @@ struct ext_func_tab extcmdlist[] = { /* internal commands: only used by game core, not available for user */ { '\0', "clicklook", NULL, doclicklook, INTERNALCMD, NULL }, { '\0', "altdip", NULL, dip_into, INTERNALCMD, NULL }, + { '\0', "altadjust", NULL, adjust_split, INTERNALCMD, NULL }, { '\0', (char *) 0, (char *) 0, donull, 0, (char *) 0 } /* sentinel */ }; @@ -5124,20 +5125,25 @@ click_to_cmd(int x, int y, int mod) /* gather typed digits into a number in *count; return the next non-digit */ char get_count( - char *allowchars, - char inkey, - long maxcount, - cmdcount_nht *count, - boolean historicmsg) /* whether to include in ^P history: True => yes */ + const char *allowchars, /* what comes after digits; if Null, anything */ + char inkey, /* if caller already got first digit, this is it */ + long maxcount, /* if user tries to enter a bigger count, use this */ + cmdcount_nht *count, /* primary output */ + unsigned gc_flags) /* control flags: GC_SAVEHIST, GC_ECHOFIRST */ { char qbuf[QBUFSZ]; int key; long cnt = 0L; - boolean backspaced = FALSE; + boolean backspaced = FALSE, showzero = TRUE, + /* should "Count: 123" go into message history? */ + historicmsg = (gc_flags & GC_SAVEHIST) != 0, + /* normally "Count: 12" isn't echoed until the second digit */ + echoalways = (gc_flags & GC_ECHOFIRST) != 0; /* this should be done in port code so that we have erase_char and kill_char available; we can at least fake erase_char */ #define STANDBY_erase_char '\177' + *count = 0; for (;;) { if (inkey) { key = inkey; @@ -5151,19 +5157,26 @@ get_count( cnt = 0L; else if (maxcount > 0L && cnt > maxcount) cnt = maxcount; - } else if (cnt && (key == '\b' || key == STANDBY_erase_char)) { + /* if we've backed up to nothing, then typed 0, show that 0 */ + showzero = (key == '0'); + } else if (key == '\b' || key == STANDBY_erase_char) { + if (!cnt && !echoalways) + break; + showzero = FALSE; cnt = cnt / 10L; backspaced = TRUE; } else if (key == g.Cmd.spkeys[NHKF_ESC]) { break; } else if (!allowchars || index(allowchars, key)) { - *count = cnt; + *count = (cmdcount_nht) cnt; + if ((long) *count != cnt) + impossible("get_count: cmdcount_nht"); break; } - if (cnt > 9 || backspaced) { + if (cnt > 9 || backspaced || echoalways) { clear_nhwindow(WIN_MESSAGE); - if (backspaced && !cnt) { + if (backspaced && !cnt && !showzero) { Sprintf(qbuf, "Count: "); } else { Sprintf(qbuf, "Count: %ld", cnt); @@ -5199,7 +5212,7 @@ parse(void) * reset to 0 by readchar() */ if (!g.Cmd.num_pad || (foo = readchar()) == g.Cmd.spkeys[NHKF_COUNT]) { foo = get_count((char *) 0, '\0', LARGEST_INT, - &g.command_count, FALSE); + &g.command_count, GC_NOFLAGS); g.last_command_count = g.command_count; } diff --git a/src/invent.c b/src/invent.c index 5d616f0ff..8289f19ee 100644 --- a/src/invent.c +++ b/src/invent.c @@ -34,6 +34,7 @@ static void menu_identify(int); static boolean tool_being_used(struct obj *); static int adjust_ok(struct obj *); static int adjust_gold_ok(struct obj *); +static int doorganize_core(struct obj *); static char obj_to_let(struct obj *); static boolean item_naming_classification(struct obj *, char *, char *); static int item_reading_classification(struct obj *, char *); @@ -1659,7 +1660,7 @@ getobj( pline("No count allowed with this command."); continue; } - ilet = get_count(NULL, ilet, LARGEST_INT, &tmpcnt, TRUE); + ilet = get_count(NULL, ilet, LARGEST_INT, &tmpcnt, GC_SAVEHIST); if (tmpcnt) { cnt = tmpcnt; cntgiven = TRUE; @@ -2777,16 +2778,15 @@ itemactions(struct obj *otmp) ia_addmenu(win, IA_ENGRAVE_OBJ, 'E', "Write on the floor with this object"); - /* i: #adjust inventory letter */ - if (otmp->oclass != COIN_CLASS) /* gold is always "letter" '$' */ + /* i: #adjust inventory letter; gold can't be adjusted unless there + is some in a slot other than '$' (which shouldn't be possible) */ + if (otmp->oclass != COIN_CLASS || check_invent_gold("item-action")) ia_addmenu(win, IA_ADJUST_OBJ, 'i', "Adjust inventory by assigning new letter"); -#if 0 /* I: #adjust inventory item by splitting its stack */ if (otmp->quan > 1L && otmp->oclass != COIN_CLASS) ia_addmenu(win, IA_ADJUST_STACK, 'I', "Adjust inventory by splitting this stack"); -#endif /* O: offer sacrifice */ if (IS_ALTAR(levl[u.ux][u.uy].typ) && !u.uswallow) { @@ -2971,11 +2971,8 @@ itemactions(struct obj *otmp) cmdq_add_key(otmp->invlet); break; case IA_ADJUST_STACK: -#if 0 /* will need an alternate command routine (like #altdip) in - * order to prompt for a count */ - cmdq_add_ec(doorganize); + cmdq_add_ec(adjust_split); /* #altadjust */ cmdq_add_key(otmp->invlet); -#endif break; case IA_SACRIFICE: cmdq_add_ec(dosacrifice); @@ -4760,18 +4757,8 @@ adjust_gold_ok(struct obj *obj) int doorganize(void) /* inventory organizer by Del Lamb */ { - struct obj *obj, *otmp, *splitting, *bumped; - int ix, cur, trycnt; - char let; -#define GOLD_INDX 0 -#define GOLD_OFFSET 1 -#define OVRFLW_INDX (GOLD_OFFSET + 52) /* past gold and 2*26 letters */ - char lets[1 + 52 + 1 + 1]; /* room for '$a-zA-Z#\0' */ - char qbuf[QBUFSZ]; - char *objname, *otmpname; - const char *adj_type; int (*adjust_filter)(struct obj *); - boolean ever_mind = FALSE, collect, isgold; + struct obj *obj; /* when no invent, or just gold in '$' slot, there's nothing to adjust */ if (!g.invent || (g.invent->oclass == COIN_CLASS @@ -4789,8 +4776,80 @@ doorganize(void) /* inventory organizer by Del Lamb */ /* get object the user wants to organize (the 'from' slot) */ obj = getobj("adjust", adjust_filter, GETOBJ_PROMPT | GETOBJ_ALLOWCNT); + + return doorganize_core(obj); +} + +/* alternate version of #adjust used by itemactions() for splitting */ +int +adjust_split(void) +{ + struct obj *obj; + cmdcount_nht splitamount = 0L; + char let, dig = '\0'; + + /* invlet should be queued so no getobj prompting is expected */ + obj = getobj("split", adjust_ok, GETOBJ_NOFLAGS); + if (!obj || obj->quan < 2L || obj->otyp == GOLD_PIECE) + return ECMD_FAIL; /* caller has set things up to avoid this */ + + if (obj->quan == 2L) { + splitamount = 1L; + } else { + /* get first digit; doesn't wait for */ + dig = yn_function("Split off how many?", (char *) 0, '\0'); + if (!digit(dig)) { + pline1(Never_mind); + return ECMD_CANCEL; + } + /* got first digit, get more until next non-digit (except for + backspace/delete which will take away most recent digit and + keep going; we expect one of ' ', '\n', or '\r') */ + let = get_count(NULL, dig, LARGEST_INT, &splitamount, GC_ECHOFIRST); + /* \033 is in quitchars[] so we need to check for it separately + in order to treat it as cancel rather than as accept */ + if (!let || let == '\033' || !index(quitchars, let)) { + pline1(Never_mind); + return ECMD_CANCEL; + } + } + if (splitamount < 1L || splitamount >= obj->quan) { + static const char + Amount[] = "Amount to split from current stack must be"; + + if (splitamount < 1L) + pline("%s at least 1.", Amount); + else + pline("%s less than %ld.", Amount, obj->quan); + return ECMD_CANCEL; + } + + /* normally a split would take place in getobj() if player supplies + a count there, so doorganize_core() figures out 'splitamount' + from the object; it will undo the split if player cancels while + selecting the destination slot */ + obj = splitobj(obj, (long) splitamount); + return doorganize_core(obj); +} + +static int +doorganize_core(struct obj *obj) +{ + struct obj *otmp, *splitting, *bumped; + int ix, cur, trycnt; + char let; +#define GOLD_INDX 0 +#define GOLD_OFFSET 1 +#define OVRFLW_INDX (GOLD_OFFSET + 52) /* past gold and 2*26 letters */ + char lets[1 + 52 + 1 + 1]; /* room for '$a-zA-Z#\0' */ + char qbuf[QBUFSZ]; + char *objname, *otmpname; + const char *adj_type; + boolean ever_mind = FALSE, collect, isgold; + if (!obj) return ECMD_CANCEL; + /* 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 @@ -4843,7 +4902,11 @@ doorganize(void) /* inventory organizer by Del Lamb */ compactify(lets); /* get 'to' slot to use as destination */ - Sprintf(qbuf, "Adjust letter to what [%s]%s?", lets, + if (!splitting) + Strcpy(qbuf, "Adjust letter"); + else /* note: splitting->quan is the amount being left in original slot */ + Sprintf(qbuf, "Split %ld", obj->quan); + Sprintf(eos(qbuf), " to what [%s]%s?", lets, g.invent ? " (? see used letters)" : ""); for (trycnt = 1; ; ++trycnt) { let = !isgold ? yn_function(qbuf, (char *) 0, '\0') : GOLD_SYM; diff --git a/win/curses/cursmisc.c b/win/curses/cursmisc.c index fca63ed05..ea4b9e9a8 100644 --- a/win/curses/cursmisc.c +++ b/win/curses/cursmisc.c @@ -702,9 +702,11 @@ curses_get_count(int first_digit) curses's message window will display that in count window instead */ current_char = get_count(NULL, (char) first_digit, /* 0L => no limit on value unless it wraps - * to negative; - * FALSE => suppress from message history */ - 0L, ¤t_count, FALSE); + to negative */ + 0L, ¤t_count, + /* default: don't put into message history, + don't echo until second digit entered */ + GC_NOFLAGS); ungetch(current_char); if (current_char == '\033') { /* Cancelled with escape */