From 79515570572912dff759eb45da96b76670ad894e Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 6 Jun 2018 17:45:44 -0700 Subject: [PATCH 1/6] makeknown() Noticed while investigating the report about sortloot interacting with persistent inventory window when identifying all of invent and possibly skipping some items. [This doesn't fix that.] End of game disclosure was using makeknown() on inventory. It is a jacket around discover_object() which passes the flag to exercise Wisdom. That's useless at end of game [now; conceivably wrong if disclosure of characteristics exercise ever got added], so call discover_object() directly to suppress exercise of Wisdom. discover_object() was also calling update_inventory() for every item being discovered. That's not useful when looping through inventory at end of game. --- doc/fixes36.2 | 2 ++ src/end.c | 10 +++++----- src/o_init.c | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/fixes36.2 b/doc/fixes36.2 index 802ce2507..935394f98 100644 --- a/doc/fixes36.2 +++ b/doc/fixes36.2 @@ -30,6 +30,8 @@ using 'O' to set up a hilite_status rule for string comparison, the menu for when finishing using 'O' to examine or set hilite_status rules, if the 'statushilites' option is 0 and there is at least one rule, give a reminder about setting it to non-zero to activate highlighting +end of game disclosure was exercising Wisdom when revealing inventory and + also repeatedly updating persistent inventory window if enabled Fixes to Post-3.6.1 Problems that Were Exposed Via git Repository diff --git a/src/end.c b/src/end.c index d0b51df53..ebb45187c 100644 --- a/src/end.c +++ b/src/end.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 end.c $NHDT-Date: 1512803167 2017/12/09 07:06:07 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.137 $ */ +/* NetHack 3.6 end.c $NHDT-Date: 1528332335 2018/06/07 00:45:35 $ $NHDT-Branch: NetHack-3.6.2 $:$NHDT-Revision: 1.141 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ @@ -985,7 +985,7 @@ winid endwin; if (counting) { nowrap_add(u.urexp, points); } else { - makeknown(otmp->otyp); + discover_object(otmp->otyp, TRUE, FALSE); otmp->known = otmp->dknown = otmp->bknown = otmp->rknown = 1; /* assumes artifacts don't have quan > 1 */ Sprintf(pbuf, "%s%s (worth %ld %s and %ld points)", @@ -1178,7 +1178,7 @@ int how; * it in both of those places. */ for (obj = invent; obj; obj = obj->nobj) { - makeknown(obj->otyp); + discover_object(obj->otyp, TRUE, FALSE); obj->known = obj->bknown = obj->dknown = obj->rknown = 1; if (Is_container(obj) || obj->otyp == STATUE) obj->cknown = obj->lknown = 1; @@ -1400,7 +1400,7 @@ int how; continue; if (objects[typ].oc_class != GEM_CLASS || typ <= LAST_GEM) { otmp = mksobj(typ, FALSE, FALSE); - makeknown(otmp->otyp); + discover_object(otmp->otyp, TRUE, FALSE); otmp->known = 1; /* for fake amulets */ otmp->dknown = 1; /* seen it (blindness fix) */ if (has_oname(otmp)) @@ -1509,7 +1509,7 @@ boolean identified, all_containers, reportempty; putstr(tmpwin, 0, ""); for (obj = box->cobj; obj; obj = obj->nobj) { if (identified) { - makeknown(obj->otyp); + discover_object(obj->otyp, TRUE, FALSE); obj->known = obj->bknown = obj->dknown = obj->rknown = 1; if (Is_container(obj) || obj->otyp == STATUE) diff --git a/src/o_init.c b/src/o_init.c index 3edadd811..b4fc440ee 100644 --- a/src/o_init.c +++ b/src/o_init.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 o_init.c $NHDT-Date: 1450318588 2015/12/17 02:16:28 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.22 $ */ +/* NetHack 3.6 o_init.c $NHDT-Date: 1528332336 2018/06/07 00:45:36 $ $NHDT-Branch: NetHack-3.6.2 $:$NHDT-Revision: 1.24 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -361,7 +361,8 @@ boolean credit_hero; if (credit_hero) exercise(A_WIS, TRUE); } - if (moves > 1L) + /* moves==1L => initial inventory, gameover => final disclosure */ + if (moves > 1L && !program_state.gameover) update_inventory(); } } From 77d478c9399a3faf4109056f316ee985faa88d91 Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 9 Jun 2018 18:03:37 -0700 Subject: [PATCH 2/6] fix #H7205, #H7120, #H5216 - sortloot H7205 - full-pack identify might skip items if perm_invent is on because updating the inventory window might reorder 'invent' while the identify code is in the midst of traversing it; H7120 - pickup that doesn't pick anything up can change the glyph shown on the map because the pile might be reordered such that a different item is on top; H5216 - performing a sortloot operation on a pile and then switching back to sortloot:none doesn't restore pile's original order. The 'revamp' that changed the contributed sortloot feature to switch to simpler usage (object list itself was sorted rather than having a parallel array that needed to be constructed, sorted, traversed, and discarded) turns out to have too many problems. This reverts to a hybrid solution that constructs an array for traversal, leaving the linked list in its original order, but hides most of the details of that from sortloot() callers. The 'revamp' benefit of being able to use normal list traversal is lost, as is the potential to skip sorting when the list turns out to already be in the desired order. This could stand to have a lot more testing than it's had so far. --- doc/fixes36.2 | 4 ++ include/extern.h | 5 +- include/hack.h | 11 +++- src/end.c | 15 +++--- src/invent.c | 136 +++++++++++++++++++++++++++++++++++++++-------- src/pickup.c | 22 ++++---- src/worn.c | 24 +++++++++ 7 files changed, 176 insertions(+), 41 deletions(-) diff --git a/doc/fixes36.2 b/doc/fixes36.2 index 935394f98..bb35fa432 100644 --- a/doc/fixes36.2 +++ b/doc/fixes36.2 @@ -32,6 +32,10 @@ when finishing using 'O' to examine or set hilite_status rules, if the reminder about setting it to non-zero to activate highlighting end of game disclosure was exercising Wisdom when revealing inventory and also repeatedly updating persistent inventory window if enabled +internals for 'sortloot' option have been changed to not reorder the actual + list of objects, so changing it to 'n'one will get the original order + back and having a persistent inventory window open when performing + full-pack identify won't result in possibly skipping some items Fixes to Post-3.6.1 Problems that Were Exposed Via git Repository diff --git a/include/extern.h b/include/extern.h index d570bc864..a7ed1fef6 100644 --- a/include/extern.h +++ b/include/extern.h @@ -934,7 +934,9 @@ E void FDECL(strbuf_nl_to_crlf, (strbuf_t *)); /* ### invent.c ### */ -E void FDECL(sortloot, (struct obj **, unsigned, BOOLEAN_P)); +E Loot *FDECL(sortloot, (struct obj **, unsigned, BOOLEAN_P, + boolean (*)(OBJ_P))); +E void FDECL(unsortloot, (Loot **)); E void FDECL(assigninvlet, (struct obj *)); E struct obj *FDECL(merge_choice, (struct obj *, struct obj *)); E int FDECL(merged, (struct obj **, struct obj **)); @@ -2841,6 +2843,7 @@ E void FDECL(bypass_obj, (struct obj *)); E void NDECL(clear_bypasses); E void FDECL(bypass_objlist, (struct obj *, BOOLEAN_P)); E struct obj *FDECL(nxt_unbypassed_obj, (struct obj *)); +E struct obj *FDECL(nxt_unbypassed_loot, (Loot *, struct obj *)); E int FDECL(racial_exception, (struct monst *, struct obj *)); /* ### write.c ### */ diff --git a/include/hack.h b/include/hack.h index 7d493e962..3b670be1a 100644 --- a/include/hack.h +++ b/include/hack.h @@ -190,6 +190,14 @@ enum hmon_atkmode_types { HMON_DRAGGED /* attached iron ball, pulled into mon */ }; +/* sortloot() return type; needed before extern.h */ +struct sortloot_item { + struct obj *obj; + int indx; /* signed int, because sortloot()'s qsort comparison routine + assumes (a->indx - b->indx) might yield a negative result */ +}; +typedef struct sortloot_item Loot; + #define MATCH_WARN_OF_MON(mon) \ (Warn_of_mon && ((context.warntype.obj \ && (context.warntype.obj & (mon)->data->mflags2)) \ @@ -215,8 +223,7 @@ enum hmon_atkmode_types { #define SYM_OFF_X (SYM_OFF_W + WARNCOUNT) #define SYM_MAX (SYM_OFF_X + MAXOTHER) -#ifdef USE_TRAMPOLI /* This doesn't belong here, but we have little choice \ - */ +#ifdef USE_TRAMPOLI /* this doesn't belong here, but we have little choice */ #undef NDECL #define NDECL(f) f() #endif diff --git a/src/end.c b/src/end.c index ebb45187c..589733078 100644 --- a/src/end.c +++ b/src/end.c @@ -1498,16 +1498,18 @@ boolean identified, all_containers, reportempty; continue; /* wrong type of container */ } else if (box->cobj) { winid tmpwin = create_nhwindow(NHW_MENU); + Loot *sortedcobj, *srtc; + unsigned sortflags; - sortloot(&box->cobj, - (((flags.sortloot == 'l' || flags.sortloot == 'f') - ? SORTLOOT_LOOT : 0) - | (flags.sortpack ? SORTLOOT_PACK : 0)), - FALSE); Sprintf(buf, "Contents of %s:", the(xname(box))); putstr(tmpwin, 0, buf); putstr(tmpwin, 0, ""); - for (obj = box->cobj; obj; obj = obj->nobj) { + sortflags = (((flags.sortloot == 'l' || flags.sortloot == 'f') + ? SORTLOOT_LOOT : 0) + | (flags.sortpack ? SORTLOOT_PACK : 0)); + sortedcobj = sortloot(&box->cobj, sortflags, FALSE, + (boolean FDECL((*), (OBJ_P))) 0); + for (srtc = sortedcobj; ((obj = srtc->obj) != 0); ++srtc) { if (identified) { discover_object(obj->otyp, TRUE, FALSE); obj->known = obj->bknown = obj->dknown @@ -1517,6 +1519,7 @@ boolean identified, all_containers, reportempty; } putstr(tmpwin, 0, doname(obj)); } + unsortloot(&sortedcobj); if (cat) putstr(tmpwin, 0, "Schroedinger's cat"); else if (deadcat) diff --git a/src/invent.c b/src/invent.c index 13951f104..1262fee1f 100644 --- a/src/invent.c +++ b/src/invent.c @@ -46,10 +46,6 @@ static int lastinvnr = 51; /* 0 ... 51 (never saved&restored) */ */ static char venom_inv[] = { VENOM_CLASS, 0 }; /* (constant) */ -struct sortloot_item { - struct obj *obj; - int indx; -}; unsigned sortlootmode = 0; /* qsort comparison routine for sortloot() */ @@ -211,6 +207,95 @@ tiebreak: return (sli1->indx - sli2->indx); } +/* + * sortloot() - the story so far... + * + * The original implementation constructed and returned an array + * of pointers to objects in the requested order. Callers had to + * count the number of objects, allocate the array, pass one + * object at a time to the routine which populates it, traverse + * the objects via stepping through the array, then free the + * array. The ordering process used a basic insertion sort which + * is fine for short lists but inefficient for long ones. + * + * 3.6.0 (and continuing with 3.6.1) changed all that so that + * sortloot was self-contained as far as callers were concerned. + * It reordered the linked list into the requested order and then + * normal list traversal was used to process it. It also switched + * to qsort() on the assumption that the C library implementation + * put some effort into sorting efficiently. It also checked + * whether the list was already sorted as it got ready to do the + * sorting, so re-examining inventory or a pile of objects without + * having changed anything would gobble up less CPU than a full + * sort. But it had as least two problems (aside from the ordinary + * complement of bugs): + * 1) some players wanted to get the original order back when they + * changed the 'sortloot' option back to 'none', but the list + * reordering made that infeasible; + * 2) object identification giving the 'ID whole pack' result + * would call makeknown() on each newly ID'd object, that would + * call update_inventory() to update the persistent inventory + * window if one existed, the interface would call the inventory + * display routine which would call sortloot() which might change + * the order of the list being traversed by the identify code, + * possibly skipping the ID of some objects. That could have been + * avoided by suppressing 'perm_invent' during identification + * (fragile) or by avoiding sortloot() during inventory display + * (more robust). + * + * 3.6.2 reverts to the temporary array of ordered obj pointers + * but has sortloot() do the counting and allocation. Callers + * need to use array traversal instead of linked list traversal + * and need to free the temporary array when done. And the + * array contains 'struct sortloot_item' (aka 'Loot') entries + * instead of simple 'struct obj *' entries. + */ +Loot * +sortloot(olist, mode, by_nexthere, filterfunc) +struct obj **olist; /* previous version might have changed *olist, we don't */ +unsigned mode; /* flags for sortloot_cmp() */ +boolean by_nexthere; /* T: traverse via obj->nexthere, F: via obj->nobj */ +boolean FDECL((*filterfunc), (OBJ_P)); +{ + Loot *sliarray; + struct obj *o; + unsigned n, i; + + for (n = 0, o = *olist; o; o = by_nexthere ? o->nexthere : o->nobj) + ++n; + /* note: if there is a filter function, this might overallocate */ + sliarray = (Loot *) alloc((n + 1) * sizeof *sliarray); + + /* populate aliarray[0..n-1] */ + for (i = 0, o = *olist; o; ++i, o = by_nexthere ? o->nexthere : o->nobj) { + if (filterfunc && !(*filterfunc)(o)) + continue; + sliarray[i].obj = o, sliarray[i].indx = (int) i; + } + n = i; + /* add a terminator so that we don't have to pass 'n' back to caller */ + sliarray[n].obj = (struct obj *) 0, sliarray[n].indx = -1; + + /* do the sort; if no sorting is requested, we'll just return + a sortloot_item array reflecting the current ordering */ + if (mode) { + sortlootmode = mode; /* extra input for sortloot_cmp() */ + qsort((genericptr_t) sliarray, n, sizeof *sliarray, sortloot_cmp); + sortlootmode = 0; /* reset static mode flags */ + } + return sliarray; +} + +/* sortloot() callers should use this to free up memory it allocates */ +void +unsortloot(loot_array_p) +Loot **loot_array_p; +{ + if (*loot_array_p) + free((genericptr_t) *loot_array_p), *loot_array_p = (Loot *) 0; +} + +#if 0 /* 3.6.0 'revamp' */ void sortloot(olist, mode, by_nexthere) struct obj **olist; @@ -248,6 +333,7 @@ boolean by_nexthere; /* T: traverse via obj->nexthere, F: via obj->nobj */ } sortlootmode = 0; } +#endif /*0*/ void assigninvlet(otmp) @@ -1097,6 +1183,7 @@ register const char *let, *word; boolean msggiven = FALSE; boolean oneloop = FALSE; long dummymask; + Loot *sortedinvent, *srtinv; if (*let == ALLOW_COUNT) let++, allowcnt = 1; @@ -1133,15 +1220,13 @@ register const char *let, *word; if (!flags.invlet_constant) reassign(); - else - /* in case invent is in packorder, force it to be in invlet - order before collecing candidate inventory letters; - if player responds with '?' or '*' it will be changed - back by display_pickinv(), but by then we'll have 'lets' - and so won't have to re-sort in the for(;;) loop below */ - sortloot(&invent, SORTLOOT_INVLET, FALSE); - for (otmp = invent; otmp; otmp = otmp->nobj) { + /* force invent to be in invlet order before collecting candidate + inventory letters */ + sortedinvent = sortloot(&invent, SORTLOOT_INVLET, FALSE, + (boolean FDECL((*), (OBJ_P))) 0); + + for (srtinv = sortedinvent; (otmp = srtinv->obj) != 0; ++srtinv) { if (&bp[foo] == &buf[sizeof buf - 1] || ap == &altlets[sizeof altlets - 1]) { /* we must have a huge number of NOINVSYM items somehow */ @@ -1285,6 +1370,7 @@ register const char *let, *word; allowall = usegold = TRUE; } } + unsortloot(&sortedinvent); bp[foo] = 0; if (foo == 0 && bp > buf && bp[-1] == ' ') @@ -1763,16 +1849,17 @@ unsigned *resultflags; */ int askchain(objchn, olets, allflag, fn, ckfn, mx, word) -struct obj **objchn; +struct obj **objchn; /* *objchn might change */ int allflag, mx; const char *olets, *word; /* olets is an Obj Class char array */ int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); { struct obj *otmp, *otmpo; register char sym, ilet; - register int cnt = 0, dud = 0, tmp; + int cnt = 0, dud = 0, tmp; boolean takeoff, nodot, ident, take_out, put_in, first, ininv, bycat; char qbuf[QBUFSZ], qpfx[QBUFSZ]; + Loot *sortedchn = 0; takeoff = taking_off(word); ident = !strcmp(word, "identify"); @@ -1787,7 +1874,8 @@ int FDECL((*fn), (OBJ_P)), FDECL((*ckfn), (OBJ_P)); /* someday maybe we'll sort by 'olets' too (temporarily replace flags.packorder and pass SORTLOOT_PACK), but not yet... */ - sortloot(objchn, SORTLOOT_INVLET, FALSE); + sortedchn = sortloot(objchn, SORTLOOT_INVLET, FALSE, + (boolean FDECL((*), (OBJ_P))) 0); first = TRUE; /* @@ -1812,7 +1900,7 @@ nextclass: * track of which list elements have already been processed. */ bypass_objlist(*objchn, FALSE); /* clear chain's bypass bits */ - while ((otmp = nxt_unbypassed_obj(*objchn)) != 0) { + while ((otmp = nxt_unbypassed_loot(sortedchn, *objchn)) != 0) { if (ilet == 'z') ilet = 'A'; else if (ilet == 'Z') @@ -1900,11 +1988,13 @@ nextclass: } if (olets && *olets && *++olets) goto nextclass; + if (!takeoff && (dud || cnt)) pline("That was all."); else if (!dud && !cnt) pline("No applicable objects."); ret: + unsortloot(&sortedchn); bypass_objlist(*objchn, FALSE); return cnt; } @@ -2195,6 +2285,8 @@ long *out_cnt; winid win; /* windows being used */ anything any; menu_item *selected; + unsigned sortflags; + Loot *sortedinvent, *srtinv; if (lets && !*lets) lets = 0; /* simplify tests: (lets) instead of (lets && *lets) */ @@ -2268,10 +2360,11 @@ long *out_cnt; return ret; } - sortloot(&invent, - (((flags.sortloot == 'f') ? SORTLOOT_LOOT : SORTLOOT_INVLET) - | (flags.sortpack ? SORTLOOT_PACK : 0)), - FALSE); + sortflags = (flags.sortloot == 'f') ? SORTLOOT_LOOT : SORTLOOT_INVLET; + if (flags.sortpack) + sortflags |= SORTLOOT_PACK; + sortedinvent = sortloot(&invent, sortflags, FALSE, + (boolean FDECL((*), (OBJ_P))) 0); start_menu(win); any = zeroany; @@ -2296,7 +2389,7 @@ long *out_cnt; } nextclass: classcount = 0; - for (otmp = invent; otmp; otmp = otmp->nobj) { + for (srtinv = sortedinvent; (otmp = srtinv->obj) != 0; ++srtinv) { if (lets && !index(lets, otmp->invlet)) continue; if (!flags.sortpack || otmp->oclass == *invlet) { @@ -2330,6 +2423,7 @@ nextclass: add_menu(win, NO_GLYPH, &any, '*', 0, ATR_NONE, "(list everything)", MENU_UNSELECTED); } + unsortloot(&sortedinvent); /* for permanent inventory where we intend to show everything but nothing has been listed (because there isn't anyhing to list; recognized via any.a_char still being zero; the n==0 case above diff --git a/src/pickup.c b/src/pickup.c index 78206230d..683395d55 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -849,6 +849,8 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ boolean printed_type_name, first, sorted = (qflags & INVORDER_SORT) != 0, engulfer = (qflags & INCLUDE_HERO) != 0; + unsigned sortflags; + Loot *sortedolist, *srtoli; *pick_list = (menu_item *) 0; if (!olist && !engulfer) @@ -876,16 +878,13 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ return 1; } - if (sorted || flags.sortloot != 'n') { - sortloot(&olist, - (((flags.sortloot == 'f' - || (flags.sortloot == 'l' && !(qflags & USE_INVLET))) - ? SORTLOOT_LOOT - : (qflags & USE_INVLET) ? SORTLOOT_INVLET : 0) - | (flags.sortpack ? SORTLOOT_PACK : 0)), - (qflags & BY_NEXTHERE) ? TRUE : FALSE); - *olist_p = olist; - } + sortflags = (((flags.sortloot == 'f' + || (flags.sortloot == 'l' && !(qflags & USE_INVLET))) + ? SORTLOOT_LOOT + : (qflags & USE_INVLET) ? SORTLOOT_INVLET : 0) + | (flags.sortpack ? SORTLOOT_PACK : 0)); + sortedolist = sortloot(&olist, sortflags, + (qflags & BY_NEXTHERE) ? TRUE : FALSE, allow); win = create_nhwindow(NHW_MENU); start_menu(win); @@ -900,7 +899,7 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ first = TRUE; do { printed_type_name = FALSE; - for (curr = olist; curr; curr = FOLLOW(curr, qflags)) { + for (srtoli = sortedolist; ((curr = srtoli->obj) != 0); ++srtoli) { if (sorted && curr->oclass != *pack) continue; if ((qflags & FEEL_COCKATRICE) && curr->otyp == CORPSE @@ -932,6 +931,7 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ } pack++; } while (sorted && *pack); + unsortloot(&sortedolist); if (engulfer) { char buf[BUFSZ]; diff --git a/src/worn.c b/src/worn.c index cf45277a9..a2819c4e3 100644 --- a/src/worn.c +++ b/src/worn.c @@ -777,6 +777,30 @@ struct obj *objchain; return objchain; } +/* like nxt_unbypassed_obj() but operates on sortloot_item array rather + than an object linked list; the array contains obj==Null terminator; + there's an added complication that the array may have stale pointers + for deleted objects (see Multiple-Drop case in askchain(invent.c)) */ +struct obj * +nxt_unbypassed_loot(lootarray, listhead) +Loot *lootarray; +struct obj *listhead; +{ + struct obj *o, *obj; + + while ((obj = lootarray->obj) != 0) { + for (o = listhead; o; o = o->nobj) + if (o == obj) + break; + if (o && !obj->bypass) { + bypass_obj(obj); + break; + } + ++lootarray; + } + return obj; +} + void mon_break_armor(mon, polyspot) struct monst *mon; From 595ad9a5e979eca6a330fa8125521bc1dfe276d3 Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 10 Jun 2018 18:02:20 -0700 Subject: [PATCH 3/6] more sortloot - picking up cockatrice corpses Yesterday's sortloot() overhaul didn't include some cockatrice corpse handling for pickup. If there's an object class filter in place and pickup has been told to care about cockatrice corpses, have sortloot() include them in the loot array even if food class isn't accepted by the filter. In the pre-sortloot days, and in 3.6.[01] which didn't attempt to deliver a filtered subset of loot, the check for such corpses was done before pickup checks the filter. They need to be in the loot array to retain the same behavior. --- include/hack.h | 1 + src/invent.c | 7 ++++++- src/pickup.c | 12 +++++++----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/include/hack.h b/include/hack.h index 3b670be1a..e94e43250 100644 --- a/include/hack.h +++ b/include/hack.h @@ -376,6 +376,7 @@ enum explosion_types { #define SORTLOOT_PACK 0x01 #define SORTLOOT_INVLET 0x02 #define SORTLOOT_LOOT 0x04 +#define SORTLOOT_PETRIFY 0x20 /* override filter func for c-trice corpses */ /* flags for xkilled() [note: meaning of first bit used to be reversed, 1 to give message and 0 to suppress] */ diff --git a/src/invent.c b/src/invent.c index 1262fee1f..5522d7b09 100644 --- a/src/invent.c +++ b/src/invent.c @@ -260,21 +260,26 @@ boolean FDECL((*filterfunc), (OBJ_P)); Loot *sliarray; struct obj *o; unsigned n, i; + boolean augment_filter; for (n = 0, o = *olist; o; o = by_nexthere ? o->nexthere : o->nobj) ++n; /* note: if there is a filter function, this might overallocate */ sliarray = (Loot *) alloc((n + 1) * sizeof *sliarray); + augment_filter = (mode & SORTLOOT_PETRIFY) ? TRUE : FALSE; /* populate aliarray[0..n-1] */ for (i = 0, o = *olist; o; ++i, o = by_nexthere ? o->nexthere : o->nobj) { - if (filterfunc && !(*filterfunc)(o)) + if (filterfunc && !(*filterfunc)(o) + && (!augment_filter || o->otyp != CORPSE + || !touch_petrifies(&mons[o->corpsenm]))) continue; sliarray[i].obj = o, sliarray[i].indx = (int) i; } n = i; /* add a terminator so that we don't have to pass 'n' back to caller */ sliarray[n].obj = (struct obj *) 0, sliarray[n].indx = -1; + mode &= ~SORTLOOT_PETRIFY; /* do the sort; if no sorting is requested, we'll just return a sortloot_item array reflecting the current ordering */ diff --git a/src/pickup.c b/src/pickup.c index 683395d55..fae1e2f03 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -577,7 +577,8 @@ int what; /* should be a long */ if (flags.menu_style != MENU_TRADITIONAL || iflags.menu_requested) { /* use menus exclusively */ - traverse_how |= AUTOSELECT_SINGLE | INVORDER_SORT; + traverse_how |= AUTOSELECT_SINGLE + | (flags.sortpack ? INVORDER_SORT : 0); if (count) { /* looking for N of something */ char qbuf[QBUFSZ]; @@ -881,8 +882,9 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ sortflags = (((flags.sortloot == 'f' || (flags.sortloot == 'l' && !(qflags & USE_INVLET))) ? SORTLOOT_LOOT - : (qflags & USE_INVLET) ? SORTLOOT_INVLET : 0) - | (flags.sortpack ? SORTLOOT_PACK : 0)); + : ((qflags & USE_INVLET) ? SORTLOOT_INVLET : 0)) + | (flags.sortpack ? SORTLOOT_PACK : 0) + | ((qflags & FEEL_COCKATRICE) ? SORTLOOT_PETRIFY : 0)); sortedolist = sortloot(&olist, sortflags, (qflags & BY_NEXTHERE) ? TRUE : FALSE, allow); @@ -892,8 +894,8 @@ boolean FDECL((*allow), (OBJ_P)); /* allow function */ /* * Run through the list and add the objects to the menu. If * INVORDER_SORT is set, we'll run through the list once for - * each type so we can group them. The allow function will only - * be called once per object in the list. + * each type so we can group them. The allow function was + * called by sortloot() and will be called once per item here. */ pack = flags.inv_order; first = TRUE; From 0b93d262698374988e154db170c632be0dd5b3c6 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 12 Jun 2018 16:33:35 -0700 Subject: [PATCH 4/6] sortloot - enhanced sorting [re-revamp anyone?] When objects are in the same class, sortloot orders them by their formatted name. It was reformatting each object every time it got compared to another object. Change that to remember the formatted name so that any given object is formatted at most once (during the current sort; future sorts will need to format it again). Armor and weapon classes are subdivided into smaller subclasses and the formatting plus alpha compare is only done for items in the same subclass, so helms come out before cloaks and don't get their names compared, for instance. [That was from my 'revamp' rather than the original implementation.] This adds a couple more subclass sets: food (named fruit, 'other' food, tins, eggs, corpses, globs) and tools (containers, pseudo-containers [bag of tricks and horn of plenty once those have become discovered; prior to discovery, bag of tricks is classified as a container and horn of plenty as an instrument], instruments, 'other' tools). The main difference, aside from the formatting efficiency improvement, is to change the previous sort order | pink potion | potion of enlightenment | purple-red potion to be | pink potion | purple-red potion | potion of enlightenment by grouping undiscovered items before discovered items when class and subclass match. So discovery state is essentially a sub-subclass and formatting plus string comparison is only done for members of the same sub-subclass. There are actually four state values: unseen (which applies to particular objects rather than to their type), unknown (not discovered and not named), named (not discovered but has player-assigned type name), and discovered (either fully discovered or considered not interesting to discover [no alternate description, not nameable]). My testing was primarily done with pickup ('m,' with menustyle:T) and sortloot:Loot (the default) plus !sortpack (not the default and not a setting I ordinarily use, but less verbose without the class separators). It won't astonish me if oddities crop up with other usage combinations. --- doc/fixes36.2 | 3 + include/hack.h | 4 + src/invent.c | 322 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 263 insertions(+), 66 deletions(-) diff --git a/doc/fixes36.2 b/doc/fixes36.2 index bb35fa432..aafdcfd25 100644 --- a/doc/fixes36.2 +++ b/doc/fixes36.2 @@ -80,6 +80,9 @@ status_hilite options which use comparisons may now use <= and >= in addition to previous < and >; in 3.6.1 the latter operated as if they were <= and >= but now behave as conventional less than and greater than; old highlight rules using them should be updated +sortloot option has been enhanced to improve object ordering; primarily, + items of undiscovered type come out before items of discovered type + within each class or sub-class of objects Code Cleanup and Reorganization diff --git a/include/hack.h b/include/hack.h index e94e43250..0d962e5e3 100644 --- a/include/hack.h +++ b/include/hack.h @@ -193,8 +193,12 @@ enum hmon_atkmode_types { /* sortloot() return type; needed before extern.h */ struct sortloot_item { struct obj *obj; + char *str; /* result of loot_xname(obj) in some cases, otherwise null */ int indx; /* signed int, because sortloot()'s qsort comparison routine assumes (a->indx - b->indx) might yield a negative result */ + xchar class; /* order rather than object class; 0 => not yet init'd */ + xchar subclass; /* subclass for some classes */ + xchar disco; /* discovery status */ }; typedef struct sortloot_item Loot; diff --git a/src/invent.c b/src/invent.c index 5522d7b09..87a000228 100644 --- a/src/invent.c +++ b/src/invent.c @@ -9,6 +9,8 @@ #define CONTAINED_SYM '>' /* designator for inside a container */ #define HANDS_SYM '-' +STATIC_DCL void FDECL(loot_classify, (Loot *, struct obj *)); +STATIC_DCL char *FDECL(loot_xname, (struct obj *)); STATIC_DCL int FDECL(CFDECLSPEC sortloot_cmp, (const genericptr, const genericptr)); STATIC_DCL void NDECL(reorder_invent); @@ -46,7 +48,210 @@ static int lastinvnr = 51; /* 0 ... 51 (never saved&restored) */ */ static char venom_inv[] = { VENOM_CLASS, 0 }; /* (constant) */ -unsigned sortlootmode = 0; +/* sortloot() classification; called at most once for each object sorted */ +STATIC_OVL void +loot_classify(sort_item, obj) +Loot *sort_item; +struct obj *obj; +{ + /* we may eventually make this a settable option to always use + with sortloot instead of only when the 'sortpack' option isn't + set; it is similar to sortpack's inv_order but items most + likely to be picked up are moved to the front */ + static char def_srt_order[MAXOCLASSES] = { + COIN_CLASS, AMULET_CLASS, RING_CLASS, WAND_CLASS, POTION_CLASS, + SCROLL_CLASS, SPBOOK_CLASS, GEM_CLASS, FOOD_CLASS, TOOL_CLASS, + WEAPON_CLASS, ARMOR_CLASS, ROCK_CLASS, BALL_CLASS, CHAIN_CLASS, 0, + }; + static char armcat[8]; + const char *classorder; + char *p; + int k, otyp = obj->otyp, oclass = obj->oclass; + + /* + * For the value types assigned by this classification, sortloot() + * will put lower valued ones before higher valued ones. + */ + if (!Blind) + obj->dknown = 1; /* xname(obj) does this; we want it sooner */ + /* class order */ + classorder = flags.sortpack ? flags.inv_order : def_srt_order; + p = index(classorder, oclass); + if (p) + k = 1 + (int) (p - classorder); + else + k = 1 + (int) strlen(classorder) + (oclass != VENOM_CLASS); + sort_item->class = (xchar) k; + /* subclass designation; only a few classes have subclasses + and the non-armor ones we use are fairly arbitrary */ + switch (oclass) { + case ARMOR_CLASS: + if (!armcat[7]) { + /* one-time init; we use a different order than the subclass + values defined by objclass.h */ + armcat[ARM_HELM] = 1; /* [2] */ + armcat[ARM_GLOVES] = 2; /* [3] */ + armcat[ARM_BOOTS] = 3; /* [4] */ + armcat[ARM_SHIELD] = 4; /* [1] */ + armcat[ARM_CLOAK] = 5; /* [5] */ + armcat[ARM_SHIRT] = 6; /* [6] */ + armcat[ARM_SUIT] = 7; /* [0] */ + armcat[7] = 8; /* sanity protection */ + } + k = objects[otyp].oc_armcat; + /* oc_armcat overloads oc_subtyp which is an 'schar' so guard + against somebody assigning something unexpected to it */ + if (k < 0 || k >= 7) + k = 7; + k = armcat[k]; + break; + case WEAPON_CLASS: + /* for weapons, group by ammo (arrows, bolts), launcher (bows), + missile (darts, boomerangs), stackable (daggers, knives, spears), + 'other' (swords, axes, &c), polearms */ + k = objects[otyp].oc_skill; + k = (k < 0) ? ((k >= -P_CROSSBOW && k <= -P_BOW) ? 1 : 3) + : ((k >= P_BOW && k <= P_CROSSBOW) ? 2 + : (k == P_SPEAR || k == P_DAGGER || k == P_KNIFE) ? 4 + : !is_pole(obj) ? 5 : 6); + break; + case TOOL_CLASS: + if (obj->dknown && objects[otyp].oc_name_known + && (otyp == BAG_OF_TRICKS || otyp == HORN_OF_PLENTY)) + k = 2; /* known pseudo-container */ + else if (Is_container(obj)) + k = 1; /* regular container or unknown bag of tricks */ + else + switch (otyp) { + case WOODEN_FLUTE: + case MAGIC_FLUTE: + case TOOLED_HORN: + case FROST_HORN: + case FIRE_HORN: + case WOODEN_HARP: + case MAGIC_HARP: + case BUGLE: + case LEATHER_DRUM: + case DRUM_OF_EARTHQUAKE: + case HORN_OF_PLENTY: /* not a musical instrument */ + k = 3; /* instrument or unknown horn of plenty */ + default: + k = 4; /* 'other' tool */ + } + break; + case FOOD_CLASS: + /* [what about separating "partly eaten" within each group?] */ + switch (otyp) { + case SLIME_MOLD: + k = 1; + break; + default: + /* [maybe separate one-bite foods from rations and such?] */ + k = obj->globby ? 6 : 2; + break; + case TIN: + k = 3; + break; + case EGG: + k = 4; + break; + case CORPSE: + k = 5; + break; + } + break; + default: + /* other classes don't have subclasses; we assign a nonzero + value because sortloot() uses 0 to mean 'not yet classified' */ + k = 1; /* any non-zero would do */ + break; + } + sort_item->subclass = (xchar) k; + /* discovery status */ + k = !obj->dknown ? 1 /* unseen */ + : (objects[otyp].oc_name_known || !OBJ_DESCR(objects[otyp])) ? 4 + : (objects[otyp].oc_uname)? 3 /* named (partially discovered) */ + : 2; /* undiscovered */ + sort_item->disco = (xchar) k; +} + +/* sortloot() formatting routine; for alphabetizing, not shown to user */ +STATIC_OVL char * +loot_xname(obj) +struct obj *obj; +{ + struct obj saveo; + boolean save_debug; + char *res, *save_oname; + + /* + * Deal with things that xname() includes as a prefix. We don't + * want such because they change alphabetical ordering. First, + * remember 'obj's current settings. + */ + saveo.odiluted = obj->odiluted; + saveo.blessed = obj->blessed, saveo.cursed = obj->cursed; + saveo.spe = obj->spe; + saveo.owt = obj->owt; + save_oname = has_oname(obj) ? ONAME(obj) : 0; + save_debug = flags.debug; + /* suppress "diluted" for potions and "holy/unholy" for water; + sortloot() will deal with them using other criteria than name */ + if (obj->oclass == POTION_CLASS) { + obj->odiluted = 0; + if (obj->otyp == POT_WATER) + obj->blessed = 0, obj->cursed = 0; + } + /* make "wet towel" and "moist towel" format as "towel" so that all + three group together */ + if (obj->otyp == TOWEL) + obj->spe = 0; + /* group " glob of " by rather than by */ + if (obj->globby) + obj->owt = 200; /* 200: weight of combined glob from ten creatures + (five or fewer is "small", more than fifteen is + "large", in between has no prefix) */ + /* suppress user-assigned name */ + if (save_oname && !obj->oartifact) + ONAME(obj) = 0; + flags.debug = FALSE; /* avoid wizard mode formatting variations */ + + res = cxname_singular(obj); + + flags.debug = save_debug; + /* restore the object */ + if (obj->oclass == POTION_CLASS) { + obj->odiluted = saveo.odiluted; + if (obj->otyp == POT_WATER) + obj->blessed = saveo.blessed, obj->cursed = saveo.cursed; + } + if (obj->otyp == TOWEL) { + obj->spe = saveo.spe; + /* give "towel" a suffix that will force wet ones to come first, + moist ones next, and dry ones last regardless of whether + they've been flagged as having spe known */ + Strcat(res, is_wet_towel(obj) ? ((obj->spe >= 3) ? "x" : "y") : "z"); + } + if (obj->globby) { + obj->owt = saveo.owt; + /* we've suppressed the size prefix (above); there normally won't + be more than one of a given creature type because they coalesce, + but globs with different bless/curse state won't merge so it is + feasible to have multiple at the same location; add a suffix to + get such sorted by size (small first) */ + Strcat(res, (obj->owt <= 100) ? "a" + : (obj->owt <= 300) ? "b" + : (obj->owt <= 500) ? "c" + : "d"); + } + if (save_oname && !obj->oartifact) + ONAME(obj) = save_oname; + + return res; +} + +/* set by sortloot() for use by sortloot_cmp(); reset by sortloot when done */ +static unsigned sortlootmode = 0; /* qsort comparison routine for sortloot() */ STATIC_OVL int CFDECLSPEC @@ -57,57 +262,46 @@ const genericptr vptr2; struct sortloot_item *sli1 = (struct sortloot_item *) vptr1, *sli2 = (struct sortloot_item *) vptr2; struct obj *obj1 = sli1->obj, - *obj2 = sli2->obj, - sav1, sav2; - char *cls1, *cls2, nam1[BUFSZ], nam2[BUFSZ]; + *obj2 = sli2->obj; + char *nam1, *nam2; int val1, val2, c, namcmp; - /* order by object class like inventory display */ - if ((sortlootmode & SORTLOOT_PACK) != 0) { - cls1 = index(flags.inv_order, obj1->oclass); - cls2 = index(flags.inv_order, obj2->oclass); - if (cls1 != cls2) - return (int) (cls1 - cls2); + /* order by object class unless we're doing by-invlet without sortpack */ + if ((sortlootmode & (SORTLOOT_PACK | SORTLOOT_INVLET)) + != SORTLOOT_INVLET) { + /* Classify each object at most once no matter how many + comparisons it is involved in. */ + if (!sli1->class) + loot_classify(sli1, obj1); + if (!sli2->class) + loot_classify(sli2, obj2); - if ((sortlootmode & SORTLOOT_INVLET) != 0) { - ; /* skip sub-classes when sorting by packorder+invlet */ + /* Sort by class. */ + val1 = sli1->class; + val2 = sli2->class; + if (val1 != val2) + return (int) (val1 - val2); - /* for armor, group by sub-category */ - } else if (obj1->oclass == ARMOR_CLASS) { - static int armcat[7 + 1]; - - if (!armcat[7]) { - /* one-time init; we want to control the order */ - armcat[ARM_HELM] = 1; /* [2] */ - armcat[ARM_GLOVES] = 2; /* [3] */ - armcat[ARM_BOOTS] = 3; /* [4] */ - armcat[ARM_SHIELD] = 4; /* [1] */ - armcat[ARM_CLOAK] = 5; /* [5] */ - armcat[ARM_SHIRT] = 6; /* [6] */ - armcat[ARM_SUIT] = 7; /* [0] */ - armcat[7] = 8; - } - val1 = armcat[objects[obj1->otyp].oc_armcat]; - val2 = armcat[objects[obj2->otyp].oc_armcat]; + /* skip sub-classes when ordering by sortpack+invlet */ + if ((sortlootmode & SORTLOOT_INVLET) == 0) { + /* Class matches; sort by subclass. */ + val1 = sli1->subclass; + val2 = sli2->subclass; if (val1 != val2) return val1 - val2; - /* for weapons, group by ammo (arrows, bolts), launcher (bows), - missile (dart, boomerang), stackable (daggers, knives, spears), - 'other' (swords, axes, &c), polearm */ - } else if (obj1->oclass == WEAPON_CLASS) { - val1 = objects[obj1->otyp].oc_skill; - val1 = (val1 < 0) - ? (val1 >= -P_CROSSBOW && val1 <= -P_BOW) ? 1 : 3 - : (val1 >= P_BOW && val1 <= P_CROSSBOW) ? 2 - : (val1 == P_SPEAR || val1 == P_DAGGER - || val1 == P_KNIFE) ? 4 : !is_pole(obj1) ? 5 : 6; - val2 = objects[obj2->otyp].oc_skill; - val2 = (val2 < 0) - ? (val2 >= -P_CROSSBOW && val2 <= -P_BOW) ? 1 : 3 - : (val2 >= P_BOW && val2 <= P_CROSSBOW) ? 2 - : (val2 == P_SPEAR || val2 == P_DAGGER - || val2 == P_KNIFE) ? 4 : !is_pole(obj2) ? 5 : 6; + /* Class and subclass match; sort by discovery status: + * first unseen, then seen but not named or discovered, + * then named, lastly discovered. + * 1) potion + * 2) pink potion + * 3) dark green potion called confusion + * 4) potion of healing + * Multiple entries within each group will be put into + * alphabetical order below. + */ + val1 = sli1->disco; + val2 = sli2->disco; if (val1 != val2) return val1 - val2; } @@ -136,28 +330,16 @@ const genericptr vptr2; /* * Sort object names in lexicographical order, ignoring quantity. + * + * Each obj gets formatted at most once (per sort) no matter how many + * comparisons it gets subjected to. */ - /* Force diluted potions to come out after undiluted of same type; - obj->odiluted overloads obj->oeroded. */ - sav1.odiluted = obj1->odiluted; - sav2.odiluted = obj2->odiluted; - if (obj1->oclass == POTION_CLASS) - obj1->odiluted = 0; - if (obj1->oclass == POTION_CLASS) - obj2->odiluted = 0; - /* Force holy and unholy water to sort adjacent to water rather - than among 'h's and 'u's. BUCX order will keep them distinct. */ - Strcpy(nam1, cxname_singular(obj1)); - if (obj1->otyp == POT_WATER && obj1->bknown - && (obj1->blessed || obj1->cursed)) - (void) strsubst(nam1, obj1->blessed ? "holy " : "unholy ", ""); - Strcpy(nam2, cxname_singular(obj2)); - if (obj2->otyp == POT_WATER && obj2->bknown - && (obj2->blessed || obj2->cursed)) - (void) strsubst(nam2, obj2->blessed ? "holy " : "unholy ", ""); - obj1->odiluted = sav1.odiluted; - obj2->odiluted = sav2.odiluted; - + nam1 = sli1->str; + if (!nam1) + nam1 = sli1->str = dupstr(loot_xname(obj1)); + nam2 = sli2->str; + if (!nam2) + nam2 = sli2->str = dupstr(loot_xname(obj2)); if ((namcmp = strcmpi(nam1, nam2)) != 0) return namcmp; @@ -275,10 +457,14 @@ boolean FDECL((*filterfunc), (OBJ_P)); || !touch_petrifies(&mons[o->corpsenm]))) continue; sliarray[i].obj = o, sliarray[i].indx = (int) i; + sliarray[i].str = (char *) 0; + sliarray[i].class = sliarray[i].subclass = sliarray[i].disco = 0; } n = i; /* add a terminator so that we don't have to pass 'n' back to caller */ sliarray[n].obj = (struct obj *) 0, sliarray[n].indx = -1; + sliarray[n].str = (char *) 0; + sliarray[n].class = sliarray[n].subclass = sliarray[n].disco = 0; mode &= ~SORTLOOT_PETRIFY; /* do the sort; if no sorting is requested, we'll just return @@ -287,6 +473,10 @@ boolean FDECL((*filterfunc), (OBJ_P)); sortlootmode = mode; /* extra input for sortloot_cmp() */ qsort((genericptr_t) sliarray, n, sizeof *sliarray, sortloot_cmp); sortlootmode = 0; /* reset static mode flags */ + /* if sortloot_cmp formatted any objects, discard their strings now */ + for (i = 0; i < n; ++i) + if (sliarray[i].str) + free((genericptr_t) sliarray[i].str), sliarray[i].str = 0; } return sliarray; } From 3eb452ad946fe7e759f2a7e25bbc3145a1f878f1 Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 14 Jun 2018 17:29:59 -0700 Subject: [PATCH 5/6] another sortloot tweak The code that formats an object for use in alphabetic comparisons during sorting is forcing off wizard mode to avoid any alternate formatting that might produce. Add a guarantee that doing this can't be used as a backdoor to create a normal mode panic file if someone figures out a way to make xname() panic. --- src/invent.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/invent.c b/src/invent.c index 87a000228..9a66d0883 100644 --- a/src/invent.c +++ b/src/invent.c @@ -214,11 +214,20 @@ struct obj *obj; /* suppress user-assigned name */ if (save_oname && !obj->oartifact) ONAME(obj) = 0; - flags.debug = FALSE; /* avoid wizard mode formatting variations */ + /* avoid wizard mode formatting variations */ + if (wizard) { /* flags.debug */ + /* paranoia: before toggling off wizard mode, guard against a + panic in xname() producing a normal mode panic save file */ + program_state.something_worth_saving = 0; + flags.debug = FALSE; + } res = cxname_singular(obj); - flags.debug = save_debug; + if (save_debug) { + flags.debug = TRUE; + program_state.something_worth_saving = 1; + } /* restore the object */ if (obj->oclass == POTION_CLASS) { obj->odiluted = saveo.odiluted; @@ -409,7 +418,7 @@ tiebreak: * whether the list was already sorted as it got ready to do the * sorting, so re-examining inventory or a pile of objects without * having changed anything would gobble up less CPU than a full - * sort. But it had as least two problems (aside from the ordinary + * sort. But it had at least two problems (aside from the ordinary * complement of bugs): * 1) some players wanted to get the original order back when they * changed the 'sortloot' option back to 'none', but the list @@ -449,10 +458,15 @@ boolean FDECL((*filterfunc), (OBJ_P)); /* note: if there is a filter function, this might overallocate */ sliarray = (Loot *) alloc((n + 1) * sizeof *sliarray); + /* the 'keep cockatrice corpses' flag is overloaded with sort mode */ augment_filter = (mode & SORTLOOT_PETRIFY) ? TRUE : FALSE; + mode &= ~SORTLOOT_PETRIFY; /* remove flag, leaving mode */ /* populate aliarray[0..n-1] */ for (i = 0, o = *olist; o; ++i, o = by_nexthere ? o->nexthere : o->nobj) { if (filterfunc && !(*filterfunc)(o) + /* caller may be asking us to override filterfunc (in order + to do a cockatrice corpse touch check during pickup even + if/when the filter rejects food class) */ && (!augment_filter || o->otyp != CORPSE || !touch_petrifies(&mons[o->corpsenm]))) continue; @@ -465,11 +479,10 @@ boolean FDECL((*filterfunc), (OBJ_P)); sliarray[n].obj = (struct obj *) 0, sliarray[n].indx = -1; sliarray[n].str = (char *) 0; sliarray[n].class = sliarray[n].subclass = sliarray[n].disco = 0; - mode &= ~SORTLOOT_PETRIFY; /* do the sort; if no sorting is requested, we'll just return a sortloot_item array reflecting the current ordering */ - if (mode) { + if (mode && n > 1) { sortlootmode = mode; /* extra input for sortloot_cmp() */ qsort((genericptr_t) sliarray, n, sizeof *sliarray, sortloot_cmp); sortlootmode = 0; /* reset static mode flags */ From f81818e85dcd17c3ef0f956bc2ac9e759df6f683 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 15 Jun 2018 16:24:02 -0700 Subject: [PATCH 6/6] fix #H7226 - vault guard should have whistle Implement the suggestion that since teleporting away from the vault while being confronted by the guard results in a shrill whistling sound, the vault guard ought to have a tin whistle in his inventory. I also added a check that he does have the whistle and to give an alternate message if not, but after half a dozen tries to have a squad of beefed up monkeys steal the whistle, they never accomplished that. At least three times they took everything except the whistle but I never succeeded in verifying the alternate message. --- doc/fixes36.2 | 2 ++ src/makemon.c | 18 ++++++++++++++---- src/vault.c | 5 ++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/doc/fixes36.2 b/doc/fixes36.2 index aafdcfd25..ea039baaa 100644 --- a/doc/fixes36.2 +++ b/doc/fixes36.2 @@ -36,6 +36,8 @@ internals for 'sortloot' option have been changed to not reorder the actual list of objects, so changing it to 'n'one will get the original order back and having a persistent inventory window open when performing full-pack identify won't result in possibly skipping some items +give vault guards a cursed tin whistle since there is a shrill whistling + sound if hero teleports out of vault while being confronted by guard Fixes to Post-3.6.1 Problems that Were Exposed Via git Repository diff --git a/src/makemon.c b/src/makemon.c index efca15e5d..a40121e0a 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -653,16 +653,26 @@ register struct monst *mtmp; nhUse(mac); /* suppress 'dead increment' from static analyzer */ - if (ptr != &mons[PM_GUARD] && ptr != &mons[PM_WATCHMAN] - && ptr != &mons[PM_WATCH_CAPTAIN]) { + if (ptr == &mons[PM_WATCH_CAPTAIN]) { + ; /* better weapon rather than extra gear here */ + } else if (ptr == &mons[PM_WATCHMAN]) { + if (rn2(3)) /* most watchmen carry a whistle */ + (void) mongets(mtmp, TIN_WHISTLE); + } else if (ptr == &mons[PM_GUARD]) { + /* if hero teleports out of a vault while being confronted + by the vault's guard, there is a shrill whistling sound, + so guard evidently carries a cursed whistle */ + otmp = mksobj(TIN_WHISTLE, TRUE, FALSE); + curse(otmp); + (void) mpickobj(mtmp, otmp); + } else { /* soldiers and their officers */ if (!rn2(3)) (void) mongets(mtmp, K_RATION); if (!rn2(2)) (void) mongets(mtmp, C_RATION); if (ptr != &mons[PM_SOLDIER] && !rn2(3)) (void) mongets(mtmp, BUGLE); - } else if (ptr == &mons[PM_WATCHMAN] && rn2(3)) - (void) mongets(mtmp, TIN_WHISTLE); + } } else if (ptr == &mons[PM_SHOPKEEPER]) { (void) mongets(mtmp, SKELETON_KEY); switch (rn2(4)) { diff --git a/src/vault.c b/src/vault.c index e3bfb4ef0..0f5724925 100644 --- a/src/vault.c +++ b/src/vault.c @@ -665,7 +665,10 @@ register struct monst *grd; grd->mpeaceful = 0; letknow: if (!cansee(grd->mx, grd->my) || !mon_visible(grd)) - You_hear("the shrill sound of a guard's whistle."); + You_hear("%s.", + m_carrying(grd, TIN_WHISTLE) + ? "the shrill sound of a guard's whistle" + : "angry shouting"); else You(um_dist(grd->mx, grd->my, 2) ? "see %s approaching."