allmain.c: In function ‘debug_fields’:
allmain.c:1133:16: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
1133 | while ((op = strchr(opts, ',')) != 0) {
| ^
cfgfiles.c: In function ‘find_optparam’:
cfgfiles.c:590:10: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
590 | bufp = strchr(buf, '=');
| ^
cfgfiles.c:591:10: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
591 | altp = strchr(buf, ':');
| ^
end.c: In function ‘should_query_disclose_option’:
end.c:482:14: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
482 | if ((dop = strchr(disclosure_options, category)) != 0) {
| ^
invent.c: In function ‘loot_classify’:
invent.c:175:7: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
175 | p = strchr(classorder, oclass);
| ^
o_init.c: In function ‘dodiscovered’:
o_init.c:774:33: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
774 | if (!flags.discosort || !(p = strchr(disco_order_let, flags.discosort)))
| ^
o_init.c: In function ‘doclassdisco’:
o_init.c:907:33: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
907 | if (!flags.discosort || !(p = strchr(disco_order_let, flags.discosort)))
| ^
objnam.c: In function ‘the’:
objnam.c:2200:19: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
2200 | if (((tmp = strrchr(str, ' ')) != 0 || (tmp = strrchr(str, '-')) != 0)
| ^
objnam.c:2200:53: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
2200 | if (((tmp = strrchr(str, ' ')) != 0 || (tmp = strrchr(str, '-')) != 0)
| ^
options.c: In function ‘optfn_disclose’:
options.c:1529:17: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
1529 | dop = strchr(disclosure_options, c);
| ^
pager.c: In function ‘add_cmap_descr’:
pager.c:1212:28: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
1212 | || ((p = strchr(x_str, ' ')) != 0 && !strcmpi(p, " ice"))
| ^
1222 lines
42 KiB
C
1222 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, oclass, prev_class,
|
|
classes[MAXOCLASSES], buf[BUFSZ],
|
|
*sorted_lines[NUM_OBJECTS]; /* overkill */
|
|
const char *p;
|
|
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 *s, c, oclass, menulet, allclasses[MAXOCLASSES],
|
|
discosyms[3 + MAXOCLASSES + 1], buf[BUFSZ],
|
|
*sorted_lines[NUM_OBJECTS]; /* overkill */
|
|
const char *p;
|
|
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) {
|
|
char *sl;
|
|
|
|
sl = sorted_lines[i];
|
|
if (lootsort) {
|
|
sl[6] = sl[0]; /* '*' or ' ' */
|
|
sl += 6;
|
|
}
|
|
putstr(tmpwin, 0, sl);
|
|
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*/
|