The fix to prevent naming an unknown gray stone "the Heart of Ahriman" from revealing whether the object was a luckstone was inadequate to prevent using the same trick with "the Mitre of Holiness" to determine if an unknown helmet was a helm of brilliance. (I don't know whether whoever figured out the first one has realized the second yet; no one had mentioned it in the newsgroup the last time I looked.) To get this right we need to check for objects sharing the same set of shuffled descriptions in addition to testing whether they have identical descriptions. Doing that meant reorganizing how object shuffling is done, but it produces the same behavior as before.
467 lines
12 KiB
C
467 lines
12 KiB
C
/* SCCS Id: @(#)o_init.c 3.5 2005/11/14 */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
#include "lev.h" /* save & restore info */
|
|
|
|
STATIC_DCL void FDECL(setgemprobs, (d_level*));
|
|
STATIC_DCL void FDECL(shuffle,(int,int,BOOLEAN_P));
|
|
STATIC_DCL void NDECL(shuffle_all);
|
|
STATIC_DCL boolean FDECL(interesting_to_discover,(int));
|
|
|
|
|
|
static NEARDATA short disco[NUM_OBJECTS] = DUMMY;
|
|
|
|
#ifdef USE_TILES
|
|
STATIC_DCL void NDECL(shuffle_tiles);
|
|
extern short glyph2tile[]; /* from tile.c */
|
|
|
|
/* 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.
|
|
*/
|
|
STATIC_OVL void
|
|
shuffle_tiles()
|
|
{
|
|
int i;
|
|
short tmp_tilemap[NUM_OBJECTS];
|
|
|
|
for (i = 0; i < NUM_OBJECTS; i++)
|
|
tmp_tilemap[i] =
|
|
glyph2tile[objects[i].oc_descr_idx + GLYPH_OBJ_OFF];
|
|
|
|
for (i = 0; i < NUM_OBJECTS; i++)
|
|
glyph2tile[i + GLYPH_OBJ_OFF] = tmp_tilemap[i];
|
|
}
|
|
#endif /* USE_TILES */
|
|
|
|
STATIC_OVL void
|
|
setgemprobs(dlev)
|
|
d_level *dlev;
|
|
{
|
|
int j, first, lev;
|
|
|
|
if (dlev)
|
|
lev = (ledger_no(dlev) > maxledgerno())
|
|
? maxledgerno() : ledger_no(dlev);
|
|
else
|
|
lev = 0;
|
|
first = bases[GEM_CLASS];
|
|
|
|
for(j = 0; j < 9-lev/3; j++)
|
|
objects[first+j].oc_prob = 0;
|
|
first += j;
|
|
if (first > LAST_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_GEM);
|
|
wait_synch();
|
|
}
|
|
for (j = first; j <= LAST_GEM; j++)
|
|
objects[j].oc_prob = (171+j-first)/(LAST_GEM+1-first);
|
|
}
|
|
|
|
/* shuffle descriptions on objects o_low to o_high */
|
|
STATIC_OVL void
|
|
shuffle(o_low, o_high, domaterial)
|
|
int o_low, 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()
|
|
{
|
|
register int i, first, last, sum;
|
|
register char oclass;
|
|
#ifdef TEXTCOLOR
|
|
# 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
|
|
#else
|
|
# define COPY_OBJ_DESCR(o_dst,o_src) o_dst.oc_descr_idx = o_src.oc_descr_idx
|
|
#endif
|
|
|
|
/* bug fix to prevent "initialization error" abort on Intel Xenix.
|
|
* reported by mikew@semike
|
|
*/
|
|
for (i = 0; i < MAXOCLASSES; i++)
|
|
bases[i] = 0;
|
|
/* 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 = 0;
|
|
while( first < NUM_OBJECTS ) {
|
|
oclass = objects[first].oc_class;
|
|
last = first+1;
|
|
while (last < NUM_OBJECTS && objects[last].oc_class == oclass) last++;
|
|
bases[(int)oclass] = first;
|
|
|
|
if (oclass == GEM_CLASS) {
|
|
setgemprobs((d_level *)0);
|
|
|
|
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;
|
|
}
|
|
}
|
|
check:
|
|
sum = 0;
|
|
for(i = first; i < last; i++) sum += objects[i].oc_prob;
|
|
if(sum == 0) {
|
|
for(i = first; i < last; i++)
|
|
objects[i].oc_prob = (1000+i-first)/(last-first);
|
|
goto check;
|
|
}
|
|
if(sum != 1000)
|
|
error("init-prob error for class %d (%d%%)", oclass, sum);
|
|
first = last;
|
|
}
|
|
/* shuffle descriptions */
|
|
shuffle_all();
|
|
#ifdef USE_TILES
|
|
shuffle_tiles();
|
|
#endif
|
|
}
|
|
|
|
/* retrieve the range of objects that otyp shares descriptions with */
|
|
void
|
|
obj_shuffle_range(otyp, lo_p, hi_p)
|
|
int otyp; /* input: representative item */
|
|
int *lo_p, *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 = 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 = 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 = bases[ocls];
|
|
for (i = *lo_p; objects[i].oc_class == ocls; i++) continue;
|
|
*hi_p = i - 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 */
|
|
STATIC_OVL void
|
|
shuffle_all()
|
|
{
|
|
/* 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(bases[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;
|
|
}
|
|
|
|
/* find the object index for snow boots; used [once] by slippery ice code */
|
|
int
|
|
find_skates()
|
|
{
|
|
register int i;
|
|
register const char *s;
|
|
|
|
for (i = SPEED_BOOTS; i <= LEVITATION_BOOTS; i++)
|
|
if ((s = OBJ_DESCR(objects[i])) != 0 && !strcmp(s, "snow boots"))
|
|
return i;
|
|
|
|
impossible("snow boots not found?");
|
|
return -1; /* not 0, or caller would try again each move */
|
|
}
|
|
|
|
void
|
|
oinit() /* level dependent initialization */
|
|
{
|
|
setgemprobs(&u.uz);
|
|
}
|
|
|
|
void
|
|
savenames(fd, mode)
|
|
int fd, mode;
|
|
{
|
|
register int i;
|
|
unsigned int len;
|
|
|
|
if (perform_bwrite(mode)) {
|
|
bwrite(fd, (genericptr_t)bases, sizeof bases);
|
|
bwrite(fd, (genericptr_t)disco, sizeof disco);
|
|
bwrite(fd, (genericptr_t)objects,
|
|
sizeof(struct objclass) * NUM_OBJECTS);
|
|
}
|
|
/* 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 (perform_bwrite(mode)) {
|
|
len = strlen(objects[i].oc_uname)+1;
|
|
bwrite(fd, (genericptr_t)&len, sizeof len);
|
|
bwrite(fd, (genericptr_t)objects[i].oc_uname, len);
|
|
}
|
|
if (release_data(mode)) {
|
|
free((genericptr_t)objects[i].oc_uname);
|
|
objects[i].oc_uname = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
restnames(fd)
|
|
register int fd;
|
|
{
|
|
register int i;
|
|
unsigned int len;
|
|
|
|
mread(fd, (genericptr_t) bases, sizeof bases);
|
|
mread(fd, (genericptr_t) disco, sizeof disco);
|
|
mread(fd, (genericptr_t) objects, sizeof(struct objclass) * NUM_OBJECTS);
|
|
for (i = 0; i < NUM_OBJECTS; i++)
|
|
if (objects[i].oc_uname) {
|
|
mread(fd, (genericptr_t) &len, sizeof len);
|
|
objects[i].oc_uname = (char *) alloc(len);
|
|
mread(fd, (genericptr_t)objects[i].oc_uname, len);
|
|
}
|
|
#ifdef USE_TILES
|
|
shuffle_tiles();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
discover_object(oindx, mark_as_known, credit_hero)
|
|
register int oindx;
|
|
boolean mark_as_known;
|
|
boolean credit_hero;
|
|
{
|
|
if (!objects[oindx].oc_name_known) {
|
|
register 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 = bases[acls]; disco[dindx] != 0; dindx++)
|
|
if (disco[dindx] == oindx) break;
|
|
disco[dindx] = oindx;
|
|
|
|
if (mark_as_known) {
|
|
objects[oindx].oc_name_known = 1;
|
|
if (credit_hero) exercise(A_WIS, TRUE);
|
|
}
|
|
if (moves > 1L) update_inventory();
|
|
}
|
|
}
|
|
|
|
/* if a class name has been cleared, we may need to purge it from disco[] */
|
|
void
|
|
undiscover_object(oindx)
|
|
register int oindx;
|
|
{
|
|
if (!objects[oindx].oc_name_known) {
|
|
register int dindx, acls = objects[oindx].oc_class;
|
|
register boolean found = FALSE;
|
|
|
|
/* find the object; shift those behind it forward one slot */
|
|
for (dindx = bases[acls];
|
|
dindx < NUM_OBJECTS && disco[dindx] != 0
|
|
&& objects[dindx].oc_class == acls; dindx++)
|
|
if (found)
|
|
disco[dindx-1] = disco[dindx];
|
|
else if (disco[dindx] == oindx)
|
|
found = TRUE;
|
|
|
|
/* clear last slot */
|
|
if (found) disco[dindx-1] = 0;
|
|
else impossible("named object not in disco");
|
|
update_inventory();
|
|
}
|
|
}
|
|
|
|
STATIC_OVL boolean
|
|
interesting_to_discover(i)
|
|
register int i;
|
|
{
|
|
/* Pre-discovered objects are now printed with a '*' */
|
|
return((boolean)(objects[i].oc_uname != (char *)0 ||
|
|
(objects[i].oc_name_known && OBJ_DESCR(objects[i]) != (char *)0)));
|
|
}
|
|
|
|
/* items that should stand out once they're known */
|
|
static short uniq_objs[] = {
|
|
AMULET_OF_YENDOR,
|
|
SPE_BOOK_OF_THE_DEAD,
|
|
CANDELABRUM_OF_INVOCATION,
|
|
BELL_OF_OPENING,
|
|
};
|
|
|
|
int
|
|
dodiscovered() /* free after Robert Viduya */
|
|
{
|
|
register int i, dis;
|
|
int ct = 0;
|
|
char *s, oclass, prev_class, classes[MAXOCLASSES];
|
|
winid tmpwin;
|
|
char buf[BUFSZ];
|
|
|
|
tmpwin = create_nhwindow(NHW_MENU);
|
|
putstr(tmpwin, 0, "Discoveries");
|
|
putstr(tmpwin, 0, "");
|
|
|
|
/* gather "unique objects" into a pseudo-class; note that they'll
|
|
also be displayed individually within their regular class */
|
|
for (i = dis = 0; i < SIZE(uniq_objs); i++)
|
|
if (objects[uniq_objs[i]].oc_name_known) {
|
|
if (!dis++)
|
|
putstr(tmpwin, iflags.menu_headings, "Unique Items");
|
|
Sprintf(buf, " %s", OBJ_NAME(objects[uniq_objs[i]]));
|
|
putstr(tmpwin, 0, buf);
|
|
++ct;
|
|
}
|
|
/* display any known artifacts as another pseudo-class */
|
|
ct += disp_artifact_discoveries(tmpwin);
|
|
|
|
/* several classes are omitted from packorder; one is of interest here */
|
|
Strcpy(classes, flags.inv_order);
|
|
if (!index(classes, VENOM_CLASS)) {
|
|
s = eos(classes);
|
|
*s++ = VENOM_CLASS;
|
|
*s = '\0';
|
|
}
|
|
|
|
for (s = classes; *s; s++) {
|
|
oclass = *s;
|
|
prev_class = oclass + 1; /* forced different from oclass */
|
|
for (i = bases[(int)oclass];
|
|
i < NUM_OBJECTS && objects[i].oc_class == oclass; i++) {
|
|
if ((dis = disco[i]) && interesting_to_discover(dis)) {
|
|
ct++;
|
|
if (oclass != prev_class) {
|
|
putstr(tmpwin, iflags.menu_headings, let_to_name(oclass, FALSE));
|
|
prev_class = oclass;
|
|
}
|
|
Sprintf(buf, "%s %s",(objects[dis].oc_pre_discovered ? "*" : " "),
|
|
obj_typename(dis));
|
|
putstr(tmpwin, 0, buf);
|
|
}
|
|
}
|
|
}
|
|
if (ct == 0) {
|
|
You("haven't discovered anything yet...");
|
|
} else
|
|
display_nhwindow(tmpwin, TRUE);
|
|
destroy_nhwindow(tmpwin);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*o_init.c*/
|