make rank() static again
[NetHack.git] / src / symbols.c
blob7c32af3e30d8e229eaa7474f1da2c630d7ab6d7b
1 /* NetHack 3.7 symbols.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.123 $ */
2 /* Copyright (c) NetHack Development Team 2020. */
3 /* NetHack may be freely redistributed. See license for details. */
5 #include "hack.h"
6 #include "tcap.h"
8 staticfn void savedsym_add(const char *, const char *, int);
9 staticfn struct _savedsym *savedsym_find(const char *, int);
11 extern const uchar def_r_oc_syms[MAXOCLASSES]; /* drawing.c */
13 #if defined(TERMLIB) || defined(CURSES_GRAPHICS)
14 void (*decgraphics_mode_callback)(void) = 0; /* set in term_start_screen() */
15 #endif /* TERMLIB || CURSES */
17 #ifdef PC9800
18 void (*ibmgraphics_mode_callback)(void) = 0; /* set in term_start_screen() */
19 void (*ascgraphics_mode_callback)(void) = 0; /* set in term_start_screen() */
20 #endif
21 #ifdef CURSES_GRAPHICS
22 void (*cursesgraphics_mode_callback)(void) = 0;
23 #endif
24 #if defined(TTY_GRAPHICS) && defined(WIN32)
25 void (*ibmgraphics_mode_callback)(void) = 0;
26 #endif
27 #ifdef ENHANCED_SYMBOLS
28 void (*utf8graphics_mode_callback)(void) = 0; /* set in term_start_screen and
29 * found in unixtty,windtty,&c */
30 #endif
33 * Explanations of the functions found below:
35 * init_symbols()
36 * Sets the current display symbols, the
37 * loadable symbols to the default NetHack
38 * symbols, including the rogue_syms rogue level
39 * symbols. This would typically be done
40 * immediately after execution begins. Any
41 * previously loaded external symbol sets are
42 * discarded.
44 * switch_symbols(arg)
45 * Called to swap in new current display symbols
46 * (showsyms) from either the default symbols,
47 * or from the loaded symbols.
49 * If (arg == 0) then showsyms are taken from
50 * defsyms, def_oc_syms, and def_monsyms.
52 * If (arg != 0), which is the normal expected
53 * usage, then showsyms are taken from the
54 * adjustable display symbols found in gp.primary_syms.
55 * gp.primary_syms may have been loaded from an external
56 * symbol file by config file options or interactively
57 * in the Options menu.
59 * assign_graphics(arg)
61 * This is used in the game core to toggle in and
62 * out of other {rogue} level display modes.
64 * If arg is ROGUESET, this places the rogue level
65 * symbols from gr.rogue_syms into gs.showsyms.
67 * If arg is PRIMARYSET, this places the symbols
68 * from gp.primary_syms into gs.showsyms.
70 * update_primary_symset()
71 * Update a member of the primary(primary_*) symbol set.
73 * update_rogue_symset()
74 * Update a member of the rogue (rogue_*) symbol set.
76 * update_ov_primary_symset()
77 * Update a member of the overrides for primary symbol set.
79 * update_ov_rogue_symset()
80 * Update a member of the overrides for rogue symbol set.
84 void
85 init_symbols(void)
87 init_ov_primary_symbols();
88 init_ov_rogue_symbols();
89 init_primary_symbols();
90 init_showsyms();
91 init_rogue_symbols();
94 void
95 init_showsyms(void)
97 int i;
99 for (i = 0; i < MAXPCHARS; i++)
100 gs.showsyms[i + SYM_OFF_P] = defsyms[i].sym;
101 for (i = 0; i < MAXOCLASSES; i++)
102 gs.showsyms[i + SYM_OFF_O] = def_oc_syms[i].sym;
103 for (i = 0; i < MAXMCLASSES; i++)
104 gs.showsyms[i + SYM_OFF_M] = def_monsyms[i].sym;
105 for (i = 0; i < WARNCOUNT; i++)
106 gs.showsyms[i + SYM_OFF_W] = def_warnsyms[i].sym;
107 for (i = 0; i < MAXOTHER; i++)
108 gs.showsyms[i + SYM_OFF_X] = get_othersym(i, PRIMARYSET);
111 /* initialize defaults for the overrides to the rogue symset */
112 void
113 init_ov_rogue_symbols(void)
115 int i;
117 for (i = 0; i < SYM_MAX; i++)
118 go.ov_rogue_syms[i] = (nhsym) 0;
120 /* initialize defaults for the overrides to the primary symset */
121 void
122 init_ov_primary_symbols(void)
124 int i;
126 for (i = 0; i < SYM_MAX; i++)
127 go.ov_primary_syms[i] = (nhsym) 0;
130 nhsym
131 get_othersym(int idx, int which_set)
133 nhsym sym = (nhsym) 0;
134 int oidx = idx + SYM_OFF_X;
136 if (which_set == ROGUESET)
137 sym = go.ov_rogue_syms[oidx] ? go.ov_rogue_syms[oidx]
138 : gr.rogue_syms[oidx];
139 else
140 sym = go.ov_primary_syms[oidx] ? go.ov_primary_syms[oidx]
141 : gp.primary_syms[oidx];
142 if (!sym) {
143 switch(idx) {
144 case SYM_NOTHING:
145 case SYM_UNEXPLORED:
146 sym = DEF_NOTHING;
147 break;
148 case SYM_BOULDER:
149 sym = def_oc_syms[ROCK_CLASS].sym;
150 break;
151 case SYM_INVISIBLE:
152 sym = DEF_INVISIBLE;
153 break;
154 #if 0
155 /* these intentionally have no defaults */
156 case SYM_PET_OVERRIDE:
157 case SYM_HERO_OVERRIDE:
158 break;
159 #endif
162 return sym;
165 /* initialize defaults for the primary symset */
166 void
167 init_primary_symbols(void)
169 int i;
171 for (i = 0; i < MAXPCHARS; i++)
172 gp.primary_syms[i + SYM_OFF_P] = defsyms[i].sym;
173 for (i = 0; i < MAXOCLASSES; i++)
174 gp.primary_syms[i + SYM_OFF_O] = def_oc_syms[i].sym;
175 for (i = 0; i < MAXMCLASSES; i++)
176 gp.primary_syms[i + SYM_OFF_M] = def_monsyms[i].sym;
177 for (i = 0; i < WARNCOUNT; i++)
178 gp.primary_syms[i + SYM_OFF_W] = def_warnsyms[i].sym;
179 for (i = 0; i < MAXOTHER; i++)
180 gp.primary_syms[i + SYM_OFF_X] = get_othersym(i, PRIMARYSET);
182 clear_symsetentry(PRIMARYSET, FALSE);
185 /* initialize defaults for the rogue symset */
186 void
187 init_rogue_symbols(void)
189 int i;
191 /* These are defaults that can get overwritten
192 later by the roguesymbols option */
194 for (i = 0; i < MAXPCHARS; i++)
195 gr.rogue_syms[i + SYM_OFF_P] = defsyms[i].sym;
196 gr.rogue_syms[S_vodoor] = gr.rogue_syms[S_hodoor]
197 = gr.rogue_syms[S_ndoor] = '+';
198 gr.rogue_syms[S_upstair] = gr.rogue_syms[S_dnstair] = '%';
200 for (i = 0; i < MAXOCLASSES; i++)
201 gr.rogue_syms[i + SYM_OFF_O] = def_r_oc_syms[i];
202 for (i = 0; i < MAXMCLASSES; i++)
203 gr.rogue_syms[i + SYM_OFF_M] = def_monsyms[i].sym;
204 for (i = 0; i < WARNCOUNT; i++)
205 gr.rogue_syms[i + SYM_OFF_W] = def_warnsyms[i].sym;
206 for (i = 0; i < MAXOTHER; i++)
207 gr.rogue_syms[i + SYM_OFF_X] = get_othersym(i, ROGUESET);
209 clear_symsetentry(ROGUESET, FALSE);
210 /* default on Rogue level is no color
211 * but some symbol sets can override that
213 gs.symset[ROGUESET].nocolor = 1;
216 void
217 assign_graphics(int whichset)
219 int i;
221 switch (whichset) {
222 case ROGUESET:
223 /* Adjust graphics display characters on Rogue levels */
225 for (i = 0; i < SYM_MAX; i++)
226 gs.showsyms[i] = go.ov_rogue_syms[i] ? go.ov_rogue_syms[i]
227 : gr.rogue_syms[i];
229 #if defined(MSDOS) && defined(TILES_IN_GLYPHMAP)
230 if (iflags.grmode)
231 tileview(FALSE);
232 #endif
233 gc.currentgraphics = ROGUESET;
234 break;
236 case PRIMARYSET:
237 default:
238 for (i = 0; i < SYM_MAX; i++)
239 gs.showsyms[i] = go.ov_primary_syms[i] ? go.ov_primary_syms[i]
240 : gp.primary_syms[i];
242 #if defined(MSDOS) && defined(TILES_IN_GLYPHMAP)
243 if (iflags.grmode)
244 tileview(TRUE);
245 #endif
246 gc.currentgraphics = PRIMARYSET;
247 break;
249 reset_glyphmap(gm_symchange);
252 void
253 switch_symbols(int nondefault)
255 int i;
257 if (nondefault) {
258 for (i = 0; i < SYM_MAX; i++)
259 gs.showsyms[i] = go.ov_primary_syms[i] ? go.ov_primary_syms[i]
260 : gp.primary_syms[i];
261 #ifdef PC9800
262 if (SYMHANDLING(H_IBM) && ibmgraphics_mode_callback)
263 (*ibmgraphics_mode_callback)();
264 else if (SYMHANDLING(H_UNK) && ascgraphics_mode_callback)
265 (*ascgraphics_mode_callback)();
266 #endif
267 #if defined(TERMLIB) || defined(CURSES_GRAPHICS)
268 /* curses doesn't assign any routine to dec..._callback but
269 probably does the expected initialization under the hood
270 for terminals capable of rendering DECgraphics */
271 if (SYMHANDLING(H_DEC) && decgraphics_mode_callback)
272 (*decgraphics_mode_callback)();
273 # ifdef CURSES_GRAPHICS
274 /* there aren't any symbol sets with CURS handling, and the
275 curses interface never assigns a routine to curses..._callback */
276 if (SYMHANDLING(H_CURS) && cursesgraphics_mode_callback)
277 (*cursesgraphics_mode_callback)();
278 # endif
279 #endif
280 #if defined(TTY_GRAPHICS) && defined(WIN32)
281 if (SYMHANDLING(H_IBM) && ibmgraphics_mode_callback)
282 (*ibmgraphics_mode_callback)();
283 #endif
284 #ifdef ENHANCED_SYMBOLS
285 if (SYMHANDLING(H_UTF8) && utf8graphics_mode_callback)
286 (*utf8graphics_mode_callback)();
287 #endif
288 } else {
289 init_primary_symbols();
290 init_showsyms();
294 void
295 update_ov_primary_symset(const struct symparse *symp, int val)
297 go.ov_primary_syms[symp->idx] = val;
300 void
301 update_ov_rogue_symset(const struct symparse *symp, int val)
303 go.ov_rogue_syms[symp->idx] = val;
306 void
307 update_primary_symset(const struct symparse *symp, int val)
309 gp.primary_syms[symp->idx] = val;
312 void
313 update_rogue_symset(const struct symparse *symp, int val)
315 gr.rogue_syms[symp->idx] = val;
318 void
319 clear_symsetentry(int which_set, boolean name_too)
321 #ifdef ENHANCED_SYMBOLS
322 int other_set = (which_set == PRIMARYSET) ? ROGUESET : PRIMARYSET;
323 enum symset_handling_types old_handling = gs.symset[which_set].handling;
324 #endif
326 if (gs.symset[which_set].desc)
327 free((genericptr_t) gs.symset[which_set].desc);
328 gs.symset[which_set].desc = (char *) 0;
330 gs.symset[which_set].handling = H_UNK;
331 gs.symset[which_set].nocolor = 0;
332 /* initialize restriction bits */
333 gs.symset[which_set].primary = 0;
334 gs.symset[which_set].rogue = 0;
336 if (name_too) {
337 if (gs.symset[which_set].name)
338 free((genericptr_t) gs.symset[which_set].name);
339 gs.symset[which_set].name = (char *) 0;
341 #ifdef ENHANCED_SYMBOLS
342 /* if 'which_set' was using UTF8, it isn't anymore; if the other set
343 isn't using UTF8, discard the data for that */
344 if (old_handling == H_UTF8 && gs.symset[other_set].handling != H_UTF8)
345 free_all_glyphmap_u();
346 #endif
347 purge_custom_entries(which_set);
348 clear_all_glyphmap_colors();
351 /* called from windmain.c */
352 boolean
353 symset_is_compatible(
354 enum symset_handling_types handling,
355 unsigned long wincap2)
357 #ifdef ENHANCED_SYMBOLS
358 #define WC2_utf8_bits (WC2_U_UTF8STR)
359 if (handling == H_UTF8 && ((wincap2 & WC2_utf8_bits) != WC2_utf8_bits))
360 return FALSE;
361 #undef WC2_bits
362 #else
363 nhUse(handling);
364 nhUse(wincap2);
365 #endif
366 return TRUE;
370 * If you are adding code somewhere to be able to recognize
371 * particular types of symset "handling", define a
372 * H_XXX macro in include/sym.h and add the name
373 * to this array at the matching offset.
374 * Externally referenced from files.c, options.c, utf8map.c.
376 const char *const known_handling[] = {
377 "UNKNOWN", /* H_UNK */
378 "IBM", /* H_IBM */
379 "DEC", /* H_DEC */
380 "CURS", /* H_CURS */
381 "MAC", /* H_MAC -- pre-OSX MACgraphics */
382 "UTF8", /* H_UTF8 */
383 (const char *) 0,
387 * Accepted keywords for symset restrictions.
388 * These can be virtually anything that you want to
389 * be able to test in the code someplace.
390 * Be sure to:
391 * - add a corresponding Bitfield to the symsetentry struct in sym.h
392 * - initialize the field to zero in parse_sym_line in the SYM_CONTROL
393 * case 0 section of the idx switch. The location is prefaced with
394 * with a comment stating "initialize restriction bits".
395 * - set the value appropriately based on the index of your keyword
396 * under the case 5 sections of the same SYM_CONTROL idx switches.
397 * - add the field to clear_symsetentry()
399 const char *const known_restrictions[] = {
400 "primary", "rogue", (const char *) 0,
403 const struct symparse loadsyms[] = {
404 { SYM_CONTROL, 0, "start" },
405 { SYM_CONTROL, 0, "begin" },
406 { SYM_CONTROL, 1, "finish" },
407 { SYM_CONTROL, 2, "handling" },
408 { SYM_CONTROL, 3, "description" },
409 { SYM_CONTROL, 4, "color" },
410 { SYM_CONTROL, 4, "colour" },
411 { SYM_CONTROL, 5, "restrictions" },
412 #define PCHAR_PARSE
413 #include "defsym.h"
414 #undef PCHAR_PARSE
415 #define OBJCLASS_PARSE
416 #include "defsym.h"
417 #undef OBJCLASS_PARSE
418 #define MONSYMS_PARSE
419 #include "defsym.h"
420 #undef MONSYMS_PARSE
421 { SYM_OTH, SYM_NOTHING + SYM_OFF_X, "S_nothing" },
422 { SYM_OTH, SYM_UNEXPLORED + SYM_OFF_X, "S_unexplored" },
423 { SYM_OTH, SYM_BOULDER + SYM_OFF_X, "S_boulder" },
424 { SYM_OTH, SYM_INVISIBLE + SYM_OFF_X, "S_invisible" },
425 { SYM_OTH, SYM_PET_OVERRIDE + SYM_OFF_X, "S_pet_override" },
426 { SYM_OTH, SYM_HERO_OVERRIDE + SYM_OFF_X, "S_hero_override" },
427 { SYM_INVALID, 0, (const char *) 0 } /* fence post */
430 boolean
431 proc_symset_line(char *buf)
433 return !((boolean) parse_sym_line(buf, gs.symset_which_set));
436 /* returns 0 on error */
438 parse_sym_line(char *buf, int which_set)
440 int val, i;
441 const struct symparse *symp = (struct symparse *) 0;
442 char *bufp, *commentp, *altp;
443 int glyph = NO_GLYPH;
444 boolean enhanced_unavailable = FALSE, is_glyph = FALSE;
446 if (strlen(buf) >= BUFSZ)
447 buf[BUFSZ - 1] = '\0';
448 /* convert each instance of whitespace (tabs, consecutive spaces)
449 into a single space; leading and trailing spaces are stripped */
450 mungspaces(buf);
452 /* remove trailing comment, if any (this isn't strictly needed for
453 individual symbols, and it won't matter if "X#comment" without
454 separating space slips through; for handling or set description,
455 symbol set creator is responsible for preceding '#' with a space
456 and that comment itself doesn't contain " #") */
457 if ((commentp = strrchr(buf, '#')) != 0 && commentp[-1] == ' ')
458 commentp[-1] = '\0';
460 /* find the '=' or ':' */
461 bufp = strchr(buf, '=');
462 altp = strchr(buf, ':');
464 if (!bufp || (altp && altp < bufp))
465 bufp = altp;
467 if (!bufp) {
468 if (strncmpi(buf, "finish", 6) == 0) {
469 /* end current graphics set */
470 if (gc.chosen_symset_start)
471 gc.chosen_symset_end = TRUE;
472 gc.chosen_symset_start = FALSE;
473 return 1;
475 config_error_add("No \"finish\"");
476 return 0;
478 /* skip '=' and space which follows, if any */
479 ++bufp;
480 if (*bufp == ' ')
481 ++bufp;
483 symp = match_sym(buf);
484 if (!symp && buf[0] == 'G' && buf[1] == '_') {
485 if (gc.chosen_symset_start) {
486 is_glyph = match_glyph(buf);
487 } else {
488 is_glyph = TRUE; /* report error only once */
490 #ifdef ENHANCED_SYMBOLS
491 enhanced_unavailable = FALSE;
492 #else
493 enhanced_unavailable = TRUE;
494 #endif
496 if (!symp && !is_glyph && !enhanced_unavailable) {
497 config_error_add("Unknown sym keyword");
498 return 0;
500 if (symp) {
501 if (!gs.symset[which_set].name) {
502 /* A null symset name indicates that we're just
503 building a pick-list of possible symset
504 values from the file, so only do that */
505 if (symp->range == SYM_CONTROL) {
506 struct symsetentry *tmpsp, *lastsp;
508 for (lastsp = gs.symset_list; lastsp; lastsp = lastsp->next)
509 if (!lastsp->next)
510 break;
511 switch (symp->idx) {
512 case 0:
513 tmpsp = (struct symsetentry *) alloc(sizeof *tmpsp);
514 tmpsp->next = (struct symsetentry *) 0;
515 if (!lastsp)
516 gs.symset_list = tmpsp;
517 else
518 lastsp->next = tmpsp;
519 tmpsp->idx = gs.symset_count++;
520 tmpsp->name = dupstr(bufp);
521 tmpsp->desc = (char *) 0;
522 tmpsp->handling = H_UNK;
523 /* initialize restriction bits */
524 tmpsp->nocolor = 0;
525 tmpsp->primary = 0;
526 tmpsp->rogue = 0;
527 break;
528 case 2:
529 /* handler type identified */
530 tmpsp = lastsp; /* most recent symset */
531 for (i = 0; known_handling[i]; ++i)
532 if (!strcmpi(known_handling[i], bufp)) {
533 if (tmpsp)
534 tmpsp->handling = i;
535 break; /* for loop */
537 break;
538 case 3:
539 /* description:something */
540 tmpsp = lastsp; /* most recent symset */
541 if (tmpsp && !tmpsp->desc)
542 tmpsp->desc = dupstr(bufp);
543 break;
544 case 5:
545 /* restrictions: xxxx*/
546 tmpsp = lastsp; /* most recent symset */
547 for (i = 0; known_restrictions[i]; ++i) {
548 if (!strcmpi(known_restrictions[i], bufp)) {
549 if (tmpsp) {
550 switch (i) {
551 case 0:
552 tmpsp->primary = 1;
553 break;
554 case 1:
555 tmpsp->rogue = 1;
556 break;
559 break; /* while loop */
562 break;
565 return 1;
567 if (symp->range && symp->range == SYM_CONTROL) {
568 switch (symp->idx) {
569 case 0:
570 /* start of symset */
571 if (!strcmpi(bufp, gs.symset[which_set].name)) {
572 /* matches desired one */
573 gc.chosen_symset_start = TRUE;
574 /* these init_*() functions clear symset fields too */
575 if (which_set == ROGUESET)
576 init_rogue_symbols();
577 else if (which_set == PRIMARYSET)
578 init_primary_symbols();
580 break;
581 case 1:
582 /* finish symset */
583 if (gc.chosen_symset_start)
584 gc.chosen_symset_end = TRUE;
585 gc.chosen_symset_start = FALSE;
586 break;
587 case 2:
588 /* handler type identified */
589 if (gc.chosen_symset_start)
590 set_symhandling(bufp, which_set);
591 break;
592 /* case 3: (description) is ignored here */
593 case 4: /* color:off */
594 if (gc.chosen_symset_start) {
595 if (bufp) {
596 if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes")
597 || !strcmpi(bufp, "on"))
598 gs.symset[which_set].nocolor = 0;
599 else if (!strcmpi(bufp, "false")
600 || !strcmpi(bufp, "no")
601 || !strcmpi(bufp, "off"))
602 gs.symset[which_set].nocolor = 1;
605 break;
606 case 5: /* restrictions: xxxx*/
607 if (gc.chosen_symset_start) {
608 int n = 0;
610 while (known_restrictions[n]) {
611 if (!strcmpi(known_restrictions[n], bufp)) {
612 switch (n) {
613 case 0:
614 gs.symset[which_set].primary = 1;
615 break;
616 case 1:
617 gs.symset[which_set].rogue = 1;
618 break;
620 break; /* while loop */
622 n++;
625 break;
627 } else {
628 /* Not SYM_CONTROL */
629 if (gs.symset[which_set].handling != H_UTF8) {
630 if (gc.chosen_symset_start) {
631 val = sym_val(bufp);
632 if (which_set == PRIMARYSET) {
633 update_primary_symset(symp, val);
634 } else if (which_set == ROGUESET) {
635 update_rogue_symset(symp, val);
638 #ifdef ENHANCED_SYMBOLS
639 } else {
640 if (gc.chosen_symset_start) {
641 glyphrep_to_custom_map_entries(buf, &glyph);
643 #endif
646 } else if (gc.chosen_symset_start) {
647 /* glyph, not symbol */
648 glyphrep_to_custom_map_entries(buf, &glyph);
650 #ifndef ENHANCED_SYMBOLS
651 nhUse(glyph);
652 #endif
653 return 1;
656 void
657 set_symhandling(char *handling, int which_set)
659 int i = 0;
661 gs.symset[which_set].handling = H_UNK;
662 while (known_handling[i]) {
663 if (!strcmpi(known_handling[i], handling)) {
664 gs.symset[which_set].handling = i;
665 return;
667 i++;
671 /* bundle some common usage into one easy-to-use routine */
673 load_symset(const char *s, int which_set)
675 clear_symsetentry(which_set, TRUE);
677 if (gs.symset[which_set].name)
678 free((genericptr_t) gs.symset[which_set].name);
679 gs.symset[which_set].name = dupstr(s);
681 if (read_sym_file(which_set)) {
682 switch_symbols(TRUE);
683 apply_customizations(gc.currentgraphics,
684 do_custom_symbols | do_custom_colors);
685 } else {
686 clear_symsetentry(which_set, TRUE);
687 return 0;
689 return 1;
692 void
693 free_symsets(void)
695 clear_symsetentry(PRIMARYSET, TRUE);
696 clear_symsetentry(ROGUESET, TRUE);
698 /* symset_list is cleaned up as soon as it's used, so we shouldn't
699 have to anything about it here */
700 /* assert( symset_list == NULL ); */
703 struct _savedsym {
704 char *name;
705 char *val;
706 int which_set;
707 struct _savedsym *next;
709 struct _savedsym *saved_symbols = NULL;
711 void
712 savedsym_free(void)
714 struct _savedsym *tmp = saved_symbols, *tmp2;
716 while (tmp) {
717 tmp2 = tmp->next;
718 free(tmp->name);
719 free(tmp->val);
720 free(tmp);
721 tmp = tmp2;
725 staticfn struct _savedsym *
726 savedsym_find(const char *name, int which_set)
728 struct _savedsym *tmp = saved_symbols;
730 while (tmp) {
731 if (which_set == tmp->which_set && !strcmp(name, tmp->name))
732 return tmp;
733 tmp = tmp->next;
735 return NULL;
738 staticfn void
739 savedsym_add(const char *name, const char *val, int which_set)
741 struct _savedsym *tmp = NULL;
743 if ((tmp = savedsym_find(name, which_set)) != 0) {
744 free(tmp->val);
745 tmp->val = dupstr(val);
746 } else {
747 tmp = (struct _savedsym *) alloc(sizeof *tmp);
748 tmp->name = dupstr(name);
749 tmp->val = dupstr(val);
750 tmp->which_set = which_set;
751 tmp->next = saved_symbols;
752 saved_symbols = tmp;
756 void
757 savedsym_strbuf(strbuf_t *sbuf)
759 struct _savedsym *tmp = saved_symbols;
760 char buf[BUFSZ];
762 while (tmp) {
763 Sprintf(buf, "%sSYMBOLS=%s:%s\n",
764 (tmp->which_set == ROGUESET) ? "ROGUE" : "",
765 tmp->name, tmp->val);
766 strbuf_append(sbuf, buf);
767 tmp = tmp->next;
771 /* Parse the value of a SYMBOLS line from a config file */
772 boolean
773 parsesymbols(char *opts, int which_set)
775 int val;
776 char *symname, *strval, *ch,
777 *first_unquoted_comma = 0, *first_unquoted_colon = 0;
778 const struct symparse *symp;
779 boolean is_glyph = FALSE;
781 /* are there any commas or colons that aren't quoted? */
782 for (ch = opts + 1; *ch; ++ch) {
783 char *prech, *postch;
785 prech = ch - 1;
786 postch = ch + 1;
787 if (!*postch)
788 break;
789 if (*ch == ',') {
790 if (*prech == '\'' && *postch == '\'')
791 continue;
792 if (*prech == '\\')
793 continue;
795 if (*ch == ':') {
796 if (*prech == '\'' && *postch == '\'')
797 continue;
799 if (*ch == ',' && !first_unquoted_comma)
800 first_unquoted_comma = ch;
801 if (*ch == ':' && !first_unquoted_colon)
802 first_unquoted_colon = ch;
804 if (first_unquoted_comma != 0) {
805 *first_unquoted_comma++ = '\0';
806 if (!parsesymbols(first_unquoted_comma, which_set))
807 return FALSE;
810 /* S_sample:string */
811 symname = opts;
812 strval = first_unquoted_colon;
813 if (!strval)
814 strval = strchr(opts, '=');
815 if (!strval)
816 return FALSE;
817 *strval++ = '\0';
819 /* strip leading and trailing white space from symname and strval */
820 mungspaces(symname);
821 mungspaces(strval);
823 symp = match_sym(symname);
824 if (!symp && symname[0] == 'G' && symname[1] == '_') {
825 is_glyph = match_glyph(symname);
827 if (!symp && !is_glyph)
828 return FALSE;
829 if (symp) {
830 if (symp->range && symp->range != SYM_CONTROL) {
831 if (gs.symset[which_set].handling == H_UTF8
832 || (lowc(strval[0]) == 'u' && strval[1] == '+')) {
833 char buf[BUFSZ];
834 int glyph;
836 Snprintf(buf, sizeof buf, "%s:%s", opts, strval);
837 glyphrep_to_custom_map_entries(buf, &glyph);
838 } else {
839 val = sym_val(strval);
840 if (which_set == ROGUESET)
841 update_ov_rogue_symset(symp, val);
842 else
843 update_ov_primary_symset(symp, val);
847 savedsym_add(opts, strval, which_set);
848 return TRUE;
851 const struct symparse *
852 match_sym(char *buf)
854 static struct alternate_parse {
855 const char *altnm;
856 const char *nm;
857 } alternates[] = {
858 { "S_armour", "S_armor" },
859 /* alt explosion names are numbered in phone key/button layout */
860 { "S_explode1", "S_expl_tl" },
861 { "S_explode2", "S_expl_tc" }, { "S_explode3", "S_expl_tr" },
862 { "S_explode4", "S_expl_ml" }, { "S_explode5", "S_expl_mc" },
863 { "S_explode6", "S_expl_mr" }, { "S_explode7", "S_expl_bl" },
864 { "S_explode8", "S_expl_bc" }, { "S_explode9", "S_expl_br" },
866 int i;
867 size_t len = strlen(buf);
868 const char *p = strchr(buf, ':'), *q = strchr(buf, '=');
869 const struct symparse *sp = loadsyms;
871 /* G_ lines will never match here */
872 if ((buf[0] == 'G' || buf[0] == 'g') && buf[1] == '_')
873 return (struct symparse *) 0;
875 if (!p || (q && q < p))
876 p = q;
877 if (p) {
878 /* note: there will be at most one space before the '='
879 because caller has condensed buf[] with mungspaces() */
880 if (p > buf && p[-1] == ' ')
881 p--;
882 len = (int) (p - buf);
884 while (sp->range) {
885 if ((len >= strlen(sp->name)) && !strncmpi(buf, sp->name, len))
886 return sp;
887 sp++;
889 for (i = 0; i < SIZE(alternates); ++i) {
890 if ((len >= strlen(alternates[i].altnm))
891 && !strncmpi(buf, alternates[i].altnm, len)) {
892 sp = loadsyms;
893 while (sp->range) {
894 if (!strcmp(alternates[i].nm, sp->name))
895 return sp;
896 sp++;
900 return (struct symparse *) 0;
903 DISABLE_WARNING_FORMAT_NONLITERAL
906 * this is called from options.c to do the symset work.
909 do_symset(boolean rogueflag)
911 winid tmpwin;
912 anything any;
913 int n;
914 char buf[BUFSZ];
915 menu_item *symset_pick = (menu_item *) 0;
916 boolean ready_to_switch = FALSE,
917 nothing_to_do = FALSE;
918 char *symset_name, fmtstr[20];
919 struct symsetentry *sl;
920 int res, which_set, setcount = 0, chosen = -2, defindx = 0;
921 int clr = NO_COLOR;
923 which_set = rogueflag ? ROGUESET : PRIMARYSET;
924 gs.symset_list = (struct symsetentry *) 0;
925 /* clear symset[].name as a flag to read_sym_file() to build list */
926 symset_name = gs.symset[which_set].name;
927 gs.symset[which_set].name = (char *) 0;
929 res = read_sym_file(which_set);
930 /* put symset name back */
931 gs.symset[which_set].name = symset_name;
933 if (res && gs.symset_list) {
934 int thissize,
935 biggest = (int) (sizeof "Default Symbols" - sizeof ""),
936 big_desc = 0;
938 for (sl = gs.symset_list; sl; sl = sl->next) {
939 /* check restrictions */
940 if (rogueflag ? sl->primary : sl->rogue)
941 continue;
942 #ifndef MAC_GRAPHICS_ENV
943 if (sl->handling == H_MAC)
944 continue;
945 #endif
946 #ifndef ENHANCED_SYMBOLS
947 if (sl->handling == H_UTF8)
948 continue;
949 #endif
950 setcount++;
951 /* find biggest name */
952 thissize = sl->name ? (int) strlen(sl->name) : 0;
953 if (thissize > biggest)
954 biggest = thissize;
955 thissize = sl->desc ? (int) strlen(sl->desc) : 0;
956 if (thissize > big_desc)
957 big_desc = thissize;
959 if (!setcount) {
960 There("are no appropriate %s symbol sets available.",
961 rogueflag ? "rogue level" : "primary");
962 return TRUE;
965 Sprintf(fmtstr, "%%-%ds %%s", biggest + 2);
966 tmpwin = create_nhwindow(NHW_MENU);
967 start_menu(tmpwin, MENU_BEHAVE_STANDARD);
968 any = cg.zeroany;
969 any.a_int = 1; /* -1 + 2 [see 'if (sl->name) {' below]*/
970 if (!symset_name)
971 defindx = any.a_int;
972 add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
973 clr, "Default Symbols",
974 (any.a_int == defindx) ? MENU_ITEMFLAGS_SELECTED
975 : MENU_ITEMFLAGS_NONE);
977 for (sl = gs.symset_list; sl; sl = sl->next) {
978 /* check restrictions */
979 if (rogueflag ? sl->primary : sl->rogue)
980 continue;
981 #ifndef MAC_GRAPHICS_ENV
982 if (sl->handling == H_MAC)
983 continue;
984 #endif
985 #ifndef ENHANCED_SYMBOLS
986 if (sl->handling == H_UTF8)
987 continue;
988 #endif
989 if (sl->name) {
990 /* +2: sl->idx runs from 0 to N-1 for N symsets;
991 +1 because Defaults are implicitly in slot [0];
992 +1 again so that valid data is never 0 */
993 any.a_int = sl->idx + 2;
994 if (symset_name && !strcmpi(sl->name, symset_name))
995 defindx = any.a_int;
996 Sprintf(buf, fmtstr, sl->name, sl->desc ? sl->desc : "");
997 add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0,
998 ATR_NONE, clr, buf,
999 (any.a_int == defindx) ? MENU_ITEMFLAGS_SELECTED
1000 : MENU_ITEMFLAGS_NONE);
1003 Sprintf(buf, "Select %ssymbol set:",
1004 rogueflag ? "rogue level " : "");
1005 end_menu(tmpwin, buf);
1006 n = select_menu(tmpwin, PICK_ONE, &symset_pick);
1007 if (n > 0) {
1008 chosen = symset_pick[0].item.a_int;
1009 /* if picking non-preselected entry yields 2, make sure
1010 that we're going with the non-preselected one */
1011 if (n == 2 && chosen == defindx)
1012 chosen = symset_pick[1].item.a_int;
1013 chosen -= 2; /* convert menu index to symset index;
1014 * "Default symbols" have index -1 */
1015 free((genericptr_t) symset_pick);
1016 } else if (n == 0 && defindx > 0) {
1017 chosen = defindx - 2;
1019 destroy_nhwindow(tmpwin);
1021 if (chosen > -1) {
1022 /* chose an actual symset name from file */
1023 for (sl = gs.symset_list; sl; sl = sl->next)
1024 if (sl->idx == chosen)
1025 break;
1026 if (sl) {
1027 /* free the now stale attributes */
1028 clear_symsetentry(which_set, TRUE);
1030 /* transfer only the name of the symbol set */
1031 gs.symset[which_set].name = dupstr(sl->name);
1032 ready_to_switch = TRUE;
1034 } else if (chosen == -1) {
1035 /* explicit selection of defaults */
1036 /* free the now stale symset attributes */
1037 clear_symsetentry(which_set, TRUE);
1038 } else
1039 nothing_to_do = TRUE;
1040 } else if (!res) {
1041 /* The symbols file could not be accessed */
1042 pline("Unable to access \"%s\" file.", SYMBOLS);
1043 return TRUE;
1044 } else if (!gs.symset_list) {
1045 /* The symbols file was empty */
1046 There("were no symbol sets found in \"%s\".", SYMBOLS);
1047 return TRUE;
1050 /* clean up */
1051 while ((sl = gs.symset_list) != 0) {
1052 gs.symset_list = sl->next;
1053 if (sl->name)
1054 free((genericptr_t) sl->name), sl->name = (char *) 0;
1055 if (sl->desc)
1056 free((genericptr_t) sl->desc), sl->desc = (char *) 0;
1057 free((genericptr_t) sl);
1060 if (nothing_to_do)
1061 return TRUE;
1063 /* Set default symbols and clear the handling value */
1064 if (rogueflag)
1065 init_rogue_symbols();
1066 else
1067 init_primary_symbols();
1069 if (gs.symset[which_set].name) {
1070 /* non-default symbols */
1071 int ok;
1072 if (!glyphid_cache_status()) {
1073 fill_glyphid_cache();
1075 ok = read_sym_file(which_set);
1076 if (glyphid_cache_status()) {
1077 free_glyphid_cache();
1079 if (ok) {
1080 ready_to_switch = TRUE;
1081 } else {
1082 clear_symsetentry(which_set, TRUE);
1083 return TRUE;
1087 if (ready_to_switch)
1088 switch_symbols(TRUE);
1090 if (Is_rogue_level(&u.uz)) {
1091 if (rogueflag)
1092 assign_graphics(ROGUESET);
1093 } else if (!rogueflag)
1094 assign_graphics(PRIMARYSET);
1095 apply_customizations(rogueflag ? ROGUESET : PRIMARYSET,
1096 (do_custom_symbols | do_custom_colors));
1097 preference_update("symset");
1098 return TRUE;
1101 RESTORE_WARNING_FORMAT_NONLITERAL
1103 /*symbols.c*/