These are displayed in discoveries, and a new 'price_quotes' option allows them to be displayed for un-IDed objects in other contexts too (the idea is that you turn on the option while identifying objects and off for general play). Invalidates existing save files.
1218 lines
42 KiB
C
1218 lines
42 KiB
C
/* NetHack 3.7 o_init.c $NHDT-Date: 1771216675 2026/02/15 20:37:55 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.101 $ */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/*-Copyright (c) Robert Patrick Rankin, 2011. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
#ifndef SFCTOOL
|
|
staticfn void setgemprobs(d_level *);
|
|
staticfn void randomize_gem_colors(void);
|
|
staticfn void shuffle(int, int, boolean);
|
|
staticfn void shuffle_all(void);
|
|
staticfn int QSORTCALLBACK discovered_cmp(const genericptr, const genericptr);
|
|
staticfn char *sortloot_descr(int, char *);
|
|
staticfn char *disco_typename(int);
|
|
staticfn void disco_append_typename(char *, int);
|
|
staticfn void disco_fmt_uniq(int, char *outbuf) NONNULLARG2;
|
|
staticfn void disco_output_sorted(winid, char **, int, boolean);
|
|
staticfn char *oclass_to_name(char, char *);
|
|
|
|
#ifdef TILES_IN_GLYPHMAP
|
|
extern glyph_map glyphmap[MAX_GLYPH];
|
|
staticfn void shuffle_tiles(void);
|
|
|
|
/* Shuffle tile assignments to match descriptions, so a red potion isn't
|
|
* displayed with a blue tile and so on.
|
|
*
|
|
* Tile assignments are not saved, and shouldn't be so that a game can
|
|
* be resumed on an otherwise identical non-tile-using binary, so we have
|
|
* to reshuffle the assignments from oc_descr_idx information when a game
|
|
* is restored. So might as well do that the first time instead of writing
|
|
* another routine.
|
|
*/
|
|
staticfn void
|
|
shuffle_tiles(void)
|
|
{
|
|
int i;
|
|
short tmp_tilemap[2][NUM_OBJECTS];
|
|
|
|
for (i = 0; i < NUM_OBJECTS; i++) {
|
|
tmp_tilemap[0][i] = glyphmap[objects[i].oc_descr_idx
|
|
+ GLYPH_OBJ_OFF].tileidx;
|
|
tmp_tilemap[1][i] = glyphmap[objects[i].oc_descr_idx
|
|
+ GLYPH_OBJ_PILETOP_OFF].tileidx;
|
|
}
|
|
for (i = 0; i < NUM_OBJECTS; i++) {
|
|
glyphmap[i + GLYPH_OBJ_OFF].tileidx = tmp_tilemap[0][i];
|
|
glyphmap[i + GLYPH_OBJ_PILETOP_OFF].tileidx = tmp_tilemap[1][i];
|
|
}
|
|
}
|
|
#endif /* TILES_IN_GLYPHMAP */
|
|
|
|
staticfn void
|
|
setgemprobs(d_level *dlev)
|
|
{
|
|
int j, first, lev, sum = 0;
|
|
|
|
if (dlev)
|
|
lev = (ledger_no(dlev) > maxledgerno()) ? maxledgerno()
|
|
: ledger_no(dlev);
|
|
else
|
|
lev = 0;
|
|
first = svb.bases[GEM_CLASS];
|
|
|
|
for (j = 0; j < 9 - lev / 3; j++)
|
|
objects[first + j].oc_prob = 0;
|
|
first += j;
|
|
if (first > LAST_REAL_GEM || objects[first].oc_class != GEM_CLASS
|
|
|| OBJ_NAME(objects[first]) == (char *) 0) {
|
|
raw_printf("Not enough gems? - first=%d j=%d LAST_GEM=%d", first, j,
|
|
LAST_REAL_GEM);
|
|
wait_synch();
|
|
}
|
|
for (j = first; j <= LAST_REAL_GEM; j++)
|
|
objects[j].oc_prob = (171 + j - first) / (LAST_REAL_GEM + 1 - first);
|
|
|
|
/* recompute GEM_CLASS total oc_prob - including rocks/stones */
|
|
for (j = svb.bases[GEM_CLASS]; j < svb.bases[GEM_CLASS + 1]; j++)
|
|
sum += objects[j].oc_prob;
|
|
go.oclass_prob_totals[GEM_CLASS] = sum;
|
|
}
|
|
|
|
/* some gems can have different colors */
|
|
staticfn void
|
|
randomize_gem_colors(void)
|
|
{
|
|
#define COPY_OBJ_DESCR(o_dst, o_src) \
|
|
o_dst.oc_descr_idx = o_src.oc_descr_idx, o_dst.oc_color = o_src.oc_color
|
|
if (rn2(2)) { /* change turquoise from green to blue? */
|
|
COPY_OBJ_DESCR(objects[TURQUOISE], objects[SAPPHIRE]);
|
|
}
|
|
if (rn2(2)) { /* change aquamarine from green to blue? */
|
|
COPY_OBJ_DESCR(objects[AQUAMARINE], objects[SAPPHIRE]);
|
|
}
|
|
switch (rn2(4)) { /* change fluorite from violet? */
|
|
case 0:
|
|
break;
|
|
case 1: /* blue */
|
|
COPY_OBJ_DESCR(objects[FLUORITE], objects[SAPPHIRE]);
|
|
break;
|
|
case 2: /* white */
|
|
COPY_OBJ_DESCR(objects[FLUORITE], objects[DIAMOND]);
|
|
break;
|
|
case 3: /* green */
|
|
COPY_OBJ_DESCR(objects[FLUORITE], objects[EMERALD]);
|
|
break;
|
|
}
|
|
#undef COPY_OBJ_DESCR
|
|
}
|
|
|
|
/* shuffle descriptions on objects o_low to o_high */
|
|
staticfn void
|
|
shuffle(int o_low, int o_high, boolean domaterial)
|
|
{
|
|
int i, j, num_to_shuffle;
|
|
short sw;
|
|
int color;
|
|
|
|
for (num_to_shuffle = 0, j = o_low; j <= o_high; j++)
|
|
if (!objects[j].oc_name_known)
|
|
num_to_shuffle++;
|
|
if (num_to_shuffle < 2)
|
|
return;
|
|
|
|
for (j = o_low; j <= o_high; j++) {
|
|
if (objects[j].oc_name_known)
|
|
continue;
|
|
do
|
|
i = j + rn2(o_high - j + 1);
|
|
while (objects[i].oc_name_known);
|
|
sw = objects[j].oc_descr_idx;
|
|
objects[j].oc_descr_idx = objects[i].oc_descr_idx;
|
|
objects[i].oc_descr_idx = sw;
|
|
sw = objects[j].oc_tough;
|
|
objects[j].oc_tough = objects[i].oc_tough;
|
|
objects[i].oc_tough = sw;
|
|
color = objects[j].oc_color;
|
|
objects[j].oc_color = objects[i].oc_color;
|
|
objects[i].oc_color = color;
|
|
|
|
/* shuffle material */
|
|
if (domaterial) {
|
|
sw = objects[j].oc_material;
|
|
objects[j].oc_material = objects[i].oc_material;
|
|
objects[i].oc_material = sw;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
init_objects(void)
|
|
{
|
|
int i, first, last, prevoclass;
|
|
char oclass;
|
|
|
|
for (i = 0; i <= MAXOCLASSES; i++) {
|
|
svb.bases[i] = 0;
|
|
if (i > 0 && i < MAXOCLASSES && objects[i].oc_class != i)
|
|
panic(
|
|
"init_objects: class for generic object #%d doesn't match (%d)",
|
|
i, objects[i].oc_class);
|
|
}
|
|
/* initialize object descriptions */
|
|
for (i = 0; i < NUM_OBJECTS; i++)
|
|
objects[i].oc_name_idx = objects[i].oc_descr_idx = i;
|
|
/* init base; if probs given check that they add up to 1000,
|
|
otherwise compute probs */
|
|
first = MAXOCLASSES;
|
|
prevoclass = -1;
|
|
while (first < NUM_OBJECTS) {
|
|
oclass = objects[first].oc_class;
|
|
/*
|
|
* objects[] sanity check: must be in ascending oc_class order to
|
|
* be able to use bases[class+1]-1 for the end of a class's range.
|
|
* Also catches a non-contiguous class because reverting to any
|
|
* earlier class would involve switching back to a lower class
|
|
* number after having moved on to one or more other classes.
|
|
*/
|
|
if ((int) oclass < prevoclass)
|
|
panic("objects[%d] class #%d not in order!", first, oclass);
|
|
|
|
last = first + 1;
|
|
while (last < NUM_OBJECTS && objects[last].oc_class == oclass)
|
|
last++;
|
|
svb.bases[(int) oclass] = first;
|
|
|
|
if (oclass == GEM_CLASS) {
|
|
setgemprobs((d_level *) 0);
|
|
randomize_gem_colors();
|
|
}
|
|
first = last;
|
|
prevoclass = (int) oclass;
|
|
}
|
|
/* extra entry allows deriving the range of a class via
|
|
bases[class] through bases[class+1]-1 for all classes
|
|
(except for ILLOBJ_CLASS which is separated from WEAPON_CLASS
|
|
by generic objects); second extra entry is to prevent an
|
|
unexplained crash in doclassdisco(), where the code ended up
|
|
attempting to process non-existent class MAXOCLASSES; the
|
|
[MAXOCLASSES+1] element gives that non-class 0 objects
|
|
when traversing objects[] from bases[X] through bases[X+1]-1 */
|
|
svb.bases[MAXOCLASSES] = svb.bases[MAXOCLASSES + 1] = NUM_OBJECTS;
|
|
/* hypothetically someone might remove all objects of some class,
|
|
or be adding a new class and not populated it yet, leaving gaps
|
|
in bases[]; guarantee that there are no such gaps */
|
|
for (last = MAXOCLASSES - 1; last >= 0; --last)
|
|
if (!svb.bases[last])
|
|
svb.bases[last] = svb.bases[last + 1];
|
|
|
|
/* check objects[].oc_name_known */
|
|
for (i = MAXOCLASSES; i < NUM_OBJECTS; ++i) {
|
|
int nmkn = objects[i].oc_name_known != 0;
|
|
|
|
if (!OBJ_DESCR(objects[i]) ^ nmkn) {
|
|
if (iflags.sanity_check) {
|
|
impossible(
|
|
"obj #%d (%s) name is %s despite%s alternate description",
|
|
i, OBJ_NAME(objects[i]),
|
|
nmkn ? "pre-known" : "not known",
|
|
nmkn ? "" : " no");
|
|
}
|
|
/* repair the mistake and keep going */
|
|
objects[i].oc_name_known = nmkn ? 0 : 1;
|
|
}
|
|
}
|
|
/* compute oclass_prob_totals */
|
|
init_oclass_probs();
|
|
|
|
/* shuffle descriptions */
|
|
shuffle_all();
|
|
#ifdef TILES_IN_GLYPHMAP
|
|
shuffle_tiles();
|
|
#endif
|
|
objects[WAN_NOTHING].oc_dir = rn2(2) ? NODIR : IMMEDIATE;
|
|
}
|
|
|
|
/* Compute the total probability of each object class.
|
|
* Assumes svb.bases[] has already been set. */
|
|
void
|
|
init_oclass_probs(void)
|
|
{
|
|
int i;
|
|
short sum;
|
|
int oclass;
|
|
for (oclass = 0; oclass < MAXOCLASSES; ++oclass) {
|
|
sum = 0;
|
|
/* note: for ILLOBJ_CLASS, bases[oclass+1]-1 isn't the last item
|
|
in the class; but all the generic items have probability 0 so
|
|
adding them to 'sum' has no impact */
|
|
for (i = svb.bases[oclass]; i < svb.bases[oclass + 1]; ++i) {
|
|
sum += objects[i].oc_prob;
|
|
}
|
|
if (sum <= 0 && oclass != ILLOBJ_CLASS
|
|
&& svb.bases[oclass] != svb.bases[oclass + 1]) {
|
|
impossible("%s (%d) probability total for oclass %d",
|
|
!sum ? "zero" : "negative", sum, oclass);
|
|
/* gracefully fail by setting all members of this class to 1 */
|
|
for (i = svb.bases[oclass]; i < svb.bases[oclass + 1]; ++i) {
|
|
objects[i].oc_prob = 1;
|
|
sum++;
|
|
}
|
|
}
|
|
go.oclass_prob_totals[oclass] = sum;
|
|
}
|
|
}
|
|
|
|
/* retrieve the range of objects that otyp shares descriptions with */
|
|
void
|
|
obj_shuffle_range(
|
|
int otyp, /* input: representative item */
|
|
int *lo_p, int *hi_p) /* output: range that item belongs among */
|
|
{
|
|
int i, ocls = objects[otyp].oc_class;
|
|
|
|
/* default is just the object itself */
|
|
*lo_p = *hi_p = otyp;
|
|
|
|
switch (ocls) {
|
|
case ARMOR_CLASS:
|
|
if (otyp >= HELMET && otyp <= HELM_OF_TELEPATHY)
|
|
*lo_p = HELMET, *hi_p = HELM_OF_TELEPATHY;
|
|
else if (otyp >= LEATHER_GLOVES && otyp <= GAUNTLETS_OF_DEXTERITY)
|
|
*lo_p = LEATHER_GLOVES, *hi_p = GAUNTLETS_OF_DEXTERITY;
|
|
else if (otyp >= CLOAK_OF_PROTECTION && otyp <= CLOAK_OF_DISPLACEMENT)
|
|
*lo_p = CLOAK_OF_PROTECTION, *hi_p = CLOAK_OF_DISPLACEMENT;
|
|
else if (otyp >= SPEED_BOOTS && otyp <= LEVITATION_BOOTS)
|
|
*lo_p = SPEED_BOOTS, *hi_p = LEVITATION_BOOTS;
|
|
break;
|
|
case POTION_CLASS:
|
|
/* potion of water has the only fixed description */
|
|
*lo_p = svb.bases[POTION_CLASS];
|
|
*hi_p = POT_WATER - 1;
|
|
break;
|
|
case AMULET_CLASS:
|
|
case SCROLL_CLASS:
|
|
case SPBOOK_CLASS:
|
|
/* exclude non-magic types and also unique ones */
|
|
*lo_p = svb.bases[ocls];
|
|
for (i = *lo_p; objects[i].oc_class == ocls; i++)
|
|
if (objects[i].oc_unique || !objects[i].oc_magic)
|
|
break;
|
|
*hi_p = i - 1;
|
|
break;
|
|
case RING_CLASS:
|
|
case WAND_CLASS:
|
|
case VENOM_CLASS:
|
|
/* entire class */
|
|
*lo_p = svb.bases[ocls];
|
|
*hi_p = svb.bases[ocls + 1] - 1;
|
|
break;
|
|
}
|
|
|
|
/* artifact checking might ask about item which isn't part of any range
|
|
but fell within the classes that do have ranges specified above */
|
|
if (otyp < *lo_p || otyp > *hi_p)
|
|
*lo_p = *hi_p = otyp;
|
|
return;
|
|
}
|
|
|
|
/* randomize object descriptions */
|
|
staticfn void
|
|
shuffle_all(void)
|
|
{
|
|
/* entire classes; obj_shuffle_range() handles their exceptions */
|
|
static char shuffle_classes[] = {
|
|
AMULET_CLASS, POTION_CLASS, RING_CLASS, SCROLL_CLASS,
|
|
SPBOOK_CLASS, WAND_CLASS, VENOM_CLASS,
|
|
};
|
|
/* sub-class type ranges (one item from each group) */
|
|
static short shuffle_types[] = {
|
|
HELMET, LEATHER_GLOVES, CLOAK_OF_PROTECTION, SPEED_BOOTS,
|
|
};
|
|
int first, last, idx;
|
|
|
|
/* do whole classes (amulets, &c) */
|
|
for (idx = 0; idx < SIZE(shuffle_classes); idx++) {
|
|
obj_shuffle_range(svb.bases[(int) shuffle_classes[idx]],
|
|
&first, &last);
|
|
shuffle(first, last, TRUE);
|
|
}
|
|
/* do type ranges (helms, &c) */
|
|
for (idx = 0; idx < SIZE(shuffle_types); idx++) {
|
|
obj_shuffle_range(shuffle_types[idx], &first, &last);
|
|
shuffle(first, last, FALSE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Return TRUE if the provided string matches the unidentified description of
|
|
* the provided object. */
|
|
boolean
|
|
objdescr_is(struct obj *obj, const char *descr)
|
|
{
|
|
const char *objdescr;
|
|
|
|
if (!obj) {
|
|
impossible("objdescr_is: null obj");
|
|
return FALSE;
|
|
}
|
|
|
|
objdescr = OBJ_DESCR(objects[obj->otyp]);
|
|
if (!objdescr)
|
|
return FALSE; /* no obj description, no match */
|
|
return !strcmp(objdescr, descr);
|
|
}
|
|
|
|
/* level dependent initialization */
|
|
void
|
|
oinit(void)
|
|
{
|
|
setgemprobs(&u.uz);
|
|
}
|
|
|
|
void
|
|
savenames(NHFILE *nhfp)
|
|
{
|
|
int i;
|
|
unsigned int len;
|
|
|
|
if (update_file(nhfp)) {
|
|
for (i = 0; i < (MAXOCLASSES + 2); ++i) {
|
|
Sfo_int(nhfp, &svb.bases[i], "names-bases");
|
|
}
|
|
for (i = 0; i < NUM_OBJECTS; ++i) {
|
|
Sfo_short(nhfp, &svd.disco[i], "names-disco");
|
|
}
|
|
for (i = 0; i < NUM_OBJECTS; ++i) {
|
|
Sfo_objclass(nhfp, &objects[i], "names-objclass");
|
|
}
|
|
}
|
|
/* as long as we use only one version of Hack we
|
|
need not save oc_name and oc_descr, but we must save
|
|
oc_uname for all objects */
|
|
for (i = 0; i < NUM_OBJECTS; i++)
|
|
if (objects[i].oc_uname) {
|
|
if (update_file(nhfp)) {
|
|
len = Strlen(objects[i].oc_uname) + 1;
|
|
Sfo_unsigned(nhfp, &len, "names-len");
|
|
Sfo_char(nhfp, objects[i].oc_uname, "names-oc_uname",
|
|
(int) len);
|
|
}
|
|
if (release_data(nhfp)) {
|
|
free((genericptr_t) objects[i].oc_uname);
|
|
objects[i].oc_uname = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif /* !SFCTOOL */
|
|
|
|
void
|
|
restnames(NHFILE *nhfp)
|
|
{
|
|
int i;
|
|
unsigned int len = 0;
|
|
|
|
for (i = 0; i < (MAXOCLASSES + 2); ++i) {
|
|
Sfi_int(nhfp, &svb.bases[i], "names-bases");
|
|
}
|
|
for (i = 0; i < NUM_OBJECTS; ++i) {
|
|
Sfi_short(nhfp, &svd.disco[i], "names-disco");
|
|
}
|
|
for (i = 0; i < NUM_OBJECTS; ++i) {
|
|
Sfi_objclass(nhfp, &objects[i], "names-objclass");
|
|
}
|
|
for (i = 0; i < NUM_OBJECTS; i++) {
|
|
if (objects[i].oc_uname) {
|
|
Sfi_unsigned(nhfp, &len, "names-len");
|
|
objects[i].oc_uname = (char *) alloc(len);
|
|
Sfi_char(nhfp, objects[i].oc_uname, "names-oc_uname", (int) len);
|
|
}
|
|
}
|
|
#ifndef SFCTOOL
|
|
#ifdef TILES_IN_GLYPHMAP
|
|
shuffle_tiles();
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
#ifndef SFCTOOL
|
|
/* make the object dknown and mark it as encountered */
|
|
void
|
|
observe_object(struct obj *obj)
|
|
{
|
|
int oindx = obj->otyp;
|
|
|
|
/* skip for generic objects and for STRANGE_OBJECT */
|
|
if (oindx >= FIRST_OBJECT && !Hallucination) {
|
|
obj->dknown = 1;
|
|
discover_object(oindx, FALSE, TRUE, FALSE);
|
|
}
|
|
}
|
|
|
|
void
|
|
discover_object(
|
|
int oindx, /* type of object */
|
|
boolean mark_as_known, /* discover the type */
|
|
boolean mark_as_encountered, /* mark the type as having been seen/felt */
|
|
boolean credit_hero) /* exercise wisdom */
|
|
{
|
|
if (oindx < FIRST_OBJECT) /* don't discover generic objects */
|
|
return;
|
|
|
|
if ((!objects[oindx].oc_name_known && mark_as_known)
|
|
|| (!objects[oindx].oc_encountered && mark_as_encountered)
|
|
|| (Role_if(PM_SAMURAI)
|
|
&& Japanese_item_name(oindx, (const char *) 0))) {
|
|
int dindx, acls = objects[oindx].oc_class;
|
|
|
|
/* Loop thru disco[] 'til we find the target (which may have been
|
|
uname'd) or the next open slot; one or the other will be found
|
|
before we reach the next class... */
|
|
for (dindx = svb.bases[acls]; svd.disco[dindx] != 0; dindx++)
|
|
if (svd.disco[dindx] == oindx)
|
|
break;
|
|
svd.disco[dindx] = oindx;
|
|
|
|
if (mark_as_encountered)
|
|
objects[oindx].oc_encountered = 1;
|
|
|
|
if (!objects[oindx].oc_name_known && mark_as_known) {
|
|
objects[oindx].oc_name_known = 1;
|
|
if (credit_hero)
|
|
exercise(A_WIS, TRUE);
|
|
|
|
/* !in_moveloop => initial inventory,
|
|
gameover => final disclosure */
|
|
if (program_state.in_moveloop && !program_state.gameover) {
|
|
if (objects[oindx].oc_class == GEM_CLASS)
|
|
gem_learned(oindx); /* could affect price of unpaid gems */
|
|
update_inventory();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if a class name has been cleared, we may need to purge it from disco[] */
|
|
void
|
|
undiscover_object(int oindx)
|
|
{
|
|
if (!objects[oindx].oc_name_known && !objects[oindx].oc_encountered) {
|
|
int dindx, acls = objects[oindx].oc_class;
|
|
boolean found = FALSE;
|
|
|
|
/* find the object; shift those behind it forward one slot */
|
|
for (dindx = svb.bases[acls];
|
|
dindx < NUM_OBJECTS && svd.disco[dindx] != 0
|
|
&& objects[dindx].oc_class == acls;
|
|
dindx++)
|
|
if (found)
|
|
svd.disco[dindx - 1] = svd.disco[dindx];
|
|
else if (svd.disco[dindx] == oindx)
|
|
found = TRUE;
|
|
|
|
/* clear last slot */
|
|
if (found)
|
|
svd.disco[dindx - 1] = 0;
|
|
else
|
|
impossible("named object not in disco");
|
|
|
|
if (objects[oindx].oc_class == GEM_CLASS)
|
|
gem_learned(oindx); /* ok, it's actually been unlearned */
|
|
}
|
|
}
|
|
|
|
boolean
|
|
interesting_to_discover(int i)
|
|
{
|
|
/* most players who don't speak Japanese manage to figure out what
|
|
gunyoki, osaku, and so forth mean, but treat them as pre-discovered
|
|
to be disclosed by '\' */
|
|
if (Role_if(PM_SAMURAI) && Japanese_item_name(i, (const char *) 0))
|
|
return TRUE;
|
|
|
|
/* Objects that were discovered without encountering them are now printed
|
|
with a '*' */
|
|
return (boolean) (objects[i].oc_uname != (char *) 0
|
|
|| ((objects[i].oc_name_known
|
|
|| objects[i].oc_encountered)
|
|
&& OBJ_DESCR(objects[i]) != (char *) 0));
|
|
}
|
|
|
|
/* items that should stand out once they're known */
|
|
static const short uniq_objs[] = {
|
|
AMULET_OF_YENDOR,
|
|
/* same order as major oracularity; alphabetical when fully IDed */
|
|
BELL_OF_OPENING,
|
|
SPE_BOOK_OF_THE_DEAD,
|
|
CANDELABRUM_OF_INVOCATION,
|
|
};
|
|
|
|
/* discoveries qsort comparison function */
|
|
staticfn int QSORTCALLBACK
|
|
discovered_cmp(const genericptr v1, const genericptr v2)
|
|
{
|
|
const char *s1 = *(const char **) v1;
|
|
const char *s2 = *(const char **) v2;
|
|
/* each element starts with "* " or " " but we don't sort by those */
|
|
int res = strcmpi(s1 + 2, s2 + 2);
|
|
|
|
if (res == 0) {
|
|
; /* no tie-breaker needed */
|
|
}
|
|
return res;
|
|
}
|
|
|
|
staticfn char *
|
|
sortloot_descr(int otyp, char *outbuf)
|
|
{
|
|
Loot sl_cookie;
|
|
struct obj o;
|
|
|
|
o = cg.zeroobj;
|
|
o.otyp = otyp;
|
|
o.oclass = objects[otyp].oc_class;
|
|
o.dknown = 1; /* not observe_object, this isn't a real object */
|
|
o.known = (objects[otyp].oc_name_known || !objects[otyp].oc_uses_known)
|
|
? 1 : 0;
|
|
o.corpsenm = NON_PM; /* suppress statue and figurine details */
|
|
/* but suppressing fruit details leads to "bad fruit #0" */
|
|
if (otyp == SLIME_MOLD)
|
|
o.spe = svc.context.current_fruit;
|
|
|
|
(void) memset((genericptr_t) &sl_cookie, 0, sizeof sl_cookie);
|
|
sl_cookie.obj = (struct obj *) 0;
|
|
sl_cookie.str = (char *) 0;
|
|
|
|
loot_classify(&sl_cookie, &o);
|
|
Sprintf(outbuf, "%02d%02d%1d ",
|
|
sl_cookie.orderclass, sl_cookie.subclass, sl_cookie.disco);
|
|
return outbuf;
|
|
}
|
|
#endif /* !SFCTOOL */
|
|
|
|
#define DISCO_BYCLASS 0 /* by discovery order within each class */
|
|
#define DISCO_SORTLOOT 1 /* by discovery order within each subclass */
|
|
#define DISCO_ALPHABYCLASS 2 /* alphabetized within each class */
|
|
#define DISCO_ALPHABETIZED 3 /* alphabetized across all classes */
|
|
/* also used in options.c (optfn_sortdiscoveries) */
|
|
static const char disco_order_let[] = "osca";
|
|
static const char *const disco_orders_descr[] = {
|
|
"by order of discovery within each class",
|
|
"sortloot order (by class with some sub-class groupings)",
|
|
"alphabetical within each class",
|
|
"alphabetical across all classes",
|
|
(char *) 0
|
|
};
|
|
|
|
#ifndef SFCTOOL
|
|
|
|
int
|
|
choose_disco_sort(
|
|
int mode) /* 0 => 'O' cmd, 1 => full discoveries; 2 => class disco */
|
|
{
|
|
winid tmpwin;
|
|
menu_item *selected;
|
|
anything any;
|
|
int i, n, choice;
|
|
int clr = NO_COLOR;
|
|
|
|
tmpwin = create_nhwindow(NHW_MENU);
|
|
start_menu(tmpwin, MENU_BEHAVE_STANDARD);
|
|
any = cg.zeroany; /* zero out all bits */
|
|
for (i = 0; disco_orders_descr[i]; ++i) {
|
|
any.a_int = disco_order_let[i];
|
|
add_menu(tmpwin, &nul_glyphinfo, &any, (char) any.a_int,
|
|
0, ATR_NONE, clr,
|
|
disco_orders_descr[i],
|
|
(disco_order_let[i] == flags.discosort)
|
|
? MENU_ITEMFLAGS_SELECTED
|
|
: MENU_ITEMFLAGS_NONE);
|
|
}
|
|
if (mode == 2) {
|
|
/* called via 'm `' where full alphabetize doesn't make sense
|
|
(only showing one class so can't span all classes) but the
|
|
chosen sort will stick and also apply to '\' usage */
|
|
add_menu_str(tmpwin, "");
|
|
add_menu_str(tmpwin,
|
|
"Note: full alphabetical and alphabetical within class");
|
|
add_menu_str(tmpwin,
|
|
" are equivalent for single class discovery, but");
|
|
add_menu_str(tmpwin,
|
|
" will matter for future use of total discoveries.");
|
|
}
|
|
end_menu(tmpwin, "Ordering of discoveries");
|
|
|
|
n = select_menu(tmpwin, PICK_ONE, &selected);
|
|
destroy_nhwindow(tmpwin);
|
|
if (n > 0) {
|
|
choice = selected[0].item.a_int;
|
|
/* skip preselected entry if we have more than one item chosen */
|
|
if (n > 1 && choice == (int) flags.discosort)
|
|
choice = selected[1].item.a_int;
|
|
free((genericptr_t) selected);
|
|
flags.discosort = choice;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* augment obj_typename() with explanation of Japanese item names */
|
|
staticfn char *
|
|
disco_typename(int otyp)
|
|
{
|
|
char *result = obj_typename(otyp);
|
|
|
|
if (Role_if(PM_SAMURAI) && Japanese_item_name(otyp, (const char *) 0)) {
|
|
char buf[BUFSZ];
|
|
const char *actualn = (((otyp != MAGIC_HARP && otyp != WOODEN_HARP)
|
|
|| objects[otyp].oc_name_known)
|
|
? OBJ_NAME(objects[otyp])
|
|
/* undiscovered harp (since wooden harp is
|
|
non-magic so pre-discovered, only applies
|
|
to magic harp and will only be seen if
|
|
magic harp has been 'called' something) */
|
|
: "harp");
|
|
|
|
if (!actualn) { /* won't happen; used to pacify static analyzer */
|
|
;
|
|
} else if (strstri(result, " called")) {
|
|
Sprintf(buf, " [%s] called", actualn);
|
|
(void) strsubst(result, " called", buf);
|
|
} else if (strstri(result, " (")) {
|
|
Sprintf(buf, " [%s] (", actualn);
|
|
(void) strsubst(result, " (", buf);
|
|
} else {
|
|
Sprintf(eos(result), " [%s]", actualn);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* append typename(dis) to buf[], possibly truncating in the process;
|
|
also append price quote information if it fits */
|
|
staticfn void
|
|
disco_append_typename(char *buf, int dis)
|
|
{
|
|
size_t len = strlen(buf);
|
|
char *p, *typnm = disco_typename(dis);
|
|
size_t typnm_len = strlen(typnm);
|
|
char *eos;
|
|
|
|
if (len + typnm_len < BUFSZ) {
|
|
/* ordinary */
|
|
Strcat(buf, typnm);
|
|
eos = buf + len + typnm_len;
|
|
} else if ((p = strrchr(typnm, '(')) != 0
|
|
&& p > typnm && p[-1] == ' ' && strchr(p, ')') != 0) {
|
|
/* typename() returned "really long user-applied name (actual type)"
|
|
and we want to truncate from "really long user-applied name" while
|
|
keeping " (actual type)" intact */
|
|
--p; /* back up to space in front of open paren */
|
|
(void) strncat(buf, typnm, BUFSZ - 1 - (len + (unsigned) strlen(p)));
|
|
Strcat(buf, p);
|
|
eos = buf + strlen(buf);
|
|
} else {
|
|
/* unexpected; just truncate from end of typename */
|
|
(void) strncat(buf, typnm, BUFSZ - 1 - len);
|
|
eos = buf + strlen(buf);
|
|
}
|
|
|
|
append_price_quote(buf, &eos, dis);
|
|
}
|
|
|
|
/* minor fixup for Book of the Dead needed in more than one place */
|
|
staticfn void
|
|
disco_fmt_uniq(int uidx, char *outbuf)
|
|
{
|
|
Sprintf(outbuf, " %s", objects[uidx].oc_name_known
|
|
? OBJ_NAME(objects[uidx])
|
|
: OBJ_DESCR(objects[uidx]));
|
|
/* in the spellbooks section of main discoveries list, encountered
|
|
but not fully discovered Book of the Dead is shown as
|
|
"spellbook (papyrus)" like other encountered but not discovered books;
|
|
in the unique/relics section we want "papyrus spellbook" instead */
|
|
if (!objects[uidx].oc_name_known
|
|
&& objects[uidx].oc_class == SPBOOK_CLASS)
|
|
Strcat(outbuf, " spellbook");
|
|
}
|
|
|
|
/* sort and output sorted_lines to window and free the lines */
|
|
staticfn void
|
|
disco_output_sorted(
|
|
winid tmpwin,
|
|
char **sorted_lines, int sorted_ct,
|
|
boolean lootsort)
|
|
{
|
|
char *p;
|
|
int j;
|
|
|
|
qsort(sorted_lines, sorted_ct, sizeof (char *), discovered_cmp);
|
|
for (j = 0; j < sorted_ct; ++j) {
|
|
p = sorted_lines[j];
|
|
assert(p != NULL); /* pacify static analyzer */
|
|
if (lootsort) {
|
|
p[6] = p[0]; /* '*' or ' ' */
|
|
p += 6;
|
|
}
|
|
putstr(tmpwin, 0, p);
|
|
free(sorted_lines[j]), sorted_lines[j] = 0;
|
|
}
|
|
}
|
|
|
|
/* the #known command - show discovered object types */
|
|
int
|
|
dodiscovered(void) /* free after Robert Viduya */
|
|
{
|
|
winid tmpwin;
|
|
char *s, *p, oclass, prev_class,
|
|
classes[MAXOCLASSES], buf[BUFSZ],
|
|
*sorted_lines[NUM_OBJECTS]; /* overkill */
|
|
int i, dis, ct, uniq_ct, arti_ct, sorted_ct, uidx;
|
|
long sortindx; // should be ptrdiff_t, but we don't require that exists
|
|
boolean alphabetized, alphabyclass, lootsort;
|
|
|
|
if (!flags.discosort || !(p = strchr(disco_order_let, flags.discosort)))
|
|
flags.discosort = 'o';
|
|
|
|
if (iflags.menu_requested) {
|
|
if (choose_disco_sort(1) < 0)
|
|
return ECMD_OK;
|
|
}
|
|
alphabyclass = (flags.discosort == 'c');
|
|
alphabetized = (flags.discosort == 'a' || alphabyclass);
|
|
lootsort = (flags.discosort == 's');
|
|
sortindx = strchr(disco_order_let, flags.discosort) - disco_order_let;
|
|
|
|
tmpwin = create_nhwindow(NHW_TEXT);
|
|
Sprintf(buf, "Discoveries, %s", disco_orders_descr[sortindx]);
|
|
putstr(tmpwin, 0, buf);
|
|
putstr(tmpwin, 0, "");
|
|
|
|
/*
|
|
* FIXME?
|
|
* relics and artifacts don't obey player's sort order even though
|
|
* the header line states that they're shown in such-and-such order.
|
|
*/
|
|
|
|
/* gather "unique objects", also called "relics", into a pseudo-class;
|
|
they'll also be displayed individually within their regular class */
|
|
uniq_ct = 0;
|
|
for (i = dis = 0; i < SIZE(uniq_objs); i++) {
|
|
uidx = uniq_objs[i];
|
|
if (objects[uidx].oc_name_known
|
|
|| (objects[uidx].oc_encountered && uidx != AMULET_OF_YENDOR)) {
|
|
if (!dis++)
|
|
putstr(tmpwin, iflags.menu_headings.attr,
|
|
"Unique items or Relics");
|
|
++uniq_ct;
|
|
disco_fmt_uniq(uidx, buf);
|
|
putstr(tmpwin, 0, buf);
|
|
}
|
|
}
|
|
/* display any known artifacts as another pseudo-class */
|
|
arti_ct = disp_artifact_discoveries(tmpwin);
|
|
|
|
/* several classes are omitted from packorder; one is of interest here */
|
|
Strcpy(classes, flags.inv_order);
|
|
if (!strchr(classes, VENOM_CLASS))
|
|
(void) strkitten(classes, VENOM_CLASS); /* append char to string */
|
|
|
|
ct = uniq_ct + arti_ct;
|
|
sorted_ct = 0;
|
|
for (s = classes; *s; s++) {
|
|
oclass = *s;
|
|
prev_class = oclass + 1; /* forced different from oclass */
|
|
for (i = svb.bases[(int) oclass];
|
|
i < NUM_OBJECTS && objects[i].oc_class == oclass; i++) {
|
|
if ((dis = svd.disco[i]) != 0 && interesting_to_discover(dis)) {
|
|
ct++;
|
|
if (oclass != prev_class) {
|
|
if ((alphabyclass || lootsort) && sorted_ct) {
|
|
/* output previous class */
|
|
disco_output_sorted(tmpwin, sorted_lines, sorted_ct,
|
|
lootsort);
|
|
sorted_ct = 0;
|
|
}
|
|
if (!alphabetized || alphabyclass) {
|
|
/* header for new class */
|
|
putstr(tmpwin, iflags.menu_headings.attr,
|
|
let_to_name(oclass, FALSE, FALSE));
|
|
prev_class = oclass;
|
|
}
|
|
}
|
|
Strcpy(buf, objects[dis].oc_encountered ? " " : "* ");
|
|
if (lootsort)
|
|
(void) sortloot_descr(dis, &buf[2]);
|
|
disco_append_typename(buf, dis);
|
|
|
|
if (!alphabetized && !lootsort)
|
|
putstr(tmpwin, 0, buf);
|
|
else
|
|
sorted_lines[sorted_ct++] = dupstr(buf);
|
|
}
|
|
}
|
|
}
|
|
if (ct == 0) {
|
|
You("haven't discovered anything yet...");
|
|
} else {
|
|
if (sorted_ct) {
|
|
/* if we're alphabetizing by class, we've already shown the
|
|
relevant header above; if we're alphabetizing across all
|
|
classes, we normally don't need a header; but it we showed
|
|
any unique items or any artifacts then we do need one */
|
|
if ((uniq_ct || arti_ct) && alphabetized && !alphabyclass)
|
|
putstr(tmpwin, iflags.menu_headings.attr, "Discovered items");
|
|
disco_output_sorted(tmpwin, sorted_lines, sorted_ct, lootsort);
|
|
}
|
|
display_nhwindow(tmpwin, TRUE);
|
|
}
|
|
destroy_nhwindow(tmpwin);
|
|
|
|
return ECMD_OK;
|
|
}
|
|
|
|
/* lower case let_to_name() output, which differs from def_oc_syms[].name */
|
|
staticfn char *
|
|
oclass_to_name(char oclass, char *buf)
|
|
{
|
|
char *s;
|
|
|
|
Strcpy(buf, let_to_name(oclass, FALSE, FALSE));
|
|
for (s = buf; *s; ++s)
|
|
*s = lowc(*s);
|
|
return buf;
|
|
}
|
|
|
|
/* the #knownclass command - show discovered object types for one class;
|
|
in addition to actual object classes, supports pseudo-class 'a' for
|
|
discovered artifacts and 'u' (or 'r', for "relics") for unique items */
|
|
int
|
|
doclassdisco(void)
|
|
{
|
|
static NEARDATA const char
|
|
prompt[] = "View discoveries for which sort of objects?",
|
|
havent_discovered_any[] = "haven't discovered any %s yet.",
|
|
unique_items[] = "unique items or relics",
|
|
artifact_items[] = "artifacts";
|
|
winid tmpwin = WIN_ERR;
|
|
menu_item *pick_list = 0;
|
|
anything any;
|
|
char *p, *s, c, oclass, menulet, allclasses[MAXOCLASSES],
|
|
discosyms[3 + MAXOCLASSES + 1], buf[BUFSZ],
|
|
*sorted_lines[NUM_OBJECTS]; /* overkill */
|
|
int i, ct, dis, xtras, sorted_ct, uidx;
|
|
boolean traditional, alphabetized, lootsort;
|
|
int clr = NO_COLOR;
|
|
|
|
if (!flags.discosort || !(p = strchr(disco_order_let, flags.discosort)))
|
|
flags.discosort = 'o';
|
|
|
|
if (iflags.menu_requested) {
|
|
if (choose_disco_sort(2) < 0)
|
|
return ECMD_OK;
|
|
}
|
|
alphabetized = (flags.discosort == 'a' || flags.discosort == 'c');
|
|
lootsort = (flags.discosort == 's');
|
|
|
|
discosyms[0] = '\0';
|
|
traditional = (flags.menu_style == MENU_TRADITIONAL
|
|
|| flags.menu_style == MENU_COMBINATION);
|
|
if (!traditional) {
|
|
tmpwin = create_nhwindow(NHW_MENU);
|
|
start_menu(tmpwin, MENU_BEHAVE_STANDARD);
|
|
}
|
|
any = cg.zeroany;
|
|
menulet = 'a';
|
|
|
|
/*
|
|
* FIXME?
|
|
* relics and artifacts don't obey player's sort order even though
|
|
* the header line states that they're shown in such-and-such order.
|
|
*/
|
|
|
|
/* check whether we've discovered any unique objects (primarily the
|
|
invocation items; the Guidebook calls unique items "relics" but the
|
|
Amulet of Yendor is unique too so we haven't made a blanket change
|
|
from 'u' to 'r') */
|
|
for (i = 0; i < SIZE(uniq_objs); i++) {
|
|
uidx = uniq_objs[i];
|
|
if (objects[uidx].oc_name_known
|
|
|| (objects[uidx].oc_encountered && uidx != AMULET_OF_YENDOR)) {
|
|
Strcat(discosyms, "u");
|
|
if (!traditional) {
|
|
any.a_int = 'u';
|
|
/* FIXME: having 'r' as an accelerator to provide an unseen
|
|
synonym works but doesn't make much sense since the main
|
|
selector is 'a' (implicit lootabc) rather than 'u' */
|
|
add_menu(tmpwin, &nul_glyphinfo, &any, menulet++, 'r',
|
|
ATR_NONE, clr, unique_items, MENU_ITEMFLAGS_NONE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* check whether we've discovered any artifacts */
|
|
if (disp_artifact_discoveries(WIN_ERR) > 0) {
|
|
Strcat(discosyms, "a");
|
|
if (!traditional) {
|
|
any.a_int = 'a';
|
|
add_menu(tmpwin, &nul_glyphinfo, &any, menulet++, 0,
|
|
ATR_NONE, clr, artifact_items, MENU_ITEMFLAGS_NONE);
|
|
}
|
|
}
|
|
|
|
/* collect classes with discoveries, in packorder ordering; several
|
|
classes are omitted from packorder and one is of interest here */
|
|
Strcpy(allclasses, flags.inv_order);
|
|
if (!strchr(allclasses, VENOM_CLASS))
|
|
(void) strkitten(allclasses, VENOM_CLASS); /* append char to string */
|
|
/* construct discosyms[] */
|
|
for (s = allclasses; *s; ++s) {
|
|
oclass = *s;
|
|
c = def_oc_syms[(int) oclass].sym;
|
|
for (i = svb.bases[(int) oclass];
|
|
i < NUM_OBJECTS && objects[i].oc_class == oclass; ++i)
|
|
if ((dis = svd.disco[i]) != 0 && interesting_to_discover(dis)) {
|
|
if (!strchr(discosyms, c)) {
|
|
(void) strkitten(discosyms, c);
|
|
if (!traditional) {
|
|
any.a_int = c;
|
|
add_menu(tmpwin, &nul_glyphinfo, &any,
|
|
menulet++, c, ATR_NONE, clr,
|
|
oclass_to_name(oclass, buf),
|
|
MENU_ITEMFLAGS_NONE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* there might not be anything for us to do... */
|
|
if (!discosyms[0]) {
|
|
You(havent_discovered_any, "items");
|
|
if (tmpwin != WIN_ERR)
|
|
destroy_nhwindow(tmpwin);
|
|
return ECMD_OK;
|
|
}
|
|
|
|
/* have player choose a class */
|
|
c = '\0'; /* class not chosen yet */
|
|
if (traditional) {
|
|
char allclasses_plustwo[sizeof allclasses + 3];
|
|
|
|
/* we'll prompt even if there's only one viable class; we add all
|
|
nonviable classes as unseen acceptable choices so player can ask
|
|
for discoveries of any class whether it has discoveries or not */
|
|
Sprintf(allclasses_plustwo, "%s%c%c%c", allclasses, 'a', 'u', 'r');
|
|
for (s = allclasses_plustwo, xtras = 0; *s; ++s) {
|
|
c = strchr("aur", *s) ? *s : def_oc_syms[(int) *s].sym;
|
|
if (!strchr(discosyms, c)) {
|
|
if (!xtras++)
|
|
(void) strkitten(discosyms, '\033');
|
|
(void) strkitten(discosyms, c);
|
|
}
|
|
}
|
|
/* get the class (via its symbol character) */
|
|
c = yn_function(prompt, discosyms, '\0', TRUE);
|
|
if (!c)
|
|
clear_nhwindow(WIN_MESSAGE);
|
|
} else {
|
|
/* menustyle:full or menustyle:partial */
|
|
if (!discosyms[1] && flags.menu_style == MENU_PARTIAL) {
|
|
/* only one class; menustyle:partial normally jumps past class
|
|
filtering straight to final menu so skip class filter here */
|
|
c = discosyms[0];
|
|
} else {
|
|
/* more than one choice, or menustyle:full which normally has
|
|
an intermediate class selection menu before the final menu */
|
|
end_menu(tmpwin, prompt);
|
|
i = select_menu(tmpwin, PICK_ONE, &pick_list);
|
|
if (i > 0) {
|
|
c = pick_list[0].item.a_int;
|
|
free((genericptr_t) pick_list);
|
|
} /* else c stays 0 */
|
|
}
|
|
destroy_nhwindow(tmpwin);
|
|
}
|
|
if (!c)
|
|
return ECMD_OK; /* player declined to make a selection */
|
|
|
|
/*
|
|
* show discoveries for object class c
|
|
*/
|
|
tmpwin = create_nhwindow(NHW_TEXT);
|
|
ct = 0;
|
|
switch (c) {
|
|
case 'u':
|
|
case 'r':
|
|
putstr(tmpwin, iflags.menu_headings.attr,
|
|
upstart(strcpy(buf, unique_items)));
|
|
for (i = 0; i < SIZE(uniq_objs); i++) {
|
|
uidx = uniq_objs[i];
|
|
if (objects[uidx].oc_name_known
|
|
|| (objects[uidx].oc_encountered
|
|
&& uidx != AMULET_OF_YENDOR)) {
|
|
++ct;
|
|
disco_fmt_uniq(uidx, buf);
|
|
putstr(tmpwin, 0, buf);
|
|
}
|
|
}
|
|
if (!ct)
|
|
You(havent_discovered_any, unique_items);
|
|
break;
|
|
case 'a':
|
|
/* note: this will work all the time for menustyle traditional
|
|
but requires at least one artifact discovery for other styles
|
|
[could fix that by forcing the 'a' choice into the pick-class
|
|
menu when running in wizard mode] */
|
|
if (wizard && y_n("Dump information about all artifacts?") == 'y') {
|
|
dump_artifact_info(tmpwin);
|
|
ct = NROFARTIFACTS; /* non-zero vs zero is what matters below */
|
|
break;
|
|
}
|
|
/* disp_artifact_discoveries() includes a header */
|
|
ct = disp_artifact_discoveries(tmpwin);
|
|
if (!ct)
|
|
You(havent_discovered_any, artifact_items);
|
|
break;
|
|
default:
|
|
oclass = def_char_to_objclass(c);
|
|
/* this should never happen but has been observed via the fuzzer */
|
|
if (oclass == MAXOCLASSES)
|
|
impossible("doclassdisco: invalid object class '%s'", visctrl(c));
|
|
Sprintf(buf, "Discovered %s in %s", let_to_name(oclass, FALSE, FALSE),
|
|
(flags.discosort == 'o') ? "order of discovery"
|
|
: (flags.discosort == 's') ? "'sortloot' order"
|
|
: "alphabetical order");
|
|
putstr(tmpwin, 0, buf); /* skip iflags.menu_headings */
|
|
sorted_ct = 0;
|
|
for (i = svb.bases[(int) oclass]; i <= svb.bases[oclass + 1] - 1;
|
|
++i) {
|
|
if ((dis = svd.disco[i]) != 0 && interesting_to_discover(dis)) {
|
|
++ct;
|
|
Strcpy(buf, objects[dis].oc_encountered ? " " : "* ");
|
|
if (lootsort)
|
|
(void) sortloot_descr(dis, &buf[2]);
|
|
disco_append_typename(buf, dis);
|
|
|
|
if (!alphabetized && !lootsort)
|
|
putstr(tmpwin, 0, buf);
|
|
else
|
|
sorted_lines[sorted_ct++] = dupstr(buf);
|
|
}
|
|
}
|
|
if (!ct) {
|
|
You(havent_discovered_any, oclass_to_name(oclass, buf));
|
|
} else if (sorted_ct) {
|
|
qsort(sorted_lines, sorted_ct, sizeof (char *), discovered_cmp);
|
|
for (i = 0; i < sorted_ct; ++i) {
|
|
p = sorted_lines[i];
|
|
if (lootsort) {
|
|
p[6] = p[0]; /* '*' or ' ' */
|
|
p += 6;
|
|
}
|
|
putstr(tmpwin, 0, p);
|
|
free(sorted_lines[i]), sorted_lines[i] = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (ct)
|
|
display_nhwindow(tmpwin, TRUE);
|
|
destroy_nhwindow(tmpwin);
|
|
return ECMD_OK;
|
|
}
|
|
|
|
/* put up nameable subset of discoveries list as a menu */
|
|
void
|
|
rename_disco(void)
|
|
{
|
|
int i, dis;
|
|
int ct = 0, mn = 0, sl;
|
|
char *s, oclass, prev_class;
|
|
winid tmpwin;
|
|
anything any;
|
|
menu_item *selected = 0;
|
|
int clr = NO_COLOR;
|
|
char buf[BUFSZ];
|
|
|
|
any = cg.zeroany;
|
|
tmpwin = create_nhwindow(NHW_MENU);
|
|
start_menu(tmpwin, MENU_BEHAVE_STANDARD);
|
|
|
|
/*
|
|
* Skip the "unique objects" section (each will appear within its
|
|
* regular class if it is nameable) and the artifacts section.
|
|
* We assume that classes omitted from packorder aren't nameable
|
|
* so we skip venom too.
|
|
*/
|
|
|
|
/* for each class, show discoveries in that class */
|
|
for (s = flags.inv_order; *s; s++) {
|
|
oclass = *s;
|
|
prev_class = oclass + 1; /* forced different from oclass */
|
|
for (i = svb.bases[(int) oclass];
|
|
i < NUM_OBJECTS && objects[i].oc_class == oclass; i++) {
|
|
dis = svd.disco[i];
|
|
if (!dis || !interesting_to_discover(dis))
|
|
continue;
|
|
ct++;
|
|
if (!objtyp_is_callable(dis))
|
|
continue;
|
|
mn++;
|
|
|
|
if (oclass != prev_class) {
|
|
any.a_int = 0;
|
|
add_menu_heading(tmpwin,
|
|
let_to_name(oclass, FALSE, FALSE));
|
|
prev_class = oclass;
|
|
}
|
|
any.a_int = dis;
|
|
*buf = '\0';
|
|
disco_append_typename(buf, dis);
|
|
add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0,
|
|
ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE);
|
|
}
|
|
}
|
|
if (ct == 0) {
|
|
You("haven't discovered anything yet...");
|
|
} else if (mn == 0) {
|
|
pline("None of your discoveries can be assigned names...");
|
|
} else {
|
|
end_menu(tmpwin, "Pick an object type to name");
|
|
dis = STRANGE_OBJECT;
|
|
sl = select_menu(tmpwin, PICK_ONE, &selected);
|
|
if (sl > 0) {
|
|
dis = selected[0].item.a_int;
|
|
free((genericptr_t) selected);
|
|
}
|
|
if (dis != STRANGE_OBJECT) {
|
|
struct obj odummy;
|
|
|
|
odummy = cg.zeroobj;
|
|
odummy.otyp = dis;
|
|
odummy.oclass = objects[dis].oc_class;
|
|
odummy.quan = 1L;
|
|
odummy.known = !objects[dis].oc_uses_known;
|
|
odummy.dknown = 1; /* not observe_object: it isn't real */
|
|
docall(&odummy);
|
|
}
|
|
}
|
|
destroy_nhwindow(tmpwin);
|
|
return;
|
|
}
|
|
#endif /* !SFCTOOL */
|
|
|
|
void
|
|
get_sortdisco(char *opts, boolean cnf)
|
|
{
|
|
const char *p = strchr(disco_order_let, flags.discosort);
|
|
|
|
if (!p)
|
|
flags.discosort = 'o', p = disco_order_let;
|
|
if (cnf)
|
|
Sprintf(opts, "%c", flags.discosort);
|
|
else
|
|
Strcpy(opts, disco_orders_descr[p - disco_order_let]);
|
|
}
|
|
/*o_init.c*/
|