Files
nethack/src/glyphs.c
T
Ingo Paschke 598253eac8 glyphs: rotate-5 in glyph_hash eliminates true collisions
The previous rotate-1-xor put consecutive characters' bits in
adjacent positions, so short similar names like fox/bat or
jaguar/lichen collided -- 164 colliding buckets across the 9577
named glyphs.  Rotate-5 spreads each character across a 5-bit
window: zero true collisions, one m68k ROL.L, no multiplication.

The 16 remaining same-hash buckets are name duplicates from a
separate compose_glyph_name bug, addressed by its own fix.
2026-05-25 15:45:25 -04:00

1258 lines
40 KiB
C

/* NetHack 5.0 glyphs.c TODO: add NHDT branch/date/revision tags */
/* Copyright (c) Michael Allison, 2021. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
extern const struct symparse loadsyms[];
extern glyph_map glyphmap[MAX_GLYPH];
extern struct enum_dump monsdump[];
extern struct enum_dump objdump[];
#define Fprintf (void) fprintf
enum reserved_activities { res_nothing, res_dump_glyphnames };
enum things_to_find { find_nothing, find_pm, find_oc, find_cmap, find_glyph };
struct find_struct {
enum things_to_find findtype;
int val;
int loadsyms_offset;
int loadsyms_count;
int *extraval;
uint32 color;
const char *unicode_val; /* U+NNNN format */
void (*callback)(int glyph, struct find_struct *);
enum reserved_activities restype;
genericptr_t reserved;
};
static const struct find_struct zero_find = { 0 };
struct glyphname_hash_index_entry_t {
uint32 hash;
int glyphnum; /* NO_GLYPH (==MAX_GLYPH) marks an empty bucket */
};
static struct glyphname_hash_index_entry_t *glyphname_hash_indices_ptr;
static size_t glyphname_hash_indices_count;
static struct find_struct to_custom_symbol_find;
static const long nonzero_black = CLR_BLACK | NH_BASIC_COLOR;
staticfn int find_glyph_in_hashtable(const char *id);
staticfn int cmp_glyphname_entry(const void *a, const void *b);
staticfn uint32 glyph_hash(const char *id);
staticfn int compose_glyph_name(int glyph, char *buf, size_t bufsz);
staticfn void to_custom_symset_entry_callback(int glyph,
struct find_struct *findwhat);
staticfn int parse_id(const char *id, struct find_struct *findwhat);
staticfn int glyph_find_core(const char *id, struct find_struct *findwhat);
staticfn char *fix_glyphname(char *str);
staticfn void shuffle_customizations(void);
/* staticfn void purge_custom_entries(enum graphics_sets which_set); */
staticfn void
to_custom_symset_entry_callback(
int glyph,
struct find_struct *findwhat)
{
int idx = gs.symset_which_set;
#ifdef ENHANCED_SYMBOLS
uint8 utf8str[6] = { 0, 0, 0, 0, 0, 0 };
int uval = 0;
#endif
if (findwhat->extraval)
*findwhat->extraval = glyph;
assert(idx >= 0 && idx < NUM_GRAPHICS);
#ifdef ENHANCED_SYMBOLS
if (findwhat->unicode_val)
uval = unicode_val(findwhat->unicode_val);
if (uval && unicodeval_to_utf8str(uval, utf8str, sizeof utf8str)) {
/* presently the customizations are affiliated with a particular
* symset but if we don't have any symset context, ignore it for now
* in order to avoid a segfault.
* FIXME:
* One future idea might be to store the U+ entries under "UTF8"
* and apply those customizations to any current symset if it has
* a UTF8 handler. Similar approach for unaffiliated glyph/symbols
* non-UTF color customizations
*/
if (gs.symset[idx].name) {
add_custom_urep_entry(gs.symset[idx].name, glyph, uval,
utf8str, gs.symset_which_set);
} else {
static int glyphnag = 0;
if (!glyphnag++)
config_error_add("Unimplemented customization feature,"
" ignoring for now");
}
}
#endif
if (findwhat->color) {
if (gs.symset[idx].name) {
add_custom_nhcolor_entry(gs.symset[idx].name, glyph,
findwhat->color, gs.symset_which_set);
} else {
static int colornag = 0;
if (!colornag++)
config_error_add("Unimplemented customization feature,"
" ignoring for now");
}
}
}
/*
* Return value:
* 1 = success
* 0 = failure
*/
int
glyphrep_to_custom_map_entries(
const char *op,
int *glyphptr)
{
to_custom_symbol_find = zero_find;
char buf[BUFSZ], *c_glyphname, *c_unicode, *c_colorval, *cp;
int reslt = 0;
long rgb = 0L;
boolean slash = FALSE, colon = FALSE;
if (!glyphname_hash_indices_ptr)
reslt = 1; /* for debugger use only; no cache available */
nhUse(reslt);
Snprintf(buf, sizeof buf, "%s", op);
c_unicode = c_colorval = (char *) 0;
c_glyphname = cp = buf;
while (*cp) {
if (*cp == ':' || *cp == '/') {
if (*cp == ':') {
colon = TRUE;
*cp = '\0';
}
if (*cp == '/') {
slash = TRUE;
*cp = '\0';
}
}
cp++;
if (colon) {
c_unicode = cp;
colon = FALSE;
}
if (slash) {
c_colorval = cp;
slash = FALSE;
}
}
/* some sanity checks */
if (c_glyphname && *c_glyphname == ' ')
c_glyphname++;
if (c_colorval && *c_colorval == ' ')
c_colorval++;
if (c_unicode && *c_unicode == ' ') {
while (*c_unicode == ' ') {
c_unicode++;
}
}
if (c_unicode && !*c_unicode)
c_unicode = 0;
if ((c_colorval && (rgb = rgbstr_to_int32(c_colorval)) != -1L)
|| !c_colorval) {
/* if the color 0 is an actual color, as opposed to just "not set"
we set a marker bit outside the 24-bit range to indicate a
valid color value 0. That allows valid color 0, but allows a
simple checking for 0 to detect "not set". The window port that
implements the color switch, needs to either check that bit
or appropriately mask colors with 0xFFFFFF. */
to_custom_symbol_find.color = (rgb == -1 || !c_colorval) ? 0L
: (rgb == 0L) ? nonzero_black
: rgb;
}
if (c_unicode)
to_custom_symbol_find.unicode_val = c_unicode;
to_custom_symbol_find.extraval = glyphptr;
to_custom_symbol_find.callback = to_custom_symset_entry_callback;
reslt = glyph_find_core(c_glyphname, &to_custom_symbol_find);
return reslt;
}
staticfn char *
fix_glyphname(char *str)
{
char *c;
for (c = str; *c; c++) {
if (*c >= 'A' && *c <= 'Z')
*c += (char) ('a' - 'A');
else if (*c >= '0' && *c <= '9')
;
else if (*c < 'a' || *c > 'z')
*c = '_';
}
return str;
}
/* Build the canonical "G_xxx" identifier for the given glyph into buf.
* Returns 1 if a name was produced, 0 if this glyph has no canonical name
* (a few unused object indices); buf[0] is set to '\0' in that case.
* Used by parse_id's bulk-iteration paths, by find_glyph_in_hashtable to
* verify hash matches, by populate_glyphname_hash_indices to fill the table,
* and by wizcustom_glyphnames. */
staticfn int
compose_glyph_name(int glyph, char *buf, size_t bufsz)
{
int i, j, mnum, cmap_offset = 0;
boolean skip_base = FALSE;
const char *buf2, *buf3, *buf4;
char tmpbuf[4][QBUFSZ];
if (bufsz < 2)
return 0;
buf[0] = '\0';
tmpbuf[0][0] = tmpbuf[1][0] = tmpbuf[2][0] = tmpbuf[3][0] = '\0';
/* compute cmap_offset (the start of SYM_PCHAR entries in loadsyms[]) */
i = 0;
while (loadsyms[i].range) {
if (!cmap_offset && loadsyms[i].range == SYM_PCHAR)
cmap_offset = i;
i++;
}
if (glyph_is_monster(glyph)) {
buf2 = "";
buf3 = monsdump[glyph_to_mon(glyph)].nm;
if (glyph_is_normal_male_monster(glyph)) {
buf2 = "male_";
} else if (glyph_is_normal_female_monster(glyph)) {
buf2 = "female_";
} else if (glyph_is_ridden_male_monster(glyph)) {
buf2 = "ridden_male_";
} else if (glyph_is_ridden_female_monster(glyph)) {
buf2 = "ridden_female_";
} else if (glyph_is_detected_male_monster(glyph)) {
buf2 = "detected_male_";
} else if (glyph_is_detected_female_monster(glyph)) {
buf2 = "detected_female_";
} else if (glyph_is_male_pet(glyph)) {
buf2 = "pet_male_";
} else if (glyph_is_female_pet(glyph)) {
buf2 = "pet_female_";
}
Strcpy(buf, "G_");
Strcat(buf, buf2);
Strcat(buf, buf3);
} else if (glyph_is_body(glyph)) {
buf2 = glyph_is_body_piletop(glyph) ? "piletop_body_" : "body_";
buf3 = monsdump[glyph_to_body_corpsenm(glyph)].nm;
Strcpy(buf, "G_");
Strcat(buf, buf2);
Strcat(buf, buf3);
} else if (glyph_is_statue(glyph)) {
buf2 = glyph_is_fem_statue_piletop(glyph)
? "piletop_statue_of_female_"
: glyph_is_fem_statue(glyph)
? "statue_of_female_"
: glyph_is_male_statue_piletop(glyph)
? "piletop_statue_of_male_"
: glyph_is_male_statue(glyph)
? "statue_of_male_"
: "";
buf3 = monsdump[glyph_to_statue_corpsenm(glyph)].nm;
Strcpy(buf, "G_");
Strcat(buf, buf2);
Strcat(buf, buf3);
} else if (glyph_is_object(glyph)) {
i = glyph_to_obj(glyph);
if (((i > SCR_STINKING_CLOUD) && (i < SCR_MAIL))
|| ((i > WAN_LIGHTNING) && (i < GOLD_PIECE))) {
return 0;
}
if ((i >= WAN_LIGHT) && (i <= WAN_LIGHTNING))
buf2 = "wand of ";
else if ((i >= SPE_DIG) && (i < SPE_BLANK_PAPER))
buf2 = "spellbook of ";
else if ((i >= SCR_ENCHANT_ARMOR) && (i <= SCR_STINKING_CLOUD))
buf2 = "scroll of ";
else if ((i >= POT_GAIN_ABILITY) && (i <= POT_WATER))
buf2 = (i == POT_WATER) ? "flask of n" : "potion of ";
else if ((i >= RIN_ADORNMENT) && (i <= RIN_PROTECTION_FROM_SHAPE_CHAN))
buf2 = "ring of ";
else if (i == LAND_MINE)
buf2 = "unset ";
else
buf2 = "";
buf3 = (i == SCR_BLANK_PAPER) ? "blank scroll"
: (i == SPE_BLANK_PAPER) ? "blank spellbook"
: (i == SLIME_MOLD) ? "slime mold"
: obj_descr[i].oc_name
? obj_descr[i].oc_name
: obj_descr[i].oc_descr;
Strcpy(buf, "G_");
if (glyph_is_normal_piletop_obj(glyph)
|| glyph_is_piletop_generic_obj(glyph))
Strcat(buf, "piletop_");
Strcat(buf, buf2);
Strcat(buf, buf3);
} else if (glyph_is_cmap(glyph) || glyph_is_cmap_zap(glyph)
|| glyph_is_swallow(glyph) || glyph_is_explosion(glyph)) {
int cmap = -1;
buf2 = "";
buf3 = "";
buf4 = "";
if (glyph == GLYPH_CMAP_OFF) {
cmap = S_stone;
buf3 = "stone substrate";
skip_base = TRUE;
} else if (glyph_is_cmap_gehennom(glyph)) {
cmap = (glyph - GLYPH_CMAP_GEH_OFF) + S_vwall;
buf4 = "_gehennom";
} else if (glyph_is_cmap_knox(glyph)) {
cmap = (glyph - GLYPH_CMAP_KNOX_OFF) + S_vwall;
buf4 = "_knox";
} else if (glyph_is_cmap_main(glyph)) {
cmap = (glyph - GLYPH_CMAP_MAIN_OFF) + S_vwall;
buf4 = "_main";
} else if (glyph_is_cmap_mines(glyph)) {
cmap = (glyph - GLYPH_CMAP_MINES_OFF) + S_vwall;
buf4 = "_mines";
} else if (glyph_is_cmap_sokoban(glyph)) {
cmap = (glyph - GLYPH_CMAP_SOKO_OFF) + S_vwall;
buf4 = "_sokoban";
} else if (glyph_is_cmap_a(glyph)) {
cmap = (glyph - GLYPH_CMAP_A_OFF) + S_ndoor;
} else if (glyph_is_cmap_altar(glyph)) {
static const char *const altar_text[] = {
"unaligned", "chaotic", "neutral",
"lawful", "other",
};
j = (glyph - GLYPH_ALTAR_OFF);
cmap = S_altar;
if (j != altar_other) {
Snprintf(tmpbuf[2], sizeof tmpbuf[2], "%s_", altar_text[j]);
buf2 = tmpbuf[2];
} else {
buf3 = "altar other";
skip_base = TRUE;
}
} else if (glyph_is_cmap_b(glyph)) {
cmap = (glyph - GLYPH_CMAP_B_OFF) + S_grave;
} else if (glyph_is_cmap_zap(glyph)) {
static const char *const zap_texts[] = {
"missile", "fire", "frost", "sleep",
"death", "lightning", "poison gas", "acid"
};
j = (glyph - GLYPH_ZAP_OFF);
cmap = (j % 4) + S_vbeam;
Snprintf(tmpbuf[2], sizeof tmpbuf[2], "%s",
loadsyms[cmap + cmap_offset].name + 2);
Snprintf(tmpbuf[3], sizeof tmpbuf[3], "%s zap %s",
zap_texts[j / 4], fix_glyphname(tmpbuf[2]));
buf3 = tmpbuf[3];
buf2 = "";
skip_base = TRUE;
} else if (glyph_is_cmap_c(glyph)) {
cmap = (glyph - GLYPH_CMAP_C_OFF) + S_digbeam;
} else if (glyph_is_swallow(glyph)) {
static const char *const swallow_texts[] = {
"top left", "top center", "top right",
"middle left", "middle right", "bottom left",
"bottom center", "bottom right",
};
j = glyph - GLYPH_SWALLOW_OFF;
cmap = glyph_to_swallow(glyph);
mnum = j / ((S_sw_br - S_sw_tl) + 1);
Strcpy(tmpbuf[3], "swallow ");
Strcat(tmpbuf[3], monsdump[mnum].nm);
Strcat(tmpbuf[3], " ");
Strcat(tmpbuf[3], swallow_texts[cmap]);
buf3 = tmpbuf[3];
skip_base = TRUE;
} else if (glyph_is_explosion(glyph)) {
static const char *const expl_type_texts[] = {
"dark", "noxious", "muddy", "wet",
"magical", "fiery", "frosty",
};
static const char *const expl_texts[] = {
"tl", "tc", "tr", "ml", "mc",
"mr", "bl", "bc", "br",
};
int expl;
j = glyph - GLYPH_EXPLODE_OFF;
expl = j / ((S_expl_br - S_expl_tl) + 1);
cmap = glyph_to_explosion(glyph) + S_expl_tl;
i = cmap - S_expl_tl;
Snprintf(tmpbuf[2], sizeof tmpbuf[2], "%s ",
expl_type_texts[expl]);
buf2 = tmpbuf[2];
Snprintf(tmpbuf[3], sizeof tmpbuf[3], "%s%s", "expl_",
expl_texts[i]);
buf3 = tmpbuf[3];
skip_base = TRUE;
}
if (!skip_base) {
if (cmap >= 0 && cmap < MAXPCHARS)
buf3 = loadsyms[cmap + cmap_offset].name + 2;
}
Strcpy(buf, "G_");
Strcat(buf, buf2);
Strcat(buf, buf3);
Strcat(buf, buf4);
} else if (glyph_is_invisible(glyph)) {
Strcpy(buf, "G_invisible");
} else if (glyph_is_nothing(glyph)) {
Strcpy(buf, "G_nothing");
} else if (glyph_is_unexplored(glyph)) {
Strcpy(buf, "G_unexplored");
} else if (glyph_is_warning(glyph)) {
j = glyph - GLYPH_WARNING_OFF;
Snprintf(buf, bufsz, "G_%s%d", "warning", j);
}
if (buf[0] == '\0')
return 0;
if (memchr(buf, '\0', bufsz) == NULL) {
/* defensive: caller passed an undersized buffer */
buf[bufsz - 1] = '\0';
return 0;
}
fix_glyphname(buf + 2);
nhUse(mnum);
return 1;
}
int
glyph_to_cmap(int glyph)
{
if (glyph == GLYPH_CMAP_STONE_OFF)
return S_stone;
else if (glyph_is_cmap_main(glyph))
return (glyph - GLYPH_CMAP_MAIN_OFF) + S_vwall;
else if (glyph_is_cmap_mines(glyph))
return (glyph - GLYPH_CMAP_MINES_OFF) + S_vwall;
else if (glyph_is_cmap_gehennom(glyph))
return (glyph - GLYPH_CMAP_GEH_OFF) + S_vwall;
else if (glyph_is_cmap_knox(glyph))
return (glyph - GLYPH_CMAP_KNOX_OFF) + S_vwall;
else if (glyph_is_cmap_sokoban(glyph))
return (glyph - GLYPH_CMAP_SOKO_OFF) + S_vwall;
else if (glyph_is_cmap_a(glyph))
return (glyph - GLYPH_CMAP_A_OFF) + S_ndoor;
else if (glyph_is_cmap_altar(glyph))
return S_altar;
else if (glyph_is_cmap_b(glyph))
return (glyph - GLYPH_CMAP_B_OFF) + S_grave;
else if (glyph_is_cmap_c(glyph))
return (glyph - GLYPH_CMAP_C_OFF) + S_digbeam;
else if (glyph_is_cmap_zap(glyph))
return ((glyph - GLYPH_ZAP_OFF) % 4) + S_vbeam;
else if (glyph_is_swallow(glyph))
return glyph_to_swallow(glyph) + S_sw_tl;
else if (glyph_is_explosion(glyph))
return glyph_to_explosion(glyph) + S_expl_tl;
else
return MAXPCHARS; /* MAXPCHARS is legal array index because
* of trailing fencepost entry */
}
staticfn int
glyph_find_core(
const char *id,
struct find_struct *findwhat)
{
int glyph;
boolean do_callback, end_find = FALSE;
if (parse_id(id, findwhat)) {
if (findwhat->findtype == find_glyph) {
(*findwhat->callback)(findwhat->val, findwhat);
} else {
for (glyph = 0; glyph < MAX_GLYPH; ++glyph) {
do_callback = FALSE;
switch (findwhat->findtype) {
case find_cmap:
if (glyph_to_cmap(glyph) == findwhat->val)
do_callback = TRUE;
break;
case find_pm:
if (glyph_is_monster(glyph)
&& mons[glyph_to_mon(glyph)].mlet
== findwhat->val)
do_callback = TRUE;
break;
case find_oc:
if (glyph_is_object(glyph)
&& glyph_to_obj(glyph) == findwhat->val)
do_callback = TRUE;
break;
case find_glyph:
if (glyph == findwhat->val) {
do_callback = TRUE;
end_find = TRUE;
}
break;
case find_nothing:
default:
end_find = TRUE;
break;
}
if (do_callback)
(findwhat->callback)(glyph, findwhat);
if (end_find)
break;
}
}
return 1;
}
return 0;
}
/*
* glyphname_hash_indices is a sorted (hash, glyph) index of canonical
* "G_xxx" identifiers. populate_glyphname_hash_indices() allocates one
* block of MAX_GLYPH * sizeof(entry) (some entries go unused for the
* scroll/gem appearance gaps), fills it via compose_glyph_name(), and
* sorts ascending by hash. Lookup is bsearch on the hash with
* compose_glyph_name()+strcmpi to disambiguate the rare collision.
*
* The name "hashtable" is historical; this is a sorted array, not an
* open-addressed hash table. The on-disk API in extern.h is kept so
* that callers (wizcmds.c, options.c) don't need to change.
*
* Memory: one alloc, ~75 KB for the 9577 named slots on a typical 5.0
* build (struct is 8 bytes, no per-name strings). The same data in
* upstream's open-addressed table cost ~256 KB for the buckets plus
* ~290 KB of dupstr'd names plus malloc overhead.
*/
staticfn int
cmp_glyphname_entry(const void *a, const void *b)
{
uint32 ha = ((const struct glyphname_hash_index_entry_t *) a)->hash;
uint32 hb = ((const struct glyphname_hash_index_entry_t *) b)->hash;
if (ha < hb)
return -1;
if (ha > hb)
return 1;
return 0;
}
void
populate_glyphname_hash_indices(void)
{
int glyph;
size_t n = 0;
char buf[BUFSZ];
if (glyphname_hash_indices_ptr)
return;
glyphname_hash_indices_ptr = (struct glyphname_hash_index_entry_t *) alloc(
MAX_GLYPH * sizeof (struct glyphname_hash_index_entry_t));
for (glyph = 0; glyph < MAX_GLYPH; ++glyph) {
if (compose_glyph_name(glyph, buf, sizeof buf)) {
glyphname_hash_indices_ptr[n].hash = glyph_hash(buf);
glyphname_hash_indices_ptr[n].glyphnum = glyph;
++n;
}
}
qsort(glyphname_hash_indices_ptr, n,
sizeof glyphname_hash_indices_ptr[0], cmp_glyphname_entry);
glyphname_hash_indices_count = n;
}
void
empty_glyphname_hash_indices(void)
{
if (!glyphname_hash_indices_ptr)
return;
free(glyphname_hash_indices_ptr);
glyphname_hash_indices_ptr = (struct glyphname_hash_index_entry_t *) 0;
glyphname_hash_indices_count = 0;
}
staticfn int
find_glyph_in_hashtable(const char *id)
{
uint32 want = glyph_hash(id);
size_t lo = 0, hi = glyphname_hash_indices_count, mid;
char buf[BUFSZ];
/* Binary-search the sorted array for the first entry whose hash >= want. */
while (lo < hi) {
mid = (lo + hi) >> 1;
if (glyphname_hash_indices_ptr[mid].hash < want)
lo = mid + 1;
else
hi = mid;
}
/* Walk forward across any equal-hash neighbours, verifying each by
reconstructing the canonical name and strcmpi'ing it back. */
while (lo < glyphname_hash_indices_count
&& glyphname_hash_indices_ptr[lo].hash == want) {
int g = glyphname_hash_indices_ptr[lo].glyphnum;
if (compose_glyph_name(g, buf, sizeof buf) && !strcmpi(id, buf))
return g;
++lo;
}
return -1;
}
staticfn uint32
glyph_hash(const char *id)
{
uint32 hash = 0;
size_t i;
for (i = 0; id[i] != '\0'; ++i) {
char ch = id[i];
if ('A' <= ch && ch <= 'Z') {
ch += 'a' - 'A';
}
hash = (hash << 5) | (hash >> 27);
hash ^= ch;
}
return hash;
}
boolean
glyphname_hash_indices_loaded(void)
{
return (glyphname_hash_indices_ptr != 0);
}
int
match_glyph(char *buf)
{
char workbuf[BUFSZ];
/* buf contains a G_ glyph reference, not an S_ symbol.
There could be an R-G-B color attached too.
Let's get a copy to work with. */
Snprintf(workbuf, sizeof workbuf, "%s", buf); /* get a copy */
return glyphrep(workbuf);
}
int
glyphrep(const char *op)
{
int reslt = 0, glyph = NO_GLYPH;
if (!glyphname_hash_indices_ptr)
reslt = 1; /* for debugger use only; no cache available */
nhUse(reslt);
reslt = glyphrep_to_custom_map_entries(op, &glyph);
if (reslt)
return 1;
return 0;
}
int
add_custom_nhcolor_entry(
const char *customization_name,
int glyphidx,
uint32 nhcolor,
enum graphics_sets which_set)
{
struct symset_customization *gdc
= &gs.sym_customizations[which_set][custom_nhcolor];
struct customization_detail *details, *newdetails = 0;
#if 0
static uint32 closecolor = 0;
static int clridx = 0;
#endif
if (!gdc->details) {
gdc->customization_name = dupstr(customization_name);
gdc->custtype = custom_nhcolor;
gdc->details = 0;
gdc->details_end = 0;
}
details = find_matching_customization(customization_name,
custom_nhcolor, which_set);
if (details) {
while (details) {
if (details->content.ccolor.glyphidx == glyphidx) {
details->content.ccolor.nhcolor = nhcolor;
return 1;
}
details = details->next;
}
}
/* create new details entry */
newdetails = (struct customization_detail *) alloc(sizeof *newdetails);
newdetails->content.urep.glyphidx = glyphidx;
newdetails->content.ccolor.nhcolor = nhcolor;
newdetails->next = (struct customization_detail *) 0;
if (gdc->details == NULL) {
gdc->details = newdetails;
} else {
gdc->details_end->next = newdetails;
}
gdc->details_end = newdetails;
gdc->count++;
return 1;
}
void
apply_customizations(
enum graphics_sets which_set,
enum do_customizations docustomize)
{
glyph_map *gmap;
struct customization_detail *details;
struct symset_customization *sc;
boolean at_least_one = FALSE,
do_colors = ((docustomize & do_custom_colors) != 0),
do_symbols = ((docustomize & do_custom_symbols) != 0);
int custs;
for (custs = 0; custs < (int) custom_count; ++custs) {
sc = &gs.sym_customizations[which_set][custs];
if (sc->count && sc->details) {
at_least_one = TRUE;
/* These glyph customizations get applied to the glyphmap array,
not to symset entries */
details = sc->details;
while (details) {
#ifdef ENHANCED_SYMBOLS
if (iflags.customsymbols && do_symbols) {
if (sc->custtype == custom_ureps) {
gmap = &glyphmap[details->content.urep.glyphidx];
if (gs.symset[which_set].handling == H_UTF8)
(void) set_map_u(gmap,
details->content.urep.u.utf32ch,
details->content.urep.u.utf8str);
}
}
#endif
if (iflags.customcolors && do_colors) {
if (sc->custtype == custom_nhcolor) {
gmap = &glyphmap[details->content.ccolor.glyphidx];
(void) set_map_customcolor(gmap,
details->content.ccolor.nhcolor);
}
}
details = details->next;
}
}
}
iflags.pending_customizations = at_least_one;
}
/* Shuffle the customizations to match shuffled object descriptions,
* so a red potion isn't displayed with a blue customization, and so on.
*/
void
maybe_shuffle_customizations(void)
{
if (iflags.pending_customizations) {
shuffle_customizations();
iflags.pending_customizations = 0;
}
}
#if 0
staticfn void
shuffle_customizations(void)
{
static const int offsets[2] = { GLYPH_OBJ_OFF, GLYPH_OBJ_PILETOP_OFF };
int j;
for (j = 0; j < SIZE(offsets); j++) {
glyph_map *obj_glyphs = glyphmap + offsets[j];
int i;
struct unicode_representation *tmp_u[NUM_OBJECTS];
int duplicate[NUM_OBJECTS];
for (i = 0; i < NUM_OBJECTS; i++) {
duplicate[i] = -1;
tmp_u[i] = (struct unicode_representation *) 0;
}
for (i = 0; i < NUM_OBJECTS; i++) {
int idx = objects[i].oc_descr_idx;
/*
* Shuffling gem appearances can cause the same oc_descr_idx to
* appear more than once. Detect this condition and ensure that
* each pointer points to a unique allocation.
*/
if (duplicate[idx] >= 0) {
/* Current structure already appears in tmp_u */
struct unicode_representation *other = tmp_u[duplicate[idx]];
tmp_u[i] = (struct unicode_representation *)
alloc(sizeof *tmp_u[i]);
*tmp_u[i] = *other;
if (other->utf8str != NULL) {
tmp_u[i]->utf8str = (uint8 *)
dupstr((const char *) other->utf8str);
}
} else {
tmp_u[i] = obj_glyphs[idx].u;
if (obj_glyphs[idx].u != NULL) {
duplicate[idx] = i;
obj_glyphs[idx].u = NULL;
}
}
}
for (i = 0; i < NUM_OBJECTS; i++) {
/* Some glyphmaps may not have been transferred */
if (obj_glyphs[i].u != NULL) {
free(obj_glyphs[i].u->utf8str);
free(obj_glyphs[i].u);
}
obj_glyphs[i].u = tmp_u[i];
}
}
}
#else
staticfn void
shuffle_customizations(void)
{
static const int offsets[2] = { GLYPH_OBJ_OFF, GLYPH_OBJ_PILETOP_OFF };
int j;
for (j = 0; j < SIZE(offsets); j++) {
glyph_map *obj_glyphs = glyphmap + offsets[j];
int i;
#ifdef ENHANCED_SYMBOLS
struct unicode_representation *tmp_u[NUM_OBJECTS];
#endif
uint32 tmp_customcolor[NUM_OBJECTS];
uint16 tmp_color256idx[NUM_OBJECTS];
int duplicate[NUM_OBJECTS];
for (i = 0; i < NUM_OBJECTS; i++) {
duplicate[i] = -1;
#ifdef ENHANCED_SYMBOLS
tmp_u[i] = (struct unicode_representation *) 0;
#endif
tmp_customcolor[i] = 0;
tmp_color256idx[i] = 0;
}
for (i = 0; i < NUM_OBJECTS; i++) {
int idx = objects[i].oc_descr_idx;
/*
* Shuffling gem appearances can cause the same oc_descr_idx to
* appear more than once. Detect this condition and ensure that
* each pointer points to a unique allocation.
*/
if (duplicate[idx] >= 0) {
#ifdef ENHANCED_SYMBOLS
/* Current structure already appears in tmp_u */
struct unicode_representation *other = tmp_u[duplicate[idx]];
#endif
uint32 other_customcolor = tmp_customcolor[duplicate[idx]];
uint16 other_color256idx = tmp_color256idx[duplicate[idx]];
tmp_customcolor[i] = other_customcolor;
tmp_color256idx[i] = other_color256idx;
#ifdef ENHANCED_SYMBOLS
if (other) {
tmp_u[i] = (struct unicode_representation *) alloc(
sizeof *tmp_u[i]);
*tmp_u[i] = *other;
if (other->utf8str != NULL) {
tmp_u[i]->utf8str =
(uint8 *) dupstr((const char *) other->utf8str);
}
}
#endif
} else {
tmp_customcolor[i] = obj_glyphs[idx].customcolor;
tmp_color256idx[i] = obj_glyphs[idx].color256idx;
#ifdef ENHANCED_SYMBOLS
tmp_u[i] = obj_glyphs[idx].u;
#endif
if (
#ifdef ENHANCED_SYMBOLS
obj_glyphs[idx].u != NULL ||
#endif
obj_glyphs[idx].customcolor != 0) {
duplicate[idx] = i;
#ifdef ENHANCED_SYMBOLS
obj_glyphs[idx].u = NULL;
#endif
obj_glyphs[idx].customcolor = 0;
obj_glyphs[idx].color256idx = 0;
}
}
}
for (i = 0; i < NUM_OBJECTS; i++) {
/* Some glyphmaps may not have been transferred */
#ifdef ENHANCED_SYMBOLS
if (obj_glyphs[i].u != NULL) {
free(obj_glyphs[i].u->utf8str);
free(obj_glyphs[i].u);
}
obj_glyphs[i].u = tmp_u[i];
#endif
obj_glyphs[i].customcolor = tmp_customcolor[i];
obj_glyphs[i].color256idx = tmp_color256idx[i];
}
}
}
#endif
struct customization_detail *
find_matching_customization(
const char *customization_name,
enum customization_types custtype,
enum graphics_sets which_set)
{
struct symset_customization *gdc
= &gs.sym_customizations[which_set][custtype];
if ((gdc->custtype == custtype) && gdc->customization_name
&& (strcmp(customization_name, gdc->customization_name) == 0))
return gdc->details;
return (struct customization_detail *) 0;
}
void
purge_all_custom_entries(void)
{
int i;
for (i = 0; i < NUM_GRAPHICS + 1; ++i) {
purge_custom_entries(i);
}
}
void
purge_custom_entries(enum graphics_sets which_set)
{
enum customization_types custtype;
struct symset_customization *gdc;
struct customization_detail *details, *next;
for (custtype = custom_none; custtype < custom_count; ++custtype) {
gdc = &gs.sym_customizations[which_set][custtype];
details = gdc->details;
while (details) {
next = details->next;
if (gdc->custtype == custom_ureps) {
if (details->content.urep.u.utf8str)
free(details->content.urep.u.utf8str);
details->content.urep.u.utf8str = (uint8 *) 0;
} else if (gdc->custtype == custom_symbols) {
details->content.sym.symparse = (struct symparse *) 0;
details->content.sym.val = 0;
} else if (gdc->custtype == custom_nhcolor) {
details->content.ccolor.nhcolor = 0;
details->content.ccolor.glyphidx = 0;
}
free(details);
details = next;
}
gdc->details = 0;
gdc->details_end = 0;
if (gdc->customization_name) {
free((genericptr_t) gdc->customization_name);
gdc->customization_name = 0;
}
gdc->count = 0;
}
}
void
dump_all_glyphnames(FILE *fp)
{
struct find_struct dump_glyphname_find = zero_find;
dump_glyphname_find.findtype = find_nothing;
dump_glyphname_find.reserved = (genericptr_t) fp;
dump_glyphname_find.restype = res_dump_glyphnames;
(void) parse_id((char *) 0, &dump_glyphname_find);
}
void
wizcustom_glyphnames(winid win)
{
int glyphnum;
char buf[BUFSZ];
for (glyphnum = 0; glyphnum < MAX_GLYPH; ++glyphnum) {
if (compose_glyph_name(glyphnum, buf, sizeof buf))
wizcustom_callback(win, glyphnum, buf);
}
}
staticfn int
parse_id(
const char *id,
struct find_struct *findwhat)
{
FILE *fp = (FILE *) 0;
int i = 0, glyph,
pm_offset = 0, oc_offset = 0, cmap_offset = 0,
pm_count = 0, oc_count = 0, cmap_count = 0;
boolean dump_ids = FALSE, is_S = FALSE, is_G = FALSE;
char buf[BUFSZ];
if (findwhat->findtype == find_nothing && findwhat->restype) {
if (findwhat->restype == res_dump_glyphnames) {
if (findwhat->reserved) {
fp = (FILE *) findwhat->reserved;
dump_ids = TRUE;
} else {
return 0;
}
}
}
is_G = (id && id[0] == 'G' && id[1] == '_');
is_S = (id && id[0] == 'S' && id[1] == '_');
if (dump_ids || is_S) {
while (loadsyms[i].range) {
if (!pm_offset && loadsyms[i].range == SYM_MON)
pm_offset = i;
if (!pm_count && pm_offset && loadsyms[i].range != SYM_MON)
pm_count = i - pm_offset;
if (!oc_offset && loadsyms[i].range == SYM_OC)
oc_offset = i;
if (!oc_count && oc_offset && loadsyms[i].range != SYM_OC)
oc_count = i - oc_offset;
if (!cmap_offset && loadsyms[i].range == SYM_PCHAR)
cmap_offset = i;
if (!cmap_count && cmap_offset && loadsyms[i].range != SYM_PCHAR)
cmap_count = i - cmap_offset;
i++;
}
}
if (is_G && id) {
/* Populate the hash table lazily, on first G_xxx lookup. */
if (!glyphname_hash_indices_ptr)
populate_glyphname_hash_indices();
if (glyphname_hash_indices_ptr) {
int val = find_glyph_in_hashtable(id);
if (val >= 0) {
findwhat->findtype = find_glyph;
findwhat->val = val;
findwhat->loadsyms_offset = 0;
return 1;
}
}
return 0;
}
if (dump_ids) {
/* iterate and dump every named glyph */
for (glyph = 0; glyph < MAX_GLYPH; ++glyph) {
if (compose_glyph_name(glyph, buf, sizeof buf))
Fprintf(fp, "(%04d) %s\n", glyph, buf);
}
return 1;
}
if (is_S) {
/* cmap entries */
for (i = 0; i < cmap_count; ++i) {
if (!strcmpi(loadsyms[i + cmap_offset].name + 2, id + 2)) {
findwhat->findtype = find_cmap;
findwhat->val = i;
findwhat->loadsyms_offset = i + cmap_offset;
return 1;
}
}
/* objclass entries */
for (i = 0; i < oc_count; ++i) {
if (!strcmpi(loadsyms[i + oc_offset].name + 2, id + 2)) {
findwhat->findtype = find_oc;
findwhat->val = i;
findwhat->loadsyms_offset = i + oc_offset;
return 1;
}
}
/* permonst entries */
for (i = 0; i <= pm_count; ++i) {
if (!strcmpi(loadsyms[i + pm_offset].name + 2, id + 2)) {
findwhat->findtype = find_pm;
findwhat->val = i + 1; /* starts at 1 */
findwhat->loadsyms_offset = i + pm_offset;
return 1;
}
}
}
findwhat->findtype = find_nothing;
findwhat->val = 0;
findwhat->loadsyms_offset = 0;
return 0;
}
/* extern glyph_map glyphmap[MAX_GLYPH]; */
void
clear_all_glyphmap_colors(void)
{
int glyph;
for (glyph = 0; glyph < MAX_GLYPH; ++glyph) {
if (glyphmap[glyph].customcolor)
glyphmap[glyph].customcolor = 0;
glyphmap[glyph].color256idx = 0;
}
}
void
reset_customcolors(void)
{
clear_all_glyphmap_colors();
apply_customizations(gc.currentgraphics, do_custom_colors);
}
/* not used yet */
#if 0
staticfn struct customization_detail *find_display_sym_customization(
const char *customization_name, const struct symparse *symparse,
enum graphics_sets which_set);
staticfn struct customization_detail *find_display_urep_customization(
const char *customization_name, int glyphidx,
enum graphics_sets which_set);
struct customization_detail *
find_display_sym_customization(
const char *customization_name,
const struct symparse *symparse,
enum graphics_sets which_set)
{
struct symset_customization *gdc;
struct customization_detail *symdetails;
gdc = &gs.sym_customizations[which_set][custom_symbols];
if ((gdc->custtype == custom_symbols)
&& (strcmp(customization_name, gdc->customization_name) == 0)) {
symdetails = gdc->details;
while (symdetails) {
if (symdetails->content.sym.symparse == symparse)
return symdetails;
symdetails = symdetails->next;
}
}
return (struct customization_detail *) 0;
}
struct customization_detail *
find_display_urep_customization(
const char *customization_name,
int glyphidx,
enum graphics_sets which_set)
{
struct symset_customization *gdc = &gs.sym_customizations[which_set];
struct customization_detail *urepdetails;
if ((gdc->custtype == custom_reps)
|| (strcmp(customization_name, gdc->customization_name) == 0)) {
urepdetails = gdc->details;
while (urepdetails) {
if (urepdetails->content.urep.glyphidx == glyphidx)
return urepdetails;
urepdetails = urepdetails->next;
}
}
return (struct customization_detail *) 0;
}
#endif /* 0 not used yet */
#ifdef TEST_GLYPHNAMES
static struct {
int idx;
const char *nm1;
const char *nm2;
} cmapname[MAXPCHARS] = {
#define PCHAR_TILES
#include "defsym.h"
#undef PCHAR_TILES
};
void
test_glyphnames(void)
{
int reslt;
reslt = find_glyphs("G_potion_of_monster_detection");
reslt = find_glyphs("G_piletop_body_chickatrice");
reslt = find_glyphs("G_detected_male_homunculus");
reslt = find_glyphs("S_pool");
reslt = find_glyphs("S_dog");
reslt = glyphs_to_unicode("S_dog", "U+130E6", 0L);
}
staticfn void
just_find_callback(int glyph UNUSED, struct find_struct *findwhat UNUSED)
{
return;
}
staticfn int
find_glyphs(const char *id)
{
struct find_struct find_only = zero_find;
find_only.unicode_val = 0;
find_only.callback = just_find_callback;
return glyph_find_core(id, &find_only);
}
staticfn void
to_unicode_callback(int glyph UNUSED, struct find_struct *findwhat)
{
int uval;
#ifdef NO_PARSING_SYMSET
glyph_map *gm = &glyphmap[glyph];
#endif
uint8 utf8str[6];
if (!findwhat->unicode_val)
return;
uval = unicode_val(findwhat->unicode_val);
if (unicodeval_to_utf8str(uval, utf8str, sizeof utf8str)) {
#ifdef NO_PARSING_SYMSET
set_map_u(gm, uval, utf8str,
(findwhat->color != 0L) ? findwhat->color : 0L);
#else
#endif
}
}
int
glyphs_to_unicode(const char *id, const char *unicode_val, long clr)
{
struct find_struct to_unicode = zero_find;
to_unicode.unicode_val = unicode_val;
to_unicode.callback = to_unicode_callback;
/* if the color 0 is an actual color, as opposed to just "not set"
we set a marker bit outside the 24-bit range to indicate a
valid color value 0. That allows valid color 0, but allows a
simple checking for 0 to detect "not set". The window port that
implements the color switch, needs to either check that bit
or appropriately mask colors with 0xFFFFFF. */
to_unicode.color = (clr == -1) ? 0L : (clr == 0L) ? nonzero_black : clr;
return glyph_find_core(id, &to_unicode);
}
#endif /* SOME TEST STUFF */
/* glyphs.c */