diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 06db99247..66b27f5bf 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -2326,6 +2326,19 @@ the appearance of the display, not the way the game treats you. Show your approximate accumulated score on bottom line (default off). .lp "silent " Suppress terminal beeps (default on). +.lp sortloot +Controls the sorting behavior of the pickup lists for inventory +and #loot commands and some others. +The possible values are: +.PS full +.PL full +always sort the lists; +.PL loot +only sort the lists that don't use inventory letters, like with +the #loot and pickup commands; +.PL none +show lists the traditional way without sorting. +.PE .lp sortpack Sort the pack contents by type when displaying inventory (default on). .lp sparkle diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 427d720a0..ae3cfa8a7 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -2792,6 +2792,20 @@ Show your approximate accumulated score on bottom line (default off). \item[\ib{silent}] Suppress terminal beeps (default on). %.lp +\item[\ib{sortloot}] +Controls the sorting behavior of pickup lists for inventory +and \#loot commands and some others. + +The possible values are: +%.sd +%.si +{\tt full} --- always sort the lists;\\ +{\tt loot} --- only sort the lists that don't use inventory + letters, like with the \#loot and pickup commands;\\ +{\tt none} --- show lists the traditional way without sorting. +%.ei +%.ed +%.lp \item[\ib{sortpack}] Sort the pack contents by type when displaying inventory (default on). %.lp diff --git a/doc/fixes35.0 b/doc/fixes35.0 index 96dc67f9d..437ed2347 100644 --- a/doc/fixes35.0 +++ b/doc/fixes35.0 @@ -1152,6 +1152,7 @@ adopt/adapt/improve Dungeon Overview Aardvark Joe's Extended Logfile Michael Deutschmann's use_darkgray Clive Crous' dark_room +Jukka Lahtinen's sortloot Code Cleanup and Reorganization diff --git a/include/extern.h b/include/extern.h index f36f0243b..19f318b55 100644 --- a/include/extern.h +++ b/include/extern.h @@ -857,6 +857,8 @@ E int NDECL(midnight); /* ### invent.c ### */ +E struct obj **FDECL(objarr_init, (int)); +E void FDECL(objarr_set, (struct obj *, int, struct obj **, BOOLEAN_P)); E void FDECL(assigninvlet, (struct obj *)); E struct obj *FDECL(merge_choice, (struct obj *,struct obj *)); E int FDECL(merged, (struct obj **,struct obj **)); @@ -1523,6 +1525,7 @@ E char *FDECL(doname, (struct obj *)); E boolean FDECL(not_fully_identified, (struct obj *)); E char *FDECL(corpse_xname, (struct obj *,const char *,unsigned)); E char *FDECL(cxname, (struct obj *)); +E char *FDECL(cxname_singular, (struct obj *)); E char *FDECL(killer_xname, (struct obj *)); E char *FDECL(short_oname, (struct obj *,char *(*)(OBJ_P),char *(*)(OBJ_P), unsigned)); diff --git a/include/flag.h b/include/flag.h index 734806b38..dc58375e4 100644 --- a/include/flag.h +++ b/include/flag.h @@ -48,6 +48,7 @@ struct flag { boolean showexp; /* show experience points */ boolean showscore; /* show score */ boolean silent; /* whether the bell rings or not */ + boolean sortloot; /* sort items alphabetically when looting */ boolean sortpack; /* sorted inventory */ boolean sparkle; /* show "resisting" special FX (Scott Bigham) */ boolean standout; /* use standout for --More-- */ diff --git a/src/end.c b/src/end.c index 35a5393bd..42acca2ba 100644 --- a/src/end.c +++ b/src/end.c @@ -1237,6 +1237,9 @@ struct obj *list; boolean identified, all_containers, reportempty; { register struct obj *box, *obj; + struct obj **oarray; + int i,j,n; + char *invlet; char buf[BUFSZ]; boolean cat, deadcat; @@ -1256,10 +1259,30 @@ boolean identified, all_containers, reportempty; continue; /* wrong type of container */ } else if (box->cobj) { winid tmpwin = create_nhwindow(NHW_MENU); + + /* count the number of items */ + for (n = 0, obj = box->cobj; obj; obj = obj->nobj) n++; + /* Make a temporary array to store the objects sorted */ + oarray = objarr_init(n); + + /* Add objects to the array */ + i = 0; + invlet = flags.inv_order; + nextclass: + for (obj = box->cobj; obj; obj = obj->nobj) { + if (!flags.sortpack || obj->oclass == *invlet) { + objarr_set(obj, i++, oarray, (flags.sortloot == 'f' || flags.sortloot == 'l') ); + } + } /* for loop */ + if (flags.sortpack) { + if (*++invlet) goto nextclass; + } + Sprintf(buf, "Contents of %s:", the(xname(box))); putstr(tmpwin, 0, buf); putstr(tmpwin, 0, ""); - for (obj = box->cobj; obj; obj = obj->nobj) { + for (i = 0; i < n; i++) { + obj = oarray[i]; if (identified) { makeknown(obj->otyp); obj->known = obj->bknown = @@ -1269,6 +1292,7 @@ boolean identified, all_containers, reportempty; } putstr(tmpwin, 0, doname(obj)); } + free(oarray); if (cat) putstr(tmpwin, 0, "Schroedinger's cat"); else if (deadcat) putstr(tmpwin, 0, "Schroedinger's dead cat"); display_nhwindow(tmpwin, TRUE); diff --git a/src/invent.c b/src/invent.c index 2cc9e9004..8f4515551 100644 --- a/src/invent.c +++ b/src/invent.c @@ -8,6 +8,7 @@ #define NOINVSYM '#' #define CONTAINED_SYM '>' /* designator for inside a container */ +STATIC_DCL int FDECL(sortloot_cmp, (struct obj *, struct obj *)); STATIC_DCL void NDECL(reorder_invent); STATIC_DCL boolean FDECL(mergable,(struct obj *,struct obj *)); STATIC_DCL void FDECL(noarmor, (BOOLEAN_P)); @@ -42,6 +43,88 @@ static int lastinvnr = 51; /* 0 ... 51 (never saved&restored) */ */ static char venom_inv[] = { VENOM_CLASS, 0 }; /* (constant) */ + +int +sortloot_cmp(obj1, obj2) +struct obj *obj1; +struct obj *obj2; +{ + int val1 = 0; + int val2 = 0; + + /* Sort object names in lexicographical order, ignoring quantity. */ + int name_cmp = strcmpi(cxname_singular(obj1), cxname_singular(obj2)); + + if (name_cmp != 0) { + return name_cmp; + } + + /* Sort by BUC. Map blessed to 4, uncursed to 2, cursed to 1, and unknown to 0. */ + val1 = obj1->bknown ? (obj1->blessed << 2) + ((!obj1->blessed && !obj1->cursed) << 1) + obj1->cursed : 0; + val2 = obj2->bknown ? (obj2->blessed << 2) + ((!obj2->blessed && !obj2->cursed) << 1) + obj2->cursed : 0; + if (val1 != val2) { + return val2 - val1; /* Because bigger is better. */ + } + + /* Sort by greasing. This will put the objects in degreasing order. */ + val1 = obj1->greased; + val2 = obj2->greased; + if (val1 != val2) { + return val2 - val1; /* Because bigger is better. */ + } + + /* Sort by erosion. The effective amount is what matters. */ + val1 = greatest_erosion(obj1); + val2 = greatest_erosion(obj2); + if (val1 != val2) { + return val1 - val2; /* Because bigger is WORSE. */ + } + + /* Sort by erodeproofing. Map known-invulnerable to 1, and both + * known-vulnerable and unknown-vulnerability to 0, because that's how they're displayed. */ + val1 = obj1->rknown && obj1->oerodeproof; + val2 = obj2->rknown && obj2->oerodeproof; + if (val1 != val2) { + return val2 - val1; /* Because bigger is better. */ + } + + /* Sort by enchantment. Map unknown to -1000, which is comfortably below the range of ->spe. */ + val1 = obj1->known ? obj1->spe : -1000; + val2 = obj2->known ? obj2->spe : -1000; + if (val1 != val2) { + return val2 - val1; /* Because bigger is better. */ + } + + return 0; /* They're identical, as far as we're concerned. */ +} + +struct obj ** +objarr_init(n) +int n; +{ + return (struct obj **)alloc(n * sizeof(struct obj *)); +} + +void +objarr_set(otmp, idx, oarray, dosort) +struct obj *otmp; +int idx; +struct obj **oarray; +boolean dosort; +{ + if (dosort) { + int j; + for (j = idx; j; j--) { + if (sortloot_cmp(otmp, oarray[j-1]) > 0) break; + oarray[j] = oarray[j-1]; + } + oarray[j] = otmp; + } else { + oarray[idx] = otmp; + } +} + + void assigninvlet(otmp) register struct obj *otmp; @@ -1722,6 +1805,8 @@ long* out_cnt; static winid local_win = WIN_ERR; /* window for partial menus */ anything any; menu_item *selected; + struct obj **oarray; + int i, j; /* overriden by global flag */ if (flags.perm_invent) { @@ -1768,6 +1853,19 @@ long* out_cnt; return ret; } + /* count the number of items */ + for (n = 0, otmp = invent; otmp; otmp = otmp->nobj) + if (!lets || !*lets || index(lets, otmp->invlet)) n++; + + oarray = objarr_init(n); + + /* Add objects to the array */ + i = 0; + for (otmp = invent; otmp; otmp = otmp->nobj) + if (!lets || !*lets || index(lets, otmp->invlet)) { + objarr_set(otmp, i++, oarray, (flags.sortloot == 'f')); + } + start_menu(win); if (wizard && iflags.override_ID) { char prompt[BUFSZ]; @@ -1782,22 +1880,21 @@ long* out_cnt; nextclass: classcount = 0; any = zeroany; /* set all bits to zero */ - for(otmp = invent; otmp; otmp = otmp->nobj) { - ilet = otmp->invlet; - if(!lets || !*lets || index(lets, ilet)) { - any = zeroany; /* zero */ - if (!flags.sortpack || otmp->oclass == *invlet) { - if (flags.sortpack && !classcount) { - add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, + for(i = 0; i < n; i++) { + otmp = oarray[i]; + ilet = otmp->invlet; + any = zeroany; /* zero */ + if (!flags.sortpack || otmp->oclass == *invlet) { + if (flags.sortpack && !classcount) { + add_menu(win, NO_GLYPH, &any, 0, 0, iflags.menu_headings, let_to_name(*invlet, FALSE, (want_reply && iflags.menu_head_objsym)), MENU_UNSELECTED); - classcount++; - } - any.a_char = ilet; - add_menu(win, obj_to_glyph(otmp), - &any, ilet, 0, ATR_NONE, doname(otmp), - MENU_UNSELECTED); - } + classcount++; } + any.a_char = ilet; + add_menu(win, obj_to_glyph(otmp), + &any, ilet, 0, ATR_NONE, doname(otmp), + MENU_UNSELECTED); + } } if (flags.sortpack) { if (*++invlet) goto nextclass; @@ -1806,6 +1903,7 @@ nextclass: goto nextclass; } } + free(oarray); end_menu(win, (char *) 0); n = select_menu(win, want_reply ? PICK_ONE : PICK_NONE, &selected); diff --git a/src/objnam.c b/src/objnam.c index f40222c3b..25165a4b3 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -19,6 +19,8 @@ STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *)); STATIC_DCL boolean FDECL(singplur_lookup, (char *,char *,BOOLEAN_P, const char *const *)); STATIC_DCL char *FDECL(singplur_compound, (char *)); +STATIC_DCL char *FDECL(xname_flags, (struct obj *, unsigned)); + struct Jitem { int item; @@ -237,7 +239,15 @@ boolean juice; /* whether or not to append " juice" to the name */ char * xname(obj) +struct obj *obj; +{ + return xname_flags(obj, CXN_NORMAL); +} + +char * +xname_flags(obj, cxn_flags) register struct obj *obj; +unsigned cxn_flags; /* bitmask of CXN_xxx values */ { register char *buf; register int typ = obj->otyp; @@ -246,7 +256,7 @@ register struct obj *obj; const char *actualn = OBJ_NAME(*ocl); const char *dn = OBJ_DESCR(*ocl); const char *un = ocl->oc_uname; - boolean pluralize = (obj->quan != 1L); + boolean pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR); boolean known, dknown, bknown; buf = nextobuf() + PREFIX; /* leave room for "17 -3 " */ @@ -1066,6 +1076,16 @@ struct obj *obj; return xname(obj); } +/* like cxname, but ignores quantity */ +char * +cxname_singular(obj) +struct obj *obj; +{ + if (obj->otyp == CORPSE) + return corpse_xname(obj, (const char *)0, CXN_SINGULAR); + return xname_flags(obj, CXN_SINGULAR); +} + /* treat an object as fully ID'd when it might be used as reason for death */ char * killer_xname(obj) diff --git a/src/options.c b/src/options.c index 1b834e616..c65f3db28 100644 --- a/src/options.c +++ b/src/options.c @@ -347,6 +347,7 @@ static struct Comp_Opt { "scroll_amount", "amount to scroll map when scroll_margin is reached", 20, DISP_IN_GAME }, /*WC*/ { "scroll_margin", "scroll map when this far from the edge", 20, DISP_IN_GAME }, /*WC*/ + { "sortloot", "sort object selection lists by description", 4, SET_IN_GAME }, #ifdef MSDOS { "soundcard", "type of sound card to use", 20, SET_IN_FILE }, #endif @@ -659,6 +660,7 @@ initoptions_init() (genericptr_t)def_inv_order, sizeof flags.inv_order); flags.pickup_types[0] = '\0'; flags.pickup_burden = MOD_ENCUMBER; + flags.sortloot = 'l'; /* sort only loot by default */ for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) flags.end_disclose[i] = DISCLOSE_PROMPT_DEFAULT_NO; @@ -2360,6 +2362,22 @@ goodfruit: return; } + fullname = "sortloot"; + if (match_optname(opts, fullname, 4, TRUE)) { + op = string_for_env_opt(fullname, opts, FALSE); + if (op) { + switch (tolower(*op)) { + case 'n': + case 'l': + case 'f': flags.sortloot = tolower(*op); + break; + default: badoption(opts); + return; + } + } + return; + } + fullname = "suppress_alert"; if (match_optname(opts, fullname, 4, TRUE)) { if (duplicate) complain_about_duplicate(opts,1); @@ -2896,6 +2914,10 @@ static NEARDATA const char *runmodes[] = { "teleport", "run", "walk", "crawl" }; +static NEARDATA const char *sortltype[] = { + "none", "loot", "full" +}; + /* * Convert the given string of object classes to a string of default object * symbols. @@ -3204,7 +3226,8 @@ boolean setinitial,setfromfile; char buf[BUFSZ]; /* Special handling of menustyle, pickup_burden, pickup_types, - * disclose, runmode, msg_window, menu_headings, and number_pad options. + * disclose, runmode, msg_window, menu_headings, sortloot, + * and number_pad options. * Also takes care of interactive autopickup_exception_handling changes. */ if (!strcmp("menustyle", optname)) { @@ -3385,6 +3408,23 @@ boolean setinitial,setfromfile; } destroy_nhwindow(tmpwin); #endif + } else if (!strcmp("sortloot", optname)) { + const char *sortl_name; + menu_item *sortl_pick = (menu_item *)0; + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin); + for (i = 0; i < SIZE(sortltype); i++) { + sortl_name = sortltype[i]; + any.a_char = *sortl_name; + add_menu(tmpwin, NO_GLYPH, &any, *sortl_name, 0, + ATR_NONE, sortl_name, MENU_UNSELECTED); + } + end_menu(tmpwin, "Select loot sorting type:"); + if (select_menu(tmpwin, PICK_ONE, &sortl_pick) > 0) { + flags.sortloot = sortl_pick->item.a_char; + free((genericptr_t)sortl_pick); + } + destroy_nhwindow(tmpwin); } else if (!strcmp("align_message", optname) || !strcmp("align_status", optname)) { menu_item *window_pick = (menu_item *)0; @@ -3974,6 +4014,15 @@ char *buf; if (iflags.wc_scroll_margin) Sprintf(buf, "%d",iflags.wc_scroll_margin); else Strcpy(buf, defopt); } + else if (!strcmp(optname, "sortloot")) { + char *sortname = (char *)NULL; + for (i=0; i < SIZE(sortltype) && sortname==(char *)NULL; i++) { + if (flags.sortloot == sortltype[i][0]) + sortname = (char *)sortltype[i]; + } + if (sortname != (char *)NULL) + Sprintf(buf, "%s", sortname); + } else if (!strcmp(optname, "player_selection")) Sprintf(buf, "%s", iflags.wc_player_selection ? "prompts" : "dialog"); #ifdef MSDOS diff --git a/src/pickup.c b/src/pickup.c index bf5d06129..ff8bcff25 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -708,9 +708,10 @@ menu_item **pick_list; /* return list of items picked */ int how; /* type of query */ boolean FDECL((*allow), (OBJ_P));/* allow function */ { - int n; + int i, j, n; winid win; struct obj *curr, *last, fake_hero_object; + struct obj **oarray; char *pack; anything any; boolean printed_type_name, @@ -743,6 +744,16 @@ boolean FDECL((*allow), (OBJ_P));/* allow function */ return 1; } + oarray = objarr_init(n); + /* Add objects to the array */ + i = 0; + for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { + if ((*allow)(curr)) { + objarr_set(curr, i++, oarray, (flags.sortloot == 'f' || + (flags.sortloot == 'l' && !(qflags & USE_INVLET)))); + } + } + win = create_nhwindow(NHW_MENU); start_menu(win); any = zeroany; @@ -756,7 +767,8 @@ boolean FDECL((*allow), (OBJ_P));/* allow function */ pack = flags.inv_order; do { printed_type_name = FALSE; - for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { + for (i = 0; i < n; i++) { + curr = oarray[i]; if ((qflags & FEEL_COCKATRICE) && curr->otyp == CORPSE && will_feel_cockatrice(curr, FALSE)) { destroy_nhwindow(win); /* stop the menu and revert */ @@ -784,6 +796,7 @@ boolean FDECL((*allow), (OBJ_P));/* allow function */ } pack++; } while (sorted && *pack); + free(oarray); if (engulfer) { char buf[BUFSZ];