#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);
*/
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 "<size> glob of <foo>" by <foo> rather than by <size> */
+ 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
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);
-
- if ((sortlootmode & SORTLOOT_INVLET) != 0) {
- ; /* skip sub-classes when sorting by packorder+invlet */
-
- /* 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];
+ /* 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);
+
+ /* Sort by class. */
+ val1 = sli1->class;
+ val2 = sli2->class;
+ if (val1 != val2)
+ return (int) (val1 - val2);
+
+ /* 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;
}
/*
* 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;
|| !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
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;
}