1 /* NetHack 3.6	options.c	$NHDT-Date: 1578996303 2020/01/14 10:05:03 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.396 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Michael Allison, 2008. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #ifdef OPTION_LISTS_ONLY /* (AMIGA) external program for opt lists */
7 #include "config.h"
8 #include "objclass.h"
9 #include "flag.h"
10 NEARDATA struct flag flags; /* provide linkage */
11 #ifdef SYSFLAGS
12 NEARDATA struct sysflag sysflags; /* provide linkage */
13 #endif
14 NEARDATA struct instance_flags iflags; /* provide linkage */
15 #define static
16 #else
17 #include "hack.h"
18 #include "tcap.h"
19 #include <ctype.h>
20 #endif
21 
22 #define BACKWARD_COMPAT
23 
24 #ifdef DEFAULT_WC_TILED_MAP
25 #define PREFER_TILED TRUE
26 #else
27 #define PREFER_TILED FALSE
28 #endif
29 
30 #ifdef CURSES_GRAPHICS
31 extern int curses_read_attrs(const char *attrs);
32 extern char *curses_fmt_attrs(char *);
33 #endif
34 
35 enum window_option_types {
36     MESSAGE_OPTION = 1,
37     STATUS_OPTION,
38     MAP_OPTION,
39     MENU_OPTION,
40     TEXT_OPTION
41 };
42 
43 #define PILE_LIMIT_DFLT 5
44 
45 static char empty_optstr[] = { '\0' };
46 
47 /*
48  *  NOTE:  If you add (or delete) an option, please update the short
49  *  options help (option_help()), the long options help (dat/opthelp),
50  *  and the current options setting display function (doset()),
51  *  and also the Guidebooks.
52  *
53  *  The order matters.  If an option is a an initial substring of another
54  *  option (e.g. time and timed_delay) the shorter one must come first.
55  */
56 
57 static struct Bool_Opt {
58     const char *name;
59     boolean *addr, initvalue;
60     int optflags;
61 } boolopt[] = {
62     { "acoustics", &flags.acoustics, TRUE, SET_IN_GAME },
63 #if defined(SYSFLAGS) && defined(AMIGA)
64     /* Amiga altmeta causes Alt+key to be converted into Meta+key by
65        low level nethack code; on by default, can be toggled off if
66        Alt+key is needed for some ASCII chars on non-ASCII keyboard */
67     { "altmeta", &sysflags.altmeta, TRUE, DISP_IN_GAME },
68 #else
69 #ifdef ALTMETA
70     /* non-Amiga altmeta causes nethack's top level command loop to treat
71        two character sequence "ESC c" as M-c, for terminals or emulators
72        which send "ESC c" when Alt+c is pressed; off by default, enabling
73        this can potentially make trouble if user types ESC when nethack
74        is honoring this conversion request (primarily after starting a
75        count prefix prior to a command and then deciding to cancel it) */
76     { "altmeta", &iflags.altmeta, FALSE, SET_IN_GAME },
77 #else
78     { "altmeta", (boolean *) 0, TRUE, DISP_IN_GAME },
79 #endif
80 #endif
81     { "ascii_map", &iflags.wc_ascii_map, !PREFER_TILED, SET_IN_GAME }, /*WC*/
82 #if defined(SYSFLAGS) && defined(MFLOPPY)
83     { "asksavedisk", &sysflags.asksavedisk, FALSE, SET_IN_GAME },
84 #else
85     { "asksavedisk", (boolean *) 0, FALSE, SET_IN_FILE },
86 #endif
87     { "autodescribe", &iflags.autodescribe, TRUE, SET_IN_GAME },
88     { "autodig", &flags.autodig, FALSE, SET_IN_GAME },
89     { "autoopen", &flags.autoopen, TRUE, SET_IN_GAME },
90     { "autopickup", &flags.pickup, TRUE, SET_IN_GAME },
91     { "autoquiver", &flags.autoquiver, FALSE, SET_IN_GAME },
92 #if defined(MICRO) && !defined(AMIGA)
93     { "BIOS", &iflags.BIOS, FALSE, SET_IN_FILE },
94 #else
95     { "BIOS", (boolean *) 0, FALSE, SET_IN_FILE },
96 #endif
97     { "blind", &u.uroleplay.blind, FALSE, DISP_IN_GAME },
98     { "bones", &flags.bones, TRUE, SET_IN_FILE },
99 #ifdef INSURANCE
100     { "checkpoint", &flags.ins_chkpt, TRUE, SET_IN_GAME },
101 #else
102     { "checkpoint", (boolean *) 0, FALSE, SET_IN_FILE },
103 #endif
104 #ifdef MFLOPPY
105     { "checkspace", &iflags.checkspace, TRUE, SET_IN_GAME },
106 #else
107     { "checkspace", (boolean *) 0, FALSE, SET_IN_FILE },
108 #endif
109     { "clicklook", &iflags.clicklook, FALSE, SET_IN_GAME },
110     { "cmdassist", &iflags.cmdassist, TRUE, SET_IN_GAME },
111 #if defined(MICRO) || defined(WIN32) || defined(CURSES_GRAPHICS)
112     { "color", &iflags.wc_color, TRUE, SET_IN_GAME }, /* on/off: use WC or not */
113 #else /* systems that support multiple terminals, many monochrome */
114     { "color", &iflags.wc_color, FALSE, SET_IN_GAME },
115 #endif
116     { "confirm", &flags.confirm, TRUE, SET_IN_GAME },
117     { "dark_room", &flags.dark_room, TRUE, SET_IN_GAME },
118     { "eight_bit_tty", &iflags.wc_eight_bit_input, FALSE, SET_IN_GAME }, /*WC*/
119 #if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS) || defined(X11_GRAPHICS)
120     { "extmenu", &iflags.extmenu, FALSE, SET_IN_GAME },
121 #else
122     { "extmenu", (boolean *) 0, FALSE, SET_IN_FILE },
123 #endif
124 #ifdef OPT_DISPMAP
125     { "fast_map", &flags.fast_map, TRUE, SET_IN_GAME },
126 #else
127     { "fast_map", (boolean *) 0, TRUE, SET_IN_FILE },
128 #endif
129     { "female", &flags.female, FALSE, DISP_IN_GAME },
130     { "fixinv", &flags.invlet_constant, TRUE, SET_IN_GAME },
131 #if defined(SYSFLAGS) && defined(AMIFLUSH)
132     { "flush", &sysflags.amiflush, FALSE, SET_IN_GAME },
133 #else
134     { "flush", (boolean *) 0, FALSE, SET_IN_FILE },
135 #endif
136     { "force_invmenu", &iflags.force_invmenu, FALSE, SET_IN_GAME },
137     { "fullscreen", &iflags.wc2_fullscreen, FALSE, SET_IN_FILE }, /*WC2*/
138     { "goldX", &iflags.goldX, FALSE, SET_IN_GAME },
139     { "guicolor", &iflags.wc2_guicolor, TRUE, SET_IN_GAME}, /*WC2*/
140     { "help", &flags.help, TRUE, SET_IN_GAME },
141     { "herecmd_menu", &iflags.herecmd_menu, FALSE, SET_IN_GAME },
142     { "hilite_pet", &iflags.wc_hilite_pet, FALSE, SET_IN_GAME }, /*WC*/
143     { "hilite_pile", &iflags.hilite_pile, FALSE, SET_IN_GAME },
144     { "hitpointbar", &iflags.wc2_hitpointbar, FALSE, SET_IN_GAME }, /*WC2*/
145 #ifndef MAC
146     { "ignintr", &flags.ignintr, FALSE, SET_IN_GAME },
147 #else
148     { "ignintr", (boolean *) 0, FALSE, SET_IN_FILE },
149 #endif
150     { "implicit_uncursed", &iflags.implicit_uncursed, TRUE, SET_IN_GAME },
151     { "large_font", &iflags.obsolete, FALSE, SET_IN_FILE }, /* OBSOLETE */
152     { "legacy", &flags.legacy, TRUE, DISP_IN_GAME },
153     { "lit_corridor", &flags.lit_corridor, FALSE, SET_IN_GAME },
154     { "lootabc", &flags.lootabc, FALSE, SET_IN_GAME },
155 #ifdef MAIL
156     { "mail", &flags.biff, TRUE, SET_IN_GAME },
157 #else
158     { "mail", (boolean *) 0, TRUE, SET_IN_FILE },
159 #endif
160     { "mention_walls", &iflags.mention_walls, FALSE, SET_IN_GAME },
161     { "menucolors", &iflags.use_menu_color, FALSE, SET_IN_GAME },
162     /* for menu debugging only*/
163     { "menu_tab_sep", &iflags.menu_tab_sep, FALSE, SET_IN_WIZGAME },
164     { "menu_objsyms", &iflags.menu_head_objsym, FALSE, SET_IN_GAME },
165 #ifdef TTY_GRAPHICS
166     { "menu_overlay", &iflags.menu_overlay, TRUE, SET_IN_GAME },
167 #else
168     { "menu_overlay", (boolean *) 0, FALSE, SET_IN_FILE },
169 #endif
170     { "monpolycontrol", &iflags.mon_polycontrol, FALSE, SET_IN_WIZGAME },
171 #ifdef NEWS
172     { "news", &iflags.news, TRUE, DISP_IN_GAME },
173 #else
174     { "news", (boolean *) 0, FALSE, SET_IN_FILE },
175 #endif
176     { "nudist", &u.uroleplay.nudist, FALSE, DISP_IN_GAME },
177     { "null", &flags.null, TRUE, SET_IN_GAME },
178 #if defined(SYSFLAGS) && defined(MAC)
179     { "page_wait", &sysflags.page_wait, TRUE, SET_IN_GAME },
180 #else
181     { "page_wait", (boolean *) 0, FALSE, SET_IN_FILE },
182 #endif
183     /* moved perm_invent from flags to iflags and out of save file in 3.6.2 */
184     { "perm_invent", &iflags.perm_invent, FALSE, SET_IN_GAME },
185     { "pickup_thrown", &flags.pickup_thrown, TRUE, SET_IN_GAME },
186     { "popup_dialog", &iflags.wc_popup_dialog, FALSE, SET_IN_GAME },   /*WC*/
187     { "preload_tiles", &iflags.wc_preload_tiles, TRUE, DISP_IN_GAME }, /*WC*/
188     { "pushweapon", &flags.pushweapon, FALSE, SET_IN_GAME },
189 #if defined(MICRO) && !defined(AMIGA)
190     { "rawio", &iflags.rawio, FALSE, DISP_IN_GAME },
191 #else
192     { "rawio", (boolean *) 0, FALSE, SET_IN_FILE },
193 #endif
194     { "rest_on_space", &flags.rest_on_space, FALSE, SET_IN_GAME },
195 #ifdef RLECOMP
196     { "rlecomp", &iflags.rlecomp,
197 #if defined(COMPRESS) || defined(ZLIB_COMP)
198       FALSE,
199 #else
200       TRUE,
201 #endif
202       DISP_IN_GAME },
203 #endif
204     { "safe_pet", &flags.safe_dog, TRUE, SET_IN_GAME },
205     { "sanity_check", &iflags.sanity_check, FALSE, SET_IN_WIZGAME },
206     { "selectsaved", &iflags.wc2_selectsaved, TRUE, DISP_IN_GAME }, /*WC*/
207     { "showexp", &flags.showexp, FALSE, SET_IN_GAME },
208     { "showrace", &flags.showrace, FALSE, SET_IN_GAME },
209 #ifdef SCORE_ON_BOTL
210     { "showscore", &flags.showscore, FALSE, SET_IN_GAME },
211 #else
212     { "showscore", (boolean *) 0, FALSE, SET_IN_FILE },
213 #endif
214     { "silent", &flags.silent, TRUE, SET_IN_GAME },
215     { "softkeyboard", &iflags.wc2_softkeyboard, FALSE, SET_IN_FILE }, /*WC2*/
216     { "sortpack", &flags.sortpack, TRUE, SET_IN_GAME },
217     { "sparkle", &flags.sparkle, TRUE, SET_IN_GAME },
218     { "splash_screen", &iflags.wc_splash_screen, TRUE, DISP_IN_GAME }, /*WC*/
219     { "standout", &flags.standout, FALSE, SET_IN_GAME },
220     { "status_updates", &iflags.status_updates, TRUE, DISP_IN_GAME },
221     { "tiled_map", &iflags.wc_tiled_map, PREFER_TILED, DISP_IN_GAME }, /*WC*/
222     { "time", &flags.time, FALSE, SET_IN_GAME },
223 #ifdef TIMED_DELAY
224     { "timed_delay", &flags.nap, TRUE, SET_IN_GAME },
225 #else
226     { "timed_delay", (boolean *) 0, FALSE, SET_IN_GAME },
227 #endif
228     { "tombstone", &flags.tombstone, TRUE, SET_IN_GAME },
229     { "toptenwin", &iflags.toptenwin, FALSE, SET_IN_GAME },
230     { "travel", &flags.travelcmd, TRUE, SET_IN_GAME },
231 #ifdef DEBUG
232     { "travel_debug", &iflags.trav_debug, FALSE, SET_IN_WIZGAME }, /*hack.c*/
233 #endif
234     { "use_darkgray", &iflags.wc2_darkgray, TRUE, SET_IN_FILE }, /*WC2*/
235 #ifdef WIN32
236     { "use_inverse", &iflags.wc_inverse, TRUE, SET_IN_GAME }, /*WC*/
237 #else
238     { "use_inverse", &iflags.wc_inverse, FALSE, SET_IN_GAME }, /*WC*/
239 #endif
240     { "verbose", &flags.verbose, TRUE, SET_IN_GAME },
241 #ifdef TTY_TILES_ESCCODES
242     { "vt_tiledata", &iflags.vt_tiledata, FALSE, SET_IN_FILE },
243 #else
244     { "vt_tiledata", (boolean *) 0, FALSE, SET_IN_FILE },
245 #endif
246     { "whatis_menu", &iflags.getloc_usemenu, FALSE, SET_IN_GAME },
247     { "whatis_moveskip", &iflags.getloc_moveskip, FALSE, SET_IN_GAME },
248     { "wizweight", &iflags.wizweight, FALSE, SET_IN_WIZGAME },
249     { "wraptext", &iflags.wc2_wraptext, FALSE, SET_IN_GAME }, /*WC2*/
250 #ifdef ZEROCOMP
251     { "zerocomp", &iflags.zerocomp,
252 #if defined(COMPRESS) || defined(ZLIB_COMP)
253       FALSE,
254 #else
255       TRUE,
256 #endif
257       DISP_IN_GAME },
258 #endif
259     { (char *) 0, (boolean *) 0, FALSE, 0 }
260 };
261 
262 /* compound options, for option_help() and external programs like Amiga
263  * frontend */
264 static struct Comp_Opt {
265     const char *name, *descr;
266     int size; /* for frontends and such allocating space --
267                * usually allowed size of data in game, but
268                * occasionally maximum reasonable size for
269                * typing when game maintains information in
270                * a different format */
271     int optflags;
272 } compopt[] = {
273     { "align", "your starting alignment (lawful, neutral, or chaotic)", 8,
274       DISP_IN_GAME },
275     { "align_message", "message window alignment", 20, DISP_IN_GAME }, /*WC*/
276     { "align_status", "status window alignment", 20, DISP_IN_GAME },   /*WC*/
277     { "altkeyhandler", "alternate key handler", 20, SET_IN_GAME },
278 #ifdef BACKWARD_COMPAT
279     { "boulder", "deprecated (use S_boulder in sym file instead)", 1,
280       SET_IN_GAME },
281 #endif
282     { "catname", "the name of your (first) cat (e.g., catname:Tabby)",
283       PL_PSIZ, DISP_IN_GAME },
284     { "disclose", "the kinds of information to disclose at end of game",
285       sizeof flags.end_disclose * 2, SET_IN_GAME },
286     { "dogname", "the name of your (first) dog (e.g., dogname:Fang)", PL_PSIZ,
287       DISP_IN_GAME },
288     { "dungeon", "the symbols to use in drawing the dungeon map",
289       MAXDCHARS + 1, SET_IN_FILE },
290     { "effects", "the symbols to use in drawing special effects",
291       MAXECHARS + 1, SET_IN_FILE },
292     { "font_map", "the font to use in the map window", 40,
293       DISP_IN_GAME },                                              /*WC*/
294     { "font_menu", "the font to use in menus", 40, DISP_IN_GAME }, /*WC*/
295     { "font_message", "the font to use in the message window", 40,
296       DISP_IN_GAME },                                                  /*WC*/
297     { "font_size_map", "the size of the map font", 20, DISP_IN_GAME }, /*WC*/
298     { "font_size_menu", "the size of the menu font", 20,
299       DISP_IN_GAME }, /*WC*/
300     { "font_size_message", "the size of the message font", 20,
301       DISP_IN_GAME }, /*WC*/
302     { "font_size_status", "the size of the status font", 20,
303       DISP_IN_GAME }, /*WC*/
304     { "font_size_text", "the size of the text font", 20,
305       DISP_IN_GAME }, /*WC*/
306     { "font_status", "the font to use in status window", 40,
307       DISP_IN_GAME }, /*WC*/
308     { "font_text", "the font to use in text windows", 40,
309       DISP_IN_GAME }, /*WC*/
310     { "fruit", "the name of a fruit you enjoy eating", PL_FSIZ, SET_IN_GAME },
311     { "gender", "your starting gender (male or female)", 8, DISP_IN_GAME },
312     { "horsename", "the name of your (first) horse (e.g., horsename:Silver)",
313       PL_PSIZ, DISP_IN_GAME },
314     { "map_mode", "map display mode under Windows", 20, DISP_IN_GAME }, /*WC*/
315     { "menustyle", "user interface for object selection", MENUTYPELEN,
316       SET_IN_GAME },
317     { "menu_deselect_all", "deselect all items in a menu", 4, SET_IN_FILE },
318     { "menu_deselect_page", "deselect all items on this page of a menu", 4,
319       SET_IN_FILE },
320     { "menu_first_page", "jump to the first page in a menu", 4, SET_IN_FILE },
321     { "menu_headings", "text attribute for menu headings", 9, SET_IN_GAME },
322     { "menu_invert_all", "invert all items in a menu", 4, SET_IN_FILE },
323     { "menu_invert_page", "invert all items on this page of a menu", 4,
324       SET_IN_FILE },
325     { "menu_last_page", "jump to the last page in a menu", 4, SET_IN_FILE },
326     { "menu_next_page", "goto the next menu page", 4, SET_IN_FILE },
327     { "menu_previous_page", "goto the previous menu page", 4, SET_IN_FILE },
328     { "menu_search", "search for a menu item", 4, SET_IN_FILE },
329     { "menu_select_all", "select all items in a menu", 4, SET_IN_FILE },
330     { "menu_select_page", "select all items on this page of a menu", 4,
331       SET_IN_FILE },
332     { "monsters", "the symbols to use for monsters", MAXMCLASSES,
333       SET_IN_FILE },
334     { "msghistory", "number of top line messages to save", 5, DISP_IN_GAME },
335 #if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS)
336     { "msg_window", "the type of message window required", 1, SET_IN_GAME },
337 #else
338     { "msg_window", "the type of message window required", 1, SET_IN_FILE },
339 #endif
340     { "name", "your character's name (e.g., name:Merlin-W)", PL_NSIZ,
341       DISP_IN_GAME },
342     { "mouse_support", "game receives click info from mouse", 0, SET_IN_GAME },
343     { "number_pad", "use the number pad for movement", 1, SET_IN_GAME },
344     { "objects", "the symbols to use for objects", MAXOCLASSES, SET_IN_FILE },
345     { "packorder", "the inventory order of the items in your pack",
346       MAXOCLASSES, SET_IN_GAME },
347 #ifdef CHANGE_COLOR
348     { "palette",
349 #ifndef WIN32
350       "palette (00c/880/-fff is blue/yellow/reverse white)", 15, SET_IN_GAME
351 #else
352       "palette (adjust an RGB color in palette (color-R-G-B)", 15, SET_IN_FILE
353 #endif
354     },
355 #if defined(MAC)
356     { "hicolor", "same as palette, only order is reversed", 15, SET_IN_FILE },
357 #endif
358 #endif
359     { "paranoid_confirmation", "extra prompting in certain situations", 28,
360       SET_IN_GAME },
361     { "petattr",  "attributes for highlighting pets", 88, SET_IN_GAME },
362     { "pettype", "your preferred initial pet type", 4, DISP_IN_GAME },
363     { "pickup_burden", "maximum burden picked up before prompt", 20,
364       SET_IN_GAME },
365     { "pickup_types", "types of objects to pick up automatically",
366       MAXOCLASSES, SET_IN_GAME },
367     { "pile_limit", "threshold for \"there are many objects here\"", 24,
368       SET_IN_GAME },
369     { "playmode", "normal play, non-scoring explore mode, or debug mode", 8,
370       DISP_IN_GAME },
371     { "player_selection", "choose character via dialog or prompts", 12,
372       DISP_IN_GAME },
373     { "race", "your starting race (e.g., Human, Elf)", PL_CSIZ,
374       DISP_IN_GAME },
375     { "role", "your starting role (e.g., Barbarian, Valkyrie)", PL_CSIZ,
376       DISP_IN_GAME },
377     { "runmode", "display frequency when `running' or `travelling'",
378       sizeof "teleport", SET_IN_GAME },
379     { "scores", "the parts of the score list you wish to see", 32,
380       SET_IN_GAME },
381     { "scroll_amount", "amount to scroll map when scroll_margin is reached",
382       20, DISP_IN_GAME }, /*WC*/
383     { "scroll_margin", "scroll map when this far from the edge", 20,
384       DISP_IN_GAME }, /*WC*/
385     { "sortloot", "sort object selection lists by description", 4,
386       SET_IN_GAME },
387 #ifdef MSDOS
388     { "soundcard", "type of sound card to use", 20, SET_IN_FILE },
389 #endif
390     { "statushilites",
391 #ifdef STATUS_HILITES
392       "0=no status highlighting, N=show highlights for N turns",
393       20, SET_IN_GAME
394 #else
395     "highlight control", 20, SET_IN_FILE
396 #endif
397     },
398     { "statuslines",
399 #ifdef CURSES_GRAPHICS
400       "2 or 3 lines for horizontal (bottom or top) status display",
401       20, SET_IN_GAME
402 #else
403       "2 or 3 lines for status display",
404       20, SET_IN_FILE
405 #endif
406     }, /*WC2*/
407     { "symset", "load a set of display symbols from the symbols file", 70,
408       SET_IN_GAME },
409     { "roguesymset",
410       "load a set of rogue display symbols from the symbols file", 70,
411       SET_IN_GAME },
412 #ifdef WIN32
413     { "subkeyvalue", "override keystroke value", 7, SET_IN_FILE },
414 #endif
415     { "suppress_alert", "suppress alerts about version-specific features", 8,
416       SET_IN_GAME },
417     /* term_cols,term_rows -> WC2_TERM_SIZE (6: room to format 1..32767) */
418     { "term_cols", "number of columns", 6, SET_IN_FILE }, /*WC2*/
419     { "term_rows", "number of rows", 6, SET_IN_FILE }, /*WC2*/
420     { "tile_width", "width of tiles", 20, DISP_IN_GAME },   /*WC*/
421     { "tile_height", "height of tiles", 20, DISP_IN_GAME }, /*WC*/
422     { "tile_file", "name of tile file", 70, DISP_IN_GAME }, /*WC*/
423     { "traps", "the symbols to use in drawing traps", MAXTCHARS + 1,
424       SET_IN_FILE },
425     { "vary_msgcount", "show more old messages at a time", 20,
426       DISP_IN_GAME }, /*WC*/
427 #ifdef MSDOS
428     { "video", "method of video updating", 20, SET_IN_FILE },
429 #endif
430 #ifdef VIDEOSHADES
431     { "videocolors", "color mappings for internal screen routines", 40,
432       DISP_IN_GAME },
433     { "videoshades", "gray shades to map to black/gray/white", 32,
434       DISP_IN_GAME },
435 #endif
436     { "whatis_coord", "show coordinates when auto-describing cursor position",
437       1, SET_IN_GAME },
438     { "whatis_filter",
439       "filter coordinate locations when targeting next or previous",
440       1, SET_IN_GAME },
441     { "windowborders", "0 (off), 1 (on), 2 (auto)", 9, SET_IN_GAME }, /*WC2*/
442     { "windowcolors", "the foreground/background colors of windows", /*WC*/
443       80, DISP_IN_GAME },
444     { "windowtype", "windowing system to use", WINTYPELEN, DISP_IN_GAME },
445 #ifdef WINCHAIN
446     { "windowchain", "window processor to use", WINTYPELEN, SET_IN_SYS },
447 #endif
448 #ifdef BACKWARD_COMPAT
449     { "DECgraphics", "load DECGraphics display symbols", 70, SET_IN_FILE },
450     { "IBMgraphics", "load IBMGraphics display symbols", 70, SET_IN_FILE },
451 #ifdef CURSES_GRAPHICS
452     { "cursesgraphics", "load curses display symbols", 70, SET_IN_FILE },
453 #endif
454 #ifdef MAC_GRAPHICS_ENV
455     { "Macgraphics", "load MACGraphics display symbols", 70, SET_IN_FILE },
456 #endif
457 #endif
458     { (char *) 0, (char *) 0, 0, 0 }
459 };
460 
461 #ifdef OPTION_LISTS_ONLY
462 #undef static
463 
464 #else /* use rest of file */
465 
466 extern char configfile[]; /* for messages */
467 
468 extern struct symparse loadsyms[];
469 static boolean need_redraw; /* for doset() */
470 
471 #if defined(TOS) && defined(TEXTCOLOR)
472 extern boolean colors_changed;  /* in tos.c */
473 #endif
474 
475 #ifdef VIDEOSHADES
476 extern char *shade[3];          /* in sys/msdos/video.c */
477 extern char ttycolors[CLR_MAX]; /* in sys/msdos/video.c */
478 #endif
479 
480 static char def_inv_order[MAXOCLASSES] = {
481     COIN_CLASS, AMULET_CLASS, WEAPON_CLASS, ARMOR_CLASS, FOOD_CLASS,
482     SCROLL_CLASS, SPBOOK_CLASS, POTION_CLASS, RING_CLASS, WAND_CLASS,
483     TOOL_CLASS, GEM_CLASS, ROCK_CLASS, BALL_CLASS, CHAIN_CLASS, 0,
484 };
485 
486 /*
487  * Default menu manipulation command accelerators.  These may _not_ be:
488  *
489  *      + a number - reserved for counts
490  *      + an upper or lower case US ASCII letter - used for accelerators
491  *      + ESC - reserved for escaping the menu
492  *      + NULL, CR or LF - reserved for commiting the selection(s).  NULL
493  *        is kind of odd, but the tty's xwaitforspace() will return it if
494  *        someone hits a <ret>.
495  *      + a default object class symbol - used for object class accelerators
496  *
497  * Standard letters (for now) are:
498  *
499  *              <  back 1 page
500  *              >  forward 1 page
501  *              ^  first page
502  *              |  last page
503  *              :  search
504  *
505  *              page            all
506  *               ,    select     .
507  *               \    deselect   -
508  *               ~    invert     @
509  *
510  * The command name list is duplicated in the compopt array.
511  */
512 typedef struct {
513     const char *name;
514     char cmd;
515     const char *desc;
516 } menu_cmd_t;
517 
518 static const menu_cmd_t default_menu_cmd_info[] = {
519  { "menu_first_page", MENU_FIRST_PAGE, "Go to first page" },
520  { "menu_last_page", MENU_LAST_PAGE, "Go to last page" },
521  { "menu_next_page", MENU_NEXT_PAGE, "Go to next page" },
522  { "menu_previous_page", MENU_PREVIOUS_PAGE, "Go to previous page" },
523  { "menu_select_all", MENU_SELECT_ALL, "Select all items" },
524  { "menu_deselect_all", MENU_UNSELECT_ALL, "Unselect all items" },
525  { "menu_invert_all", MENU_INVERT_ALL, "Invert selection" },
526  { "menu_select_page", MENU_SELECT_PAGE, "Select items in current page" },
527  { "menu_deselect_page", MENU_UNSELECT_PAGE,
528    "Unselect items in current page" },
529  { "menu_invert_page", MENU_INVERT_PAGE, "Invert current page selection" },
530  { "menu_search", MENU_SEARCH, "Search and toggle matching items" },
531 };
532 
533 /*
534  * Allow the user to map incoming characters to various menu commands.
535  * The accelerator list must be a valid C string.
536  */
537 #define MAX_MENU_MAPPED_CMDS 32 /* some number */
538 char mapped_menu_cmds[MAX_MENU_MAPPED_CMDS + 1]; /* exported */
539 static char mapped_menu_op[MAX_MENU_MAPPED_CMDS + 1];
540 static short n_menu_mapped = 0;
541 
542 static boolean initial, from_file;
543 
544 STATIC_DCL void FDECL(nmcpy, (char *, const char *, int));
545 STATIC_DCL void FDECL(escapes, (const char *, char *));
546 STATIC_DCL void FDECL(rejectoption, (const char *));
547 STATIC_DCL char *FDECL(string_for_opt, (char *, BOOLEAN_P));
548 STATIC_DCL char *FDECL(string_for_env_opt, (const char *, char *, BOOLEAN_P));
549 STATIC_DCL void FDECL(bad_negation, (const char *, BOOLEAN_P));
550 STATIC_DCL int FDECL(change_inv_order, (char *));
551 STATIC_DCL boolean FDECL(warning_opts, (char *, const char *));
552 STATIC_DCL int FDECL(feature_alert_opts, (char *, const char *));
553 STATIC_DCL boolean FDECL(duplicate_opt_detection, (const char *, int));
554 STATIC_DCL void FDECL(complain_about_duplicate, (const char *, int));
555 
556 STATIC_DCL const char *FDECL(attr2attrname, (int));
557 STATIC_DCL const char * FDECL(msgtype2name, (int));
558 STATIC_DCL int NDECL(query_msgtype);
559 STATIC_DCL boolean FDECL(msgtype_add, (int, char *));
560 STATIC_DCL void FDECL(free_one_msgtype, (int));
561 STATIC_DCL int NDECL(msgtype_count);
562 STATIC_DCL boolean FDECL(test_regex_pattern, (const char *, const char *));
563 STATIC_DCL boolean FDECL(add_menu_coloring_parsed, (char *, int, int));
564 STATIC_DCL void FDECL(free_one_menu_coloring, (int));
565 STATIC_DCL int NDECL(count_menucolors);
566 STATIC_DCL boolean FDECL(parse_role_opts, (BOOLEAN_P, const char *,
567                                            char *, char **));
568 
569 STATIC_DCL void FDECL(doset_add_menu, (winid, const char *, int));
570 STATIC_DCL void FDECL(opts_add_others, (winid, const char *, int,
571                                         char *, int));
572 STATIC_DCL int FDECL(handle_add_list_remove, (const char *, int));
573 STATIC_DCL boolean FDECL(special_handling, (const char *,
574                                             BOOLEAN_P, BOOLEAN_P));
575 STATIC_DCL const char *FDECL(get_compopt_value, (const char *, char *));
576 STATIC_DCL void FDECL(remove_autopickup_exception,
577                       (struct autopickup_exception *));
578 
579 STATIC_DCL boolean FDECL(is_wc_option, (const char *));
580 STATIC_DCL boolean FDECL(wc_supported, (const char *));
581 STATIC_DCL boolean FDECL(is_wc2_option, (const char *));
582 STATIC_DCL boolean FDECL(wc2_supported, (const char *));
583 STATIC_DCL void FDECL(wc_set_font_name, (int, char *));
584 STATIC_DCL int FDECL(wc_set_window_colors, (char *));
585 
586 void
reglyph_darkroom()587 reglyph_darkroom()
588 {
589     xchar x, y;
590 
591     for (x = 0; x < COLNO; x++)
592         for (y = 0; y < ROWNO; y++) {
593             struct rm *lev = &levl[x][y];
594 
595             if (!flags.dark_room || !iflags.use_color
596                 || Is_rogue_level(&u.uz)) {
597                 if (lev->glyph == cmap_to_glyph(S_darkroom))
598                     lev->glyph = lev->waslit ? cmap_to_glyph(S_room)
599                                              : cmap_to_glyph(S_stone);
600             } else {
601                 if (lev->glyph == cmap_to_glyph(S_room) && lev->seenv
602                     && lev->waslit && !cansee(x, y))
603                     lev->glyph = cmap_to_glyph(S_darkroom);
604                 else if (lev->glyph == cmap_to_glyph(S_stone)
605                          && lev->typ == ROOM && lev->seenv && !cansee(x, y))
606                     lev->glyph = cmap_to_glyph(S_darkroom);
607             }
608         }
609     if (flags.dark_room && iflags.use_color)
610         showsyms[S_darkroom] = showsyms[S_room];
611     else
612         showsyms[S_darkroom] = showsyms[S_stone];
613 }
614 
615 /* check whether a user-supplied option string is a proper leading
616    substring of a particular option name; option string might have
617    a colon or equals sign and arbitrary value appended to it */
618 boolean
match_optname(user_string,opt_name,min_length,val_allowed)619 match_optname(user_string, opt_name, min_length, val_allowed)
620 const char *user_string, *opt_name;
621 int min_length;
622 boolean val_allowed;
623 {
624     int len = (int) strlen(user_string);
625 
626     if (val_allowed) {
627         const char *p = index(user_string, ':'),
628                    *q = index(user_string, '=');
629 
630         if (!p || (q && q < p))
631             p = q;
632         if (p) {
633             /* 'user_string' hasn't necessarily been through mungspaces()
634                so might have tabs or consecutive spaces */
635             while (p > user_string && isspace((uchar) *(p - 1)))
636                 p--;
637             len = (int) (p - user_string);
638         }
639     }
640 
641     return (boolean) (len >= min_length
642                       && !strncmpi(opt_name, user_string, len));
643 }
644 
645 /* most environment variables will eventually be printed in an error
646  * message if they don't work, and most error message paths go through
647  * BUFSZ buffers, which could be overflowed by a maliciously long
648  * environment variable.  If a variable can legitimately be long, or
649  * if it's put in a smaller buffer, the responsible code will have to
650  * bounds-check itself.
651  */
652 char *
nh_getenv(ev)653 nh_getenv(ev)
654 const char *ev;
655 {
656     char *getev = getenv(ev);
657 
658     if (getev && strlen(getev) <= (BUFSZ / 2))
659         return getev;
660     else
661         return (char *) 0;
662 }
663 
664 /* process options, possibly including SYSCF */
665 void
initoptions()666 initoptions()
667 {
668     initoptions_init();
669 #ifdef SYSCF
670 /* someday there may be other SYSCF alternatives besides text file */
671 #ifdef SYSCF_FILE
672     /* If SYSCF_FILE is specified, it _must_ exist... */
673     assure_syscf_file();
674     config_error_init(TRUE, SYSCF_FILE, FALSE);
675 
676     /* ... and _must_ parse correctly. */
677     if (!read_config_file(SYSCF_FILE, SET_IN_SYS)) {
678         if (config_error_done() && !iflags.initoptions_noterminate)
679             nh_terminate(EXIT_FAILURE);
680     }
681     config_error_done();
682     /*
683      * TODO [maybe]: parse the sysopt entries which are space-separated
684      * lists of usernames into arrays with one name per element.
685      */
686 #endif
687 #endif /* SYSCF */
688     initoptions_finish();
689 }
690 
691 void
initoptions_init()692 initoptions_init()
693 {
694 #if (defined(UNIX) || defined(VMS)) && defined(TTY_GRAPHICS)
695     char *opts;
696 #endif
697     int i;
698 
699     /* set up the command parsing */
700     reset_commands(TRUE); /* init */
701 
702     /* initialize the random number generator(s) */
703     init_random(rn2);
704     init_random(rn2_on_display_rng);
705 
706     /* for detection of configfile options specified multiple times */
707     iflags.opt_booldup = iflags.opt_compdup = (int *) 0;
708 
709     for (i = 0; boolopt[i].name; i++) {
710         if (boolopt[i].addr)
711             *(boolopt[i].addr) = boolopt[i].initvalue;
712     }
713 #if defined(COMPRESS) || defined(ZLIB_COMP)
714     set_savepref("externalcomp");
715     set_restpref("externalcomp");
716 #ifdef RLECOMP
717     set_savepref("!rlecomp");
718     set_restpref("!rlecomp");
719 #endif
720 #else
721 #ifdef ZEROCOMP
722     set_savepref("zerocomp");
723     set_restpref("zerocomp");
724 #endif
725 #ifdef RLECOMP
726     set_savepref("rlecomp");
727     set_restpref("rlecomp");
728 #endif
729 #endif
730 #ifdef SYSFLAGS
731     Strcpy(sysflags.sysflagsid, "sysflags");
732     sysflags.sysflagsid[9] = (char) sizeof (struct sysflag);
733 #endif
734     flags.end_own = FALSE;
735     flags.end_top = 3;
736     flags.end_around = 2;
737     flags.paranoia_bits = PARANOID_PRAY; /* old prayconfirm=TRUE */
738     flags.pile_limit = PILE_LIMIT_DFLT;  /* 5 */
739     flags.runmode = RUN_LEAP;
740     iflags.msg_history = 20;
741     /* msg_window has conflicting defaults for multi-interface binary */
742 #ifdef TTY_GRAPHICS
743     iflags.prevmsg_window = 's';
744 #else
745 #ifdef CURSES_GRAPHICS
746     iflags.prevmsg_window = 'r';
747 #endif
748 #endif
749     iflags.menu_headings = ATR_INVERSE;
750     iflags.getpos_coords = GPCOORDS_NONE;
751 
752     /* hero's role, race, &c haven't been chosen yet */
753     flags.initrole = flags.initrace = flags.initgend = flags.initalign
754         = ROLE_NONE;
755 
756     init_ov_primary_symbols();
757     init_ov_rogue_symbols();
758     /* Set the default monster and object class symbols. */
759     init_symbols();
760     for (i = 0; i < WARNCOUNT; i++)
761         warnsyms[i] = def_warnsyms[i].sym;
762 
763     /* for "special achievement" tracking (see obj.h,
764        create_object(sp_lev.c), addinv_core1(invent.c) */
765     iflags.mines_prize_type = LUCKSTONE;
766     iflags.soko_prize_type1 = BAG_OF_HOLDING;
767     iflags.soko_prize_type2 = AMULET_OF_REFLECTION;
768 
769     /* assert( sizeof flags.inv_order == sizeof def_inv_order ); */
770     (void) memcpy((genericptr_t) flags.inv_order,
771                   (genericptr_t) def_inv_order, sizeof flags.inv_order);
772     flags.pickup_types[0] = '\0';
773     flags.pickup_burden = MOD_ENCUMBER;
774     flags.sortloot = 'l'; /* sort only loot by default */
775 
776     for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++)
777         flags.end_disclose[i] = DISCLOSE_PROMPT_DEFAULT_NO;
778     switch_symbols(FALSE); /* set default characters */
779     init_rogue_symbols();
780 #if defined(UNIX) && defined(TTY_GRAPHICS)
781     /*
782      * Set defaults for some options depending on what we can
783      * detect about the environment's capabilities.
784      * This has to be done after the global initialization above
785      * and before reading user-specific initialization via
786      * config file/environment variable below.
787      */
788     /* this detects the IBM-compatible console on most 386 boxes */
789     if ((opts = nh_getenv("TERM")) && !strncmp(opts, "AT", 2)) {
790         if (!symset[PRIMARY].explicitly)
791             load_symset("IBMGraphics", PRIMARY);
792         if (!symset[ROGUESET].explicitly)
793             load_symset("RogueIBM", ROGUESET);
794         switch_symbols(TRUE);
795 #ifdef TEXTCOLOR
796         iflags.use_color = TRUE;
797 #endif
798     }
799 #endif /* UNIX && TTY_GRAPHICS */
800 #if defined(UNIX) || defined(VMS)
801 #ifdef TTY_GRAPHICS
802     /* detect whether a "vt" terminal can handle alternate charsets */
803     if ((opts = nh_getenv("TERM"))
804         /* [could also check "xterm" which emulates vtXXX by default] */
805         && !strncmpi(opts, "vt", 2)
806         && AS && AE && index(AS, '\016') && index(AE, '\017')) {
807         if (!symset[PRIMARY].explicitly)
808             load_symset("DECGraphics", PRIMARY);
809         switch_symbols(TRUE);
810     }
811 #endif
812 #endif /* UNIX || VMS */
813 
814 #if defined(MSDOS) || defined(WIN32)
815     /* Use IBM defaults. Can be overridden via config file */
816     if (!symset[PRIMARY].explicitly)
817         load_symset("IBMGraphics_2", PRIMARY);
818     if (!symset[ROGUESET].explicitly)
819         load_symset("RogueEpyx", ROGUESET);
820 #endif
821 #ifdef MAC_GRAPHICS_ENV
822     if (!symset[PRIMARY].explicitly)
823         load_symset("MACGraphics", PRIMARY);
824     switch_symbols(TRUE);
825 #endif /* MAC_GRAPHICS_ENV */
826     flags.menu_style = MENU_FULL;
827 
828     iflags.wc_align_message = ALIGN_TOP;
829     iflags.wc_align_status = ALIGN_BOTTOM;
830     /* used by tty and curses */
831     iflags.wc2_statuslines = 2;
832     /* only used by curses */
833     iflags.wc2_windowborders = 2; /* 'Auto' */
834 
835     /* since this is done before init_objects(), do partial init here */
836     objects[SLIME_MOLD].oc_name_idx = SLIME_MOLD;
837     nmcpy(pl_fruit, OBJ_NAME(objects[SLIME_MOLD]), PL_FSIZ);
838 }
839 
840 void
initoptions_finish()841 initoptions_finish()
842 {
843     nhsym sym = 0;
844 #ifndef MAC
845     char *opts = getenv("NETHACKOPTIONS");
846 
847     if (!opts)
848         opts = getenv("HACKOPTIONS");
849     if (opts) {
850         if (*opts == '/' || *opts == '\\' || *opts == '@') {
851             if (*opts == '@')
852                 opts++; /* @filename */
853             /* looks like a filename */
854             if (strlen(opts) < BUFSZ / 2) {
855                 config_error_init(TRUE, opts, CONFIG_ERROR_SECURE);
856                 read_config_file(opts, SET_IN_FILE);
857                 config_error_done();
858             }
859         } else {
860             config_error_init(TRUE, (char *) 0, FALSE);
861             read_config_file((char *) 0, SET_IN_FILE);
862             config_error_done();
863             /* let the total length of options be long;
864              * parseoptions() will check each individually
865              */
866             config_error_init(FALSE, "NETHACKOPTIONS", FALSE);
867             (void) parseoptions(opts, TRUE, FALSE);
868             config_error_done();
869         }
870     } else
871 #endif /* !MAC */
872     /*else*/ {
873         config_error_init(TRUE, (char *) 0, FALSE);
874         read_config_file((char *) 0, SET_IN_FILE);
875         config_error_done();
876     }
877 
878     (void) fruitadd(pl_fruit, (struct fruit *) 0);
879     /*
880      * Remove "slime mold" from list of object names.  This will
881      * prevent it from being wished unless it's actually present
882      * as a named (or default) fruit.  Wishing for "fruit" will
883      * result in the player's preferred fruit [better than "\033"].
884      */
885     obj_descr[SLIME_MOLD].oc_name = "fruit";
886 
887     sym = get_othersym(SYM_BOULDER,
888                 Is_rogue_level(&u.uz) ? ROGUESET : PRIMARY);
889     if (sym)
890         showsyms[SYM_BOULDER + SYM_OFF_X] = sym;
891     reglyph_darkroom();
892 
893 #ifdef STATUS_HILITES
894     /*
895      * A multi-interface binary might only support status highlighting
896      * for some of the interfaces; check whether we asked for it but are
897      * using one which doesn't.
898      *
899      * Option processing can take place before a user-decided WindowPort
900      * is even initialized, so check for that too.
901      */
902     if (!WINDOWPORT("safe-startup")) {
903         if (iflags.hilite_delta && !wc2_supported("statushilites")) {
904             raw_printf("Status highlighting not supported for %s interface.",
905                        windowprocs.name);
906             iflags.hilite_delta = 0;
907         }
908     }
909 #endif
910     return;
911 }
912 
913 /* copy up to maxlen-1 characters; 'dest' must be able to hold maxlen;
914    treat comma as alternate end of 'src' */
915 STATIC_OVL void
nmcpy(dest,src,maxlen)916 nmcpy(dest, src, maxlen)
917 char *dest;
918 const char *src;
919 int maxlen;
920 {
921     int count;
922 
923     for (count = 1; count < maxlen; count++) {
924         if (*src == ',' || *src == '\0')
925             break; /*exit on \0 terminator*/
926         *dest++ = *src++;
927     }
928     *dest = '\0';
929 }
930 
931 /*
932  * escapes(): escape expansion for showsyms.  C-style escapes understood
933  * include \n, \b, \t, \r, \xnnn (hex), \onnn (octal), \nnn (decimal).
934  * (Note: unlike in C, leading digit 0 is not used to indicate octal;
935  * the letter o (either upper or lower case) is used for that.
936  * The ^-prefix for control characters is also understood, and \[mM]
937  * has the effect of 'meta'-ing the value which follows (so that the
938  * alternate character set will be enabled).
939  *
940  * X     normal key X
941  * ^X    control-X
942  * \mX   meta-X
943  *
944  * For 3.4.3 and earlier, input ending with "\M", backslash, or caret
945  * prior to terminating '\0' would pull that '\0' into the output and then
946  * keep processing past it, potentially overflowing the output buffer.
947  * Now, trailing \ or ^ will act like \\ or \^ and add '\\' or '^' to the
948  * output and stop there; trailing \M will fall through to \<other> and
949  * yield 'M', then stop.  Any \X or \O followed by something other than
950  * an appropriate digit will also fall through to \<other> and yield 'X'
951  * or 'O', plus stop if the non-digit is end-of-string.
952  */
953 STATIC_OVL void
escapes(cp,tp)954 escapes(cp, tp)
955 const char *cp; /* might be 'tp', updating in place */
956 char *tp; /* result is never longer than 'cp' */
957 {
958     static NEARDATA const char oct[] = "01234567", dec[] = "0123456789",
959                                hex[] = "00112233445566778899aAbBcCdDeEfF";
960     const char *dp;
961     int cval, meta, dcount;
962 
963     while (*cp) {
964         /* \M has to be followed by something to do meta conversion,
965            otherwise it will just be \M which ultimately yields 'M' */
966         meta = (*cp == '\\' && (cp[1] == 'm' || cp[1] == 'M') && cp[2]);
967         if (meta)
968             cp += 2;
969 
970         cval = dcount = 0; /* for decimal, octal, hexadecimal cases */
971         if ((*cp != '\\' && *cp != '^') || !cp[1]) {
972             /* simple character, or nothing left for \ or ^ to escape */
973             cval = *cp++;
974         } else if (*cp == '^') { /* expand control-character syntax */
975             cval = (*++cp & 0x1f);
976             ++cp;
977 
978         /* remaining cases are all for backslash; we know cp[1] is not \0 */
979         } else if (index(dec, cp[1])) {
980             ++cp; /* move past backslash to first digit */
981             do {
982                 cval = (cval * 10) + (*cp - '0');
983             } while (*++cp && index(dec, *cp) && ++dcount < 3);
984         } else if ((cp[1] == 'o' || cp[1] == 'O') && cp[2]
985                    && index(oct, cp[2])) {
986             cp += 2; /* move past backslash and 'O' */
987             do {
988                 cval = (cval * 8) + (*cp - '0');
989             } while (*++cp && index(oct, *cp) && ++dcount < 3);
990         } else if ((cp[1] == 'x' || cp[1] == 'X') && cp[2]
991                    && (dp = index(hex, cp[2])) != 0) {
992             cp += 2; /* move past backslash and 'X' */
993             do {
994                 cval = (cval * 16) + ((int) (dp - hex) / 2);
995             } while (*++cp && (dp = index(hex, *cp)) != 0 && ++dcount < 2);
996         } else { /* C-style character escapes */
997             switch (*++cp) {
998             case '\\':
999                 cval = '\\';
1000                 break;
1001             case 'n':
1002                 cval = '\n';
1003                 break;
1004             case 't':
1005                 cval = '\t';
1006                 break;
1007             case 'b':
1008                 cval = '\b';
1009                 break;
1010             case 'r':
1011                 cval = '\r';
1012                 break;
1013             default:
1014                 cval = *cp;
1015             }
1016             ++cp;
1017         }
1018 
1019         if (meta)
1020             cval |= 0x80;
1021         *tp++ = (char) cval;
1022     }
1023     *tp = '\0';
1024 }
1025 
1026 STATIC_OVL void
rejectoption(optname)1027 rejectoption(optname)
1028 const char *optname;
1029 {
1030 #ifdef MICRO
1031     pline("\"%s\" settable only from %s.", optname, configfile);
1032 #else
1033     pline("%s can be set only from NETHACKOPTIONS or %s.", optname,
1034           configfile);
1035 #endif
1036 }
1037 
1038 /*
1039 
1040 # errors:
1041 OPTIONS=aaaaaaaaaa[ more than 247 (255 - 8 for 'OPTIONS=') total ]aaaaaaaaaa
1042 OPTIONS
1043 OPTIONS=
1044 MSGTYPE=stop"You swap places with "
1045 MSGTYPE=st.op "You swap places with "
1046 MSGTYPE=stop "You swap places with \"
1047 MENUCOLOR=" blessed "green&none
1048 MENUCOLOR=" holy " = green&reverse
1049 MENUCOLOR=" cursed " = red&uline
1050 MENUCOLOR=" unholy " = reed
1051 OPTIONS=!legacy:true,fooo
1052 OPTIONS=align:!pin
1053 OPTIONS=gender
1054 
1055 */
1056 
1057 STATIC_OVL char *
string_for_opt(opts,val_optional)1058 string_for_opt(opts, val_optional)
1059 char *opts;
1060 boolean val_optional;
1061 {
1062     char *colon, *equals;
1063 
1064     colon = index(opts, ':');
1065     equals = index(opts, '=');
1066     if (!colon || (equals && equals < colon))
1067         colon = equals;
1068 
1069     if (!colon || !*++colon) {
1070         if (!val_optional)
1071             config_error_add("Missing parameter for '%s'", opts);
1072         return empty_optstr;
1073     }
1074     return colon;
1075 }
1076 
1077 STATIC_OVL char *
string_for_env_opt(optname,opts,val_optional)1078 string_for_env_opt(optname, opts, val_optional)
1079 const char *optname;
1080 char *opts;
1081 boolean val_optional;
1082 {
1083     if (!initial) {
1084         rejectoption(optname);
1085         return empty_optstr;
1086     }
1087     return string_for_opt(opts, val_optional);
1088 }
1089 
1090 STATIC_OVL void
bad_negation(optname,with_parameter)1091 bad_negation(optname, with_parameter)
1092 const char *optname;
1093 boolean with_parameter;
1094 {
1095     pline_The("%s option may not %sbe negated.", optname,
1096               with_parameter ? "both have a value and " : "");
1097 }
1098 
1099 /*
1100  * Change the inventory order, using the given string as the new order.
1101  * Missing characters in the new order are filled in at the end from
1102  * the current inv_order, except for gold, which is forced to be first
1103  * if not explicitly present.
1104  *
1105  * This routine returns 1 unless there is a duplicate or bad char in
1106  * the string.
1107  */
1108 STATIC_OVL int
change_inv_order(op)1109 change_inv_order(op)
1110 char *op;
1111 {
1112     int oc_sym, num;
1113     char *sp, buf[QBUFSZ];
1114     int retval = 1;
1115 
1116     num = 0;
1117     if (!index(op, GOLD_SYM))
1118         buf[num++] = COIN_CLASS;
1119 
1120     for (sp = op; *sp; sp++) {
1121         boolean fail = FALSE;
1122         oc_sym = def_char_to_objclass(*sp);
1123         /* reject bad or duplicate entries */
1124         if (oc_sym == MAXOCLASSES) { /* not an object class char */
1125             config_error_add("Not an object class '%c'", *sp);
1126             retval = 0;
1127             fail = TRUE;
1128         } else if (!index(flags.inv_order, oc_sym)) {
1129             /* VENOM_CLASS, RANDOM_CLASS, and ILLOBJ_CLASS are excluded
1130                because they aren't in def_inv_order[] so don't make it
1131                into flags.inv_order, hence always fail this index() test */
1132             config_error_add("Object class '%c' not allowed", *sp);
1133             retval = 0;
1134             fail = TRUE;
1135         } else if (index(sp + 1, *sp)) {
1136             config_error_add("Duplicate object class '%c'", *sp);
1137             retval = 0;
1138             fail = TRUE;
1139         }
1140         /* retain good ones */
1141         if (!fail)
1142             buf[num++] = (char) oc_sym;
1143     }
1144     buf[num] = '\0';
1145 
1146     /* fill in any omitted classes, using previous ordering */
1147     for (sp = flags.inv_order; *sp; sp++)
1148         if (!index(buf, *sp))
1149             (void) strkitten(&buf[num++], *sp);
1150     buf[MAXOCLASSES - 1] = '\0';
1151 
1152     Strcpy(flags.inv_order, buf);
1153     return retval;
1154 }
1155 
1156 STATIC_OVL boolean
warning_opts(opts,optype)1157 warning_opts(opts, optype)
1158 register char *opts;
1159 const char *optype;
1160 {
1161     uchar translate[WARNCOUNT];
1162     int length, i;
1163 
1164     if ((opts = string_for_env_opt(optype, opts, FALSE)) == empty_optstr)
1165         return FALSE;
1166     escapes(opts, opts);
1167 
1168     length = (int) strlen(opts);
1169     /* match the form obtained from PC configuration files */
1170     for (i = 0; i < WARNCOUNT; i++)
1171         translate[i] = (i >= length) ? 0
1172                                      : opts[i] ? (uchar) opts[i]
1173                                                : def_warnsyms[i].sym;
1174     assign_warnings(translate);
1175     return TRUE;
1176 }
1177 
1178 void
assign_warnings(graph_chars)1179 assign_warnings(graph_chars)
1180 register uchar *graph_chars;
1181 {
1182     int i;
1183 
1184     for (i = 0; i < WARNCOUNT; i++)
1185         if (graph_chars[i])
1186             warnsyms[i] = graph_chars[i];
1187 }
1188 
1189 STATIC_OVL int
feature_alert_opts(op,optn)1190 feature_alert_opts(op, optn)
1191 char *op;
1192 const char *optn;
1193 {
1194     char buf[BUFSZ];
1195     unsigned long fnv = get_feature_notice_ver(op); /* version.c */
1196 
1197     if (fnv == 0L)
1198         return 0;
1199     if (fnv > get_current_feature_ver()) {
1200         if (!initial) {
1201             You_cant("disable new feature alerts for future versions.");
1202         } else {
1203             config_error_add(
1204                         "%s=%s Invalid reference to a future version ignored",
1205                              optn, op);
1206         }
1207         return 0;
1208     }
1209 
1210     flags.suppress_alert = fnv;
1211     if (!initial) {
1212         Sprintf(buf, "%lu.%lu.%lu", FEATURE_NOTICE_VER_MAJ,
1213                 FEATURE_NOTICE_VER_MIN, FEATURE_NOTICE_VER_PATCH);
1214         pline(
1215           "Feature change alerts disabled for NetHack %s features and prior.",
1216               buf);
1217     }
1218     return 1;
1219 }
1220 
1221 void
set_duplicate_opt_detection(on_or_off)1222 set_duplicate_opt_detection(on_or_off)
1223 int on_or_off;
1224 {
1225     int k, *optptr;
1226 
1227     if (on_or_off != 0) {
1228         /*-- ON --*/
1229         if (iflags.opt_booldup)
1230             impossible("iflags.opt_booldup already on (memory leak)");
1231         iflags.opt_booldup = (int *) alloc(SIZE(boolopt) * sizeof (int));
1232         optptr = iflags.opt_booldup;
1233         for (k = 0; k < SIZE(boolopt); ++k)
1234             *optptr++ = 0;
1235 
1236         if (iflags.opt_compdup)
1237             impossible("iflags.opt_compdup already on (memory leak)");
1238         iflags.opt_compdup = (int *) alloc(SIZE(compopt) * sizeof (int));
1239         optptr = iflags.opt_compdup;
1240         for (k = 0; k < SIZE(compopt); ++k)
1241             *optptr++ = 0;
1242     } else {
1243         /*-- OFF --*/
1244         if (iflags.opt_booldup)
1245             free((genericptr_t) iflags.opt_booldup);
1246         iflags.opt_booldup = (int *) 0;
1247         if (iflags.opt_compdup)
1248             free((genericptr_t) iflags.opt_compdup);
1249         iflags.opt_compdup = (int *) 0;
1250     }
1251 }
1252 
1253 STATIC_OVL boolean
duplicate_opt_detection(opts,iscompound)1254 duplicate_opt_detection(opts, iscompound)
1255 const char *opts;
1256 int iscompound; /* 0 == boolean option, 1 == compound */
1257 {
1258     int i, *optptr;
1259 
1260     if (!iscompound && iflags.opt_booldup && initial && from_file) {
1261         for (i = 0; boolopt[i].name; i++) {
1262             if (match_optname(opts, boolopt[i].name, 3, FALSE)) {
1263                 optptr = iflags.opt_booldup + i;
1264                 *optptr += 1;
1265                 if (*optptr > 1)
1266                     return TRUE;
1267                 else
1268                     return FALSE;
1269             }
1270         }
1271     } else if (iscompound && iflags.opt_compdup && initial && from_file) {
1272         for (i = 0; compopt[i].name; i++) {
1273             if (match_optname(opts, compopt[i].name, strlen(compopt[i].name),
1274                               TRUE)) {
1275                 optptr = iflags.opt_compdup + i;
1276                 *optptr += 1;
1277                 if (*optptr > 1)
1278                     return TRUE;
1279                 else
1280                     return FALSE;
1281             }
1282         }
1283     }
1284     return FALSE;
1285 }
1286 
1287 STATIC_OVL void
complain_about_duplicate(opts,iscompound)1288 complain_about_duplicate(opts, iscompound)
1289 const char *opts;
1290 int iscompound; /* 0 == boolean option, 1 == compound */
1291 {
1292 #ifdef MAC
1293     /* the Mac has trouble dealing with the output of messages while
1294      * processing the config file.  That should get fixed one day.
1295      * For now just return.
1296      */
1297 #else /* !MAC */
1298     config_error_add("%s option specified multiple times: %s",
1299                      iscompound ? "compound" : "boolean", opts);
1300 #endif /* ?MAC */
1301     return;
1302 }
1303 
1304 /* paranoia[] - used by parseoptions() and special_handling() */
1305 STATIC_VAR const struct paranoia_opts {
1306     int flagmask;        /* which paranoid option */
1307     const char *argname; /* primary name */
1308     int argMinLen;       /* minimum number of letters to match */
1309     const char *synonym; /* alternate name (optional) */
1310     int synMinLen;
1311     const char *explain; /* for interactive menu */
1312 } paranoia[] = {
1313     /* there are some initial-letter conflicts: "a"ttack vs "a"ll, "attack"
1314        takes precedence and "all" isn't present in the interactive menu,
1315        and "d"ie vs "d"eath, synonyms for each other so doesn't matter;
1316        (also "p"ray vs "P"aranoia, "pray" takes precedence since "Paranoia"
1317        is just a synonym for "Confirm"); "b"ones vs "br"eak-wand, the
1318        latter requires at least two letters; "e"at vs "ex"plore,
1319        "cont"inue eating vs "C"onfirm; "wand"-break vs "Were"-change,
1320        both require at least two letters during config processing and use
1321        case-senstivity for 'O's interactive menu */
1322     { PARANOID_CONFIRM, "Confirm", 1, "Paranoia", 2,
1323       "for \"yes\" confirmations, require \"no\" to reject" },
1324     { PARANOID_QUIT, "quit", 1, "explore", 2,
1325       "yes vs y to quit or to enter explore mode" },
1326     { PARANOID_DIE, "die", 1, "death", 2,
1327       "yes vs y to die (explore mode or debug mode)" },
1328     { PARANOID_BONES, "bones", 1, 0, 0,
1329       "yes vs y to save bones data when dying in debug mode" },
1330     { PARANOID_HIT, "attack", 1, "hit", 1,
1331       "yes vs y to attack a peaceful monster" },
1332     { PARANOID_BREAKWAND, "wand-break", 2, "break-wand", 2,
1333       "yes vs y to break a wand via (a)pply" },
1334     { PARANOID_EATING, "eat", 1, "continue", 4,
1335       "yes vs y to continue eating after first bite when satiated" },
1336     { PARANOID_WERECHANGE, "Were-change", 2, (const char *) 0, 0,
1337       "yes vs y to change form when lycanthropy is controllable" },
1338     { PARANOID_PRAY, "pray", 1, 0, 0,
1339       "y to pray (supersedes old \"prayconfirm\" option)" },
1340     { PARANOID_REMOVE, "Remove", 1, "Takeoff", 1,
1341       "always pick from inventory for Remove and Takeoff" },
1342     /* for config file parsing; interactive menu skips these */
1343     { 0, "none", 4, 0, 0, 0 }, /* require full word match */
1344     { ~0, "all", 3, 0, 0, 0 }, /* ditto */
1345 };
1346 
1347 extern struct menucoloring *menu_colorings;
1348 
1349 static const struct {
1350     const char *name;
1351     const int color;
1352 } colornames[] = {
1353     { "black", CLR_BLACK },
1354     { "red", CLR_RED },
1355     { "green", CLR_GREEN },
1356     { "brown", CLR_BROWN },
1357     { "blue", CLR_BLUE },
1358     { "magenta", CLR_MAGENTA },
1359     { "cyan", CLR_CYAN },
1360     { "gray", CLR_GRAY },
1361     { "orange", CLR_ORANGE },
1362     { "light green", CLR_BRIGHT_GREEN },
1363     { "yellow", CLR_YELLOW },
1364     { "light blue", CLR_BRIGHT_BLUE },
1365     { "light magenta", CLR_BRIGHT_MAGENTA },
1366     { "light cyan", CLR_BRIGHT_CYAN },
1367     { "white", CLR_WHITE },
1368     { "no color", NO_COLOR },
1369     { NULL, CLR_BLACK }, /* everything after this is an alias */
1370     { "transparent", NO_COLOR },
1371     { "purple", CLR_MAGENTA },
1372     { "light purple", CLR_BRIGHT_MAGENTA },
1373     { "bright purple", CLR_BRIGHT_MAGENTA },
1374     { "grey", CLR_GRAY },
1375     { "bright red", CLR_ORANGE },
1376     { "bright green", CLR_BRIGHT_GREEN },
1377     { "bright blue", CLR_BRIGHT_BLUE },
1378     { "bright magenta", CLR_BRIGHT_MAGENTA },
1379     { "bright cyan", CLR_BRIGHT_CYAN }
1380 };
1381 
1382 static const struct {
1383     const char *name;
1384     const int attr;
1385 } attrnames[] = {
1386     { "none", ATR_NONE },
1387     { "bold", ATR_BOLD },
1388     { "dim", ATR_DIM },
1389     { "underline", ATR_ULINE },
1390     { "blink", ATR_BLINK },
1391     { "inverse", ATR_INVERSE },
1392     { NULL, ATR_NONE }, /* everything after this is an alias */
1393     { "normal", ATR_NONE },
1394     { "uline", ATR_ULINE },
1395     { "reverse", ATR_INVERSE },
1396 };
1397 
1398 const char *
clr2colorname(clr)1399 clr2colorname(clr)
1400 int clr;
1401 {
1402     int i;
1403 
1404     for (i = 0; i < SIZE(colornames); i++)
1405         if (colornames[i].name && colornames[i].color == clr)
1406             return colornames[i].name;
1407     return (char *) 0;
1408 }
1409 
1410 int
match_str2clr(str)1411 match_str2clr(str)
1412 char *str;
1413 {
1414     int i, c = CLR_MAX;
1415 
1416     /* allow "lightblue", "light blue", and "light-blue" to match "light blue"
1417        (also junk like "_l i-gh_t---b l u e" but we won't worry about that);
1418        also copes with trailing space; caller has removed any leading space */
1419     for (i = 0; i < SIZE(colornames); i++)
1420         if (colornames[i].name
1421             && fuzzymatch(str, colornames[i].name, " -_", TRUE)) {
1422             c = colornames[i].color;
1423             break;
1424         }
1425     if (i == SIZE(colornames) && digit(*str))
1426         c = atoi(str);
1427 
1428     if (c < 0 || c >= CLR_MAX) {
1429         config_error_add("Unknown color '%.60s'", str);
1430         c = CLR_MAX; /* "none of the above" */
1431     }
1432 
1433     return c;
1434 }
1435 
1436 STATIC_OVL const char *
attr2attrname(attr)1437 attr2attrname(attr)
1438 int attr;
1439 {
1440     int i;
1441 
1442     for (i = 0; i < SIZE(attrnames); i++)
1443         if (attrnames[i].attr == attr)
1444             return attrnames[i].name;
1445     return (char *) 0;
1446 }
1447 
1448 int
match_str2attr(str,complain)1449 match_str2attr(str, complain)
1450 const char *str;
1451 boolean complain;
1452 {
1453     int i, a = -1;
1454 
1455     for (i = 0; i < SIZE(attrnames); i++)
1456         if (attrnames[i].name
1457             && fuzzymatch(str, attrnames[i].name, " -_", TRUE)) {
1458             a = attrnames[i].attr;
1459             break;
1460         }
1461 
1462     if (a == -1 && complain)
1463         config_error_add("Unknown text attribute '%.50s'", str);
1464 
1465     return a;
1466 }
1467 
1468 int
query_color(prompt)1469 query_color(prompt)
1470 const char *prompt;
1471 {
1472     winid tmpwin;
1473     anything any;
1474     int i, pick_cnt;
1475     menu_item *picks = (menu_item *) 0;
1476 
1477     tmpwin = create_nhwindow(NHW_MENU);
1478     start_menu(tmpwin);
1479     any = zeroany;
1480     for (i = 0; i < SIZE(colornames); i++) {
1481         if (!colornames[i].name)
1482             break;
1483         any.a_int = i + 1;
1484         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, colornames[i].name,
1485                  (colornames[i].color == NO_COLOR) ? MENU_SELECTED
1486                                                    : MENU_UNSELECTED);
1487     }
1488     end_menu(tmpwin, (prompt && *prompt) ? prompt : "Pick a color");
1489     pick_cnt = select_menu(tmpwin, PICK_ONE, &picks);
1490     destroy_nhwindow(tmpwin);
1491     if (pick_cnt > 0) {
1492         i = colornames[picks[0].item.a_int - 1].color;
1493         /* pick_cnt==2: explicitly picked something other than the
1494            preselected entry */
1495         if (pick_cnt == 2 && i == NO_COLOR)
1496             i = colornames[picks[1].item.a_int - 1].color;
1497         free((genericptr_t) picks);
1498         return i;
1499     } else if (pick_cnt == 0) {
1500         /* pick_cnt==0: explicitly picking preselected entry toggled it off */
1501         return NO_COLOR;
1502     }
1503     return -1;
1504 }
1505 
1506 /* ask about highlighting attribute; for menu headers and menu
1507    coloring patterns, only one attribute at a time is allowed;
1508    for status highlighting, multiple attributes are allowed [overkill;
1509    life would be much simpler if that were restricted to one also...] */
1510 int
query_attr(prompt)1511 query_attr(prompt)
1512 const char *prompt;
1513 {
1514     winid tmpwin;
1515     anything any;
1516     int i, pick_cnt;
1517     menu_item *picks = (menu_item *) 0;
1518     boolean allow_many = (prompt && !strncmpi(prompt, "Choose", 6));
1519     int default_attr = ATR_NONE;
1520 
1521     if (prompt && strstri(prompt, "menu headings"))
1522         default_attr = iflags.menu_headings;
1523     tmpwin = create_nhwindow(NHW_MENU);
1524     start_menu(tmpwin);
1525     any = zeroany;
1526     for (i = 0; i < SIZE(attrnames); i++) {
1527         if (!attrnames[i].name)
1528             break;
1529         any.a_int = i + 1;
1530         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, attrnames[i].attr,
1531                  attrnames[i].name,
1532                  (attrnames[i].attr == default_attr) ? MENU_SELECTED
1533                                                      : MENU_UNSELECTED);
1534     }
1535     end_menu(tmpwin, (prompt && *prompt) ? prompt : "Pick an attribute");
1536     pick_cnt = select_menu(tmpwin, allow_many ? PICK_ANY : PICK_ONE, &picks);
1537     destroy_nhwindow(tmpwin);
1538     if (pick_cnt > 0) {
1539         int j, k = 0;
1540 
1541         if (allow_many) {
1542             /* PICK_ANY, with one preselected entry (ATR_NONE) which
1543                should be excluded if any other choices were picked */
1544             for (i = 0; i < pick_cnt; ++i) {
1545                 j = picks[i].item.a_int - 1;
1546                 if (attrnames[j].attr != ATR_NONE || pick_cnt == 1) {
1547                     switch (attrnames[j].attr) {
1548                     case ATR_DIM:
1549                         k |= HL_DIM;
1550                         break;
1551                     case ATR_BLINK:
1552                         k |= HL_BLINK;
1553                         break;
1554                     case ATR_ULINE:
1555                         k |= HL_ULINE;
1556                         break;
1557                     case ATR_INVERSE:
1558                         k |= HL_INVERSE;
1559                         break;
1560                     case ATR_BOLD:
1561                         k |= HL_BOLD;
1562                         break;
1563                     case ATR_NONE:
1564                         k = HL_NONE;
1565                         break;
1566                     }
1567                 }
1568             }
1569         } else {
1570             /* PICK_ONE, but might get 0 or 2 due to preselected entry */
1571             j = picks[0].item.a_int - 1;
1572             /* pick_cnt==2: explicitly picked something other than the
1573                preselected entry */
1574             if (pick_cnt == 2 && attrnames[j].attr == default_attr)
1575                 j = picks[1].item.a_int - 1;
1576             k = attrnames[j].attr;
1577         }
1578         free((genericptr_t) picks);
1579         return k;
1580     } else if (pick_cnt == 0 && !allow_many) {
1581         /* PICK_ONE, preselected entry explicitly chosen */
1582         return default_attr;
1583     }
1584     /* either ESC to explicitly cancel (pick_cnt==-1) or
1585        PICK_ANY with preselected entry toggled off and nothing chosen */
1586     return -1;
1587 }
1588 
1589 static const struct {
1590     const char *name;
1591     xchar msgtyp;
1592     const char *descr;
1593 } msgtype_names[] = {
1594     { "show", MSGTYP_NORMAL, "Show message normally" },
1595     { "hide", MSGTYP_NOSHOW, "Hide message" },
1596     { "noshow", MSGTYP_NOSHOW, NULL },
1597     { "stop", MSGTYP_STOP, "Prompt for more after the message" },
1598     { "more", MSGTYP_STOP, NULL },
1599     { "norep", MSGTYP_NOREP, "Do not repeat the message" }
1600 };
1601 
1602 STATIC_OVL const char *
msgtype2name(typ)1603 msgtype2name(typ)
1604 int typ;
1605 {
1606     int i;
1607 
1608     for (i = 0; i < SIZE(msgtype_names); i++)
1609         if (msgtype_names[i].descr && msgtype_names[i].msgtyp == typ)
1610             return msgtype_names[i].name;
1611     return (char *) 0;
1612 }
1613 
1614 STATIC_OVL int
query_msgtype()1615 query_msgtype()
1616 {
1617     winid tmpwin;
1618     anything any;
1619     int i, pick_cnt;
1620     menu_item *picks = (menu_item *) 0;
1621 
1622     tmpwin = create_nhwindow(NHW_MENU);
1623     start_menu(tmpwin);
1624     any = zeroany;
1625     for (i = 0; i < SIZE(msgtype_names); i++)
1626         if (msgtype_names[i].descr) {
1627             any.a_int = msgtype_names[i].msgtyp + 1;
1628             add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
1629                  msgtype_names[i].descr, MENU_UNSELECTED);
1630         }
1631     end_menu(tmpwin, "How to show the message");
1632     pick_cnt = select_menu(tmpwin, PICK_ONE, &picks);
1633     destroy_nhwindow(tmpwin);
1634     if (pick_cnt > 0) {
1635         i = picks->item.a_int - 1;
1636         free((genericptr_t) picks);
1637         return i;
1638     }
1639     return -1;
1640 }
1641 
1642 STATIC_OVL boolean
msgtype_add(typ,pattern)1643 msgtype_add(typ, pattern)
1644 int typ;
1645 char *pattern;
1646 {
1647     struct plinemsg_type *tmp = (struct plinemsg_type *) alloc(sizeof *tmp);
1648 
1649     tmp->msgtype = typ;
1650     tmp->regex = regex_init();
1651     if (!regex_compile(pattern, tmp->regex)) {
1652         static const char *re_error = "MSGTYPE regex error";
1653 
1654         config_error_add("%s: %s", re_error, regex_error_desc(tmp->regex));
1655         regex_free(tmp->regex);
1656         free((genericptr_t) tmp);
1657         return FALSE;
1658     }
1659     tmp->pattern = dupstr(pattern);
1660     tmp->next = plinemsg_types;
1661     plinemsg_types = tmp;
1662     return TRUE;
1663 }
1664 
1665 void
msgtype_free()1666 msgtype_free()
1667 {
1668     struct plinemsg_type *tmp, *tmp2 = 0;
1669 
1670     for (tmp = plinemsg_types; tmp; tmp = tmp2) {
1671         tmp2 = tmp->next;
1672         free((genericptr_t) tmp->pattern);
1673         regex_free(tmp->regex);
1674         free((genericptr_t) tmp);
1675     }
1676     plinemsg_types = (struct plinemsg_type *) 0;
1677 }
1678 
1679 STATIC_OVL void
free_one_msgtype(idx)1680 free_one_msgtype(idx)
1681 int idx; /* 0 .. */
1682 {
1683     struct plinemsg_type *tmp = plinemsg_types;
1684     struct plinemsg_type *prev = NULL;
1685 
1686     while (tmp) {
1687         if (idx == 0) {
1688             struct plinemsg_type *next = tmp->next;
1689 
1690             regex_free(tmp->regex);
1691             free((genericptr_t) tmp->pattern);
1692             free((genericptr_t) tmp);
1693             if (prev)
1694                 prev->next = next;
1695             else
1696                 plinemsg_types = next;
1697             return;
1698         }
1699         idx--;
1700         prev = tmp;
1701         tmp = tmp->next;
1702     }
1703 }
1704 
1705 int
msgtype_type(msg,norepeat)1706 msgtype_type(msg, norepeat)
1707 const char *msg;
1708 boolean norepeat; /* called from Norep(via pline) */
1709 {
1710     struct plinemsg_type *tmp = plinemsg_types;
1711 
1712     while (tmp) {
1713         /* we don't exclude entries with negative msgtype values
1714            because then the msg might end up matching a later pattern */
1715         if (regex_match(msg, tmp->regex))
1716             return tmp->msgtype;
1717         tmp = tmp->next;
1718     }
1719     return norepeat ? MSGTYP_NOREP : MSGTYP_NORMAL;
1720 }
1721 
1722 /* negate one or more types of messages so that their type handling will
1723    be disabled or re-enabled; MSGTYPE_NORMAL (value 0) is not affected */
1724 void
hide_unhide_msgtypes(hide,hide_mask)1725 hide_unhide_msgtypes(hide, hide_mask)
1726 boolean hide;
1727 int hide_mask;
1728 {
1729     struct plinemsg_type *tmp;
1730     int mt;
1731 
1732     /* negative msgtype value won't be recognized by pline, so does nothing */
1733     for (tmp = plinemsg_types; tmp; tmp = tmp->next) {
1734         mt = tmp->msgtype;
1735         if (!hide)
1736             mt = -mt; /* unhide: negate negative, yielding positive */
1737         if (mt > 0 && ((1 << mt) & hide_mask))
1738             tmp->msgtype = -tmp->msgtype;
1739     }
1740 }
1741 
1742 STATIC_OVL int
msgtype_count(VOID_ARGS)1743 msgtype_count(VOID_ARGS)
1744 {
1745     int c = 0;
1746     struct plinemsg_type *tmp = plinemsg_types;
1747 
1748     while (tmp) {
1749         c++;
1750         tmp = tmp->next;
1751     }
1752     return c;
1753 }
1754 
1755 boolean
msgtype_parse_add(str)1756 msgtype_parse_add(str)
1757 char *str;
1758 {
1759     char pattern[256];
1760     char msgtype[11];
1761 
1762     if (sscanf(str, "%10s \"%255[^\"]\"", msgtype, pattern) == 2) {
1763         int typ = -1;
1764         int i;
1765 
1766         for (i = 0; i < SIZE(msgtype_names); i++)
1767             if (!strncmpi(msgtype_names[i].name, msgtype, strlen(msgtype))) {
1768                 typ = msgtype_names[i].msgtyp;
1769                 break;
1770             }
1771         if (typ != -1)
1772             return msgtype_add(typ, pattern);
1773         else
1774             config_error_add("Unknown message type '%s'", msgtype);
1775     } else {
1776         config_error_add("Malformed MSGTYPE");
1777     }
1778     return FALSE;
1779 }
1780 
1781 STATIC_OVL boolean
test_regex_pattern(str,errmsg)1782 test_regex_pattern(str, errmsg)
1783 const char *str;
1784 const char *errmsg;
1785 {
1786     static const char re_error[] = "Regex error";
1787     struct nhregex *match;
1788     boolean retval = TRUE;
1789 
1790     if (!str)
1791         return FALSE;
1792 
1793     match = regex_init();
1794     if (!match) {
1795         config_error_add("NHregex error");
1796         return FALSE;
1797     }
1798 
1799     if (!regex_compile(str, match)) {
1800         config_error_add("%s: %s", errmsg ? errmsg : re_error,
1801                          regex_error_desc(match));
1802         retval = FALSE;
1803     }
1804     regex_free(match);
1805     return retval;
1806 }
1807 
1808 STATIC_OVL boolean
add_menu_coloring_parsed(str,c,a)1809 add_menu_coloring_parsed(str, c, a)
1810 char *str;
1811 int c, a;
1812 {
1813     static const char re_error[] = "Menucolor regex error";
1814     struct menucoloring *tmp;
1815 
1816     if (!str)
1817         return FALSE;
1818     tmp = (struct menucoloring *) alloc(sizeof *tmp);
1819     tmp->match = regex_init();
1820     if (!regex_compile(str, tmp->match)) {
1821         config_error_add("%s: %s", re_error, regex_error_desc(tmp->match));
1822         regex_free(tmp->match);
1823         free(tmp);
1824         return FALSE;
1825     } else {
1826         tmp->next = menu_colorings;
1827         tmp->origstr = dupstr(str);
1828         tmp->color = c;
1829         tmp->attr = a;
1830         menu_colorings = tmp;
1831         return TRUE;
1832     }
1833 }
1834 
1835 /* parse '"regex_string"=color&attr' and add it to menucoloring */
1836 boolean
add_menu_coloring(tmpstr)1837 add_menu_coloring(tmpstr)
1838 char *tmpstr; /* never Null but could be empty */
1839 {
1840     int c = NO_COLOR, a = ATR_NONE;
1841     char *tmps, *cs, *amp;
1842     char str[BUFSZ];
1843 
1844     (void) strncpy(str, tmpstr, sizeof str - 1);
1845     str[sizeof str - 1] = '\0';
1846 
1847     if ((cs = index(str, '=')) == 0) {
1848         config_error_add("Malformed MENUCOLOR");
1849         return FALSE;
1850     }
1851 
1852     tmps = cs + 1; /* advance past '=' */
1853     mungspaces(tmps);
1854     if ((amp = index(tmps, '&')) != 0)
1855         *amp = '\0';
1856 
1857     c = match_str2clr(tmps);
1858     if (c >= CLR_MAX)
1859         return FALSE;
1860 
1861     if (amp) {
1862         tmps = amp + 1; /* advance past '&' */
1863         a = match_str2attr(tmps, TRUE);
1864         if (a == -1)
1865             return FALSE;
1866     }
1867 
1868     /* the regexp portion here has not been condensed by mungspaces() */
1869     *cs = '\0';
1870     tmps = str;
1871     if (*tmps == '"' || *tmps == '\'') {
1872         cs--;
1873         while (isspace((uchar) *cs))
1874             cs--;
1875         if (*cs == *tmps) {
1876             *cs = '\0';
1877             tmps++;
1878         }
1879     }
1880     return add_menu_coloring_parsed(tmps, c, a);
1881 }
1882 
1883 boolean
get_menu_coloring(str,color,attr)1884 get_menu_coloring(str, color, attr)
1885 const char *str;
1886 int *color, *attr;
1887 {
1888     struct menucoloring *tmpmc;
1889 
1890     if (iflags.use_menu_color)
1891         for (tmpmc = menu_colorings; tmpmc; tmpmc = tmpmc->next)
1892             if (regex_match(str, tmpmc->match)) {
1893                 *color = tmpmc->color;
1894                 *attr = tmpmc->attr;
1895                 return TRUE;
1896             }
1897     return FALSE;
1898 }
1899 
1900 void
free_menu_coloring()1901 free_menu_coloring()
1902 {
1903     struct menucoloring *tmp, *tmp2;
1904 
1905     for (tmp = menu_colorings; tmp; tmp = tmp2) {
1906         tmp2 = tmp->next;
1907         regex_free(tmp->match);
1908         free((genericptr_t) tmp->origstr);
1909         free((genericptr_t) tmp);
1910     }
1911 }
1912 
1913 STATIC_OVL void
free_one_menu_coloring(idx)1914 free_one_menu_coloring(idx)
1915 int idx; /* 0 .. */
1916 {
1917     struct menucoloring *tmp = menu_colorings;
1918     struct menucoloring *prev = NULL;
1919 
1920     while (tmp) {
1921         if (idx == 0) {
1922             struct menucoloring *next = tmp->next;
1923 
1924             regex_free(tmp->match);
1925             free((genericptr_t) tmp->origstr);
1926             free((genericptr_t) tmp);
1927             if (prev)
1928                 prev->next = next;
1929             else
1930                 menu_colorings = next;
1931             return;
1932         }
1933         idx--;
1934         prev = tmp;
1935         tmp = tmp->next;
1936     }
1937 }
1938 
1939 STATIC_OVL int
count_menucolors(VOID_ARGS)1940 count_menucolors(VOID_ARGS)
1941 {
1942     struct menucoloring *tmp;
1943     int count = 0;
1944 
1945     for (tmp = menu_colorings; tmp; tmp = tmp->next)
1946         count++;
1947     return count;
1948 }
1949 
1950 STATIC_OVL boolean
parse_role_opts(negated,fullname,opts,opp)1951 parse_role_opts(negated, fullname, opts, opp)
1952 boolean negated;
1953 const char *fullname;
1954 char *opts;
1955 char **opp;
1956 {
1957     char *op = *opp;
1958 
1959     if (negated) {
1960         bad_negation(fullname, FALSE);
1961     } else if ((op = string_for_env_opt(fullname, opts, FALSE))
1962                                         != empty_optstr) {
1963         boolean val_negated = FALSE;
1964 
1965         while ((*op == '!') || !strncmpi(op, "no", 2)) {
1966             if (*op == '!')
1967                 op++;
1968             else
1969                 op += 2;
1970             val_negated = !val_negated;
1971         }
1972         if (val_negated) {
1973             if (!setrolefilter(op)) {
1974                 config_error_add("Unknown negated parameter '%s'", op);
1975                 return FALSE;
1976             }
1977         } else {
1978             if (duplicate_opt_detection(opts, 1))
1979                 complain_about_duplicate(opts, 1);
1980             *opp = op;
1981             return TRUE;
1982         }
1983     }
1984     return FALSE;
1985 }
1986 
1987 /* Check if character c is illegal as a menu command key */
1988 boolean
illegal_menu_cmd_key(c)1989 illegal_menu_cmd_key(c)
1990 char c;
1991 {
1992     if (c == 0 || c == '\r' || c == '\n' || c == '\033'
1993         || c == ' ' || digit(c) || (letter(c) && c != '@')) {
1994         config_error_add("Reserved menu command key '%s'", visctrl(c));
1995         return TRUE;
1996     } else { /* reject default object class symbols */
1997         int j;
1998         for (j = 1; j < MAXOCLASSES; j++)
1999             if (c == def_oc_syms[j].sym) {
2000                 config_error_add("Menu command key '%s' is an object class",
2001                                  visctrl(c));
2002                 return TRUE;
2003             }
2004     }
2005     return FALSE;
2006 }
2007 
2008 boolean
parseoptions(opts,tinitial,tfrom_file)2009 parseoptions(opts, tinitial, tfrom_file)
2010 register char *opts;
2011 boolean tinitial, tfrom_file;
2012 {
2013     char *op;
2014     unsigned num;
2015     boolean negated, duplicate;
2016     int i;
2017     const char *fullname;
2018     boolean retval = TRUE;
2019 
2020     initial = tinitial;
2021     from_file = tfrom_file;
2022     if ((op = index(opts, ',')) != 0) {
2023         *op++ = 0;
2024         if (!parseoptions(op, initial, from_file))
2025             retval = FALSE;
2026     }
2027     if (strlen(opts) > BUFSZ / 2) {
2028         config_error_add("Option too long, max length is %i characters",
2029                          (BUFSZ / 2));
2030         return FALSE;
2031     }
2032 
2033     /* strip leading and trailing white space */
2034     while (isspace((uchar) *opts))
2035         opts++;
2036     op = eos(opts);
2037     while (--op >= opts && isspace((uchar) *op))
2038         *op = '\0';
2039 
2040     if (!*opts) {
2041         config_error_add("Empty statement");
2042         return FALSE;
2043     }
2044     negated = FALSE;
2045     while ((*opts == '!') || !strncmpi(opts, "no", 2)) {
2046         if (*opts == '!')
2047             opts++;
2048         else
2049             opts += 2;
2050         negated = !negated;
2051     }
2052 
2053     /* variant spelling */
2054 
2055     if (match_optname(opts, "colour", 5, FALSE))
2056         Strcpy(opts, "color"); /* fortunately this isn't longer */
2057 
2058     /* special boolean options */
2059 
2060     if (match_optname(opts, "female", 3, FALSE)) {
2061         if (duplicate_opt_detection(opts, 0))
2062             complain_about_duplicate(opts, 0);
2063         if (!initial && flags.female == negated) {
2064             config_error_add("That is not anatomically possible.");
2065             return FALSE;
2066         } else
2067             flags.initgend = flags.female = !negated;
2068         return retval;
2069     }
2070 
2071     if (match_optname(opts, "male", 4, FALSE)) {
2072         if (duplicate_opt_detection(opts, 0))
2073             complain_about_duplicate(opts, 0);
2074         if (!initial && flags.female != negated) {
2075             config_error_add("That is not anatomically possible.");
2076             return FALSE;
2077         } else
2078             flags.initgend = flags.female = negated;
2079         return retval;
2080     }
2081 
2082 #if defined(MICRO) && !defined(AMIGA)
2083     /* included for compatibility with old NetHack.cnf files */
2084     if (match_optname(opts, "IBM_", 4, FALSE)) {
2085         iflags.BIOS = !negated;
2086         return retval;
2087     }
2088 #endif /* MICRO */
2089 
2090     /* compound options */
2091 
2092     /* This first batch can be duplicated if their values are negated */
2093 
2094     /* align:string */
2095     fullname = "align";
2096     if (match_optname(opts, fullname, sizeof "align" - 1, TRUE)) {
2097         if (parse_role_opts(negated, fullname, opts, &op)) {
2098             if ((flags.initalign = str2align(op)) == ROLE_NONE) {
2099                 config_error_add("Unknown %s '%s'", fullname, op);
2100                 return FALSE;
2101             }
2102         } else
2103             return FALSE;
2104         return retval;
2105     }
2106 
2107     /* role:string or character:string */
2108     fullname = "role";
2109     if (match_optname(opts, fullname, 4, TRUE)
2110         || match_optname(opts, (fullname = "character"), 4, TRUE)) {
2111         if (parse_role_opts(negated, fullname, opts, &op)) {
2112             if ((flags.initrole = str2role(op)) == ROLE_NONE) {
2113                 config_error_add("Unknown %s '%s'", fullname, op);
2114                 return FALSE;
2115             } else /* Backwards compatibility */
2116                 nmcpy(pl_character, op, PL_NSIZ);
2117         } else
2118             return FALSE;
2119         return retval;
2120     }
2121 
2122     /* race:string */
2123     fullname = "race";
2124     if (match_optname(opts, fullname, 4, TRUE)) {
2125         if (parse_role_opts(negated, fullname, opts, &op)) {
2126             if ((flags.initrace = str2race(op)) == ROLE_NONE) {
2127                 config_error_add("Unknown %s '%s'", fullname, op);
2128                 return FALSE;
2129             } else /* Backwards compatibility */
2130                 pl_race = *op;
2131         } else
2132             return FALSE;
2133         return retval;
2134     }
2135 
2136     /* gender:string */
2137     fullname = "gender";
2138     if (match_optname(opts, fullname, 4, TRUE)) {
2139         if (parse_role_opts(negated, fullname, opts, &op)) {
2140             if ((flags.initgend = str2gend(op)) == ROLE_NONE) {
2141                 config_error_add("Unknown %s '%s'", fullname, op);
2142                 return FALSE;
2143             } else
2144                 flags.female = flags.initgend;
2145         } else
2146             return FALSE;
2147         return retval;
2148     }
2149 
2150     /* We always check for duplicates on the remaining compound options,
2151        although individual option processing can choose to complain or not */
2152 
2153     duplicate = duplicate_opt_detection(opts, 1); /* 1: check compounds */
2154 
2155     fullname = "pettype";
2156     if (match_optname(opts, fullname, 3, TRUE)) {
2157         if (duplicate)
2158             complain_about_duplicate(opts, 1);
2159         if ((op = string_for_env_opt(fullname, opts, negated))
2160                                      != empty_optstr) {
2161             if (negated) {
2162                 bad_negation(fullname, TRUE);
2163                 return FALSE;
2164             } else
2165                 switch (lowc(*op)) {
2166                 case 'd': /* dog */
2167                     preferred_pet = 'd';
2168                     break;
2169                 case 'c': /* cat */
2170                 case 'f': /* feline */
2171                     preferred_pet = 'c';
2172                     break;
2173                 case 'h': /* horse */
2174                 case 'q': /* quadruped */
2175                     /* avoids giving "unrecognized type of pet" but
2176                        pet_type(dog.c) won't actually honor this */
2177                     preferred_pet = 'h';
2178                     break;
2179                 case 'n': /* no pet */
2180                     preferred_pet = 'n';
2181                     break;
2182                 case '*': /* random */
2183                     preferred_pet = '\0';
2184                     break;
2185                 default:
2186                     config_error_add("Unrecognized pet type '%s'.", op);
2187                     return FALSE;
2188                     break;
2189                 }
2190         } else if (negated)
2191             preferred_pet = 'n';
2192         return retval;
2193     }
2194 
2195     fullname = "catname";
2196     if (match_optname(opts, fullname, 3, TRUE)) {
2197         if (duplicate)
2198             complain_about_duplicate(opts, 1);
2199         if (negated) {
2200             bad_negation(fullname, FALSE);
2201             return FALSE;
2202         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
2203                                             != empty_optstr) {
2204             nmcpy(catname, op, PL_PSIZ);
2205         } else
2206             return FALSE;
2207         sanitize_name(catname);
2208         return retval;
2209     }
2210 
2211     fullname = "dogname";
2212     if (match_optname(opts, fullname, 3, TRUE)) {
2213         if (duplicate)
2214             complain_about_duplicate(opts, 1);
2215         if (negated) {
2216             bad_negation(fullname, FALSE);
2217             return FALSE;
2218         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
2219                                             != empty_optstr) {
2220             nmcpy(dogname, op, PL_PSIZ);
2221         } else
2222             return FALSE;
2223         sanitize_name(dogname);
2224         return retval;
2225     }
2226 
2227     fullname = "horsename";
2228     if (match_optname(opts, fullname, 5, TRUE)) {
2229         if (duplicate)
2230             complain_about_duplicate(opts, 1);
2231         if (negated) {
2232             bad_negation(fullname, FALSE);
2233             return FALSE;
2234         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
2235                                             != empty_optstr) {
2236             nmcpy(horsename, op, PL_PSIZ);
2237         } else
2238             return FALSE;
2239         sanitize_name(horsename);
2240         return retval;
2241     }
2242 
2243     fullname = "mouse_support";
2244     if (match_optname(opts, fullname, 13, TRUE)) {
2245         boolean compat = (strlen(opts) <= 13);
2246 
2247         if (duplicate)
2248             complain_about_duplicate(opts, 1);
2249         op = string_for_opt(opts, (compat || !initial));
2250         if (op == empty_optstr) {
2251             if (compat || negated || initial) {
2252                 /* for backwards compatibility, "mouse_support" without a
2253                    value is a synonym for mouse_support:1 */
2254                 iflags.wc_mouse_support = !negated;
2255             }
2256         } else if (negated) {
2257             bad_negation(fullname, TRUE);
2258             return FALSE;
2259         } else {
2260             int mode = atoi(op);
2261 
2262             if (mode < 0 || mode > 2 || (mode == 0 && *op != '0')) {
2263                 config_error_add("Illegal %s parameter '%s'", fullname, op);
2264                 return FALSE;
2265             } else { /* mode >= 0 */
2266                 iflags.wc_mouse_support = mode;
2267             }
2268         }
2269         return retval;
2270     }
2271 
2272     fullname = "number_pad";
2273     if (match_optname(opts, fullname, 10, TRUE)) {
2274         boolean compat = (strlen(opts) <= 10);
2275 
2276         if (duplicate)
2277             complain_about_duplicate(opts, 1);
2278         op = string_for_opt(opts, (compat || !initial));
2279         if (op == empty_optstr) {
2280             if (compat || negated || initial) {
2281                 /* for backwards compatibility, "number_pad" without a
2282                    value is a synonym for number_pad:1 */
2283                 iflags.num_pad = !negated;
2284                 iflags.num_pad_mode = 0;
2285             }
2286         } else if (negated) {
2287             bad_negation(fullname, TRUE);
2288             return FALSE;
2289         } else {
2290             int mode = atoi(op);
2291 
2292             if (mode < -1 || mode > 4 || (mode == 0 && *op != '0')) {
2293                 config_error_add("Illegal %s parameter '%s'", fullname, op);
2294                 return FALSE;
2295             } else if (mode <= 0) {
2296                 iflags.num_pad = FALSE;
2297                 /* German keyboard; y and z keys swapped */
2298                 iflags.num_pad_mode = (mode < 0); /* 0 or 1 */
2299             } else {                              /* mode > 0 */
2300                 iflags.num_pad = TRUE;
2301                 iflags.num_pad_mode = 0;
2302                 /* PC Hack / MSDOS compatibility */
2303                 if (mode == 2 || mode == 4)
2304                     iflags.num_pad_mode |= 1;
2305                 /* phone keypad layout */
2306                 if (mode == 3 || mode == 4)
2307                     iflags.num_pad_mode |= 2;
2308             }
2309         }
2310         reset_commands(FALSE);
2311         number_pad(iflags.num_pad ? 1 : 0);
2312         return retval;
2313     }
2314 
2315     fullname = "roguesymset";
2316     if (match_optname(opts, fullname, 7, TRUE)) {
2317         if (duplicate)
2318             complain_about_duplicate(opts, 1);
2319         if (negated) {
2320             bad_negation(fullname, FALSE);
2321             return FALSE;
2322         } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) {
2323             symset[ROGUESET].name = dupstr(op);
2324             if (!read_sym_file(ROGUESET)) {
2325                 clear_symsetentry(ROGUESET, TRUE);
2326                 config_error_add(
2327                                "Unable to load symbol set \"%s\" from \"%s\"",
2328                                  op, SYMBOLS);
2329                 return FALSE;
2330             } else {
2331                 if (!initial && Is_rogue_level(&u.uz))
2332                     assign_graphics(ROGUESET);
2333                 need_redraw = TRUE;
2334             }
2335         } else
2336             return FALSE;
2337         return retval;
2338     }
2339 
2340     fullname = "symset";
2341     if (match_optname(opts, fullname, 6, TRUE)) {
2342         if (duplicate)
2343             complain_about_duplicate(opts, 1);
2344         if (negated) {
2345             bad_negation(fullname, FALSE);
2346             return FALSE;
2347         } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) {
2348             symset[PRIMARY].name = dupstr(op);
2349             if (!read_sym_file(PRIMARY)) {
2350                 clear_symsetentry(PRIMARY, TRUE);
2351                 config_error_add(
2352                                "Unable to load symbol set \"%s\" from \"%s\"",
2353                                  op, SYMBOLS);
2354                 return FALSE;
2355             } else {
2356                 switch_symbols(symset[PRIMARY].name != (char *) 0);
2357                 need_redraw = TRUE;
2358             }
2359         } else
2360             return FALSE;
2361         return retval;
2362     }
2363 
2364     fullname = "runmode";
2365     if (match_optname(opts, fullname, 4, TRUE)) {
2366         if (duplicate)
2367             complain_about_duplicate(opts, 1);
2368         if (negated) {
2369             flags.runmode = RUN_TPORT;
2370         } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) {
2371             if (!strncmpi(op, "teleport", strlen(op)))
2372                 flags.runmode = RUN_TPORT;
2373             else if (!strncmpi(op, "run", strlen(op)))
2374                 flags.runmode = RUN_LEAP;
2375             else if (!strncmpi(op, "walk", strlen(op)))
2376                 flags.runmode = RUN_STEP;
2377             else if (!strncmpi(op, "crawl", strlen(op)))
2378                 flags.runmode = RUN_CRAWL;
2379             else {
2380                 config_error_add("Unknown %s parameter '%s'", fullname, op);
2381                 return FALSE;
2382             }
2383         } else
2384             return FALSE;
2385         return retval;
2386     }
2387 
2388     /* menucolor:"regex_string"=color */
2389     fullname = "menucolor";
2390     if (match_optname(opts, fullname, 9, TRUE)) {
2391         if (negated) {
2392             bad_negation(fullname, FALSE);
2393             return FALSE;
2394         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
2395                                             != empty_optstr) {
2396             if (!add_menu_coloring(op))
2397                 return FALSE;
2398         } else
2399             return FALSE;
2400         return retval;
2401     }
2402 
2403     fullname = "msghistory";
2404     if (match_optname(opts, fullname, 3, TRUE)) {
2405         if (duplicate)
2406             complain_about_duplicate(opts, 1);
2407         op = string_for_env_opt(fullname, opts, negated);
2408         if ((negated && op == empty_optstr)
2409             || (!negated && op != empty_optstr)) {
2410             iflags.msg_history = negated ? 0 : atoi(op);
2411         } else if (negated) {
2412             bad_negation(fullname, TRUE);
2413             return FALSE;
2414         }
2415         return retval;
2416     }
2417 
2418     fullname = "msg_window";
2419     /* msg_window:single, combo, full or reversed */
2420     if (match_optname(opts, fullname, 4, TRUE)) {
2421 /* allow option to be silently ignored by non-tty ports */
2422 #ifdef TTY_GRAPHICS
2423         int tmp;
2424 
2425         if (duplicate)
2426             complain_about_duplicate(opts, 1);
2427         if ((op = string_for_opt(opts, TRUE)) == empty_optstr) {
2428             tmp = negated ? 's' : 'f';
2429         } else {
2430             if (negated) {
2431                 bad_negation(fullname, TRUE);
2432                 return FALSE;
2433             }
2434             tmp = lowc(*op);
2435         }
2436         switch (tmp) {
2437         case 's': /* single message history cycle (default if negated) */
2438             iflags.prevmsg_window = 's';
2439             break;
2440         case 'c': /* combination: two singles, then full page */
2441             iflags.prevmsg_window = 'c';
2442             break;
2443         case 'f': /* full page (default if specified without argument) */
2444             iflags.prevmsg_window = 'f';
2445             break;
2446         case 'r': /* full page (reversed) */
2447             iflags.prevmsg_window = 'r';
2448             break;
2449         default:
2450             config_error_add("Unknown %s parameter '%s'", fullname, op);
2451             retval = FALSE;
2452         }
2453 #endif
2454         return retval;
2455     }
2456 
2457     /* WINCAP
2458      * setting font options  */
2459     fullname = "font";
2460     if (!strncmpi(opts, fullname, 4)) {
2461         int opttype = -1;
2462         char *fontopts = opts + 4;
2463 
2464         if (!strncmpi(fontopts, "map", 3) || !strncmpi(fontopts, "_map", 4))
2465             opttype = MAP_OPTION;
2466         else if (!strncmpi(fontopts, "message", 7)
2467                  || !strncmpi(fontopts, "_message", 8))
2468             opttype = MESSAGE_OPTION;
2469         else if (!strncmpi(fontopts, "text", 4)
2470                  || !strncmpi(fontopts, "_text", 5))
2471             opttype = TEXT_OPTION;
2472         else if (!strncmpi(fontopts, "menu", 4)
2473                  || !strncmpi(fontopts, "_menu", 5))
2474             opttype = MENU_OPTION;
2475         else if (!strncmpi(fontopts, "status", 6)
2476                  || !strncmpi(fontopts, "_status", 7))
2477             opttype = STATUS_OPTION;
2478         else if (!strncmpi(fontopts, "_size", 5)) {
2479             if (!strncmpi(fontopts, "_size_map", 8))
2480                 opttype = MAP_OPTION;
2481             else if (!strncmpi(fontopts, "_size_message", 12))
2482                 opttype = MESSAGE_OPTION;
2483             else if (!strncmpi(fontopts, "_size_text", 9))
2484                 opttype = TEXT_OPTION;
2485             else if (!strncmpi(fontopts, "_size_menu", 9))
2486                 opttype = MENU_OPTION;
2487             else if (!strncmpi(fontopts, "_size_status", 11))
2488                 opttype = STATUS_OPTION;
2489             else {
2490                 config_error_add("Unknown %s parameter '%s'", fullname, opts);
2491                 return FALSE;
2492             }
2493             if (duplicate)
2494                 complain_about_duplicate(opts, 1);
2495             if (opttype > 0 && !negated
2496                 && (op = string_for_opt(opts, FALSE)) != empty_optstr) {
2497                 switch (opttype) {
2498                 case MAP_OPTION:
2499                     iflags.wc_fontsiz_map = atoi(op);
2500                     break;
2501                 case MESSAGE_OPTION:
2502                     iflags.wc_fontsiz_message = atoi(op);
2503                     break;
2504                 case TEXT_OPTION:
2505                     iflags.wc_fontsiz_text = atoi(op);
2506                     break;
2507                 case MENU_OPTION:
2508                     iflags.wc_fontsiz_menu = atoi(op);
2509                     break;
2510                 case STATUS_OPTION:
2511                     iflags.wc_fontsiz_status = atoi(op);
2512                     break;
2513                 }
2514             }
2515             return retval;
2516         } else {
2517             config_error_add("Unknown %s parameter '%s'", fullname, opts);
2518             return FALSE;
2519         }
2520         if (opttype > 0
2521             && (op = string_for_opt(opts, FALSE)) != empty_optstr) {
2522             wc_set_font_name(opttype, op);
2523 #ifdef MAC
2524             set_font_name(opttype, op);
2525 #endif
2526             return retval;
2527         } else if (negated) {
2528             bad_negation(fullname, TRUE);
2529             return FALSE;
2530         }
2531         return retval;
2532     }
2533 
2534 #ifdef CHANGE_COLOR
2535     if (match_optname(opts, "palette", 3, TRUE)
2536 #ifdef MAC
2537         || match_optname(opts, "hicolor", 3, TRUE)
2538 #endif
2539         ) {
2540         int color_number, color_incr;
2541 
2542 #ifndef WIN32
2543         if (duplicate)
2544             complain_about_duplicate(opts, 1);
2545 #endif
2546 #ifdef MAC
2547         if (match_optname(opts, "hicolor", 3, TRUE)) {
2548             if (negated) {
2549                 bad_negation("hicolor", FALSE);
2550                 return FALSE;
2551             }
2552             color_number = CLR_MAX + 4; /* HARDCODED inverse number */
2553             color_incr = -1;
2554         } else
2555 #endif
2556         {
2557             if (negated) {
2558                 bad_negation("palette", FALSE);
2559                 return FALSE;
2560             }
2561             color_number = 0;
2562             color_incr = 1;
2563         }
2564 #ifdef WIN32
2565         op = string_for_opt(opts, TRUE);
2566         if (op == empty_optstr || !alternative_palette(op)) {
2567             config_error_add("Error in palette parameter '%s'", op);
2568             return FALSE;
2569         }
2570 #else
2571         if ((op = string_for_opt(opts, FALSE)) != empty_optstr) {
2572             char *pt = op;
2573             int cnt, tmp, reverse;
2574             long rgb;
2575 
2576             while (*pt && color_number >= 0) {
2577                 cnt = 3;
2578                 rgb = 0L;
2579                 if (*pt == '-') {
2580                     reverse = 1;
2581                     pt++;
2582                 } else {
2583                     reverse = 0;
2584                 }
2585                 while (cnt-- > 0) {
2586                     if (*pt && *pt != '/') {
2587 #ifdef AMIGA
2588                         rgb <<= 4;
2589 #else
2590                         rgb <<= 8;
2591 #endif
2592                         tmp = *pt++;
2593                         if (isalpha((uchar) tmp)) {
2594                             tmp = (tmp + 9) & 0xf; /* Assumes ASCII... */
2595                         } else {
2596                             tmp &= 0xf; /* Digits in ASCII too... */
2597                         }
2598 #ifndef AMIGA
2599                         /* Add an extra so we fill f -> ff and 0 -> 00 */
2600                         rgb += tmp << 4;
2601 #endif
2602                         rgb += tmp;
2603                     }
2604                 }
2605                 if (*pt == '/')
2606                     pt++;
2607                 change_color(color_number, rgb, reverse);
2608                 color_number += color_incr;
2609             }
2610         }
2611 #endif /* !WIN32 */
2612         if (!initial) {
2613             need_redraw = TRUE;
2614         }
2615         return retval;
2616     }
2617 #endif /* CHANGE_COLOR */
2618 
2619     if (match_optname(opts, "fruit", 2, TRUE)) {
2620         struct fruit *forig = 0;
2621 
2622         if (duplicate)
2623             complain_about_duplicate(opts, 1);
2624         op = string_for_opt(opts, negated || !initial);
2625         if (negated) {
2626             if (op != empty_optstr) {
2627                 bad_negation("fruit", TRUE);
2628                 return FALSE;
2629             }
2630             op = empty_optstr;
2631             goto goodfruit;
2632         }
2633         if (op == empty_optstr)
2634             return FALSE;
2635         /* strip leading/trailing spaces, condense internal ones (3.6.2) */
2636         mungspaces(op);
2637         if (!initial) {
2638             struct fruit *f;
2639             int fnum = 0;
2640 
2641             /* count number of named fruits; if 'op' is found among them,
2642                then the count doesn't matter because we won't be adding it */
2643             f = fruit_from_name(op, FALSE, &fnum);
2644             if (!f) {
2645                 if (!flags.made_fruit)
2646                     forig = fruit_from_name(pl_fruit, FALSE, (int *) 0);
2647 
2648                 if (!forig && fnum >= 100) {
2649                     config_error_add(
2650                              "Doing that so many times isn't very fruitful.");
2651                     return retval;
2652                 }
2653             }
2654         }
2655  goodfruit:
2656         nmcpy(pl_fruit, op, PL_FSIZ);
2657         sanitize_name(pl_fruit);
2658         /* OBJ_NAME(objects[SLIME_MOLD]) won't work for this after
2659            initialization; it gets changed to generic "fruit" */
2660         if (!*pl_fruit)
2661             nmcpy(pl_fruit, "slime mold", PL_FSIZ);
2662         if (!initial) {
2663             /* if 'forig' is nonNull, we replace it rather than add
2664                a new fruit; it can only be nonNull if no fruits have
2665                been created since the previous name was put in place */
2666             (void) fruitadd(pl_fruit, forig);
2667             pline("Fruit is now \"%s\".", pl_fruit);
2668         }
2669         /* If initial, then initoptions is allowed to do it instead
2670          * of here (initoptions always has to do it even if there's
2671          * no fruit option at all.  Also, we don't want people
2672          * setting multiple fruits in their options.)
2673          */
2674         return retval;
2675     }
2676 
2677     fullname = "whatis_coord";
2678     if (match_optname(opts, fullname, 8, TRUE)) {
2679         if (duplicate)
2680             complain_about_duplicate(opts, 1);
2681         if (negated) {
2682             iflags.getpos_coords = GPCOORDS_NONE;
2683             return retval;
2684         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
2685                                             != empty_optstr) {
2686             static char gpcoords[] = { GPCOORDS_NONE, GPCOORDS_COMPASS,
2687                                        GPCOORDS_COMFULL, GPCOORDS_MAP,
2688                                        GPCOORDS_SCREEN, '\0' };
2689             char c = lowc(*op);
2690 
2691             if (c && index(gpcoords, c))
2692                 iflags.getpos_coords = c;
2693             else {
2694                 config_error_add("Unknown %s parameter '%s'", fullname, op);
2695                 return FALSE;
2696             }
2697         } else
2698             return FALSE;
2699         return retval;
2700     }
2701 
2702     fullname = "whatis_filter";
2703     if (match_optname(opts, fullname, 8, TRUE)) {
2704         if (duplicate)
2705             complain_about_duplicate(opts, 1);
2706         if (negated) {
2707             iflags.getloc_filter = GFILTER_NONE;
2708             return retval;
2709         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
2710                                             != empty_optstr) {
2711             char c = lowc(*op);
2712 
2713             switch (c) {
2714             case 'n':
2715                 iflags.getloc_filter = GFILTER_NONE;
2716                 break;
2717             case 'v':
2718                 iflags.getloc_filter = GFILTER_VIEW;
2719                 break;
2720             case 'a':
2721                 iflags.getloc_filter = GFILTER_AREA;
2722                 break;
2723             default: {
2724                 config_error_add("Unknown %s parameter '%s'", fullname, op);
2725                 return FALSE;
2726             }
2727             }
2728         } else
2729             return FALSE;
2730         return retval;
2731     }
2732 
2733     fullname = "warnings";
2734     if (match_optname(opts, fullname, 5, TRUE)) {
2735         if (duplicate)
2736             complain_about_duplicate(opts, 1);
2737         if (negated) {
2738             bad_negation(fullname, FALSE);
2739             return FALSE;
2740         }
2741         return warning_opts(opts, fullname);
2742     }
2743 
2744     /* boulder:symbol */
2745     fullname = "boulder";
2746     if (match_optname(opts, fullname, 7, TRUE)) {
2747 #ifdef BACKWARD_COMPAT
2748         int clash = 0;
2749 
2750         if (duplicate)
2751             complain_about_duplicate(opts, 1);
2752         if (negated) {
2753             bad_negation(fullname, FALSE);
2754             return FALSE;
2755         }
2756         /* if ((opts = string_for_env_opt(fullname, opts, FALSE))
2757                                           == empty_optstr)
2758          */
2759         if ((opts = string_for_opt(opts, FALSE)) == empty_optstr)
2760             return FALSE;
2761         escapes(opts, opts);
2762         /* note: dummy monclass #0 has symbol value '\0'; we allow that--
2763            attempting to set bouldersym to '^@'/'\0' will reset to default */
2764         if (def_char_to_monclass(opts[0]) != MAXMCLASSES)
2765             clash = opts[0] ? 1 : 0;
2766         else if (opts[0] >= '1' && opts[0] < WARNCOUNT + '0')
2767             clash = 2;
2768         if (clash) {
2769             /* symbol chosen matches a used monster or warning
2770                symbol which is not good - reject it */
2771             config_error_add(
2772             "Badoption - boulder symbol '%s' would conflict with a %s symbol",
2773                              visctrl(opts[0]),
2774                              (clash == 1) ? "monster" : "warning");
2775         } else {
2776             /*
2777              * Override the default boulder symbol.
2778              */
2779             ov_primary_syms[SYM_BOULDER + SYM_OFF_X] = (nhsym) opts[0];
2780             ov_rogue_syms[SYM_BOULDER + SYM_OFF_X] = (nhsym) opts[0];
2781             /* for 'initial', update of BOULDER symbol is done in
2782                initoptions_finish(), after all symset options
2783                have been processed */
2784             if (!initial) {
2785                 nhsym sym = get_othersym(SYM_BOULDER,
2786                                 Is_rogue_level(&u.uz) ? ROGUESET : PRIMARY);
2787                 if (sym)
2788                     showsyms[SYM_BOULDER + SYM_OFF_X] = sym;
2789                 need_redraw = TRUE;
2790             }
2791         }
2792         return retval;
2793 #else
2794         config_error_add("'%s' no longer supported; use S_boulder:c instead",
2795                          fullname);
2796         return FALSE;
2797 #endif
2798     }
2799 
2800     /* name:string */
2801     fullname = "name";
2802     if (match_optname(opts, fullname, 4, TRUE)) {
2803         if (duplicate)
2804             complain_about_duplicate(opts, 1);
2805         if (negated) {
2806             bad_negation(fullname, FALSE);
2807             return FALSE;
2808         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
2809                                             != empty_optstr) {
2810             nmcpy(plname, op, PL_NSIZ);
2811         } else
2812             return FALSE;
2813         return retval;
2814     }
2815 
2816     /* altkeyhandler:string */
2817     fullname = "altkeyhandler";
2818     if (match_optname(opts, fullname, 4, TRUE)) {
2819         if (duplicate)
2820             complain_about_duplicate(opts, 1);
2821         if (negated) {
2822             bad_negation(fullname, FALSE);
2823             return FALSE;
2824         } else if ((op = string_for_opt(opts, negated)) != empty_optstr) {
2825 #if defined(WIN32) && defined(TTY_GRAPHICS)
2826             set_altkeyhandler(op);
2827 #endif
2828         } else
2829             return FALSE;
2830         return retval;
2831     }
2832 
2833     /* WINCAP
2834      * align_status:[left|top|right|bottom] */
2835     fullname = "align_status";
2836     if (match_optname(opts, fullname, sizeof "align_status" - 1, TRUE)) {
2837         op = string_for_opt(opts, negated);
2838         if ((op != empty_optstr) && !negated) {
2839             if (!strncmpi(op, "left", sizeof "left" - 1))
2840                 iflags.wc_align_status = ALIGN_LEFT;
2841             else if (!strncmpi(op, "top", sizeof "top" - 1))
2842                 iflags.wc_align_status = ALIGN_TOP;
2843             else if (!strncmpi(op, "right", sizeof "right" - 1))
2844                 iflags.wc_align_status = ALIGN_RIGHT;
2845             else if (!strncmpi(op, "bottom", sizeof "bottom" - 1))
2846                 iflags.wc_align_status = ALIGN_BOTTOM;
2847             else {
2848                 config_error_add("Unknown %s parameter '%s'", fullname, op);
2849                 return FALSE;
2850             }
2851         } else if (negated) {
2852             bad_negation(fullname, TRUE);
2853             return FALSE;
2854         }
2855         return retval;
2856     }
2857 
2858     /* WINCAP
2859      * align_message:[left|top|right|bottom] */
2860     fullname = "align_message";
2861     if (match_optname(opts, fullname, sizeof "align_message" - 1, TRUE)) {
2862         if (duplicate)
2863             complain_about_duplicate(opts, 1);
2864         op = string_for_opt(opts, negated);
2865         if ((op != empty_optstr) && !negated) {
2866             if (!strncmpi(op, "left", sizeof "left" - 1))
2867                 iflags.wc_align_message = ALIGN_LEFT;
2868             else if (!strncmpi(op, "top", sizeof "top" - 1))
2869                 iflags.wc_align_message = ALIGN_TOP;
2870             else if (!strncmpi(op, "right", sizeof "right" - 1))
2871                 iflags.wc_align_message = ALIGN_RIGHT;
2872             else if (!strncmpi(op, "bottom", sizeof "bottom" - 1))
2873                 iflags.wc_align_message = ALIGN_BOTTOM;
2874             else {
2875                 config_error_add("Unknown %s parameter '%s'", fullname, op);
2876                 return FALSE;
2877             }
2878         } else if (negated) {
2879             bad_negation(fullname, TRUE);
2880             return FALSE;
2881         }
2882         return retval;
2883     }
2884 
2885     /* the order to list inventory */
2886     fullname = "packorder";
2887     if (match_optname(opts, fullname, 4, TRUE)) {
2888         if (duplicate)
2889             complain_about_duplicate(opts, 1);
2890         if (negated) {
2891             bad_negation(fullname, FALSE);
2892             return FALSE;
2893         } else if ((op = string_for_opt(opts, FALSE)) == empty_optstr)
2894             return FALSE;
2895 
2896         if (!change_inv_order(op))
2897             return FALSE;
2898         return retval;
2899     }
2900 
2901     /* user can change required response for some prompts (quit, die, hit),
2902        or add an extra prompt (pray, Remove) that isn't ordinarily there */
2903     fullname = "paranoid_confirmation";
2904     if (match_optname(opts, fullname, 8, TRUE)) {
2905         /* at present we don't complain about duplicates for this
2906            option, but we do throw away the old settings whenever
2907            we process a new one [clearing old flags is essential
2908            for handling default paranoid_confirm:pray sanely] */
2909         flags.paranoia_bits = 0; /* clear all */
2910         if (negated) {
2911             flags.paranoia_bits = 0; /* [now redundant...] */
2912         } else if ((op = string_for_opt(opts, TRUE)) != empty_optstr) {
2913             char *pp, buf[BUFSZ];
2914 
2915             strncpy(buf, op, sizeof buf - 1);
2916             buf[sizeof buf - 1] = '\0';
2917             op = mungspaces(buf);
2918             for (;;) {
2919                 /* We're looking to parse
2920                    "paranoid_confirm:whichone wheretwo whothree"
2921                    and "paranoid_confirm:" prefix has already
2922                    been stripped off by the time we get here */
2923                 pp = index(op, ' ');
2924                 if (pp)
2925                     *pp = '\0';
2926                 /* we aren't matching option names but match_optname()
2927                    does what we want once we've broken the space
2928                    delimited aggregate into separate tokens */
2929                 for (i = 0; i < SIZE(paranoia); ++i) {
2930                     if (match_optname(op, paranoia[i].argname,
2931                                       paranoia[i].argMinLen, FALSE)
2932                         || (paranoia[i].synonym
2933                             && match_optname(op, paranoia[i].synonym,
2934                                              paranoia[i].synMinLen, FALSE))) {
2935                         if (paranoia[i].flagmask)
2936                             flags.paranoia_bits |= paranoia[i].flagmask;
2937                         else /* 0 == "none", so clear all */
2938                             flags.paranoia_bits = 0;
2939                         break;
2940                     }
2941                 }
2942                 if (i == SIZE(paranoia)) {
2943                     /* didn't match anything, so arg is bad;
2944                        any flags already set will stay set */
2945                     config_error_add("Unknown %s parameter '%s'",
2946                                      fullname, op);
2947                     return FALSE;
2948                 }
2949                 /* move on to next token */
2950                 if (pp)
2951                     op = pp + 1;
2952                 else
2953                     break; /* no next token */
2954             } /* for(;;) */
2955         } else
2956             return FALSE;
2957         return retval;
2958     }
2959 
2960     /* accept deprecated boolean; superseded by paranoid_confirm:pray */
2961     fullname = "prayconfirm";
2962     if (match_optname(opts, fullname, 4, FALSE)) {
2963         if (negated)
2964             flags.paranoia_bits &= ~PARANOID_PRAY;
2965         else
2966             flags.paranoia_bits |= PARANOID_PRAY;
2967         return retval;
2968     }
2969 
2970     /* maximum burden picked up before prompt (Warren Cheung) */
2971     fullname = "pickup_burden";
2972     if (match_optname(opts, fullname, 8, TRUE)) {
2973         if (duplicate)
2974             complain_about_duplicate(opts, 1);
2975         if (negated) {
2976             bad_negation(fullname, FALSE);
2977             return FALSE;
2978         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
2979                                             != empty_optstr) {
2980             switch (lowc(*op)) {
2981             case 'u': /* Unencumbered */
2982                 flags.pickup_burden = UNENCUMBERED;
2983                 break;
2984             case 'b': /* Burdened (slight encumbrance) */
2985                 flags.pickup_burden = SLT_ENCUMBER;
2986                 break;
2987             case 's': /* streSsed (moderate encumbrance) */
2988                 flags.pickup_burden = MOD_ENCUMBER;
2989                 break;
2990             case 'n': /* straiNed (heavy encumbrance) */
2991                 flags.pickup_burden = HVY_ENCUMBER;
2992                 break;
2993             case 'o': /* OverTaxed (extreme encumbrance) */
2994             case 't':
2995                 flags.pickup_burden = EXT_ENCUMBER;
2996                 break;
2997             case 'l': /* overLoaded */
2998                 flags.pickup_burden = OVERLOADED;
2999                 break;
3000             default:
3001                 config_error_add("Unknown %s parameter '%s'", fullname, op);
3002                 return FALSE;
3003             }
3004         } else
3005             return FALSE;
3006         return retval;
3007     }
3008 
3009     /* types of objects to pick up automatically */
3010     fullname = "pickup_types";
3011     if (match_optname(opts, fullname, 8, TRUE)) {
3012         char ocl[MAXOCLASSES + 1], tbuf[MAXOCLASSES + 1],
3013              qbuf[QBUFSZ], abuf[BUFSZ];
3014         int oc_sym;
3015         boolean badopt = FALSE, compat = (strlen(opts) <= 6), use_menu;
3016 
3017         if (duplicate)
3018             complain_about_duplicate(opts, 1);
3019         oc_to_str(flags.pickup_types, tbuf);
3020         flags.pickup_types[0] = '\0'; /* all */
3021         op = string_for_opt(opts, (compat || !initial));
3022         if (op == empty_optstr) {
3023             if (compat || negated || initial) {
3024                 /* for backwards compatibility, "pickup" without a
3025                    value is a synonym for autopickup of all types
3026                    (and during initialization, we can't prompt yet) */
3027                 flags.pickup = !negated;
3028                 return retval;
3029             }
3030             oc_to_str(flags.inv_order, ocl);
3031             use_menu = TRUE;
3032             if (flags.menu_style == MENU_TRADITIONAL
3033                 || flags.menu_style == MENU_COMBINATION) {
3034                 boolean wasspace;
3035 
3036                 use_menu = FALSE;
3037                 Sprintf(qbuf, "New %s: [%s am] (%s)", fullname, ocl,
3038                         *tbuf ? tbuf : "all");
3039                 abuf[0] = '\0';
3040                 getlin(qbuf, abuf);
3041                 wasspace = (abuf[0] == ' '); /* before mungspaces */
3042                 op = mungspaces(abuf);
3043                 if (wasspace && !abuf[0])
3044                     ; /* one or more spaces will remove old value */
3045                 else if (!abuf[0] || abuf[0] == '\033')
3046                     op = tbuf; /* restore */
3047                 else if (abuf[0] == 'm')
3048                     use_menu = TRUE;
3049                 /* note: abuf[0]=='a' is already handled via clearing the
3050                    the old value (above) as a default action */
3051             }
3052             if (use_menu) {
3053                 if (wizard && !index(ocl, VENOM_SYM))
3054                     strkitten(ocl, VENOM_SYM);
3055                 (void) choose_classes_menu("Autopickup what?", 1, TRUE, ocl,
3056                                            tbuf);
3057                 op = tbuf;
3058             }
3059         }
3060         if (negated) {
3061             bad_negation(fullname, TRUE);
3062             return FALSE;
3063         }
3064         while (*op == ' ')
3065             op++;
3066         if (*op != 'a' && *op != 'A') {
3067             num = 0;
3068             while (*op) {
3069                 oc_sym = def_char_to_objclass(*op);
3070                 /* make sure all are valid obj symbols occurring once */
3071                 if (oc_sym != MAXOCLASSES
3072                     && !index(flags.pickup_types, oc_sym)) {
3073                     flags.pickup_types[num] = (char) oc_sym;
3074                     flags.pickup_types[++num] = '\0';
3075                 } else
3076                     badopt = TRUE;
3077                 op++;
3078             }
3079             if (badopt) {
3080                 config_error_add("Unknown %s parameter '%s'", fullname, op);
3081                 return FALSE;
3082             }
3083         }
3084         return retval;
3085     }
3086 
3087     /* pile limit: when walking over objects, number which triggers
3088        "there are several/many objects here" instead of listing them */
3089     fullname = "pile_limit";
3090     if (match_optname(opts, fullname, 4, TRUE)) {
3091         if (duplicate)
3092             complain_about_duplicate(opts, 1);
3093         op = string_for_opt(opts, negated);
3094         if ((negated && op == empty_optstr)
3095             || (!negated && op != empty_optstr))
3096             flags.pile_limit = negated ? 0 : atoi(op);
3097         else if (negated) {
3098             bad_negation(fullname, TRUE);
3099             return FALSE;
3100         } else /* op == empty_optstr */
3101             flags.pile_limit = PILE_LIMIT_DFLT;
3102         /* sanity check */
3103         if (flags.pile_limit < 0)
3104             flags.pile_limit = PILE_LIMIT_DFLT;
3105         return retval;
3106     }
3107 
3108     /* play mode: normal, explore/discovery, or debug/wizard */
3109     fullname = "playmode";
3110     if (match_optname(opts, fullname, 4, TRUE)) {
3111         if (duplicate)
3112             complain_about_duplicate(opts, 1);
3113         if (negated)
3114             bad_negation(fullname, FALSE);
3115         if (duplicate || negated)
3116             return FALSE;
3117         op = string_for_opt(opts, FALSE);
3118         if (op == empty_optstr)
3119             return FALSE;
3120         if (!strncmpi(op, "normal", 6) || !strcmpi(op, "play")) {
3121             wizard = discover = FALSE;
3122         } else if (!strncmpi(op, "explore", 6)
3123                    || !strncmpi(op, "discovery", 6)) {
3124             wizard = FALSE, discover = TRUE;
3125         } else if (!strncmpi(op, "debug", 5) || !strncmpi(op, "wizard", 6)) {
3126             wizard = TRUE, discover = FALSE;
3127         } else {
3128             config_error_add("Invalid value for \"%s\":%s", fullname, op);
3129             return FALSE;
3130         }
3131         return retval;
3132     }
3133 
3134     /* WINCAP
3135      * player_selection: dialog | prompt/prompts/prompting */
3136     fullname = "player_selection";
3137     if (match_optname(opts, fullname, sizeof "player_selection" - 1, TRUE)) {
3138         if (duplicate)
3139             complain_about_duplicate(opts, 1);
3140         op = string_for_opt(opts, negated);
3141         if (op != empty_optstr && !negated) {
3142             if (!strncmpi(op, "dialog", sizeof "dialog" - 1)) {
3143                 iflags.wc_player_selection = VIA_DIALOG;
3144             } else if (!strncmpi(op, "prompt", sizeof "prompt" - 1)) {
3145                 iflags.wc_player_selection = VIA_PROMPTS;
3146             } else {
3147                 config_error_add("Unknown %s parameter '%s'", fullname, op);
3148                 return FALSE;
3149             }
3150         } else if (negated) {
3151             bad_negation(fullname, TRUE);
3152             return FALSE;
3153         }
3154         return retval;
3155     }
3156 
3157     /* things to disclose at end of game */
3158     fullname = "disclose";
3159     if (match_optname(opts, fullname, 7, TRUE)) {
3160         /*
3161          * The order that the end_disclose options are stored:
3162          *      inventory, attribs, vanquished, genocided,
3163          *      conduct, overview.
3164          * There is an array in flags:
3165          *      end_disclose[NUM_DISCLOSURE_OPT];
3166          * with option settings for the each of the following:
3167          * iagvc [see disclosure_options in decl.c]:
3168          * Allowed setting values in that array are:
3169          *      DISCLOSE_PROMPT_DEFAULT_YES  ask with default answer yes
3170          *      DISCLOSE_PROMPT_DEFAULT_NO   ask with default answer no
3171          *      DISCLOSE_YES_WITHOUT_PROMPT  always disclose and don't ask
3172          *      DISCLOSE_NO_WITHOUT_PROMPT   never disclose and don't ask
3173          *      DISCLOSE_PROMPT_DEFAULT_SPECIAL  for 'vanquished' only...
3174          *      DISCLOSE_SPECIAL_WITHOUT_PROMPT  ...to set up sort order.
3175          *
3176          * Those setting values can be used in the option
3177          * string as a prefix to get the desired behaviour.
3178          *
3179          * For backward compatibility, no prefix is required,
3180          * and the presence of a i,a,g,v, or c without a prefix
3181          * sets the corresponding value to DISCLOSE_YES_WITHOUT_PROMPT.
3182          */
3183         int idx, prefix_val;
3184 
3185         if (duplicate)
3186             complain_about_duplicate(opts, 1);
3187         op = string_for_opt(opts, TRUE);
3188         if (op != empty_optstr && negated) {
3189             bad_negation(fullname, TRUE);
3190             return FALSE;
3191         }
3192         /* "disclose" without a value means "all with prompting"
3193            and negated means "none without prompting" */
3194         if (op == empty_optstr
3195             || !strcmpi(op, "all") || !strcmpi(op, "none")) {
3196             if (op != empty_optstr && !strcmpi(op, "none"))
3197                 negated = TRUE;
3198             for (num = 0; num < NUM_DISCLOSURE_OPTIONS; num++)
3199                 flags.end_disclose[num] = negated
3200                                               ? DISCLOSE_NO_WITHOUT_PROMPT
3201                                               : DISCLOSE_PROMPT_DEFAULT_YES;
3202             return retval;
3203         }
3204 
3205         num = 0;
3206         prefix_val = -1;
3207         while (*op && num < sizeof flags.end_disclose - 1) {
3208             static char valid_settings[] = {
3209                 DISCLOSE_PROMPT_DEFAULT_YES, DISCLOSE_PROMPT_DEFAULT_NO,
3210                 DISCLOSE_PROMPT_DEFAULT_SPECIAL,
3211                 DISCLOSE_YES_WITHOUT_PROMPT, DISCLOSE_NO_WITHOUT_PROMPT,
3212                 DISCLOSE_SPECIAL_WITHOUT_PROMPT, '\0'
3213             };
3214             register char c, *dop;
3215 
3216             c = lowc(*op);
3217             if (c == 'k')
3218                 c = 'v'; /* killed -> vanquished */
3219             if (c == 'd')
3220                 c = 'o'; /* dungeon -> overview */
3221             dop = index(disclosure_options, c);
3222             if (dop) {
3223                 idx = (int) (dop - disclosure_options);
3224                 if (idx < 0 || idx > NUM_DISCLOSURE_OPTIONS - 1) {
3225                     impossible("bad disclosure index %d %c", idx, c);
3226                     continue;
3227                 }
3228                 if (prefix_val != -1) {
3229                     if (*dop != 'v') {
3230                         if (prefix_val == DISCLOSE_PROMPT_DEFAULT_SPECIAL)
3231                             prefix_val = DISCLOSE_PROMPT_DEFAULT_YES;
3232                         if (prefix_val == DISCLOSE_SPECIAL_WITHOUT_PROMPT)
3233                             prefix_val = DISCLOSE_YES_WITHOUT_PROMPT;
3234                     }
3235                     flags.end_disclose[idx] = prefix_val;
3236                     prefix_val = -1;
3237                 } else
3238                     flags.end_disclose[idx] = DISCLOSE_YES_WITHOUT_PROMPT;
3239             } else if (index(valid_settings, c)) {
3240                 prefix_val = c;
3241             } else if (c == ' ') {
3242                 ; /* do nothing */
3243             } else {
3244                 config_error_add("Unknown %s parameter '%c'", fullname, *op);
3245                 return FALSE;
3246             }
3247             op++;
3248         }
3249         return retval;
3250     }
3251 
3252     /* scores:5t[op] 5a[round] o[wn] */
3253     fullname = "scores";
3254     if (match_optname(opts, fullname, 4, TRUE)) {
3255         if (duplicate)
3256             complain_about_duplicate(opts, 1);
3257         if (negated) {
3258             bad_negation(fullname, FALSE);
3259             return FALSE;
3260         }
3261         if ((op = string_for_opt(opts, FALSE)) == empty_optstr)
3262             return FALSE;
3263 
3264         while (*op) {
3265             int inum = 1;
3266 
3267             if (digit(*op)) {
3268                 inum = atoi(op);
3269                 while (digit(*op))
3270                     op++;
3271             } else if (*op == '!') {
3272                 negated = !negated;
3273                 op++;
3274             }
3275             while (*op == ' ')
3276                 op++;
3277 
3278             switch (*op) {
3279             case 't':
3280             case 'T':
3281                 flags.end_top = inum;
3282                 break;
3283             case 'a':
3284             case 'A':
3285                 flags.end_around = inum;
3286                 break;
3287             case 'o':
3288             case 'O':
3289                 flags.end_own = !negated;
3290                 break;
3291             default:
3292                 config_error_add("Unknown %s parameter '%s'", fullname, op);
3293                 return FALSE;
3294             }
3295             /* "3a" is sufficient but accept "3around" (or "3abracadabra") */
3296             while (letter(*op))
3297                 op++;
3298             /* t, a, and o can be separated by space(s) or slash or both */
3299             while (*op == ' ')
3300                 op++;
3301             if (*op == '/')
3302                 op++;
3303         }
3304         return retval;
3305     }
3306 
3307     fullname = "sortloot";
3308     if (match_optname(opts, fullname, 4, TRUE)) {
3309         op = string_for_env_opt(fullname, opts, FALSE);
3310         if (op != empty_optstr) {
3311             char c = lowc(*op);
3312 
3313             switch (c) {
3314             case 'n': /* none */
3315             case 'l': /* loot (pickup) */
3316             case 'f': /* full (pickup + invent) */
3317                 flags.sortloot = c;
3318                 break;
3319             default:
3320                 config_error_add("Unknown %s parameter '%s'", fullname, op);
3321                 return FALSE;
3322             }
3323         } else
3324             return FALSE;
3325         return retval;
3326     }
3327 
3328     fullname = "suppress_alert";
3329     if (match_optname(opts, fullname, 4, TRUE)) {
3330         if (duplicate)
3331             complain_about_duplicate(opts, 1);
3332         op = string_for_opt(opts, negated);
3333         if (negated) {
3334             bad_negation(fullname, FALSE);
3335             return FALSE;
3336         } else if (op != empty_optstr)
3337             (void) feature_alert_opts(op, fullname);
3338         return retval;
3339     }
3340 
3341 #ifdef VIDEOSHADES
3342     /* videocolors:string */
3343     fullname = "videocolors";
3344     if (match_optname(opts, fullname, 6, TRUE)
3345         || match_optname(opts, "videocolours", 10, TRUE)) {
3346         if (duplicate)
3347             complain_about_duplicate(opts, 1);
3348         if (negated) {
3349             bad_negation(fullname, FALSE);
3350             return FALSE;
3351         } else if ((opts = string_for_env_opt(fullname, opts, FALSE))
3352                                               == empty_optstr) {
3353             return FALSE;
3354         }
3355         if (!assign_videocolors(opts)) {
3356             config_error_add("Unknown error handling '%s'", fullname);
3357             return FALSE;
3358         }
3359         return retval;
3360     }
3361     /* videoshades:string */
3362     fullname = "videoshades";
3363     if (match_optname(opts, fullname, 6, TRUE)) {
3364         if (duplicate)
3365             complain_about_duplicate(opts, 1);
3366         if (negated) {
3367             bad_negation(fullname, FALSE);
3368             return FALSE;
3369         } else if ((opts = string_for_env_opt(fullname, opts, FALSE))
3370                                               == empty_optstr) {
3371             return FALSE;
3372         }
3373         if (!assign_videoshades(opts)) {
3374             config_error_add("Unknown error handling '%s'", fullname);
3375             return FALSE;
3376         }
3377         return retval;
3378     }
3379 #endif /* VIDEOSHADES */
3380 
3381 #ifdef MSDOS
3382 #ifdef NO_TERMS
3383     /* video:string -- must be after longer tests */
3384     fullname = "video";
3385     if (match_optname(opts, fullname, 5, TRUE)) {
3386         if (duplicate)
3387             complain_about_duplicate(opts, 1);
3388         if (negated) {
3389             bad_negation(fullname, FALSE);
3390             return FALSE;
3391         } else if ((opts = string_for_env_opt(fullname, opts, FALSE))
3392                                               == empty_optstr) {
3393             return FALSE;
3394         }
3395         if (!assign_video(opts)) {
3396             config_error_add("Unknown error handling '%s'", fullname);
3397             return FALSE;
3398         }
3399         return retval;
3400     }
3401 #endif /* NO_TERMS */
3402     /* soundcard:string -- careful not to match boolean 'sound' */
3403     fullname = "soundcard";
3404     if (match_optname(opts, fullname, 6, TRUE)) {
3405         if (duplicate)
3406             complain_about_duplicate(opts, 1);
3407         if (negated) {
3408             bad_negation(fullname, FALSE);
3409             return FALSE;
3410         } else if ((opts = string_for_env_opt(fullname, opts, FALSE))
3411                                               == empty_optstr) {
3412             return FALSE;
3413         }
3414         if (!assign_soundcard(opts)) {
3415             config_error_add("Unknown error handling '%s'", fullname);
3416             return FALSE;
3417         }
3418         return retval;
3419     }
3420 #endif /* MSDOS */
3421 
3422     /* WINCAP
3423      *
3424      *  map_mode:[tiles|ascii4x6|ascii6x8|ascii8x8|ascii16x8|ascii7x12
3425      *            |ascii8x12|ascii16x12|ascii12x16|ascii10x18|fit_to_screen
3426      *            |ascii_fit_to_screen|tiles_fit_to_screen]
3427      */
3428     fullname = "map_mode";
3429     if (match_optname(opts, fullname, sizeof "map_mode" - 1, TRUE)) {
3430         if (duplicate)
3431             complain_about_duplicate(opts, 1);
3432         op = string_for_opt(opts, negated);
3433         if (op != empty_optstr && !negated) {
3434             if (!strcmpi(op, "tiles"))
3435                 iflags.wc_map_mode = MAP_MODE_TILES;
3436             else if (!strncmpi(op, "ascii4x6", sizeof "ascii4x6" - 1))
3437                 iflags.wc_map_mode = MAP_MODE_ASCII4x6;
3438             else if (!strncmpi(op, "ascii6x8", sizeof "ascii6x8" - 1))
3439                 iflags.wc_map_mode = MAP_MODE_ASCII6x8;
3440             else if (!strncmpi(op, "ascii8x8", sizeof "ascii8x8" - 1))
3441                 iflags.wc_map_mode = MAP_MODE_ASCII8x8;
3442             else if (!strncmpi(op, "ascii16x8", sizeof "ascii16x8" - 1))
3443                 iflags.wc_map_mode = MAP_MODE_ASCII16x8;
3444             else if (!strncmpi(op, "ascii7x12", sizeof "ascii7x12" - 1))
3445                 iflags.wc_map_mode = MAP_MODE_ASCII7x12;
3446             else if (!strncmpi(op, "ascii8x12", sizeof "ascii8x12" - 1))
3447                 iflags.wc_map_mode = MAP_MODE_ASCII8x12;
3448             else if (!strncmpi(op, "ascii16x12", sizeof "ascii16x12" - 1))
3449                 iflags.wc_map_mode = MAP_MODE_ASCII16x12;
3450             else if (!strncmpi(op, "ascii12x16", sizeof "ascii12x16" - 1))
3451                 iflags.wc_map_mode = MAP_MODE_ASCII12x16;
3452             else if (!strncmpi(op, "ascii10x18", sizeof "ascii10x18" - 1))
3453                 iflags.wc_map_mode = MAP_MODE_ASCII10x18;
3454             else if (!strncmpi(op, "fit_to_screen",
3455                                sizeof "fit_to_screen" - 1))
3456                 iflags.wc_map_mode = MAP_MODE_ASCII_FIT_TO_SCREEN;
3457             else if (!strncmpi(op, "ascii_fit_to_screen",
3458                                sizeof "ascii_fit_to_screen" - 1))
3459                 iflags.wc_map_mode = MAP_MODE_ASCII_FIT_TO_SCREEN;
3460             else if (!strncmpi(op, "tiles_fit_to_screen",
3461                                sizeof "tiles_fit_to_screen" - 1))
3462                 iflags.wc_map_mode = MAP_MODE_TILES_FIT_TO_SCREEN;
3463             else {
3464                 config_error_add("Unknown %s parameter '%s'", fullname, op);
3465                 return FALSE;
3466             }
3467         } else if (negated) {
3468             bad_negation(fullname, TRUE);
3469             return FALSE;
3470         }
3471         return retval;
3472     }
3473 
3474     /* WINCAP
3475      * scroll_amount:nn */
3476     fullname = "scroll_amount";
3477     if (match_optname(opts, fullname, sizeof "scroll_amount" - 1, TRUE)) {
3478         if (duplicate)
3479             complain_about_duplicate(opts, 1);
3480         op = string_for_opt(opts, negated);
3481         if ((negated && op == empty_optstr)
3482             || (!negated && op != empty_optstr)) {
3483             iflags.wc_scroll_amount = negated ? 1 : atoi(op);
3484         } else if (negated) {
3485             bad_negation(fullname, TRUE);
3486             return FALSE;
3487         }
3488         return retval;
3489     }
3490 
3491     /* WINCAP
3492      * scroll_margin:nn */
3493     fullname = "scroll_margin";
3494     if (match_optname(opts, fullname, sizeof "scroll_margin" - 1, TRUE)) {
3495         if (duplicate)
3496             complain_about_duplicate(opts, 1);
3497         op = string_for_opt(opts, negated);
3498         if ((negated && op == empty_optstr)
3499             || (!negated && op != empty_optstr)) {
3500             iflags.wc_scroll_margin = negated ? 5 : atoi(op);
3501         } else if (negated) {
3502             bad_negation(fullname, TRUE);
3503             return FALSE;
3504         }
3505         return retval;
3506     }
3507 
3508     fullname = "subkeyvalue";
3509     if (match_optname(opts, fullname, 5, TRUE)) {
3510         /* no duplicate complaint here */
3511         if (negated) {
3512             bad_negation(fullname, FALSE);
3513             return FALSE;
3514 #if defined(WIN32)
3515         } else {
3516             op = string_for_opt(opts, 0);
3517             if (op == empty_optstr)
3518                 return FALSE;
3519 #ifdef TTY_GRAPHICS
3520             map_subkeyvalue(op);
3521 #endif
3522 #endif
3523         }
3524         return retval;
3525     }
3526 
3527     /* WINCAP
3528      * tile_width:nn */
3529     fullname = "tile_width";
3530     if (match_optname(opts, fullname, sizeof "tile_width" - 1, TRUE)) {
3531         if (duplicate)
3532             complain_about_duplicate(opts, 1);
3533         op = string_for_opt(opts, negated);
3534         if ((negated && op == empty_optstr)
3535             || (!negated && op != empty_optstr)) {
3536             iflags.wc_tile_width = negated ? 0 : atoi(op);
3537         } else if (negated) {
3538             bad_negation(fullname, TRUE);
3539             return FALSE;
3540         }
3541         return retval;
3542     }
3543     /* WINCAP
3544      * tile_file:name */
3545     fullname = "tile_file";
3546     if (match_optname(opts, fullname, sizeof "tile_file" - 1, TRUE)) {
3547         if (duplicate)
3548             complain_about_duplicate(opts, 1);
3549         if ((op = string_for_opt(opts, FALSE)) != empty_optstr) {
3550             if (iflags.wc_tile_file)
3551                 free(iflags.wc_tile_file);
3552             iflags.wc_tile_file = dupstr(op);
3553         } else
3554             return FALSE;
3555         return retval;
3556     }
3557     /* WINCAP
3558      * tile_height:nn */
3559     fullname = "tile_height";
3560     if (match_optname(opts, fullname, sizeof "tile_height" - 1, TRUE)) {
3561         if (duplicate)
3562             complain_about_duplicate(opts, 1);
3563         op = string_for_opt(opts, negated);
3564         if ((negated && op == empty_optstr)
3565             || (!negated && op != empty_optstr)) {
3566             iflags.wc_tile_height = negated ? 0 : atoi(op);
3567         } else if (negated) {
3568             bad_negation(fullname, TRUE);
3569             return FALSE;
3570         }
3571         return retval;
3572     }
3573 
3574     /* WINCAP
3575      * vary_msgcount:nn */
3576     fullname = "vary_msgcount";
3577     if (match_optname(opts, fullname, sizeof "vary_msgcount" - 1, TRUE)) {
3578         if (duplicate)
3579             complain_about_duplicate(opts, 1);
3580         op = string_for_opt(opts, negated);
3581         if ((negated && op == empty_optstr)
3582             || (!negated && op != empty_optstr)) {
3583             iflags.wc_vary_msgcount = negated ? 0 : atoi(op);
3584         } else if (negated) {
3585             bad_negation(fullname, TRUE);
3586             return FALSE;
3587         }
3588         return retval;
3589     }
3590 
3591     /*
3592      * windowtype:  option to choose the interface for binaries built
3593      * with support for more than one interface (tty + X11, for instance).
3594      *
3595      * Ideally, 'windowtype' should be processed first, because it
3596      * causes the wc_ and wc2_ flags to be set up.
3597      * For user, making it be first in a config file is trivial, use
3598      * OPTIONS=windowtype:Foo
3599      * as the first non-comment line of the file.
3600      * Making it first in NETHACKOPTIONS requires it to be at the _end_
3601      * because comma-separated option strings are processed from right
3602      * to left.
3603      */
3604     fullname = "windowtype";
3605     if (match_optname(opts, fullname, 3, TRUE)) {
3606         if (iflags.windowtype_locked)
3607             return retval;
3608         if (duplicate)
3609             complain_about_duplicate(opts, 1);
3610         if (negated) {
3611             bad_negation(fullname, FALSE);
3612             return FALSE;
3613         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
3614                                             != empty_optstr) {
3615             if (!iflags.windowtype_deferred) {
3616                 char buf[WINTYPELEN];
3617 
3618                 nmcpy(buf, op, WINTYPELEN);
3619                 choose_windows(buf);
3620             } else {
3621                 nmcpy(chosen_windowtype, op, WINTYPELEN);
3622             }
3623         } else
3624             return FALSE;
3625         return retval;
3626     }
3627 
3628 #ifdef WINCHAIN
3629     fullname = "windowchain";
3630     if (match_optname(opts, fullname, 3, TRUE)) {
3631         if (negated) {
3632             bad_negation(fullname, FALSE);
3633             return FALSE;
3634         } else if ((op = string_for_env_opt(fullname, opts, FALSE))
3635                                             != empty_optstr) {
3636             char buf[WINTYPELEN];
3637 
3638             nmcpy(buf, op, WINTYPELEN);
3639             addto_windowchain(buf);
3640         } else
3641             return FALSE;
3642         return retval;
3643     }
3644 #endif
3645 
3646     /* WINCAP
3647      * setting window colors
3648      * syntax: windowcolors=menu foregrnd/backgrnd text foregrnd/backgrnd
3649      */
3650     fullname = "windowcolors";
3651     if (match_optname(opts, fullname, 7, TRUE)) {
3652         if (duplicate)
3653             complain_about_duplicate(opts, 1);
3654         if ((op = string_for_opt(opts, FALSE)) != empty_optstr) {
3655             if (!wc_set_window_colors(op)) {
3656                 config_error_add("Could not set %s '%s'", fullname, op);
3657                 return FALSE;
3658             }
3659         } else if (negated) {
3660             bad_negation(fullname, TRUE);
3661             return FALSE;
3662         }
3663         return retval;
3664     }
3665 
3666 #ifdef CURSES_GRAPHICS
3667     /* WINCAP2
3668      * term_cols:amount or term_rows:amount */
3669     fullname = "term_cols";
3670     if (match_optname(opts, fullname, 8, TRUE)
3671         /* alternate spelling */
3672         || match_optname(opts, "term_columns", 9, TRUE)
3673         /* different option but identical handlng */
3674         || (fullname = "term_rows", match_optname(opts, fullname, 8, TRUE))) {
3675         long ltmp;
3676 
3677         if ((op = string_for_opt(opts, negated)) != empty_optstr) {
3678             ltmp = atol(op);
3679             if (negated) {
3680                 bad_negation(fullname, FALSE);
3681                 retval = FALSE;
3682 
3683             /* just checks atol() sanity, not logical window size sanity */
3684             } else if (ltmp <= 0L || ltmp >= (long) LARGEST_INT) {
3685                 config_error_add("Invalid %s: %ld", fullname, ltmp);
3686                 retval = FALSE;
3687 
3688             } else {
3689                 if (!strcmp(fullname, "term_rows"))
3690                     iflags.wc2_term_rows = (int) ltmp;
3691                 else /* !strcmp(fullname, "term_cols") */
3692                     iflags.wc2_term_cols = (int) ltmp;
3693             }
3694         }
3695         return retval;
3696     }
3697 
3698     /* WINCAP2
3699      * petattr:string */
3700     fullname = "petattr";
3701     if (match_optname(opts, fullname, sizeof "petattr" - 1, TRUE)) {
3702         op = string_for_opt(opts, negated);
3703         if (op != empty_optstr && negated) {
3704             bad_negation(fullname, TRUE);
3705             retval = FALSE;
3706         } else if (op != empty_optstr) {
3707 #ifdef CURSES_GRAPHICS
3708             int itmp = curses_read_attrs(op);
3709 
3710             if (itmp == -1) {
3711                 config_error_add("Unknown %s parameter '%s'", fullname, opts);
3712                 retval = FALSE;
3713             } else
3714                 iflags.wc2_petattr = itmp;
3715 #else
3716             /* non-curses windowports will not use this flag anyway
3717              * but the above will not compile if we don't have curses.
3718              * Just set it to a sensible default: */
3719             iflags.wc2_petattr = ATR_INVERSE;
3720 #endif
3721         } else if (negated) {
3722             iflags.wc2_petattr = ATR_NONE;
3723         }
3724         if (retval) {
3725             iflags.hilite_pet = (iflags.wc2_petattr != ATR_NONE);
3726             if (!initial)
3727                 need_redraw = TRUE;
3728         }
3729         return retval;
3730     }
3731 
3732     /* WINCAP2
3733      * windowborders:n */
3734     fullname = "windowborders";
3735     if (match_optname(opts, fullname, 10, TRUE)) {
3736         op = string_for_opt(opts, negated);
3737         if (negated && op != empty_optstr) {
3738             bad_negation(fullname, TRUE);
3739             retval = FALSE;
3740         } else {
3741             int itmp;
3742 
3743             if (negated)
3744                 itmp = 0; /* Off */
3745             else if (op == empty_optstr)
3746                 itmp = 1; /* On */
3747             else    /* Value supplied; expect 0 (off), 1 (on), or 2 (auto) */
3748                 itmp = atoi(op);
3749 
3750             if (itmp < 0 || itmp > 2) {
3751                 config_error_add("Invalid %s (should be 0, 1, or 2): %s",
3752                                  fullname, opts);
3753                 retval = FALSE;
3754             } else {
3755                 iflags.wc2_windowborders = itmp;
3756             }
3757         }
3758         return retval;
3759     }
3760 #endif /* CURSES_GRAPHICS */
3761 
3762     /* WINCAP2
3763      * statuslines:n */
3764     fullname = "statuslines";
3765     if (match_optname(opts, fullname, 11, TRUE)) {
3766         int itmp = 0;
3767 
3768         op = string_for_opt(opts, negated);
3769         if (negated) {
3770             bad_negation(fullname, TRUE);
3771             itmp = 2;
3772             retval = FALSE;
3773         } else if (op != empty_optstr) {
3774             itmp = atoi(op);
3775         }
3776         if (itmp < 2 || itmp > 3) {
3777             config_error_add("'%s' requires a value of 2 or 3", fullname);
3778             retval = FALSE;
3779         } else {
3780             iflags.wc2_statuslines = itmp;
3781             if (!initial)
3782                 need_redraw = TRUE;
3783         }
3784         return retval;
3785     }
3786 
3787     /* menustyle:traditional or combination or full or partial */
3788     fullname = "menustyle";
3789     if (match_optname(opts, fullname, 4, TRUE)) {
3790         int tmp;
3791         boolean val_required = (strlen(opts) > 5 && !negated);
3792 
3793         if (duplicate)
3794             complain_about_duplicate(opts, 1);
3795         if ((op = string_for_opt(opts, !val_required)) == empty_optstr) {
3796             if (val_required)
3797                 return FALSE; /* string_for_opt gave feedback */
3798             tmp = negated ? 'n' : 'f';
3799         } else {
3800             tmp = lowc(*op);
3801         }
3802         switch (tmp) {
3803         case 'n': /* none */
3804         case 't': /* traditional: prompt for class(es) by symbol,
3805                      prompt for each item within class(es) one at a time */
3806             flags.menu_style = MENU_TRADITIONAL;
3807             break;
3808         case 'c': /* combination: prompt for class(es) by symbol,
3809                      choose items within selected class(es) by menu */
3810             flags.menu_style = MENU_COMBINATION;
3811             break;
3812         case 'f': /* full: choose class(es) by first menu,
3813                      choose items within selected class(es) by second menu */
3814             flags.menu_style = MENU_FULL;
3815             break;
3816         case 'p': /* partial: skip class filtering,
3817                      choose items among all classes by menu */
3818             flags.menu_style = MENU_PARTIAL;
3819             break;
3820         default:
3821             config_error_add("Unknown %s parameter '%s'", fullname, op);
3822             return FALSE;
3823         }
3824         return retval;
3825     }
3826 
3827     fullname = "menu_headings";
3828     if (match_optname(opts, fullname, 12, TRUE)) {
3829         int tmpattr;
3830 
3831         if (duplicate)
3832             complain_about_duplicate(opts, 1);
3833         if (negated) {
3834             bad_negation(fullname, FALSE);
3835             return FALSE;
3836         } else if ((opts = string_for_env_opt(fullname, opts, FALSE))
3837                                               == empty_optstr) {
3838             return FALSE;
3839         }
3840         tmpattr = match_str2attr(opts, TRUE);
3841         if (tmpattr == -1)
3842             return FALSE;
3843         else
3844             iflags.menu_headings = tmpattr;
3845         return retval;
3846     }
3847 
3848     /* check for menu command mapping */
3849     for (i = 0; i < SIZE(default_menu_cmd_info); i++) {
3850         fullname = default_menu_cmd_info[i].name;
3851         if (duplicate)
3852             complain_about_duplicate(opts, 1);
3853         if (match_optname(opts, fullname, (int) strlen(fullname), TRUE)) {
3854             if (negated) {
3855                 bad_negation(fullname, FALSE);
3856                 return FALSE;
3857             } else if ((op = string_for_opt(opts, FALSE)) != empty_optstr) {
3858                 char c, op_buf[BUFSZ];
3859 
3860                 escapes(op, op_buf);
3861                 c = *op_buf;
3862 
3863                 if (illegal_menu_cmd_key(c))
3864                     return FALSE;
3865 
3866                 add_menu_cmd_alias(c, default_menu_cmd_info[i].cmd);
3867             }
3868             return retval;
3869         }
3870     }
3871 
3872     /* hilite fields in status prompt */
3873     fullname = "hilite_status";
3874     if (match_optname(opts, fullname, 13, TRUE)) {
3875 #ifdef STATUS_HILITES
3876         if (duplicate)
3877             complain_about_duplicate(opts, 1);
3878         op = string_for_opt(opts, TRUE);
3879         if (op != empty_optstr && negated) {
3880             clear_status_hilites();
3881             return retval;
3882         } else if (op == empty_optstr) {
3883             config_error_add("Value is mandatory for hilite_status");
3884             return FALSE;
3885         }
3886         if (!parse_status_hl1(op, tfrom_file))
3887             return FALSE;
3888         return retval;
3889 #else
3890         config_error_add("'%s' is not supported", fullname);
3891         return FALSE;
3892 #endif
3893     }
3894 
3895     /* control over whether highlights should be displayed, and for how long */
3896     fullname = "statushilites";
3897     if (match_optname(opts, fullname, 9, TRUE)) {
3898 #ifdef STATUS_HILITES
3899         if (negated) {
3900             iflags.hilite_delta = 0L;
3901         } else {
3902             op = string_for_opt(opts, TRUE);
3903             iflags.hilite_delta = (op == empty_optstr || !*op) ? 3L : atol(op);
3904             if (iflags.hilite_delta < 0L)
3905                 iflags.hilite_delta = 1L;
3906         }
3907         if (!tfrom_file)
3908             reset_status_hilites();
3909         return retval;
3910 #else
3911         config_error_add("'%s' is not supported", fullname);
3912         return FALSE;
3913 #endif
3914     }
3915 
3916     fullname = "DECgraphics";
3917     if (match_optname(opts, fullname, 3, TRUE)) {
3918 #ifdef BACKWARD_COMPAT
3919         boolean badflag = FALSE;
3920 
3921         if (duplicate)
3922             complain_about_duplicate(opts, 1);
3923         if (!negated) {
3924             /* There is no rogue level DECgraphics-specific set */
3925             if (symset[PRIMARY].name) {
3926                 badflag = TRUE;
3927             } else {
3928                 symset[PRIMARY].name = dupstr(fullname);
3929                 if (!read_sym_file(PRIMARY)) {
3930                     badflag = TRUE;
3931                     clear_symsetentry(PRIMARY, TRUE);
3932                 } else
3933                     switch_symbols(TRUE);
3934             }
3935             if (badflag) {
3936                 config_error_add("Failure to load symbol set %s.", fullname);
3937                 return FALSE;
3938             }
3939         }
3940         return retval;
3941 #else
3942         config_error_add("'%s' no longer supported; use 'symset:%s' instead",
3943                          fullname, fullname);
3944         return FALSE;
3945 #endif
3946     } /* "DECgraphics" */
3947 
3948     fullname = "IBMgraphics";
3949     if (match_optname(opts, fullname, 3, TRUE)) {
3950 #ifdef BACKWARD_COMPAT
3951         const char *sym_name = fullname;
3952         boolean badflag = FALSE;
3953 
3954         if (duplicate)
3955             complain_about_duplicate(opts, 1);
3956         if (!negated) {
3957             for (i = 0; i < NUM_GRAPHICS; ++i) {
3958                 if (symset[i].name) {
3959                     badflag = TRUE;
3960                 } else {
3961                     if (i == ROGUESET)
3962                         sym_name = "RogueIBM";
3963                     symset[i].name = dupstr(sym_name);
3964                     if (!read_sym_file(i)) {
3965                         badflag = TRUE;
3966                         clear_symsetentry(i, TRUE);
3967                         break;
3968                     }
3969                 }
3970             }
3971             if (badflag) {
3972                 config_error_add("Failure to load symbol set %s.", sym_name);
3973                 return FALSE;
3974             } else {
3975                 switch_symbols(TRUE);
3976                 if (!initial && Is_rogue_level(&u.uz))
3977                     assign_graphics(ROGUESET);
3978             }
3979         }
3980         return retval;
3981 #else
3982         config_error_add("'%s' no longer supported; use 'symset:%s' instead",
3983                          fullname, fullname);
3984         return FALSE;
3985 #endif
3986     } /* "IBMgraphics" */
3987 
3988     fullname = "MACgraphics";
3989     if (match_optname(opts, fullname, 3, TRUE)) {
3990 #if defined(MAC_GRAPHICS_ENV) && defined(BACKWARD_COMPAT)
3991         boolean badflag = FALSE;
3992 
3993         if (duplicate)
3994             complain_about_duplicate(opts, 1);
3995         if (!negated) {
3996             if (symset[PRIMARY].name) {
3997                 badflag = TRUE;
3998             } else {
3999                 symset[PRIMARY].name = dupstr(fullname);
4000                 if (!read_sym_file(PRIMARY)) {
4001                     badflag = TRUE;
4002                     clear_symsetentry(PRIMARY, TRUE);
4003                 }
4004             }
4005             if (badflag) {
4006                 config_error_add("Failure to load symbol set %s.", fullname);
4007                 return FALSE;
4008             } else {
4009                 switch_symbols(TRUE);
4010                 if (!initial && Is_rogue_level(&u.uz))
4011                     assign_graphics(ROGUESET);
4012             }
4013         }
4014         return retval;
4015 #else   /* !(MAC_GRAPHICS_ENV && BACKWARD_COMPAT) */
4016         config_error_add("'%s' %s; use 'symset:%s' instead",
4017                          fullname,
4018 #ifdef MAC_GRAPHICS_ENV /* implies BACKWARD_COMPAT is not defined */
4019                          "no longer supported",
4020 #else
4021                          "is not supported",
4022 #endif
4023                          fullname);
4024         return FALSE;
4025 #endif  /* ?(MAC_GRAPHICS_ENV && BACKWARD_COMPAT) */
4026     } /* "MACgraphics" */
4027 
4028     /*
4029      * OK, if we still haven't recognized the option, check the boolean
4030      * options list.
4031      */
4032     for (i = 0; boolopt[i].name; i++) {
4033         if (match_optname(opts, boolopt[i].name, 3, TRUE)) {
4034             /* options that don't exist */
4035             if (!boolopt[i].addr) {
4036                 if (!initial && !negated)
4037                     pline_The("\"%s\" option is not available.",
4038                               boolopt[i].name);
4039                 return retval;
4040             }
4041             /* options that must come from config file */
4042             if (!initial && (boolopt[i].optflags == SET_IN_FILE)) {
4043                 rejectoption(boolopt[i].name);
4044                 return retval;
4045             }
4046 
4047             op = string_for_opt(opts, TRUE);
4048             if (op != empty_optstr) {
4049                 if (negated) {
4050                     config_error_add(
4051                            "Negated boolean '%s' should not have a parameter",
4052                                      boolopt[i].name);
4053                     return FALSE;
4054                 }
4055                 if (!strcmp(op, "true") || !strcmp(op, "yes")) {
4056                     negated = FALSE;
4057                 } else if (!strcmp(op, "false") || !strcmp(op, "no")) {
4058                     negated = TRUE;
4059                 } else {
4060                     config_error_add("Illegal parameter for a boolean");
4061                     return FALSE;
4062                 }
4063             }
4064             if (iflags.debug_fuzzer && !initial) {
4065                 /* don't randomly toggle this/these */
4066                 if (boolopt[i].addr == &flags.silent)
4067                     return TRUE;
4068             }
4069 
4070             *(boolopt[i].addr) = !negated;
4071 
4072             /* 0 means boolean opts */
4073             if (duplicate_opt_detection(boolopt[i].name, 0))
4074                 complain_about_duplicate(boolopt[i].name, 0);
4075 #ifdef RLECOMP
4076             if (boolopt[i].addr == &iflags.rlecomp)
4077                 set_savepref(iflags.rlecomp ? "rlecomp" : "!rlecomp");
4078 #endif
4079 #ifdef ZEROCOMP
4080             if (boolopt[i].addr == &iflags.zerocomp)
4081                 set_savepref(iflags.zerocomp ? "zerocomp" : "externalcomp");
4082 #endif
4083             if (boolopt[i].addr == &iflags.wc_ascii_map) {
4084                 /* toggling ascii_map; set tiled_map to its opposite;
4085                    what does it mean to turn off ascii map if tiled map
4086                    isn't supported? -- right now, we do nothing */
4087                 iflags.wc_tiled_map = negated;
4088             } else if (boolopt[i].addr == &iflags.wc_tiled_map) {
4089                 /* toggling tiled_map; set ascii_map to its opposite;
4090                    as with ascii_map, what does it mean to turn off tiled
4091                    map if ascii map isn't supported? */
4092                 iflags.wc_ascii_map = negated;
4093             }
4094             /* only do processing below if setting with doset() */
4095             if (initial)
4096                 return retval;
4097 
4098             if (boolopt[i].addr == &flags.time
4099 #ifdef SCORE_ON_BOTL
4100                 || boolopt[i].addr == &flags.showscore
4101 #endif
4102                 || boolopt[i].addr == &flags.showexp) {
4103                 if (VIA_WINDOWPORT())
4104                     status_initialize(REASSESS_ONLY);
4105                 context.botl = TRUE;
4106             } else if (boolopt[i].addr == &flags.invlet_constant
4107                        || boolopt[i].addr == &flags.sortpack
4108                        || boolopt[i].addr == &iflags.implicit_uncursed) {
4109                 if (!flags.invlet_constant)
4110                     reassign();
4111                 update_inventory();
4112             } else if (boolopt[i].addr == &flags.lit_corridor
4113                        || boolopt[i].addr == &flags.dark_room) {
4114                 /*
4115                  * All corridor squares seen via night vision or
4116                  * candles & lamps change.  Update them by calling
4117                  * newsym() on them.  Don't do this if we are
4118                  * initializing the options --- the vision system
4119                  * isn't set up yet.
4120                  */
4121                 vision_recalc(2);       /* shut down vision */
4122                 vision_full_recalc = 1; /* delayed recalc */
4123                 if (iflags.use_color)
4124                     need_redraw = TRUE; /* darkroom refresh */
4125             } else if (boolopt[i].addr == &flags.showrace
4126                        || boolopt[i].addr == &iflags.use_inverse
4127                        || boolopt[i].addr == &iflags.hilite_pile
4128                        || boolopt[i].addr == &iflags.perm_invent
4129 #ifdef CURSES_GRAPHICS
4130                        || boolopt[i].addr == &iflags.cursesgraphics
4131 #endif
4132                        || boolopt[i].addr == &iflags.wc_ascii_map
4133                        || boolopt[i].addr == &iflags.wc_tiled_map) {
4134                 need_redraw = TRUE;
4135             } else if (boolopt[i].addr == &iflags.hilite_pet) {
4136 #ifdef CURSES_GRAPHICS
4137                 if (WINDOWPORT("curses")) {
4138                     /* if we're enabling hilite_pet and petattr isn't set,
4139                        set it to Inverse; if we're disabling, leave petattr
4140                        alone so that re-enabling will get current value back */
4141                     if (iflags.hilite_pet && !iflags.wc2_petattr)
4142                         iflags.wc2_petattr = curses_read_attrs("I");
4143                 }
4144 #endif
4145                 need_redraw = TRUE;
4146             } else if (boolopt[i].addr == &iflags.wc2_hitpointbar) {
4147                 if (VIA_WINDOWPORT()) {
4148                     /* [is reassessment really needed here?] */
4149                     status_initialize(REASSESS_ONLY);
4150                     need_redraw = TRUE;
4151                 }
4152 #ifdef TEXTCOLOR
4153             } else if (boolopt[i].addr == &iflags.use_color) {
4154                 need_redraw = TRUE;
4155 #ifdef TOS
4156                 if (iflags.BIOS) {
4157                     if (colors_changed)
4158                         restore_colors();
4159                     else
4160                         set_colors();
4161                 }
4162 #endif
4163             } else if (boolopt[i].addr == &iflags.use_menu_color
4164                        || boolopt[i].addr == &iflags.wc2_guicolor) {
4165                 update_inventory();
4166 #endif /* TEXTCOLOR */
4167             }
4168             return retval;
4169         }
4170     }
4171 
4172     /* Is it a symbol? */
4173     if (strstr(opts, "S_") == opts && parsesymbols(opts, PRIMARY)) {
4174         switch_symbols(TRUE);
4175         check_gold_symbol();
4176         return retval;
4177     }
4178 
4179     /* out of valid options */
4180     config_error_add("Unknown option '%s'", opts);
4181     return FALSE;
4182 }
4183 
4184 /* parse key:command */
4185 boolean
parsebindings(bindings)4186 parsebindings(bindings)
4187 char* bindings;
4188 {
4189     char *bind;
4190     char key;
4191     int i;
4192     boolean ret = FALSE;
4193 
4194     /* break off first binding from the rest; parse the rest */
4195     if ((bind = index(bindings, ',')) != 0) {
4196         *bind++ = 0;
4197         ret |= parsebindings(bind);
4198     }
4199 
4200     /* parse a single binding: first split around : */
4201     if (! (bind = index(bindings, ':')))
4202         return FALSE; /* it's not a binding */
4203     *bind++ = 0;
4204 
4205     /* read the key to be bound */
4206     key = txt2key(bindings);
4207     if (!key) {
4208         config_error_add("Unknown key binding key '%s'", bindings);
4209         return FALSE;
4210     }
4211 
4212     bind = trimspaces(bind);
4213 
4214     /* is it a special key? */
4215     if (bind_specialkey(key, bind))
4216         return TRUE;
4217 
4218     /* is it a menu command? */
4219     for (i = 0; i < SIZE(default_menu_cmd_info); i++) {
4220         if (!strcmp(default_menu_cmd_info[i].name, bind)) {
4221             if (illegal_menu_cmd_key(key)) {
4222                 config_error_add("Bad menu key %s:%s", visctrl(key), bind);
4223                 return FALSE;
4224             } else
4225                 add_menu_cmd_alias(key, default_menu_cmd_info[i].cmd);
4226             return TRUE;
4227         }
4228     }
4229 
4230     /* extended command? */
4231     if (!bind_key(key, bind)) {
4232         config_error_add("Unknown key binding command '%s'", bind);
4233         return FALSE;
4234     }
4235     return TRUE;
4236 }
4237 
4238 static NEARDATA const char *menutype[] = { "traditional", "combination",
4239                                            "full", "partial" };
4240 
4241 static NEARDATA const char *burdentype[] = { "unencumbered", "burdened",
4242                                              "stressed",     "strained",
4243                                              "overtaxed",    "overloaded" };
4244 
4245 static NEARDATA const char *runmodes[] = { "teleport", "run", "walk",
4246                                            "crawl" };
4247 
4248 static NEARDATA const char *sortltype[] = { "none", "loot", "full" };
4249 
4250 /*
4251  * Convert the given string of object classes to a string of default object
4252  * symbols.
4253  */
4254 void
oc_to_str(src,dest)4255 oc_to_str(src, dest)
4256 char *src, *dest;
4257 {
4258     int i;
4259 
4260     while ((i = (int) *src++) != 0) {
4261         if (i < 0 || i >= MAXOCLASSES)
4262             impossible("oc_to_str:  illegal object class %d", i);
4263         else
4264             *dest++ = def_oc_syms[i].sym;
4265     }
4266     *dest = '\0';
4267 }
4268 
4269 /*
4270  * Add the given mapping to the menu command map list.  Always keep the
4271  * maps valid C strings.
4272  */
4273 void
add_menu_cmd_alias(from_ch,to_ch)4274 add_menu_cmd_alias(from_ch, to_ch)
4275 char from_ch, to_ch;
4276 {
4277     if (n_menu_mapped >= MAX_MENU_MAPPED_CMDS) {
4278         pline("out of menu map space.");
4279     } else {
4280         mapped_menu_cmds[n_menu_mapped] = from_ch;
4281         mapped_menu_op[n_menu_mapped] = to_ch;
4282         n_menu_mapped++;
4283         mapped_menu_cmds[n_menu_mapped] = '\0';
4284         mapped_menu_op[n_menu_mapped] = '\0';
4285     }
4286 }
4287 
4288 char
get_menu_cmd_key(ch)4289 get_menu_cmd_key(ch)
4290 char ch;
4291 {
4292     char *found = index(mapped_menu_op, ch);
4293 
4294     if (found) {
4295         int idx = (int) (found - mapped_menu_op);
4296 
4297         ch = mapped_menu_cmds[idx];
4298     }
4299     return ch;
4300 }
4301 
4302 /*
4303  * Map the given character to its corresponding menu command.  If it
4304  * doesn't match anything, just return the original.
4305  */
4306 char
map_menu_cmd(ch)4307 map_menu_cmd(ch)
4308 char ch;
4309 {
4310     char *found = index(mapped_menu_cmds, ch);
4311 
4312     if (found) {
4313         int idx = (int) (found - mapped_menu_cmds);
4314 
4315         ch = mapped_menu_op[idx];
4316     }
4317     return ch;
4318 }
4319 
4320 void
show_menu_controls(win,dolist)4321 show_menu_controls(win, dolist)
4322 winid win;
4323 boolean dolist;
4324 {
4325     char buf[BUFSZ];
4326 
4327     putstr(win, 0, "Menu control keys:");
4328     if (dolist) {
4329         int i;
4330 
4331         for (i = 0; i < SIZE(default_menu_cmd_info); i++) {
4332             Sprintf(buf, "%-8s %s",
4333                     visctrl(get_menu_cmd_key(default_menu_cmd_info[i].cmd)),
4334                     default_menu_cmd_info[i].desc);
4335             putstr(win, 0, buf);
4336         }
4337     } else {
4338         const char
4339             fmt3[] = " %-12s       %-2s        %-2s  %s",
4340             fmt2[] = " %-12s       %-2s        %-2s",
4341             fmt1[] = " %10s  %-2s  %s",
4342             fmt0[] = " %14s  %s";
4343 
4344         putstr(win, 0, "");
4345         putstr(win, 0, "Selection:       On page   Full menu");
4346         Sprintf(buf, fmt2, "Select all",
4347                 visctrl(get_menu_cmd_key(MENU_SELECT_PAGE)),
4348                 visctrl(get_menu_cmd_key(MENU_SELECT_ALL)));
4349         putstr(win, 0, buf);
4350         Sprintf(buf, fmt2, "Deselect all",
4351                 visctrl(get_menu_cmd_key(MENU_UNSELECT_PAGE)),
4352                 visctrl(get_menu_cmd_key(MENU_UNSELECT_ALL)));
4353         putstr(win, 0, buf);
4354         Sprintf(buf, fmt2, "Invert all",
4355                 visctrl(get_menu_cmd_key(MENU_INVERT_PAGE)),
4356                 visctrl(get_menu_cmd_key(MENU_INVERT_ALL)));
4357         putstr(win, 0, buf);
4358         Sprintf(buf, fmt3, "Text match", "",
4359                 visctrl(get_menu_cmd_key(MENU_SEARCH)),
4360                 "Search and toggle matching entries");
4361         putstr(win, 0, buf);
4362         putstr(win, 0, "");
4363         putstr(win, 0, "Navigation:");
4364         Sprintf(buf, fmt1, "Go to     ",
4365                 visctrl(get_menu_cmd_key(MENU_NEXT_PAGE)),
4366                 "Next page");
4367         putstr(win, 0, buf);
4368         Sprintf(buf, fmt1, "",
4369                 visctrl(get_menu_cmd_key(MENU_PREVIOUS_PAGE)),
4370                 "Previous page");
4371         putstr(win, 0, buf);
4372         Sprintf(buf, fmt1, "",
4373                 visctrl(get_menu_cmd_key(MENU_FIRST_PAGE)),
4374                 "First page");
4375         putstr(win, 0, buf);
4376         Sprintf(buf, fmt1, "",
4377                 visctrl(get_menu_cmd_key(MENU_LAST_PAGE)),
4378                 "Last page");
4379         putstr(win, 0, buf);
4380         Sprintf(buf, fmt0, "SPACE", "Next page, if any, otherwise RETURN");
4381         putstr(win, 0, buf);
4382         Sprintf(buf, fmt0, "RETURN/ENTER",
4383                 "Finish menu with any selection(s) made");
4384         putstr(win, 0, buf);
4385         Sprintf(buf, fmt0, "ESCAPE",
4386                 "Cancel menu without selecting anything");
4387         putstr(win, 0, buf);
4388     }
4389 }
4390 
4391 #if defined(MICRO) || defined(MAC) || defined(WIN32)
4392 #define OPTIONS_HEADING "OPTIONS"
4393 #else
4394 #define OPTIONS_HEADING "NETHACKOPTIONS"
4395 #endif
4396 
4397 static char fmtstr_doset[] = "%s%-15s [%s]   ";
4398 static char fmtstr_doset_tab[] = "%s\t[%s]";
4399 static char n_currently_set[] = "(%d currently set)";
4400 
4401 /* doset('O' command) menu entries for compound options */
4402 STATIC_OVL void
doset_add_menu(win,option,indexoffset)4403 doset_add_menu(win, option, indexoffset)
4404 winid win;          /* window to add to */
4405 const char *option; /* option name */
4406 int indexoffset;    /* value to add to index in compopt[], or zero
4407                        if option cannot be changed */
4408 {
4409     const char *value = "unknown"; /* current value */
4410     char buf[BUFSZ], buf2[BUFSZ];
4411     anything any;
4412     int i;
4413 
4414     any = zeroany;
4415     if (indexoffset == 0) {
4416         any.a_int = 0;
4417         value = get_compopt_value(option, buf2);
4418     } else {
4419         for (i = 0; compopt[i].name; i++)
4420             if (strcmp(option, compopt[i].name) == 0)
4421                 break;
4422 
4423         if (compopt[i].name) {
4424             any.a_int = i + 1 + indexoffset;
4425             value = get_compopt_value(option, buf2);
4426         } else {
4427             /* We are trying to add an option not found in compopt[].
4428                This is almost certainly bad, but we'll let it through anyway
4429                (with a zero value, so it can't be selected). */
4430             any.a_int = 0;
4431         }
4432     }
4433     /* "    " replaces "a - " -- assumes menus follow that style */
4434     if (!iflags.menu_tab_sep)
4435         Sprintf(buf, fmtstr_doset, any.a_int ? "" : "    ", option,
4436                 value);
4437     else
4438         Sprintf(buf, fmtstr_doset_tab, option, value);
4439     add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
4440 }
4441 
4442 STATIC_OVL void
opts_add_others(win,name,id,bufx,nset)4443 opts_add_others(win, name, id, bufx, nset)
4444 winid win;
4445 const char *name;
4446 int id;
4447 char *bufx;
4448 int nset;
4449 {
4450     char buf[BUFSZ], buf2[BUFSZ];
4451     anything any = zeroany;
4452 
4453     any.a_int = id;
4454     if (!bufx)
4455         Sprintf(buf2, n_currently_set, nset);
4456     else
4457         Sprintf(buf2, "%s", bufx);
4458     if (!iflags.menu_tab_sep)
4459         Sprintf(buf, fmtstr_doset, any.a_int ? "" : "    ",
4460                 name, buf2);
4461     else
4462         Sprintf(buf, fmtstr_doset_tab, name, buf2);
4463     add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
4464 }
4465 
4466 int
count_apes(VOID_ARGS)4467 count_apes(VOID_ARGS)
4468 {
4469     int numapes = 0;
4470     struct autopickup_exception *ape = apelist;
4471 
4472     while (ape) {
4473       numapes++;
4474       ape = ape->next;
4475     }
4476 
4477     return numapes;
4478 }
4479 
4480 enum opt_other_enums {
4481     OPT_OTHER_MSGTYPE = -4,
4482     OPT_OTHER_MENUCOLOR = -3,
4483     OPT_OTHER_STATHILITE = -2,
4484     OPT_OTHER_APEXC = -1
4485     /* these must be < 0 */
4486 };
4487 
4488 static struct other_opts {
4489     const char *name;
4490     int optflags;
4491     enum opt_other_enums code;
4492     int NDECL((*othr_count_func));
4493 } othropt[] = {
4494     { "autopickup exceptions", SET_IN_GAME, OPT_OTHER_APEXC, count_apes },
4495     { "menu colors", SET_IN_GAME, OPT_OTHER_MENUCOLOR, count_menucolors },
4496     { "message types", SET_IN_GAME, OPT_OTHER_MSGTYPE, msgtype_count },
4497 #ifdef STATUS_HILITES
4498     { "status hilite rules", SET_IN_GAME, OPT_OTHER_STATHILITE,
4499       count_status_hilites },
4500 #endif
4501     { (char *) 0, 0, (enum opt_other_enums) 0 },
4502 };
4503 
4504 /* the 'O' command */
4505 int
doset()4506 doset() /* changing options via menu by Per Liboriussen */
4507 {
4508     static boolean made_fmtstr = FALSE;
4509     char buf[BUFSZ];
4510     const char *name;
4511     int i = 0, pass, boolcount, pick_cnt, pick_idx, opt_indx;
4512     boolean *bool_p;
4513     winid tmpwin;
4514     anything any;
4515     menu_item *pick_list;
4516     int indexoffset, startpass, endpass, optflags;
4517     boolean setinitial = FALSE, fromfile = FALSE;
4518     unsigned longest_name_len;
4519 
4520     tmpwin = create_nhwindow(NHW_MENU);
4521     start_menu(tmpwin);
4522 
4523 #ifdef notyet /* SYSCF */
4524     /* XXX I think this is still fragile.  Fixing initial/from_file and/or
4525        changing the SET_* etc to bitmaps will let me make this better. */
4526     if (wizard)
4527         startpass = SET_IN_SYS;
4528     else
4529 #endif
4530         startpass = DISP_IN_GAME;
4531     endpass = (wizard) ? SET_IN_WIZGAME : SET_IN_GAME;
4532 
4533     if (!made_fmtstr && !iflags.menu_tab_sep) {
4534         /* spin through the options to find the longest name
4535            and adjust the format string accordingly */
4536         longest_name_len = 0;
4537         for (pass = 0; pass <= 2; pass++)
4538             for (i = 0; (name = ((pass == 0) ? boolopt[i].name
4539                                  : (pass == 1) ? compopt[i].name
4540                                    : othropt[i].name)) != 0; i++) {
4541                 if (pass == 0 && !boolopt[i].addr)
4542                     continue;
4543                 optflags = (pass == 0) ? boolopt[i].optflags
4544                            : (pass == 1) ? compopt[i].optflags
4545                              : othropt[i].optflags;
4546                 if (optflags < startpass || optflags > endpass)
4547                     continue;
4548                 if ((is_wc_option(name) && !wc_supported(name))
4549                     || (is_wc2_option(name) && !wc2_supported(name)))
4550                     continue;
4551 
4552                 if (strlen(name) > longest_name_len)
4553                     longest_name_len = strlen(name);
4554             }
4555         Sprintf(fmtstr_doset, "%%s%%-%us [%%s]", longest_name_len);
4556         made_fmtstr = TRUE;
4557     }
4558 
4559     any = zeroany;
4560     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings,
4561              "Booleans (selecting will toggle value):", MENU_UNSELECTED);
4562     any.a_int = 0;
4563     /* first list any other non-modifiable booleans, then modifiable ones */
4564     for (pass = 0; pass <= 1; pass++)
4565         for (i = 0; (name = boolopt[i].name) != 0; i++)
4566             if ((bool_p = boolopt[i].addr) != 0
4567                 && ((boolopt[i].optflags <= DISP_IN_GAME && pass == 0)
4568                     || (boolopt[i].optflags >= SET_IN_GAME && pass == 1))) {
4569                 if (bool_p == &flags.female)
4570                     continue; /* obsolete */
4571                 if (boolopt[i].optflags == SET_IN_WIZGAME && !wizard)
4572                     continue;
4573                 if ((is_wc_option(name) && !wc_supported(name))
4574                     || (is_wc2_option(name) && !wc2_supported(name)))
4575                     continue;
4576 
4577                 any.a_int = (pass == 0) ? 0 : i + 1;
4578                 if (!iflags.menu_tab_sep)
4579                     Sprintf(buf, fmtstr_doset, (pass == 0) ? "    " : "",
4580                             name, *bool_p ? "true" : "false");
4581                 else
4582                     Sprintf(buf, fmtstr_doset_tab,
4583                             name, *bool_p ? "true" : "false");
4584                 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf,
4585                          MENU_UNSELECTED);
4586             }
4587 
4588     boolcount = i;
4589     indexoffset = boolcount;
4590     any = zeroany;
4591     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
4592     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings,
4593              "Compounds (selecting will prompt for new value):",
4594              MENU_UNSELECTED);
4595 
4596     /* deliberately put playmode, name, role+race+gender+align first */
4597     doset_add_menu(tmpwin, "playmode", 0);
4598     doset_add_menu(tmpwin, "name", 0);
4599     doset_add_menu(tmpwin, "role", 0);
4600     doset_add_menu(tmpwin, "race", 0);
4601     doset_add_menu(tmpwin, "gender", 0);
4602     doset_add_menu(tmpwin, "align", 0);
4603 
4604     for (pass = startpass; pass <= endpass; pass++)
4605         for (i = 0; (name = compopt[i].name) != 0; i++)
4606             if (compopt[i].optflags == pass) {
4607                 if (!strcmp(name, "playmode")  || !strcmp(name, "name")
4608                     || !strcmp(name, "role")   || !strcmp(name, "race")
4609                     || !strcmp(name, "gender") || !strcmp(name, "align"))
4610                     continue;
4611                 if ((is_wc_option(name) && !wc_supported(name))
4612                     || (is_wc2_option(name) && !wc2_supported(name)))
4613                     continue;
4614 
4615                 doset_add_menu(tmpwin, name,
4616                                (pass == DISP_IN_GAME) ? 0 : indexoffset);
4617             }
4618 
4619     any = zeroany;
4620     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
4621     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings,
4622              "Other settings:", MENU_UNSELECTED);
4623 
4624     for (i = 0; (name = othropt[i].name) != 0; i++) {
4625         if ((is_wc_option(name) && !wc_supported(name))
4626             || (is_wc2_option(name) && !wc2_supported(name)))
4627             continue;
4628         opts_add_others(tmpwin, name, othropt[i].code,
4629                         (char *) 0, othropt[i].othr_count_func());
4630     }
4631 
4632 #ifdef PREFIXES_IN_USE
4633     any = zeroany;
4634     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
4635     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings,
4636              "Variable playground locations:", MENU_UNSELECTED);
4637     for (i = 0; i < PREFIX_COUNT; i++)
4638         doset_add_menu(tmpwin, fqn_prefix_names[i], 0);
4639 #endif
4640     end_menu(tmpwin, "Set what options?");
4641     need_redraw = FALSE;
4642     if ((pick_cnt = select_menu(tmpwin, PICK_ANY, &pick_list)) > 0) {
4643         /*
4644          * Walk down the selection list and either invert the booleans
4645          * or prompt for new values. In most cases, call parseoptions()
4646          * to take care of options that require special attention, like
4647          * redraws.
4648          */
4649         for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) {
4650             opt_indx = pick_list[pick_idx].item.a_int - 1;
4651             if (opt_indx < -1)
4652                 opt_indx++; /* -1 offset for select_menu() */
4653             if (opt_indx == OPT_OTHER_APEXC) {
4654                 (void) special_handling("autopickup_exception", setinitial,
4655                                         fromfile);
4656 #ifdef STATUS_HILITES
4657             } else if (opt_indx == OPT_OTHER_STATHILITE) {
4658                 if (!status_hilite_menu()) {
4659                     pline("Bad status hilite(s) specified.");
4660                 } else {
4661                     if (wc2_supported("hilite_status"))
4662                         preference_update("hilite_status");
4663                 }
4664 #endif
4665             } else if (opt_indx == OPT_OTHER_MENUCOLOR) {
4666                     (void) special_handling("menu_colors", setinitial,
4667                                             fromfile);
4668             } else if (opt_indx == OPT_OTHER_MSGTYPE) {
4669                     (void) special_handling("msgtype", setinitial, fromfile);
4670             } else if (opt_indx < boolcount) {
4671                 /* boolean option */
4672                 Sprintf(buf, "%s%s", *boolopt[opt_indx].addr ? "!" : "",
4673                         boolopt[opt_indx].name);
4674                 (void) parseoptions(buf, setinitial, fromfile);
4675                 if (wc_supported(boolopt[opt_indx].name)
4676                     || wc2_supported(boolopt[opt_indx].name))
4677                     preference_update(boolopt[opt_indx].name);
4678             } else {
4679                 /* compound option */
4680                 opt_indx -= boolcount;
4681 
4682                 if (!special_handling(compopt[opt_indx].name, setinitial,
4683                                       fromfile)) {
4684                     char abuf[BUFSZ];
4685 
4686                     Sprintf(buf, "Set %s to what?", compopt[opt_indx].name);
4687                     abuf[0] = '\0';
4688                     getlin(buf, abuf);
4689                     if (abuf[0] == '\033')
4690                         continue;
4691                     Sprintf(buf, "%s:", compopt[opt_indx].name);
4692                     (void) strncat(eos(buf), abuf,
4693                                    (sizeof buf - 1 - strlen(buf)));
4694                     /* pass the buck */
4695                     (void) parseoptions(buf, setinitial, fromfile);
4696                 }
4697                 if (wc_supported(compopt[opt_indx].name)
4698                     || wc2_supported(compopt[opt_indx].name))
4699                     preference_update(compopt[opt_indx].name);
4700             }
4701         }
4702         free((genericptr_t) pick_list), pick_list = (menu_item *) 0;
4703     }
4704 
4705     destroy_nhwindow(tmpwin);
4706     if (need_redraw) {
4707         check_gold_symbol();
4708         reglyph_darkroom();
4709         (void) doredraw();
4710     }
4711     return 0;
4712 }
4713 
4714 /* common to msg-types, menu-colors, autopickup-exceptions */
4715 STATIC_OVL int
handle_add_list_remove(optname,numtotal)4716 handle_add_list_remove(optname, numtotal)
4717 const char *optname;
4718 int numtotal;
4719 {
4720     winid tmpwin;
4721     anything any;
4722     int i, pick_cnt, opt_idx;
4723     menu_item *pick_list = (menu_item *) 0;
4724     static const struct action {
4725         char letr;
4726         const char *desc;
4727     } action_titles[] = {
4728         { 'a', "add new %s" },         /* [0] */
4729         { 'l', "list %s" },            /* [1] */
4730         { 'r', "remove existing %s" }, /* [2] */
4731         { 'x', "exit this menu" },     /* [3] */
4732     };
4733 
4734     opt_idx = 0;
4735     tmpwin = create_nhwindow(NHW_MENU);
4736     start_menu(tmpwin);
4737     any = zeroany;
4738     for (i = 0; i < SIZE(action_titles); i++) {
4739         char tmpbuf[BUFSZ];
4740 
4741         any.a_int++;
4742         /* omit list and remove if there aren't any yet */
4743         if (!numtotal && (i == 1 || i == 2))
4744             continue;
4745         Sprintf(tmpbuf, action_titles[i].desc,
4746                 (i == 1) ? makeplural(optname) : optname);
4747         add_menu(tmpwin, NO_GLYPH, &any, action_titles[i].letr, 0, ATR_NONE,
4748                  tmpbuf, (i == 3) ? MENU_SELECTED : MENU_UNSELECTED);
4749     }
4750     end_menu(tmpwin, "Do what?");
4751     if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &pick_list)) > 0) {
4752         opt_idx = pick_list[0].item.a_int - 1;
4753         if (pick_cnt > 1 && opt_idx == 3)
4754             opt_idx = pick_list[1].item.a_int - 1;
4755         free((genericptr_t) pick_list);
4756     } else
4757         opt_idx = 3; /* none selected, exit menu */
4758     destroy_nhwindow(tmpwin);
4759     return opt_idx;
4760 }
4761 
4762 struct symsetentry *symset_list = 0; /* files.c will populate this with
4763                                       * list of available sets */
4764 
4765 STATIC_OVL boolean
special_handling(optname,setinitial,setfromfile)4766 special_handling(optname, setinitial, setfromfile)
4767 const char *optname;
4768 boolean setinitial, setfromfile;
4769 {
4770     winid tmpwin;
4771     anything any;
4772     int i, n;
4773     char buf[BUFSZ];
4774 
4775     /* Special handling of menustyle, pickup_burden, pickup_types,
4776      * disclose, runmode, msg_window, menu_headings, sortloot,
4777      * and number_pad options.
4778      * Also takes care of interactive autopickup_exception_handling changes.
4779      */
4780     if (!strcmp("menustyle", optname)) {
4781         const char *style_name;
4782         menu_item *style_pick = (menu_item *) 0;
4783 
4784         tmpwin = create_nhwindow(NHW_MENU);
4785         start_menu(tmpwin);
4786         any = zeroany;
4787         for (i = 0; i < SIZE(menutype); i++) {
4788             style_name = menutype[i];
4789             /* note: separate `style_name' variable used
4790                to avoid an optimizer bug in VAX C V2.3 */
4791             any.a_int = i + 1;
4792             add_menu(tmpwin, NO_GLYPH, &any, *style_name, 0, ATR_NONE,
4793                      style_name, MENU_UNSELECTED);
4794         }
4795         end_menu(tmpwin, "Select menustyle:");
4796         if (select_menu(tmpwin, PICK_ONE, &style_pick) > 0) {
4797             flags.menu_style = style_pick->item.a_int - 1;
4798             free((genericptr_t) style_pick);
4799         }
4800         destroy_nhwindow(tmpwin);
4801     } else if (!strcmp("paranoid_confirmation", optname)) {
4802         menu_item *paranoia_picks = (menu_item *) 0;
4803 
4804         tmpwin = create_nhwindow(NHW_MENU);
4805         start_menu(tmpwin);
4806         any = zeroany;
4807         for (i = 0; paranoia[i].flagmask != 0; ++i) {
4808             if (paranoia[i].flagmask == PARANOID_BONES && !wizard)
4809                 continue;
4810             any.a_int = paranoia[i].flagmask;
4811             add_menu(tmpwin, NO_GLYPH, &any, *paranoia[i].argname, 0,
4812                      ATR_NONE, paranoia[i].explain,
4813                      (flags.paranoia_bits & paranoia[i].flagmask)
4814                          ? MENU_SELECTED
4815                          : MENU_UNSELECTED);
4816         }
4817         end_menu(tmpwin, "Actions requiring extra confirmation:");
4818         i = select_menu(tmpwin, PICK_ANY, &paranoia_picks);
4819         if (i >= 0) {
4820             /* player didn't cancel; we reset all the paranoia options
4821                here even if there were no items picked, since user
4822                could have toggled off preselected ones to end up with 0 */
4823             flags.paranoia_bits = 0;
4824             if (i > 0) {
4825                 /* at least 1 item set, either preselected or newly picked */
4826                 while (--i >= 0)
4827                     flags.paranoia_bits |= paranoia_picks[i].item.a_int;
4828                 free((genericptr_t) paranoia_picks);
4829             }
4830         }
4831         destroy_nhwindow(tmpwin);
4832     } else if (!strcmp("pickup_burden", optname)) {
4833         const char *burden_name, *burden_letters = "ubsntl";
4834         menu_item *burden_pick = (menu_item *) 0;
4835 
4836         tmpwin = create_nhwindow(NHW_MENU);
4837         start_menu(tmpwin);
4838         any = zeroany;
4839         for (i = 0; i < SIZE(burdentype); i++) {
4840             burden_name = burdentype[i];
4841             any.a_int = i + 1;
4842             add_menu(tmpwin, NO_GLYPH, &any, burden_letters[i], 0, ATR_NONE,
4843                      burden_name, MENU_UNSELECTED);
4844         }
4845         end_menu(tmpwin, "Select encumbrance level:");
4846         if (select_menu(tmpwin, PICK_ONE, &burden_pick) > 0) {
4847             flags.pickup_burden = burden_pick->item.a_int - 1;
4848             free((genericptr_t) burden_pick);
4849         }
4850         destroy_nhwindow(tmpwin);
4851     } else if (!strcmp("pickup_types", optname)) {
4852         /* parseoptions will prompt for the list of types */
4853         (void) parseoptions(strcpy(buf, "pickup_types"),
4854                             setinitial, setfromfile);
4855     } else if (!strcmp("disclose", optname)) {
4856         /* order of disclose_names[] must correspond to
4857            disclosure_options in decl.c */
4858         static const char *disclosure_names[] = {
4859             "inventory", "attributes", "vanquished",
4860             "genocides", "conduct",    "overview",
4861         };
4862         int disc_cat[NUM_DISCLOSURE_OPTIONS];
4863         int pick_cnt, pick_idx, opt_idx;
4864         char c;
4865         menu_item *disclosure_pick = (menu_item *) 0;
4866 
4867         tmpwin = create_nhwindow(NHW_MENU);
4868         start_menu(tmpwin);
4869         any = zeroany;
4870         for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) {
4871             Sprintf(buf, "%-12s[%c%c]", disclosure_names[i],
4872                     flags.end_disclose[i], disclosure_options[i]);
4873             any.a_int = i + 1;
4874             add_menu(tmpwin, NO_GLYPH, &any, disclosure_options[i], 0,
4875                      ATR_NONE, buf, MENU_UNSELECTED);
4876             disc_cat[i] = 0;
4877         }
4878         end_menu(tmpwin, "Change which disclosure options categories:");
4879         pick_cnt = select_menu(tmpwin, PICK_ANY, &disclosure_pick);
4880         if (pick_cnt > 0) {
4881             for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) {
4882                 opt_idx = disclosure_pick[pick_idx].item.a_int - 1;
4883                 disc_cat[opt_idx] = 1;
4884             }
4885             free((genericptr_t) disclosure_pick);
4886             disclosure_pick = (menu_item *) 0;
4887         }
4888         destroy_nhwindow(tmpwin);
4889 
4890         for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) {
4891             if (disc_cat[i]) {
4892                 c = flags.end_disclose[i];
4893                 Sprintf(buf, "Disclosure options for %s:",
4894                         disclosure_names[i]);
4895                 tmpwin = create_nhwindow(NHW_MENU);
4896                 start_menu(tmpwin);
4897                 any = zeroany;
4898                 /* 'y','n',and '+' work as alternate selectors; '-' doesn't */
4899                 any.a_char = DISCLOSE_NO_WITHOUT_PROMPT;
4900                 add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE,
4901                          "Never disclose, without prompting",
4902                          (c == any.a_char) ? MENU_SELECTED : MENU_UNSELECTED);
4903                 any.a_char = DISCLOSE_YES_WITHOUT_PROMPT;
4904                 add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE,
4905                          "Always disclose, without prompting",
4906                          (c == any.a_char) ? MENU_SELECTED : MENU_UNSELECTED);
4907                 if (*disclosure_names[i] == 'v') {
4908                     any.a_char = DISCLOSE_SPECIAL_WITHOUT_PROMPT; /* '#' */
4909                     add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE,
4910                              "Always disclose, pick sort order from menu",
4911                              (c == any.a_char) ? MENU_SELECTED
4912                                                : MENU_UNSELECTED);
4913                 }
4914                 any.a_char = DISCLOSE_PROMPT_DEFAULT_NO;
4915                 add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE,
4916                          "Prompt, with default answer of \"No\"",
4917                          (c == any.a_char) ? MENU_SELECTED : MENU_UNSELECTED);
4918                 any.a_char = DISCLOSE_PROMPT_DEFAULT_YES;
4919                 add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE,
4920                          "Prompt, with default answer of \"Yes\"",
4921                          (c == any.a_char) ? MENU_SELECTED : MENU_UNSELECTED);
4922                 if (*disclosure_names[i] == 'v') {
4923                     any.a_char = DISCLOSE_PROMPT_DEFAULT_SPECIAL; /* '?' */
4924                     add_menu(tmpwin, NO_GLYPH, &any, 0, any.a_char, ATR_NONE,
4925                 "Prompt, with default answer of \"Ask\" to request sort menu",
4926                              (c == any.a_char) ? MENU_SELECTED
4927                                                : MENU_UNSELECTED);
4928                 }
4929                 end_menu(tmpwin, buf);
4930                 n = select_menu(tmpwin, PICK_ONE, &disclosure_pick);
4931                 if (n > 0) {
4932                     flags.end_disclose[i] = disclosure_pick[0].item.a_char;
4933                     if (n > 1 && flags.end_disclose[i] == c)
4934                         flags.end_disclose[i] = disclosure_pick[1].item.a_char;
4935                     free((genericptr_t) disclosure_pick);
4936                 }
4937                 destroy_nhwindow(tmpwin);
4938             }
4939         }
4940     } else if (!strcmp("runmode", optname)) {
4941         const char *mode_name;
4942         menu_item *mode_pick = (menu_item *) 0;
4943 
4944         tmpwin = create_nhwindow(NHW_MENU);
4945         start_menu(tmpwin);
4946         any = zeroany;
4947         for (i = 0; i < SIZE(runmodes); i++) {
4948             mode_name = runmodes[i];
4949             any.a_int = i + 1;
4950             add_menu(tmpwin, NO_GLYPH, &any, *mode_name, 0, ATR_NONE,
4951                      mode_name, MENU_UNSELECTED);
4952         }
4953         end_menu(tmpwin, "Select run/travel display mode:");
4954         if (select_menu(tmpwin, PICK_ONE, &mode_pick) > 0) {
4955             flags.runmode = mode_pick->item.a_int - 1;
4956             free((genericptr_t) mode_pick);
4957         }
4958         destroy_nhwindow(tmpwin);
4959     } else if (!strcmp("whatis_coord", optname)) {
4960         menu_item *window_pick = (menu_item *) 0;
4961         int pick_cnt;
4962         char gp = iflags.getpos_coords;
4963 
4964         tmpwin = create_nhwindow(NHW_MENU);
4965         start_menu(tmpwin);
4966         any = zeroany;
4967         any.a_char = GPCOORDS_COMPASS;
4968         add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_COMPASS, 0, ATR_NONE,
4969                  "compass ('east' or '3s' or '2n,4w')",
4970                  (gp == GPCOORDS_COMPASS) ? MENU_SELECTED : MENU_UNSELECTED);
4971         any.a_char = GPCOORDS_COMFULL;
4972         add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_COMFULL, 0, ATR_NONE,
4973                  "full compass ('east' or '3south' or '2north,4west')",
4974                  (gp == GPCOORDS_COMFULL) ? MENU_SELECTED : MENU_UNSELECTED);
4975         any.a_char = GPCOORDS_MAP;
4976         add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_MAP, 0, ATR_NONE,
4977                  "map <x,y>",
4978                  (gp == GPCOORDS_MAP) ? MENU_SELECTED : MENU_UNSELECTED);
4979         any.a_char = GPCOORDS_SCREEN;
4980         add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_SCREEN, 0, ATR_NONE,
4981                  "screen [row,column]",
4982                  (gp == GPCOORDS_SCREEN) ? MENU_SELECTED : MENU_UNSELECTED);
4983         any.a_char = GPCOORDS_NONE;
4984         add_menu(tmpwin, NO_GLYPH, &any, GPCOORDS_NONE, 0, ATR_NONE,
4985                  "none (no coordinates displayed)",
4986                  (gp == GPCOORDS_NONE) ? MENU_SELECTED : MENU_UNSELECTED);
4987         any.a_long = 0L;
4988         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
4989         Sprintf(buf, "map: upper-left: <%d,%d>, lower-right: <%d,%d>%s",
4990                 1, 0, COLNO - 1, ROWNO - 1,
4991                 flags.verbose ? "; column 0 unused, off left edge" : "");
4992         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
4993         if (strcmp(windowprocs.name, "tty")) /* only show for non-tty */
4994             add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
4995        "screen: row is offset to accommodate tty interface's use of top line",
4996                      MENU_UNSELECTED);
4997 #if COLNO == 80
4998 #define COL80ARG flags.verbose ? "; column 80 is not used" : ""
4999 #else
5000 #define COL80ARG ""
5001 #endif
5002         Sprintf(buf, "screen: upper-left: [%02d,%02d], lower-right: [%d,%d]%s",
5003                 0 + 2, 1, ROWNO - 1 + 2, COLNO - 1, COL80ARG);
5004 #undef COL80ARG
5005         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
5006         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
5007         end_menu(tmpwin,
5008             "Select coordinate display when auto-describing a map position:");
5009         if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &window_pick)) > 0) {
5010             iflags.getpos_coords = window_pick[0].item.a_char;
5011             /* PICK_ONE doesn't unselect preselected entry when
5012                selecting another one */
5013             if (pick_cnt > 1 && iflags.getpos_coords == gp)
5014                 iflags.getpos_coords = window_pick[1].item.a_char;
5015             free((genericptr_t) window_pick);
5016         }
5017         destroy_nhwindow(tmpwin);
5018     } else if (!strcmp("whatis_filter", optname)) {
5019         menu_item *window_pick = (menu_item *) 0;
5020         int pick_cnt;
5021         char gf = iflags.getloc_filter;
5022 
5023         tmpwin = create_nhwindow(NHW_MENU);
5024         start_menu(tmpwin);
5025         any = zeroany;
5026         any.a_char = (GFILTER_NONE + 1);
5027         add_menu(tmpwin, NO_GLYPH, &any, 'n',
5028                  0, ATR_NONE, "no filtering",
5029                  (gf == GFILTER_NONE) ? MENU_SELECTED : MENU_UNSELECTED);
5030         any.a_char = (GFILTER_VIEW + 1);
5031         add_menu(tmpwin, NO_GLYPH, &any, 'v',
5032                  0, ATR_NONE, "in view only",
5033                  (gf == GFILTER_VIEW) ? MENU_SELECTED : MENU_UNSELECTED);
5034         any.a_char = (GFILTER_AREA + 1);
5035         add_menu(tmpwin, NO_GLYPH, &any, 'a',
5036                  0, ATR_NONE, "in same area",
5037                  (gf == GFILTER_AREA) ? MENU_SELECTED : MENU_UNSELECTED);
5038         end_menu(tmpwin,
5039       "Select location filtering when going for next/previous map position:");
5040         if ((pick_cnt = select_menu(tmpwin, PICK_ONE, &window_pick)) > 0) {
5041             iflags.getloc_filter = (window_pick[0].item.a_char - 1);
5042             /* PICK_ONE doesn't unselect preselected entry when
5043                selecting another one */
5044             if (pick_cnt > 1 && iflags.getloc_filter == gf)
5045                 iflags.getloc_filter = (window_pick[1].item.a_char - 1);
5046             free((genericptr_t) window_pick);
5047         }
5048         destroy_nhwindow(tmpwin);
5049     } else if (!strcmp("msg_window", optname)) {
5050 #if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS)
5051         if (WINDOWPORT("tty") || WINDOWPORT("curses")) {
5052             /* by Christian W. Cooper */
5053             menu_item *window_pick = (menu_item *) 0;
5054 
5055             tmpwin = create_nhwindow(NHW_MENU);
5056             start_menu(tmpwin);
5057             any = zeroany;
5058             if (!WINDOWPORT("curses")) {
5059                 any.a_char = 's';
5060                 add_menu(tmpwin, NO_GLYPH, &any, 's', 0, ATR_NONE,
5061                          "single", MENU_UNSELECTED);
5062                 any.a_char = 'c';
5063                 add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE,
5064                          "combination", MENU_UNSELECTED);
5065             }
5066             any.a_char = 'f';
5067             add_menu(tmpwin, NO_GLYPH, &any, 'f', 0, ATR_NONE, "full",
5068                      MENU_UNSELECTED);
5069             any.a_char = 'r';
5070             add_menu(tmpwin, NO_GLYPH, &any, 'r', 0, ATR_NONE, "reversed",
5071                      MENU_UNSELECTED);
5072             end_menu(tmpwin, "Select message history display type:");
5073             if (select_menu(tmpwin, PICK_ONE, &window_pick) > 0) {
5074                 iflags.prevmsg_window = window_pick->item.a_char;
5075                 free((genericptr_t) window_pick);
5076             }
5077             destroy_nhwindow(tmpwin);
5078         } else
5079 #endif /* msg_window for tty or curses */
5080             pline("'%s' option is not supported for '%s'.",
5081                   optname, windowprocs.name);
5082     } else if (!strcmp("sortloot", optname)) {
5083         const char *sortl_name;
5084         menu_item *sortl_pick = (menu_item *) 0;
5085 
5086         tmpwin = create_nhwindow(NHW_MENU);
5087         start_menu(tmpwin);
5088         any = zeroany;
5089         for (i = 0; i < SIZE(sortltype); i++) {
5090             sortl_name = sortltype[i];
5091             any.a_char = *sortl_name;
5092             add_menu(tmpwin, NO_GLYPH, &any, *sortl_name, 0, ATR_NONE,
5093                      sortl_name, (flags.sortloot == *sortl_name)
5094                                     ? MENU_SELECTED : MENU_UNSELECTED);
5095         }
5096         end_menu(tmpwin, "Select loot sorting type:");
5097         n = select_menu(tmpwin, PICK_ONE, &sortl_pick);
5098         if (n > 0) {
5099             char c = sortl_pick[0].item.a_char;
5100 
5101             if (n > 1 && c == flags.sortloot)
5102                 c = sortl_pick[1].item.a_char;
5103             flags.sortloot = c;
5104             free((genericptr_t) sortl_pick);
5105         }
5106         destroy_nhwindow(tmpwin);
5107     } else if (!strcmp("align_message", optname)
5108                || !strcmp("align_status", optname)) {
5109         menu_item *window_pick = (menu_item *) 0;
5110         char abuf[BUFSZ];
5111         boolean msg = (*(optname + 6) == 'm');
5112 
5113         tmpwin = create_nhwindow(NHW_MENU);
5114         start_menu(tmpwin);
5115         any = zeroany;
5116         any.a_int = ALIGN_TOP;
5117         add_menu(tmpwin, NO_GLYPH, &any, 't', 0, ATR_NONE, "top",
5118                  MENU_UNSELECTED);
5119         any.a_int = ALIGN_BOTTOM;
5120         add_menu(tmpwin, NO_GLYPH, &any, 'b', 0, ATR_NONE, "bottom",
5121                  MENU_UNSELECTED);
5122         any.a_int = ALIGN_LEFT;
5123         add_menu(tmpwin, NO_GLYPH, &any, 'l', 0, ATR_NONE, "left",
5124                  MENU_UNSELECTED);
5125         any.a_int = ALIGN_RIGHT;
5126         add_menu(tmpwin, NO_GLYPH, &any, 'r', 0, ATR_NONE, "right",
5127                  MENU_UNSELECTED);
5128         Sprintf(abuf, "Select %s window placement relative to the map:",
5129                 msg ? "message" : "status");
5130         end_menu(tmpwin, abuf);
5131         if (select_menu(tmpwin, PICK_ONE, &window_pick) > 0) {
5132             if (msg)
5133                 iflags.wc_align_message = window_pick->item.a_int;
5134             else
5135                 iflags.wc_align_status = window_pick->item.a_int;
5136             free((genericptr_t) window_pick);
5137         }
5138         destroy_nhwindow(tmpwin);
5139     } else if (!strcmp("number_pad", optname)) {
5140         static const char *npchoices[] = {
5141             " 0 (off)", " 1 (on)", " 2 (on, MSDOS compatible)",
5142             " 3 (on, phone-style digit layout)",
5143             " 4 (on, phone-style layout, MSDOS compatible)",
5144             "-1 (off, 'z' to move upper-left, 'y' to zap wands)"
5145         };
5146         menu_item *mode_pick = (menu_item *) 0;
5147 
5148         tmpwin = create_nhwindow(NHW_MENU);
5149         start_menu(tmpwin);
5150         any = zeroany;
5151         for (i = 0; i < SIZE(npchoices); i++) {
5152             any.a_int = i + 1;
5153             add_menu(tmpwin, NO_GLYPH, &any, 'a' + i, 0, ATR_NONE,
5154                      npchoices[i], MENU_UNSELECTED);
5155         }
5156         end_menu(tmpwin, "Select number_pad mode:");
5157         if (select_menu(tmpwin, PICK_ONE, &mode_pick) > 0) {
5158             switch (mode_pick->item.a_int - 1) {
5159             case 0:
5160                 iflags.num_pad = FALSE;
5161                 iflags.num_pad_mode = 0;
5162                 break;
5163             case 1:
5164                 iflags.num_pad = TRUE;
5165                 iflags.num_pad_mode = 0;
5166                 break;
5167             case 2:
5168                 iflags.num_pad = TRUE;
5169                 iflags.num_pad_mode = 1;
5170                 break;
5171             case 3:
5172                 iflags.num_pad = TRUE;
5173                 iflags.num_pad_mode = 2;
5174                 break;
5175             case 4:
5176                 iflags.num_pad = TRUE;
5177                 iflags.num_pad_mode = 3;
5178                 break;
5179             /* last menu choice: number_pad == -1 */
5180             case 5:
5181                 iflags.num_pad = FALSE;
5182                 iflags.num_pad_mode = 1;
5183                 break;
5184             }
5185             reset_commands(FALSE);
5186             number_pad(iflags.num_pad ? 1 : 0);
5187             free((genericptr_t) mode_pick);
5188         }
5189         destroy_nhwindow(tmpwin);
5190     } else if (!strcmp("menu_headings", optname)) {
5191         int mhattr = query_attr("How to highlight menu headings:");
5192 
5193         if (mhattr != -1)
5194             iflags.menu_headings = mhattr;
5195     } else if (!strcmp("msgtype", optname)) {
5196         int opt_idx, nmt, mttyp;
5197         char mtbuf[BUFSZ];
5198 
5199  msgtypes_again:
5200         nmt = msgtype_count();
5201         opt_idx = handle_add_list_remove("message type", nmt);
5202         if (opt_idx == 3) { /* done */
5203             return TRUE;
5204         } else if (opt_idx == 0) { /* add new */
5205             mtbuf[0] = '\0';
5206             getlin("What new message pattern?", mtbuf);
5207             if (*mtbuf == '\033')
5208                 return TRUE;
5209             if (*mtbuf
5210                 && test_regex_pattern(mtbuf, (const char *)0)
5211                 && (mttyp = query_msgtype()) != -1
5212                 && !msgtype_add(mttyp, mtbuf)) {
5213                 pline("Error adding the message type.");
5214                 wait_synch();
5215             }
5216             goto msgtypes_again;
5217         } else { /* list (1) or remove (2) */
5218             int pick_idx, pick_cnt;
5219             int mt_idx;
5220             unsigned ln;
5221             const char *mtype;
5222             menu_item *pick_list = (menu_item *) 0;
5223             struct plinemsg_type *tmp = plinemsg_types;
5224 
5225             tmpwin = create_nhwindow(NHW_MENU);
5226             start_menu(tmpwin);
5227             any = zeroany;
5228             mt_idx = 0;
5229             while (tmp) {
5230                 mtype = msgtype2name(tmp->msgtype);
5231                 any.a_int = ++mt_idx;
5232                 Sprintf(mtbuf, "%-5s \"", mtype);
5233                 ln = sizeof mtbuf - strlen(mtbuf) - sizeof "\"";
5234                 if (strlen(tmp->pattern) > ln)
5235                     Strcat(strncat(mtbuf, tmp->pattern, ln - 3), "...\"");
5236                 else
5237                     Strcat(strcat(mtbuf, tmp->pattern), "\"");
5238                 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, mtbuf,
5239                          MENU_UNSELECTED);
5240                 tmp = tmp->next;
5241             }
5242             Sprintf(mtbuf, "%s message types",
5243                     (opt_idx == 1) ? "List of" : "Remove which");
5244             end_menu(tmpwin, mtbuf);
5245             pick_cnt = select_menu(tmpwin,
5246                                    (opt_idx == 1) ? PICK_NONE : PICK_ANY,
5247                                    &pick_list);
5248             if (pick_cnt > 0) {
5249                 for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx)
5250                     free_one_msgtype(pick_list[pick_idx].item.a_int - 1
5251                                            - pick_idx);
5252                 free((genericptr_t) pick_list), pick_list = (menu_item *) 0;
5253             }
5254             destroy_nhwindow(tmpwin);
5255             if (pick_cnt >= 0)
5256                 goto msgtypes_again;
5257         }
5258     } else if (!strcmp("menu_colors", optname)) {
5259         int opt_idx, nmc, mcclr, mcattr;
5260         char mcbuf[BUFSZ];
5261 
5262  menucolors_again:
5263         nmc = count_menucolors();
5264         opt_idx = handle_add_list_remove("menucolor", nmc);
5265         if (opt_idx == 3) { /* done */
5266  menucolors_done:
5267             /* in case we've made a change which impacts current persistent
5268                inventory window; we don't track whether an actual changed
5269                occurred, so just assume there was one and that it matters;
5270                if we're wrong, a redundant update is cheap... */
5271             if (iflags.use_menu_color)
5272                 update_inventory();
5273 
5274             /* menu colors aren't being used; if any are defined, remind
5275                player how to use them */
5276             else if (nmc > 0)
5277                 pline(
5278     "To have menu colors become active, toggle 'menucolors' option to True.");
5279             return TRUE;
5280 
5281         } else if (opt_idx == 0) { /* add new */
5282             mcbuf[0] = '\0';
5283             getlin("What new menucolor pattern?", mcbuf);
5284             if (*mcbuf == '\033')
5285                 goto menucolors_done;
5286             if (*mcbuf
5287                 && test_regex_pattern(mcbuf, (const char *)0)
5288                 && (mcclr = query_color((char *) 0)) != -1
5289                 && (mcattr = query_attr((char *) 0)) != -1
5290                 && !add_menu_coloring_parsed(mcbuf, mcclr, mcattr)) {
5291                 pline("Error adding the menu color.");
5292                 wait_synch();
5293             }
5294             goto menucolors_again;
5295 
5296         } else { /* list (1) or remove (2) */
5297             int pick_idx, pick_cnt;
5298             int mc_idx;
5299             unsigned ln;
5300             const char *sattr, *sclr;
5301             menu_item *pick_list = (menu_item *) 0;
5302             struct menucoloring *tmp = menu_colorings;
5303             char clrbuf[QBUFSZ];
5304 
5305             tmpwin = create_nhwindow(NHW_MENU);
5306             start_menu(tmpwin);
5307             any = zeroany;
5308             mc_idx = 0;
5309             while (tmp) {
5310                 sattr = attr2attrname(tmp->attr);
5311                 sclr = strcpy(clrbuf, clr2colorname(tmp->color));
5312                 (void) strNsubst(clrbuf, " ", "-", 0);
5313                 any.a_int = ++mc_idx;
5314                 /* construct suffix */
5315                 Sprintf(buf, "\"\"=%s%s%s", sclr,
5316                         (tmp->attr != ATR_NONE) ? "&" : "",
5317                         (tmp->attr != ATR_NONE) ? sattr : "");
5318                 /* now main string */
5319                 ln = sizeof buf - strlen(buf) - 1; /* length available */
5320                 Strcpy(mcbuf, "\"");
5321                 if (strlen(tmp->origstr) > ln)
5322                     Strcat(strncat(mcbuf, tmp->origstr, ln - 3), "...");
5323                 else
5324                     Strcat(mcbuf, tmp->origstr);
5325                 /* combine main string and suffix */
5326                 Strcat(mcbuf, &buf[1]); /* skip buf[]'s initial quote */
5327                 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, mcbuf,
5328                          MENU_UNSELECTED);
5329                 tmp = tmp->next;
5330             }
5331             Sprintf(mcbuf, "%s menu colors",
5332                     (opt_idx == 1) ? "List of" : "Remove which");
5333             end_menu(tmpwin, mcbuf);
5334             pick_cnt = select_menu(tmpwin,
5335                                    (opt_idx == 1) ? PICK_NONE : PICK_ANY,
5336                                    &pick_list);
5337             if (pick_cnt > 0) {
5338                 for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx)
5339                     free_one_menu_coloring(pick_list[pick_idx].item.a_int - 1
5340                                            - pick_idx);
5341                 free((genericptr_t) pick_list), pick_list = (menu_item *) 0;
5342             }
5343             destroy_nhwindow(tmpwin);
5344             if (pick_cnt >= 0)
5345                 goto menucolors_again;
5346         }
5347     } else if (!strcmp("autopickup_exception", optname)) {
5348         int opt_idx, numapes = 0;
5349         char apebuf[2 + BUFSZ]; /* so &apebuf[1] is BUFSZ long for getlin() */
5350         struct autopickup_exception *ape;
5351 
5352  ape_again:
5353         numapes = count_apes();
5354         opt_idx = handle_add_list_remove("autopickup exception", numapes);
5355         if (opt_idx == 3) { /* done */
5356             return TRUE;
5357         } else if (opt_idx == 0) { /* add new */
5358             /* EDIT_GETLIN:  assume user doesn't user want previous
5359                exception used as default input string for this one... */
5360             apebuf[0] = apebuf[1] = '\0';
5361             getlin("What new autopickup exception pattern?", &apebuf[1]);
5362             mungspaces(&apebuf[1]); /* regularize whitespace */
5363             if (apebuf[1] == '\033')
5364                 return TRUE;
5365             if (apebuf[1]) {
5366                 apebuf[0] = '\"';
5367                 /* guarantee room for \" prefix and \"\0 suffix;
5368                    -2 is good enough for apebuf[] but -3 makes
5369                    sure the whole thing fits within normal BUFSZ */
5370                 apebuf[sizeof apebuf - 2] = '\0';
5371                 Strcat(apebuf, "\"");
5372                 add_autopickup_exception(apebuf);
5373             }
5374             goto ape_again;
5375         } else { /* list (1) or remove (2) */
5376             int pick_idx, pick_cnt;
5377             menu_item *pick_list = (menu_item *) 0;
5378 
5379             tmpwin = create_nhwindow(NHW_MENU);
5380             start_menu(tmpwin);
5381             if (numapes) {
5382                 ape = apelist;
5383                 any = zeroany;
5384                 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings,
5385                          "Always pickup '<'; never pickup '>'",
5386                          MENU_UNSELECTED);
5387                 for (i = 0; i < numapes && ape; i++) {
5388                     any.a_void = (opt_idx == 1) ? 0 : ape;
5389                     /* length of pattern plus quotes (plus '<'/'>') is
5390                        less than BUFSZ */
5391                     Sprintf(apebuf, "\"%c%s\"", ape->grab ? '<' : '>',
5392                             ape->pattern);
5393                     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, apebuf,
5394                              MENU_UNSELECTED);
5395                     ape = ape->next;
5396                 }
5397             }
5398             Sprintf(apebuf, "%s autopickup exceptions",
5399                     (opt_idx == 1) ? "List of" : "Remove which");
5400             end_menu(tmpwin, apebuf);
5401             pick_cnt = select_menu(tmpwin,
5402                                    (opt_idx == 1) ? PICK_NONE : PICK_ANY,
5403                                    &pick_list);
5404             if (pick_cnt > 0) {
5405                 for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx)
5406                     remove_autopickup_exception(
5407                                          (struct autopickup_exception *)
5408                                              pick_list[pick_idx].item.a_void);
5409                 free((genericptr_t) pick_list), pick_list = (menu_item *) 0;
5410             }
5411             destroy_nhwindow(tmpwin);
5412             if (pick_cnt >= 0)
5413                 goto ape_again;
5414         }
5415     } else if (!strcmp("symset", optname)
5416                || !strcmp("roguesymset", optname)) {
5417         menu_item *symset_pick = (menu_item *) 0;
5418         boolean rogueflag = (*optname == 'r'),
5419                 ready_to_switch = FALSE,
5420                 nothing_to_do = FALSE;
5421         char *symset_name, fmtstr[20];
5422         struct symsetentry *sl;
5423         int res, which_set, setcount = 0, chosen = -2, defindx = 0;
5424 
5425         which_set = rogueflag ? ROGUESET : PRIMARY;
5426         symset_list = (struct symsetentry *) 0;
5427         /* clear symset[].name as a flag to read_sym_file() to build list */
5428         symset_name = symset[which_set].name;
5429         symset[which_set].name = (char *) 0;
5430 
5431         res = read_sym_file(which_set);
5432         /* put symset name back */
5433         symset[which_set].name = symset_name;
5434 
5435         if (res && symset_list) {
5436             int thissize,
5437                 biggest = (int) (sizeof "Default Symbols" - sizeof ""),
5438                 big_desc = 0;
5439 
5440             for (sl = symset_list; sl; sl = sl->next) {
5441                 /* check restrictions */
5442                 if (rogueflag ? sl->primary : sl->rogue)
5443                     continue;
5444 #ifndef MAC_GRAPHICS_ENV
5445                 if (sl->handling == H_MAC)
5446                     continue;
5447 #endif
5448 
5449                 setcount++;
5450                 /* find biggest name */
5451                 thissize = sl->name ? (int) strlen(sl->name) : 0;
5452                 if (thissize > biggest)
5453                     biggest = thissize;
5454                 thissize = sl->desc ? (int) strlen(sl->desc) : 0;
5455                 if (thissize > big_desc)
5456                     big_desc = thissize;
5457             }
5458             if (!setcount) {
5459                 pline("There are no appropriate %s symbol sets available.",
5460                       rogueflag ? "rogue level" : "primary");
5461                 return TRUE;
5462             }
5463 
5464             Sprintf(fmtstr, "%%-%ds %%s", biggest + 2);
5465             tmpwin = create_nhwindow(NHW_MENU);
5466             start_menu(tmpwin);
5467             any = zeroany;
5468             any.a_int = 1; /* -1 + 2 [see 'if (sl->name) {' below]*/
5469             if (!symset_name)
5470                 defindx = any.a_int;
5471             add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
5472                      "Default Symbols",
5473                      (any.a_int == defindx) ? MENU_SELECTED
5474                                             : MENU_UNSELECTED);
5475 
5476             for (sl = symset_list; sl; sl = sl->next) {
5477                 /* check restrictions */
5478                 if (rogueflag ? sl->primary : sl->rogue)
5479                     continue;
5480 #ifndef MAC_GRAPHICS_ENV
5481                 if (sl->handling == H_MAC)
5482                     continue;
5483 #endif
5484                 if (sl->name) {
5485                     /* +2: sl->idx runs from 0 to N-1 for N symsets;
5486                        +1 because Defaults are implicitly in slot [0];
5487                        +1 again so that valid data is never 0 */
5488                     any.a_int = sl->idx + 2;
5489                     if (symset_name && !strcmpi(sl->name, symset_name))
5490                         defindx = any.a_int;
5491                     Sprintf(buf, fmtstr, sl->name, sl->desc ? sl->desc : "");
5492                     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf,
5493                              (any.a_int == defindx) ? MENU_SELECTED
5494                                                     : MENU_UNSELECTED);
5495                 }
5496             }
5497             Sprintf(buf, "Select %ssymbol set:",
5498                     rogueflag ? "rogue level " : "");
5499             end_menu(tmpwin, buf);
5500             n = select_menu(tmpwin, PICK_ONE, &symset_pick);
5501             if (n > 0) {
5502                 chosen = symset_pick[0].item.a_int;
5503                 /* if picking non-preselected entry yields 2, make sure
5504                    that we're going with the non-preselected one */
5505                 if (n == 2 && chosen == defindx)
5506                     chosen = symset_pick[1].item.a_int;
5507                 chosen -= 2; /* convert menu index to symset index;
5508                               * "Default symbols" have index -1 */
5509                 free((genericptr_t) symset_pick);
5510             } else if (n == 0 && defindx > 0) {
5511                 chosen = defindx - 2;
5512             }
5513             destroy_nhwindow(tmpwin);
5514 
5515             if (chosen > -1) {
5516                 /* chose an actual symset name from file */
5517                 for (sl = symset_list; sl; sl = sl->next)
5518                     if (sl->idx == chosen)
5519                         break;
5520                 if (sl) {
5521                     /* free the now stale attributes */
5522                     clear_symsetentry(which_set, TRUE);
5523 
5524                     /* transfer only the name of the symbol set */
5525                     symset[which_set].name = dupstr(sl->name);
5526                     ready_to_switch = TRUE;
5527                 }
5528             } else if (chosen == -1) {
5529                 /* explicit selection of defaults */
5530                 /* free the now stale symset attributes */
5531                 clear_symsetentry(which_set, TRUE);
5532             } else
5533                 nothing_to_do = TRUE;
5534         } else if (!res) {
5535             /* The symbols file could not be accessed */
5536             pline("Unable to access \"%s\" file.", SYMBOLS);
5537             return TRUE;
5538         } else if (!symset_list) {
5539             /* The symbols file was empty */
5540             pline("There were no symbol sets found in \"%s\".", SYMBOLS);
5541             return TRUE;
5542         }
5543 
5544         /* clean up */
5545         while ((sl = symset_list) != 0) {
5546             symset_list = sl->next;
5547             if (sl->name)
5548                 free((genericptr_t) sl->name), sl->name = (char *) 0;
5549             if (sl->desc)
5550                 free((genericptr_t) sl->desc), sl->desc = (char *) 0;
5551             free((genericptr_t) sl);
5552         }
5553 
5554         if (nothing_to_do)
5555             return TRUE;
5556 
5557         /* Set default symbols and clear the handling value */
5558         if (rogueflag)
5559             init_rogue_symbols();
5560         else
5561             init_primary_symbols();
5562 
5563         if (symset[which_set].name) {
5564             /* non-default symbols */
5565             if (read_sym_file(which_set)) {
5566                 ready_to_switch = TRUE;
5567             } else {
5568                 clear_symsetentry(which_set, TRUE);
5569                 return TRUE;
5570             }
5571         }
5572 
5573         if (ready_to_switch)
5574             switch_symbols(TRUE);
5575 
5576         if (Is_rogue_level(&u.uz)) {
5577             if (rogueflag)
5578                 assign_graphics(ROGUESET);
5579         } else if (!rogueflag)
5580             assign_graphics(PRIMARY);
5581         preference_update("symset");
5582         need_redraw = TRUE;
5583 
5584     } else {
5585         /* didn't match any of the special options */
5586         return FALSE;
5587     }
5588     return TRUE;
5589 }
5590 
5591 #define rolestring(val, array, field) \
5592     ((val >= 0) ? array[val].field : (val == ROLE_RANDOM) ? randomrole : none)
5593 
5594 /* This is ugly. We have all the option names in the compopt[] array,
5595    but we need to look at each option individually to get the value. */
5596 STATIC_OVL const char *
get_compopt_value(optname,buf)5597 get_compopt_value(optname, buf)
5598 const char *optname;
5599 char *buf;
5600 {
5601     static const char none[] = "(none)", randomrole[] = "random",
5602                       to_be_done[] = "(to be done)",
5603                       defopt[] = "default", defbrief[] = "def";
5604     char ocl[MAXOCLASSES + 1];
5605     int i;
5606 
5607     buf[0] = '\0';
5608     if (!strcmp(optname, "align_message")
5609         || !strcmp(optname, "align_status")) {
5610         int which = !strcmp(optname, "align_status") ? iflags.wc_align_status
5611                                                      : iflags.wc_align_message;
5612         Sprintf(buf, "%s",
5613                 (which == ALIGN_TOP) ? "top"
5614                 : (which == ALIGN_LEFT) ? "left"
5615                   : (which == ALIGN_BOTTOM) ? "bottom"
5616                     : (which == ALIGN_RIGHT) ? "right"
5617                       : defopt);
5618     } else if (!strcmp(optname, "align"))
5619         Sprintf(buf, "%s", rolestring(flags.initalign, aligns, adj));
5620 #ifdef WIN32
5621     else if (!strcmp(optname, "altkeyhandler"))
5622         Sprintf(buf, "%s",
5623                 iflags.altkeyhandler[0] ? iflags.altkeyhandler : "default");
5624 #endif
5625 #ifdef BACKWARD_COMPAT
5626     else if (!strcmp(optname, "boulder"))
5627         Sprintf(buf, "%c",
5628                 ov_primary_syms[SYM_BOULDER + SYM_OFF_X]
5629                     ? ov_primary_syms[SYM_BOULDER + SYM_OFF_X]
5630                     : showsyms[(int) objects[BOULDER].oc_class + SYM_OFF_O]);
5631 #endif
5632     else if (!strcmp(optname, "catname"))
5633         Sprintf(buf, "%s", catname[0] ? catname : none);
5634     else if (!strcmp(optname, "disclose"))
5635         for (i = 0; i < NUM_DISCLOSURE_OPTIONS; i++) {
5636             if (i)
5637                 (void) strkitten(buf, ' ');
5638             (void) strkitten(buf, flags.end_disclose[i]);
5639             (void) strkitten(buf, disclosure_options[i]);
5640         }
5641     else if (!strcmp(optname, "dogname"))
5642         Sprintf(buf, "%s", dogname[0] ? dogname : none);
5643     else if (!strcmp(optname, "dungeon"))
5644         Sprintf(buf, "%s", to_be_done);
5645     else if (!strcmp(optname, "effects"))
5646         Sprintf(buf, "%s", to_be_done);
5647     else if (!strcmp(optname, "font_map"))
5648         Sprintf(buf, "%s", iflags.wc_font_map ? iflags.wc_font_map : defopt);
5649     else if (!strcmp(optname, "font_message"))
5650         Sprintf(buf, "%s",
5651                 iflags.wc_font_message ? iflags.wc_font_message : defopt);
5652     else if (!strcmp(optname, "font_status"))
5653         Sprintf(buf, "%s",
5654                 iflags.wc_font_status ? iflags.wc_font_status : defopt);
5655     else if (!strcmp(optname, "font_menu"))
5656         Sprintf(buf, "%s",
5657                 iflags.wc_font_menu ? iflags.wc_font_menu : defopt);
5658     else if (!strcmp(optname, "font_text"))
5659         Sprintf(buf, "%s",
5660                 iflags.wc_font_text ? iflags.wc_font_text : defopt);
5661     else if (!strcmp(optname, "font_size_map")) {
5662         if (iflags.wc_fontsiz_map)
5663             Sprintf(buf, "%d", iflags.wc_fontsiz_map);
5664         else
5665             Strcpy(buf, defopt);
5666     } else if (!strcmp(optname, "font_size_message")) {
5667         if (iflags.wc_fontsiz_message)
5668             Sprintf(buf, "%d", iflags.wc_fontsiz_message);
5669         else
5670             Strcpy(buf, defopt);
5671     } else if (!strcmp(optname, "font_size_status")) {
5672         if (iflags.wc_fontsiz_status)
5673             Sprintf(buf, "%d", iflags.wc_fontsiz_status);
5674         else
5675             Strcpy(buf, defopt);
5676     } else if (!strcmp(optname, "font_size_menu")) {
5677         if (iflags.wc_fontsiz_menu)
5678             Sprintf(buf, "%d", iflags.wc_fontsiz_menu);
5679         else
5680             Strcpy(buf, defopt);
5681     } else if (!strcmp(optname, "font_size_text")) {
5682         if (iflags.wc_fontsiz_text)
5683             Sprintf(buf, "%d", iflags.wc_fontsiz_text);
5684         else
5685             Strcpy(buf, defopt);
5686     } else if (!strcmp(optname, "fruit"))
5687         Sprintf(buf, "%s", pl_fruit);
5688     else if (!strcmp(optname, "gender"))
5689         Sprintf(buf, "%s", rolestring(flags.initgend, genders, adj));
5690     else if (!strcmp(optname, "horsename"))
5691         Sprintf(buf, "%s", horsename[0] ? horsename : none);
5692     else if (!strcmp(optname, "map_mode")) {
5693         i = iflags.wc_map_mode;
5694         Sprintf(buf, "%s",
5695                 (i == MAP_MODE_TILES) ? "tiles"
5696                 : (i == MAP_MODE_ASCII4x6) ? "ascii4x6"
5697                   : (i == MAP_MODE_ASCII6x8) ? "ascii6x8"
5698                     : (i == MAP_MODE_ASCII8x8) ? "ascii8x8"
5699                       : (i == MAP_MODE_ASCII16x8) ? "ascii16x8"
5700                         : (i == MAP_MODE_ASCII7x12) ? "ascii7x12"
5701                           : (i == MAP_MODE_ASCII8x12) ? "ascii8x12"
5702                             : (i == MAP_MODE_ASCII16x12) ? "ascii16x12"
5703                               : (i == MAP_MODE_ASCII12x16) ? "ascii12x16"
5704                                 : (i == MAP_MODE_ASCII10x18) ? "ascii10x18"
5705                                   : (i == MAP_MODE_ASCII_FIT_TO_SCREEN)
5706                                     ? "fit_to_screen"
5707                                     : defopt);
5708     } else if (!strcmp(optname, "menustyle"))
5709         Sprintf(buf, "%s", menutype[(int) flags.menu_style]);
5710     else if (!strcmp(optname, "menu_deselect_all"))
5711         Sprintf(buf, "%s", to_be_done);
5712     else if (!strcmp(optname, "menu_deselect_page"))
5713         Sprintf(buf, "%s", to_be_done);
5714     else if (!strcmp(optname, "menu_first_page"))
5715         Sprintf(buf, "%s", to_be_done);
5716     else if (!strcmp(optname, "menu_invert_all"))
5717         Sprintf(buf, "%s", to_be_done);
5718     else if (!strcmp(optname, "menu_headings"))
5719         Sprintf(buf, "%s", attr2attrname(iflags.menu_headings));
5720     else if (!strcmp(optname, "menu_invert_page"))
5721         Sprintf(buf, "%s", to_be_done);
5722     else if (!strcmp(optname, "menu_last_page"))
5723         Sprintf(buf, "%s", to_be_done);
5724     else if (!strcmp(optname, "menu_next_page"))
5725         Sprintf(buf, "%s", to_be_done);
5726     else if (!strcmp(optname, "menu_previous_page"))
5727         Sprintf(buf, "%s", to_be_done);
5728     else if (!strcmp(optname, "menu_search"))
5729         Sprintf(buf, "%s", to_be_done);
5730     else if (!strcmp(optname, "menu_select_all"))
5731         Sprintf(buf, "%s", to_be_done);
5732     else if (!strcmp(optname, "menu_select_page"))
5733         Sprintf(buf, "%s", to_be_done);
5734     else if (!strcmp(optname, "monsters")) {
5735         Sprintf(buf, "%s", to_be_done);
5736     } else if (!strcmp(optname, "msghistory")) {
5737         Sprintf(buf, "%u", iflags.msg_history);
5738 #ifdef TTY_GRAPHICS
5739     } else if (!strcmp(optname, "msg_window")) {
5740         Sprintf(buf, "%s", (iflags.prevmsg_window == 's') ? "single"
5741                            : (iflags.prevmsg_window == 'c') ? "combination"
5742                              : (iflags.prevmsg_window == 'f') ? "full"
5743                                : "reversed");
5744 #endif
5745     } else if (!strcmp(optname, "name")) {
5746         Sprintf(buf, "%s", plname);
5747     } else if (!strcmp(optname, "mouse_support")) {
5748 #ifdef WIN32
5749 #define MOUSEFIX1 ", QuickEdit off"
5750 #define MOUSEFIX2 ", QuickEdit unchanged"
5751 #else
5752 #define MOUSEFIX1 ", O/S adjusted"
5753 #define MOUSEFIX2 ", O/S unchanged"
5754 #endif
5755         static const char *mousemodes[][2] = {
5756             { "0=off", "" },
5757             { "1=on",  MOUSEFIX1 },
5758             { "2=on",  MOUSEFIX2 },
5759         };
5760 #undef MOUSEFIX1
5761 #undef MOUSEFIX2
5762         int ms = iflags.wc_mouse_support;
5763 
5764         if (ms >= 0 && ms <= 2)
5765             Sprintf(buf, "%s%s", mousemodes[ms][0], mousemodes[ms][1]);
5766     } else if (!strcmp(optname, "number_pad")) {
5767         static const char *numpadmodes[] = {
5768             "0=off", "1=on", "2=on, MSDOS compatible",
5769             "3=on, phone-style layout",
5770             "4=on, phone layout, MSDOS compatible",
5771             "-1=off, y & z swapped", /*[5]*/
5772         };
5773         int indx = Cmd.num_pad
5774                        ? (Cmd.phone_layout ? (Cmd.pcHack_compat ? 4 : 3)
5775                                            : (Cmd.pcHack_compat ? 2 : 1))
5776                        : Cmd.swap_yz ? 5 : 0;
5777 
5778         Strcpy(buf, numpadmodes[indx]);
5779     } else if (!strcmp(optname, "objects")) {
5780         Sprintf(buf, "%s", to_be_done);
5781     } else if (!strcmp(optname, "packorder")) {
5782         oc_to_str(flags.inv_order, ocl);
5783         Sprintf(buf, "%s", ocl);
5784 #ifdef CHANGE_COLOR
5785     } else if (!strcmp(optname, "palette")) {
5786         Sprintf(buf, "%s", get_color_string());
5787 #endif
5788     } else if (!strcmp(optname, "paranoid_confirmation")) {
5789         char tmpbuf[QBUFSZ];
5790 
5791         tmpbuf[0] = '\0';
5792         for (i = 0; paranoia[i].flagmask != 0; ++i)
5793             if (flags.paranoia_bits & paranoia[i].flagmask)
5794                 Sprintf(eos(tmpbuf), " %s", paranoia[i].argname);
5795         Strcpy(buf, tmpbuf[0] ? &tmpbuf[1] : "none");
5796     } else if (!strcmp(optname, "petattr")) {
5797 #ifdef CURSES_GRAPHICS
5798         if (WINDOWPORT("curses")) {
5799             char tmpbuf[QBUFSZ];
5800 
5801             Strcpy(buf, curses_fmt_attrs(tmpbuf));
5802         } else
5803 #endif
5804         if (iflags.wc2_petattr != 0)
5805             Sprintf(buf, "0x%08x", iflags.wc2_petattr);
5806         else
5807             Strcpy(buf, defopt);
5808     } else if (!strcmp(optname, "pettype")) {
5809         Sprintf(buf, "%s", (preferred_pet == 'c') ? "cat"
5810                            : (preferred_pet == 'd') ? "dog"
5811                              : (preferred_pet == 'h') ? "horse"
5812                                : (preferred_pet == 'n') ? "none"
5813                                  : "random");
5814     } else if (!strcmp(optname, "pickup_burden")) {
5815         Sprintf(buf, "%s", burdentype[flags.pickup_burden]);
5816     } else if (!strcmp(optname, "pickup_types")) {
5817         oc_to_str(flags.pickup_types, ocl);
5818         Sprintf(buf, "%s", ocl[0] ? ocl : "all");
5819     } else if (!strcmp(optname, "pile_limit")) {
5820         Sprintf(buf, "%d", flags.pile_limit);
5821     } else if (!strcmp(optname, "playmode")) {
5822         Strcpy(buf, wizard ? "debug" : discover ? "explore" : "normal");
5823     } else if (!strcmp(optname, "race")) {
5824         Sprintf(buf, "%s", rolestring(flags.initrace, races, noun));
5825     } else if (!strcmp(optname, "roguesymset")) {
5826         Sprintf(buf, "%s",
5827                 symset[ROGUESET].name ? symset[ROGUESET].name : "default");
5828         if (currentgraphics == ROGUESET && symset[ROGUESET].name)
5829             Strcat(buf, ", active");
5830     } else if (!strcmp(optname, "role")) {
5831         Sprintf(buf, "%s", rolestring(flags.initrole, roles, name.m));
5832     } else if (!strcmp(optname, "runmode")) {
5833         Sprintf(buf, "%s", runmodes[flags.runmode]);
5834     } else if (!strcmp(optname, "whatis_coord")) {
5835         Sprintf(buf, "%s",
5836                 (iflags.getpos_coords == GPCOORDS_MAP) ? "map"
5837                 : (iflags.getpos_coords == GPCOORDS_COMPASS) ? "compass"
5838                 : (iflags.getpos_coords == GPCOORDS_COMFULL) ? "full compass"
5839                 : (iflags.getpos_coords == GPCOORDS_SCREEN) ? "screen"
5840                 : "none");
5841     } else if (!strcmp(optname, "whatis_filter")) {
5842         Sprintf(buf, "%s",
5843                 (iflags.getloc_filter == GFILTER_VIEW) ? "view"
5844                 : (iflags.getloc_filter == GFILTER_AREA) ? "area"
5845                 : "none");
5846     } else if (!strcmp(optname, "scores")) {
5847         Sprintf(buf, "%d top/%d around%s", flags.end_top, flags.end_around,
5848                 flags.end_own ? "/own" : "");
5849     } else if (!strcmp(optname, "scroll_amount")) {
5850         if (iflags.wc_scroll_amount)
5851             Sprintf(buf, "%d", iflags.wc_scroll_amount);
5852         else
5853             Strcpy(buf, defopt);
5854     } else if (!strcmp(optname, "scroll_margin")) {
5855         if (iflags.wc_scroll_margin)
5856             Sprintf(buf, "%d", iflags.wc_scroll_margin);
5857         else
5858             Strcpy(buf, defopt);
5859     } else if (!strcmp(optname, "sortloot")) {
5860         for (i = 0; i < SIZE(sortltype); i++)
5861             if (flags.sortloot == sortltype[i][0]) {
5862                 Strcpy(buf, sortltype[i]);
5863                 break;
5864             }
5865     } else if (!strcmp(optname, "player_selection")) {
5866         Sprintf(buf, "%s", iflags.wc_player_selection ? "prompts" : "dialog");
5867 #ifdef MSDOS
5868     } else if (!strcmp(optname, "soundcard")) {
5869         Sprintf(buf, "%s", to_be_done);
5870 #endif
5871 #ifdef STATUS_HILITES
5872     } else if (!strcmp("statushilites", optname)) {
5873         if (!iflags.hilite_delta)
5874             Strcpy(buf, "0 (off: don't highlight status fields)");
5875         else
5876             Sprintf(buf, "%ld (on: highlight status for %ld turns)",
5877                     iflags.hilite_delta, iflags.hilite_delta);
5878 #endif
5879     } else if (!strcmp(optname,"statuslines")) {
5880         if (wc2_supported(optname))
5881             Strcpy(buf, (iflags.wc2_statuslines < 3) ? "2" : "3");
5882         /* else default to "unknown" */
5883     } else if (!strcmp(optname, "suppress_alert")) {
5884         if (flags.suppress_alert == 0L)
5885             Strcpy(buf, none);
5886         else
5887             Sprintf(buf, "%lu.%lu.%lu", FEATURE_NOTICE_VER_MAJ,
5888                     FEATURE_NOTICE_VER_MIN, FEATURE_NOTICE_VER_PATCH);
5889     } else if (!strcmp(optname, "symset")) {
5890         Sprintf(buf, "%s",
5891                 symset[PRIMARY].name ? symset[PRIMARY].name : "default");
5892         if (currentgraphics == PRIMARY && symset[PRIMARY].name)
5893             Strcat(buf, ", active");
5894     } else if (!strcmp(optname, "term_cols")) {
5895         if (iflags.wc2_term_cols)
5896             Sprintf(buf, "%d", iflags.wc2_term_cols);
5897         else
5898             Strcpy(buf, defopt);
5899     } else if (!strcmp(optname, "term_rows")) {
5900         if (iflags.wc2_term_rows)
5901             Sprintf(buf, "%d", iflags.wc2_term_rows);
5902         else
5903             Strcpy(buf, defopt);
5904     } else if (!strcmp(optname, "tile_file")) {
5905         Sprintf(buf, "%s",
5906                 iflags.wc_tile_file ? iflags.wc_tile_file : defopt);
5907     } else if (!strcmp(optname, "tile_height")) {
5908         if (iflags.wc_tile_height)
5909             Sprintf(buf, "%d", iflags.wc_tile_height);
5910         else
5911             Strcpy(buf, defopt);
5912     } else if (!strcmp(optname, "tile_width")) {
5913         if (iflags.wc_tile_width)
5914             Sprintf(buf, "%d", iflags.wc_tile_width);
5915         else
5916             Strcpy(buf, defopt);
5917     } else if (!strcmp(optname, "traps")) {
5918         Sprintf(buf, "%s", to_be_done);
5919     } else if (!strcmp(optname, "vary_msgcount")) {
5920         if (iflags.wc_vary_msgcount)
5921             Sprintf(buf, "%d", iflags.wc_vary_msgcount);
5922         else
5923             Strcpy(buf, defopt);
5924 #ifdef MSDOS
5925     } else if (!strcmp(optname, "video")) {
5926         Sprintf(buf, "%s", to_be_done);
5927 #endif
5928 #ifdef VIDEOSHADES
5929     } else if (!strcmp(optname, "videoshades")) {
5930         Sprintf(buf, "%s-%s-%s", shade[0], shade[1], shade[2]);
5931     } else if (!strcmp(optname, "videocolors")) {
5932         Sprintf(buf, "%d-%d-%d-%d-%d-%d-%d-%d-%d-%d-%d-%d",
5933                 ttycolors[CLR_RED], ttycolors[CLR_GREEN],
5934                 ttycolors[CLR_BROWN], ttycolors[CLR_BLUE],
5935                 ttycolors[CLR_MAGENTA], ttycolors[CLR_CYAN],
5936                 ttycolors[CLR_ORANGE], ttycolors[CLR_BRIGHT_GREEN],
5937                 ttycolors[CLR_YELLOW], ttycolors[CLR_BRIGHT_BLUE],
5938                 ttycolors[CLR_BRIGHT_MAGENTA], ttycolors[CLR_BRIGHT_CYAN]);
5939 #endif /* VIDEOSHADES */
5940     } else if (!strcmp(optname,"windowborders")) {
5941         Sprintf(buf, "%s",
5942                 (iflags.wc2_windowborders == 0) ? "0=off"
5943                 : (iflags.wc2_windowborders == 1) ? "1=on"
5944                   : (iflags.wc2_windowborders == 2) ? "2=auto"
5945                     : defopt);
5946     } else if (!strcmp(optname, "windowtype")) {
5947         Sprintf(buf, "%s", windowprocs.name);
5948     } else if (!strcmp(optname, "windowcolors")) {
5949         Sprintf(
5950             buf, "%s/%s %s/%s %s/%s %s/%s",
5951             iflags.wc_foregrnd_menu ? iflags.wc_foregrnd_menu : defbrief,
5952             iflags.wc_backgrnd_menu ? iflags.wc_backgrnd_menu : defbrief,
5953             iflags.wc_foregrnd_message ? iflags.wc_foregrnd_message
5954                                        : defbrief,
5955             iflags.wc_backgrnd_message ? iflags.wc_backgrnd_message
5956                                        : defbrief,
5957             iflags.wc_foregrnd_status ? iflags.wc_foregrnd_status : defbrief,
5958             iflags.wc_backgrnd_status ? iflags.wc_backgrnd_status : defbrief,
5959             iflags.wc_foregrnd_text ? iflags.wc_foregrnd_text : defbrief,
5960             iflags.wc_backgrnd_text ? iflags.wc_backgrnd_text : defbrief);
5961 #ifdef PREFIXES_IN_USE
5962     } else {
5963         for (i = 0; i < PREFIX_COUNT; ++i)
5964             if (!strcmp(optname, fqn_prefix_names[i]) && fqn_prefix[i])
5965                 Sprintf(buf, "%s", fqn_prefix[i]);
5966 #endif
5967     }
5968 
5969     if (!buf[0])
5970         Strcpy(buf, "unknown");
5971     return buf;
5972 }
5973 
5974 int
dotogglepickup()5975 dotogglepickup()
5976 {
5977     char buf[BUFSZ], ocl[MAXOCLASSES + 1];
5978 
5979     flags.pickup = !flags.pickup;
5980     if (flags.pickup) {
5981         oc_to_str(flags.pickup_types, ocl);
5982         Sprintf(buf, "ON, for %s objects%s", ocl[0] ? ocl : "all",
5983                 (apelist)
5984                     ? ((count_apes() == 1)
5985                            ? ", with one exception"
5986                            : ", with some exceptions")
5987                     : "");
5988     } else {
5989         Strcpy(buf, "OFF");
5990     }
5991     pline("Autopickup: %s.", buf);
5992     return 0;
5993 }
5994 
5995 int
add_autopickup_exception(mapping)5996 add_autopickup_exception(mapping)
5997 const char *mapping;
5998 {
5999     static const char
6000         APE_regex_error[] = "regex error in AUTOPICKUP_EXCEPTION",
6001         APE_syntax_error[] = "syntax error in AUTOPICKUP_EXCEPTION";
6002 
6003     struct autopickup_exception *ape;
6004     char text[256], end;
6005     int n;
6006     boolean grab = FALSE;
6007 
6008     /* scan length limit used to be 255, but smaller size allows the
6009        quoted value to fit within BUFSZ, simplifying formatting elsewhere;
6010        this used to ignore the possibility of trailing junk but now checks
6011        for it, accepting whitespace but rejecting anything else unless it
6012        starts with '#" for a comment */
6013     end = '\0';
6014     if ((n = sscanf(mapping, "\"<%253[^\"]\" %c", text, &end)) == 1
6015         || (n == 2 && end == '#')) {
6016         grab = TRUE;
6017     } else if ((n = sscanf(mapping, "\">%253[^\"]\" %c", text, &end)) == 1
6018                || (n = sscanf(mapping, "\"%253[^\"]\" %c", text, &end)) == 1
6019                || (n == 2 && end == '#')) {
6020         grab = FALSE;
6021     } else {
6022         config_error_add("%s", APE_syntax_error);
6023         return 0;
6024     }
6025 
6026     ape = (struct autopickup_exception *) alloc(sizeof *ape);
6027     ape->regex = regex_init();
6028     if (!regex_compile(text, ape->regex)) {
6029         config_error_add("%s: %s", APE_regex_error,
6030                          regex_error_desc(ape->regex));
6031         regex_free(ape->regex);
6032         free((genericptr_t) ape);
6033         return 0;
6034     }
6035 
6036     ape->pattern = dupstr(text);
6037     ape->grab = grab;
6038     ape->next = apelist;
6039     apelist = ape;
6040     return 1;
6041 }
6042 
6043 STATIC_OVL void
remove_autopickup_exception(whichape)6044 remove_autopickup_exception(whichape)
6045 struct autopickup_exception *whichape;
6046 {
6047     struct autopickup_exception *ape, *freeape, *prev = 0;
6048 
6049     for (ape = apelist; ape;) {
6050         if (ape == whichape) {
6051             freeape = ape;
6052             ape = ape->next;
6053             if (prev)
6054                 prev->next = ape;
6055             else
6056                 apelist = ape;
6057             regex_free(freeape->regex);
6058             free((genericptr_t) freeape->pattern);
6059             free((genericptr_t) freeape);
6060         } else {
6061             prev = ape;
6062             ape = ape->next;
6063         }
6064     }
6065 }
6066 
6067 void
free_autopickup_exceptions()6068 free_autopickup_exceptions()
6069 {
6070     struct autopickup_exception *ape = apelist;
6071 
6072     while ((ape = apelist) != 0) {
6073       regex_free(ape->regex);
6074       free((genericptr_t) ape->pattern);
6075       apelist = ape->next;
6076       free((genericptr_t) ape);
6077     }
6078 }
6079 
6080 /* bundle some common usage into one easy-to-use routine */
6081 int
load_symset(s,which_set)6082 load_symset(s, which_set)
6083 const char *s;
6084 int which_set;
6085 {
6086     clear_symsetentry(which_set, TRUE);
6087 
6088     if (symset[which_set].name)
6089         free((genericptr_t) symset[which_set].name);
6090     symset[which_set].name = dupstr(s);
6091 
6092     if (read_sym_file(which_set)) {
6093         switch_symbols(TRUE);
6094     } else {
6095         clear_symsetentry(which_set, TRUE);
6096         return 0;
6097     }
6098     return 1;
6099 }
6100 
6101 void
free_symsets()6102 free_symsets()
6103 {
6104     clear_symsetentry(PRIMARY, TRUE);
6105     clear_symsetentry(ROGUESET, TRUE);
6106 
6107     /* symset_list is cleaned up as soon as it's used, so we shouldn't
6108        have to anything about it here */
6109     /* assert( symset_list == NULL ); */
6110 }
6111 
6112 /* Parse the value of a SYMBOLS line from a config file */
6113 boolean
parsesymbols(opts,which_set)6114 parsesymbols(opts, which_set)
6115 register char *opts;
6116 int which_set;
6117 {
6118     int val;
6119     char *op, *symname, *strval;
6120     struct symparse *symp;
6121 
6122     if ((op = index(opts, ',')) != 0) {
6123         *op++ = 0;
6124         if (!parsesymbols(op, which_set))
6125             return FALSE;
6126     }
6127 
6128     /* S_sample:string */
6129     symname = opts;
6130     strval = index(opts, ':');
6131     if (!strval)
6132         strval = index(opts, '=');
6133     if (!strval)
6134         return FALSE;
6135     *strval++ = '\0';
6136 
6137     /* strip leading and trailing white space from symname and strval */
6138     mungspaces(symname);
6139     mungspaces(strval);
6140 
6141     symp = match_sym(symname);
6142     if (!symp)
6143         return FALSE;
6144 
6145     if (symp->range && symp->range != SYM_CONTROL) {
6146         val = sym_val(strval);
6147         if (which_set == ROGUESET)
6148             update_ov_rogue_symset(symp, val);
6149         else
6150             update_ov_primary_symset(symp, val);
6151     }
6152     return TRUE;
6153 }
6154 
6155 struct symparse *
match_sym(buf)6156 match_sym(buf)
6157 char *buf;
6158 {
6159     size_t len = strlen(buf);
6160     const char *p = index(buf, ':'), *q = index(buf, '=');
6161     struct symparse *sp = loadsyms;
6162 
6163     if (!p || (q && q < p))
6164         p = q;
6165     if (p) {
6166         /* note: there will be at most one space before the '='
6167            because caller has condensed buf[] with mungspaces() */
6168         if (p > buf && p[-1] == ' ')
6169             p--;
6170         len = (int) (p - buf);
6171     }
6172     while (sp->range) {
6173         if ((len >= strlen(sp->name)) && !strncmpi(buf, sp->name, len))
6174             return sp;
6175         sp++;
6176     }
6177     return (struct symparse *) 0;
6178 }
6179 
6180 int
sym_val(strval)6181 sym_val(strval)
6182 const char *strval; /* up to 4*BUFSZ-1 long; only first few chars matter */
6183 {
6184     char buf[QBUFSZ], tmp[QBUFSZ]; /* to hold trucated copy of 'strval' */
6185 
6186     buf[0] = '\0';
6187     if (!strval[0] || !strval[1]) { /* empty, or single character */
6188         /* if single char is space or tab, leave buf[0]=='\0' */
6189         if (!isspace((uchar) strval[0]))
6190             buf[0] = strval[0];
6191     } else if (strval[0] == '\'') { /* single quote */
6192         /* simple matching single quote; we know strval[1] isn't '\0' */
6193         if (strval[2] == '\'' && !strval[3]) {
6194             /* accepts '\' as backslash and ''' as single quote */
6195             buf[0] = strval[1];
6196 
6197         /* if backslash, handle single or double quote or second backslash */
6198         } else if (strval[1] == '\\' && strval[2] && strval[3] == '\''
6199             && index("'\"\\", strval[2]) && !strval[4]) {
6200             buf[0] = strval[2];
6201 
6202         /* not simple quote or basic backslash;
6203            strip closing quote and let escapes() deal with it */
6204         } else {
6205             char *p;
6206 
6207             /* +1: skip opening single quote */
6208             (void) strncpy(tmp, strval + 1, sizeof tmp - 1);
6209             tmp[sizeof tmp - 1] = '\0';
6210             if ((p = rindex(tmp, '\'')) != 0) {
6211                 *p = '\0';
6212                 escapes(tmp, buf);
6213             } /* else buf[0] stays '\0' */
6214         }
6215     } else { /* not lone char nor single quote */
6216         (void) strncpy(tmp, strval, sizeof tmp - 1);
6217         tmp[sizeof tmp - 1] = '\0';
6218         escapes(tmp, buf);
6219     }
6220 
6221     return (int) *buf;
6222 }
6223 
6224 /* data for option_help() */
6225 static const char *opt_intro[] = {
6226     "",
6227     "                 NetHack Options Help:", "",
6228 #define CONFIG_SLOT 3 /* fill in next value at run-time */
6229     (char *) 0,
6230 #if !defined(MICRO) && !defined(MAC)
6231     "or use `NETHACKOPTIONS=\"<options>\"' in your environment",
6232 #endif
6233     "(<options> is a list of options separated by commas)",
6234 #ifdef VMS
6235     "-- for example, $ DEFINE NETHACKOPTIONS \"noautopickup,fruit:kumquat\"",
6236 #endif
6237     "or press \"O\" while playing and use the menu.",
6238     "",
6239  "Boolean options (which can be negated by prefixing them with '!' or \"no\"):",
6240     (char *) 0
6241 };
6242 
6243 static const char *opt_epilog[] = {
6244     "",
6245     "Some of the options can be set only before the game is started; those",
6246     "items will not be selectable in the 'O' command's menu.",
6247     (char *) 0
6248 };
6249 
6250 void
option_help()6251 option_help()
6252 {
6253     char buf[BUFSZ], buf2[BUFSZ];
6254     register int i;
6255     winid datawin;
6256 
6257     datawin = create_nhwindow(NHW_TEXT);
6258     Sprintf(buf, "Set options as OPTIONS=<options> in %s", configfile);
6259     opt_intro[CONFIG_SLOT] = (const char *) buf;
6260     for (i = 0; opt_intro[i]; i++)
6261         putstr(datawin, 0, opt_intro[i]);
6262 
6263     /* Boolean options */
6264     for (i = 0; boolopt[i].name; i++) {
6265         if (boolopt[i].addr) {
6266             if (boolopt[i].addr == &iflags.sanity_check && !wizard)
6267                 continue;
6268             if (boolopt[i].addr == &iflags.menu_tab_sep && !wizard)
6269                 continue;
6270             next_opt(datawin, boolopt[i].name);
6271         }
6272     }
6273     next_opt(datawin, "");
6274 
6275     /* Compound options */
6276     putstr(datawin, 0, "Compound options:");
6277     for (i = 0; compopt[i].name; i++) {
6278         Sprintf(buf2, "`%s'", compopt[i].name);
6279         Sprintf(buf, "%-20s - %s%c", buf2, compopt[i].descr,
6280                 compopt[i + 1].name ? ',' : '.');
6281         putstr(datawin, 0, buf);
6282     }
6283 
6284     for (i = 0; opt_epilog[i]; i++)
6285         putstr(datawin, 0, opt_epilog[i]);
6286 
6287     display_nhwindow(datawin, FALSE);
6288     destroy_nhwindow(datawin);
6289     return;
6290 }
6291 
6292 /*
6293  * prints the next boolean option, on the same line if possible, on a new
6294  * line if not. End with next_opt("").
6295  */
6296 void
next_opt(datawin,str)6297 next_opt(datawin, str)
6298 winid datawin;
6299 const char *str;
6300 {
6301     static char *buf = 0;
6302     int i;
6303     char *s;
6304 
6305     if (!buf)
6306         *(buf = (char *) alloc(BUFSZ)) = '\0';
6307 
6308     if (!*str) {
6309         s = eos(buf);
6310         if (s > &buf[1] && s[-2] == ',')
6311             Strcpy(s - 2, "."); /* replace last ", " */
6312         i = COLNO;              /* (greater than COLNO - 2) */
6313     } else {
6314         i = strlen(buf) + strlen(str) + 2;
6315     }
6316 
6317     if (i > COLNO - 2) { /* rule of thumb */
6318         putstr(datawin, 0, buf);
6319         buf[0] = 0;
6320     }
6321     if (*str) {
6322         Strcat(buf, str);
6323         Strcat(buf, ", ");
6324     } else {
6325         putstr(datawin, 0, str);
6326         free((genericptr_t) buf), buf = 0;
6327     }
6328     return;
6329 }
6330 
6331 /* Returns the fid of the fruit type; if that type already exists, it
6332  * returns the fid of that one; if it does not exist, it adds a new fruit
6333  * type to the chain and returns the new one.
6334  * If replace_fruit is sent in, replace the fruit in the chain rather than
6335  * adding a new entry--for user specified fruits only.
6336  */
6337 int
fruitadd(str,replace_fruit)6338 fruitadd(str, replace_fruit)
6339 char *str;
6340 struct fruit *replace_fruit;
6341 {
6342     register int i;
6343     register struct fruit *f;
6344     int highest_fruit_id = 0, globpfx;
6345     char buf[PL_FSIZ], altname[PL_FSIZ];
6346     boolean user_specified = (str == pl_fruit);
6347     /* if not user-specified, then it's a fruit name for a fruit on
6348      * a bones level or from orctown raider's loot...
6349      */
6350 
6351     /* Note: every fruit has an id (kept in obj->spe) of at least 1;
6352      * 0 is an error.
6353      */
6354     if (user_specified) {
6355         boolean found = FALSE, numeric = FALSE;
6356 
6357         /* force fruit to be singular; this handling is not
6358            needed--or wanted--for fruits from bones because
6359            they already received it in their original game;
6360            str==pl_fruit but makesingular() creates a copy
6361            so we need to copy that back into pl_fruit */
6362         nmcpy(pl_fruit, makesingular(str), PL_FSIZ);
6363         /* (assertion doesn't matter; we use 'pl_fruit' from here on out) */
6364         /* assert( str == pl_fruit ); */
6365 
6366         /* disallow naming after other foods (since it'd be impossible
6367          * to tell the difference); globs might have a size prefix which
6368          * needs to be skipped in order to match the object type name
6369          */
6370         globpfx = (!strncmp(pl_fruit, "small ", 6)
6371                    || !strncmp(pl_fruit, "large ", 6)) ? 6
6372                   : (!strncmp(pl_fruit, "very large ", 11)) ? 11
6373                     : 0;
6374         for (i = bases[FOOD_CLASS]; objects[i].oc_class == FOOD_CLASS; i++) {
6375             if (!strcmp(OBJ_NAME(objects[i]), pl_fruit)
6376                 || (globpfx > 0
6377                     && !strcmp(OBJ_NAME(objects[i]), &pl_fruit[globpfx]))) {
6378                 found = TRUE;
6379                 break;
6380             }
6381         }
6382         if (!found) {
6383             char *c;
6384 
6385             for (c = pl_fruit; *c >= '0' && *c <= '9'; c++)
6386                 continue;
6387             if (!*c || isspace((uchar) *c))
6388                 numeric = TRUE;
6389         }
6390         if (found || numeric
6391             /* these checks for applying food attributes to actual items
6392                are case sensitive; "glob of foo" is caught by 'found'
6393                if 'foo' is a valid glob; when not valid, allow it as-is */
6394             || !strncmp(pl_fruit, "cursed ", 7)
6395             || !strncmp(pl_fruit, "uncursed ", 9)
6396             || !strncmp(pl_fruit, "blessed ", 8)
6397             || !strncmp(pl_fruit, "partly eaten ", 13)
6398             || (!strncmp(pl_fruit, "tin of ", 7)
6399                 && (!strcmp(pl_fruit + 7, "spinach")
6400                     || name_to_mon(pl_fruit + 7) >= LOW_PM))
6401             || !strcmp(pl_fruit, "empty tin")
6402             || (!strcmp(pl_fruit, "glob")
6403                 || (globpfx > 0 && !strcmp("glob", &pl_fruit[globpfx])))
6404             || ((str_end_is(pl_fruit, " corpse")
6405                  || str_end_is(pl_fruit, " egg"))
6406                 && name_to_mon(pl_fruit) >= LOW_PM)) {
6407             Strcpy(buf, pl_fruit);
6408             Strcpy(pl_fruit, "candied ");
6409             nmcpy(pl_fruit + 8, buf, PL_FSIZ - 8);
6410         }
6411         *altname = '\0';
6412         /* This flag indicates that a fruit has been made since the
6413          * last time the user set the fruit.  If it hasn't, we can
6414          * safely overwrite the current fruit, preventing the user from
6415          * setting many fruits in a row and overflowing.
6416          * Possible expansion: check for specific fruit IDs, not for
6417          * any fruit.
6418          */
6419         flags.made_fruit = FALSE;
6420         if (replace_fruit) {
6421             /* replace_fruit is already part of the fruit chain;
6422                update it in place rather than looking it up again */
6423             f = replace_fruit;
6424             copynchars(f->fname, pl_fruit, PL_FSIZ - 1);
6425             goto nonew;
6426         }
6427     } else {
6428         /* not user_supplied, so assumed to be from bones (or orc gang) */
6429         copynchars(altname, str, PL_FSIZ - 1);
6430         sanitize_name(altname);
6431         flags.made_fruit = TRUE; /* for safety.  Any fruit name added from a
6432                                   * bones level should exist anyway. */
6433     }
6434     f = fruit_from_name(*altname ? altname : str, FALSE, &highest_fruit_id);
6435     if (f)
6436         goto nonew;
6437 
6438     /* Maximum number of named fruits is 127, even if obj->spe can
6439        handle bigger values.  If adding another fruit would overflow,
6440        use a random fruit instead... we've got a lot to choose from.
6441        current_fruit remains as is. */
6442     if (highest_fruit_id >= 127)
6443         return rnd(127);
6444 
6445     f = newfruit();
6446     (void) memset((genericptr_t) f, 0, sizeof (struct fruit));
6447     copynchars(f->fname, *altname ? altname : str, PL_FSIZ - 1);
6448     f->fid = ++highest_fruit_id;
6449     /* we used to go out of our way to add it at the end of the list,
6450        but the order is arbitrary so use simpler insertion at start */
6451     f->nextf = ffruit;
6452     ffruit = f;
6453  nonew:
6454     if (user_specified)
6455         context.current_fruit = f->fid;
6456     return f->fid;
6457 }
6458 
6459 /*
6460  * This is a somewhat generic menu for taking a list of NetHack style
6461  * class choices and presenting them via a description
6462  * rather than the traditional NetHack characters.
6463  * (Benefits users whose first exposure to NetHack is via tiles).
6464  *
6465  * prompt
6466  *           The title at the top of the menu.
6467  *
6468  * category: 0 = monster class
6469  *           1 = object  class
6470  *
6471  * way
6472  *           FALSE = PICK_ONE, TRUE = PICK_ANY
6473  *
6474  * class_list
6475  *           a null terminated string containing the list of choices.
6476  *
6477  * class_selection
6478  *           a null terminated string containing the selected characters.
6479  *
6480  * Returns number selected.
6481  */
6482 int
choose_classes_menu(prompt,category,way,class_list,class_select)6483 choose_classes_menu(prompt, category, way, class_list, class_select)
6484 const char *prompt;
6485 int category;
6486 boolean way;
6487 char *class_list;
6488 char *class_select;
6489 {
6490     menu_item *pick_list = (menu_item *) 0;
6491     winid win;
6492     anything any;
6493     char buf[BUFSZ];
6494     int i, n;
6495     int ret;
6496     int next_accelerator, accelerator;
6497 
6498     if (class_list == (char *) 0 || class_select == (char *) 0)
6499         return 0;
6500     accelerator = 0;
6501     next_accelerator = 'a';
6502     any = zeroany;
6503     win = create_nhwindow(NHW_MENU);
6504     start_menu(win);
6505     while (*class_list) {
6506         const char *text;
6507         boolean selected;
6508 
6509         text = (char *) 0;
6510         selected = FALSE;
6511         switch (category) {
6512         case 0:
6513             text = def_monsyms[def_char_to_monclass(*class_list)].explain;
6514             accelerator = *class_list;
6515             Sprintf(buf, "%s", text);
6516             break;
6517         case 1:
6518             text = def_oc_syms[def_char_to_objclass(*class_list)].explain;
6519             accelerator = next_accelerator;
6520             Sprintf(buf, "%c  %s", *class_list, text);
6521             break;
6522         default:
6523             impossible("choose_classes_menu: invalid category %d", category);
6524         }
6525         if (way && *class_select) { /* Selections there already */
6526             if (index(class_select, *class_list)) {
6527                 selected = TRUE;
6528             }
6529         }
6530         any.a_int = *class_list;
6531         add_menu(win, NO_GLYPH, &any, accelerator, category ? *class_list : 0,
6532                  ATR_NONE, buf, selected);
6533         ++class_list;
6534         if (category > 0) {
6535             ++next_accelerator;
6536             if (next_accelerator == ('z' + 1))
6537                 next_accelerator = 'A';
6538             if (next_accelerator == ('Z' + 1))
6539                 break;
6540         }
6541     }
6542     if (category == 1 && next_accelerator <= 'z') {
6543         /* for objects, add "A - ' '  all classes", after a separator */
6544         any = zeroany;
6545         add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
6546         any.a_int = (int) ' ';
6547         Sprintf(buf, "%c  %s", (char) any.a_int, "all classes of objects");
6548         /* we won't preselect this even if the incoming list is empty;
6549            having it selected means that it would have to be explicitly
6550            de-selected in order to select anything else */
6551         add_menu(win, NO_GLYPH, &any, 'A', 0, ATR_NONE, buf, MENU_UNSELECTED);
6552     }
6553     end_menu(win, prompt);
6554     n = select_menu(win, way ? PICK_ANY : PICK_ONE, &pick_list);
6555     destroy_nhwindow(win);
6556     if (n > 0) {
6557         if (category == 1) {
6558             /* for object classes, first check for 'all'; it means 'use
6559                a blank list' rather than 'collect every possible choice' */
6560             for (i = 0; i < n; ++i)
6561                 if (pick_list[i].item.a_int == ' ') {
6562                     pick_list[0].item.a_int = ' ';
6563                     n = 1; /* return 1; also an implicit 'break;' */
6564                 }
6565         }
6566         for (i = 0; i < n; ++i)
6567             *class_select++ = (char) pick_list[i].item.a_int;
6568         free((genericptr_t) pick_list);
6569         ret = n;
6570     } else if (n == -1) {
6571         class_select = eos(class_select);
6572         ret = -1;
6573     } else
6574         ret = 0;
6575     *class_select = '\0';
6576     return ret;
6577 }
6578 
6579 static struct wc_Opt wc_options[] = {
6580     { "ascii_map", WC_ASCII_MAP },
6581     { "color", WC_COLOR },
6582     { "eight_bit_tty", WC_EIGHT_BIT_IN },
6583     { "hilite_pet", WC_HILITE_PET },
6584     { "popup_dialog", WC_POPUP_DIALOG },
6585     { "player_selection", WC_PLAYER_SELECTION },
6586     { "preload_tiles", WC_PRELOAD_TILES },
6587     { "tiled_map", WC_TILED_MAP },
6588     { "tile_file", WC_TILE_FILE },
6589     { "tile_width", WC_TILE_WIDTH },
6590     { "tile_height", WC_TILE_HEIGHT },
6591     { "use_inverse", WC_INVERSE },
6592     { "align_message", WC_ALIGN_MESSAGE },
6593     { "align_status", WC_ALIGN_STATUS },
6594     { "font_map", WC_FONT_MAP },
6595     { "font_menu", WC_FONT_MENU },
6596     { "font_message", WC_FONT_MESSAGE },
6597 #if 0
6598     {"perm_invent", WC_PERM_INVENT},
6599 #endif
6600     { "font_size_map", WC_FONTSIZ_MAP },
6601     { "font_size_menu", WC_FONTSIZ_MENU },
6602     { "font_size_message", WC_FONTSIZ_MESSAGE },
6603     { "font_size_status", WC_FONTSIZ_STATUS },
6604     { "font_size_text", WC_FONTSIZ_TEXT },
6605     { "font_status", WC_FONT_STATUS },
6606     { "font_text", WC_FONT_TEXT },
6607     { "map_mode", WC_MAP_MODE },
6608     { "scroll_amount", WC_SCROLL_AMOUNT },
6609     { "scroll_margin", WC_SCROLL_MARGIN },
6610     { "splash_screen", WC_SPLASH_SCREEN },
6611     { "vary_msgcount", WC_VARY_MSGCOUNT },
6612     { "windowcolors", WC_WINDOWCOLORS },
6613     { "mouse_support", WC_MOUSE_SUPPORT },
6614     { (char *) 0, 0L }
6615 };
6616 static struct wc_Opt wc2_options[] = {
6617     { "fullscreen", WC2_FULLSCREEN },
6618     { "softkeyboard", WC2_SOFTKEYBOARD },
6619     { "wraptext", WC2_WRAPTEXT },
6620     { "use_darkgray", WC2_DARKGRAY },
6621     { "hitpointbar", WC2_HITPOINTBAR },
6622     { "hilite_status", WC2_HILITE_STATUS },
6623     /* name shown in 'O' menu is different */
6624     { "status hilite rules", WC2_HILITE_STATUS },
6625     /* statushilites doesn't have its own bit */
6626     { "statushilites", WC2_HILITE_STATUS },
6627     { "term_cols", WC2_TERM_SIZE },
6628     { "term_rows", WC2_TERM_SIZE },
6629     { "petattr", WC2_PETATTR },
6630     { "guicolor", WC2_GUICOLOR },
6631     { "statuslines", WC2_STATUSLINES },
6632     { "windowborders", WC2_WINDOWBORDERS },
6633     { (char *) 0, 0L }
6634 };
6635 
6636 /*
6637  * If a port wants to change or ensure that the SET_IN_SYS,
6638  * SET_IN_FILE, DISP_IN_GAME, or SET_IN_GAME status of an option is
6639  * correct (for controlling its display in the option menu) call
6640  * set_option_mod_status()
6641  * with the appropriate second argument.
6642  */
6643 void
set_option_mod_status(optnam,status)6644 set_option_mod_status(optnam, status)
6645 const char *optnam;
6646 int status;
6647 {
6648     int k;
6649 
6650     if (SET__IS_VALUE_VALID(status)) {
6651         impossible("set_option_mod_status: status out of range %d.", status);
6652         return;
6653     }
6654     for (k = 0; boolopt[k].name; k++) {
6655         if (!strncmpi(boolopt[k].name, optnam, strlen(optnam))) {
6656             boolopt[k].optflags = status;
6657             return;
6658         }
6659     }
6660     for (k = 0; compopt[k].name; k++) {
6661         if (!strncmpi(compopt[k].name, optnam, strlen(optnam))) {
6662             compopt[k].optflags = status;
6663             return;
6664         }
6665     }
6666 }
6667 
6668 /*
6669  * You can set several wc_options in one call to
6670  * set_wc_option_mod_status() by setting
6671  * the appropriate bits for each option that you
6672  * are setting in the optmask argument
6673  * prior to calling.
6674  *    example: set_wc_option_mod_status(WC_COLOR|WC_SCROLL_MARGIN,
6675  * SET_IN_GAME);
6676  */
6677 void
set_wc_option_mod_status(optmask,status)6678 set_wc_option_mod_status(optmask, status)
6679 unsigned long optmask;
6680 int status;
6681 {
6682     int k = 0;
6683 
6684     if (SET__IS_VALUE_VALID(status)) {
6685         impossible("set_wc_option_mod_status: status out of range %d.",
6686                    status);
6687         return;
6688     }
6689     while (wc_options[k].wc_name) {
6690         if (optmask & wc_options[k].wc_bit) {
6691             set_option_mod_status(wc_options[k].wc_name, status);
6692         }
6693         k++;
6694     }
6695 }
6696 
6697 STATIC_OVL boolean
is_wc_option(optnam)6698 is_wc_option(optnam)
6699 const char *optnam;
6700 {
6701     int k = 0;
6702 
6703     while (wc_options[k].wc_name) {
6704         if (strcmp(wc_options[k].wc_name, optnam) == 0)
6705             return TRUE;
6706         k++;
6707     }
6708     return FALSE;
6709 }
6710 
6711 STATIC_OVL boolean
wc_supported(optnam)6712 wc_supported(optnam)
6713 const char *optnam;
6714 {
6715     int k;
6716 
6717     for (k = 0; wc_options[k].wc_name; ++k) {
6718         if (!strcmp(wc_options[k].wc_name, optnam))
6719             return (windowprocs.wincap & wc_options[k].wc_bit) ? TRUE : FALSE;
6720     }
6721     return FALSE;
6722 }
6723 
6724 /*
6725  * You can set several wc2_options in one call to
6726  * set_wc2_option_mod_status() by setting
6727  * the appropriate bits for each option that you
6728  * are setting in the optmask argument
6729  * prior to calling.
6730  *    example:
6731  * set_wc2_option_mod_status(WC2_FULLSCREEN|WC2_SOFTKEYBOARD|WC2_WRAPTEXT,
6732  * SET_IN_FILE);
6733  */
6734 
6735 void
set_wc2_option_mod_status(optmask,status)6736 set_wc2_option_mod_status(optmask, status)
6737 unsigned long optmask;
6738 int status;
6739 {
6740     int k = 0;
6741 
6742     if (SET__IS_VALUE_VALID(status)) {
6743         impossible("set_wc2_option_mod_status: status out of range %d.",
6744                    status);
6745         return;
6746     }
6747     while (wc2_options[k].wc_name) {
6748         if (optmask & wc2_options[k].wc_bit) {
6749             set_option_mod_status(wc2_options[k].wc_name, status);
6750         }
6751         k++;
6752     }
6753 }
6754 
6755 STATIC_OVL boolean
is_wc2_option(optnam)6756 is_wc2_option(optnam)
6757 const char *optnam;
6758 {
6759     int k = 0;
6760 
6761     while (wc2_options[k].wc_name) {
6762         if (strcmp(wc2_options[k].wc_name, optnam) == 0)
6763             return TRUE;
6764         k++;
6765     }
6766     return FALSE;
6767 }
6768 
6769 STATIC_OVL boolean
wc2_supported(optnam)6770 wc2_supported(optnam)
6771 const char *optnam;
6772 {
6773     int k;
6774 
6775     for (k = 0; wc2_options[k].wc_name; ++k) {
6776         if (!strcmp(wc2_options[k].wc_name, optnam))
6777             return (windowprocs.wincap2 & wc2_options[k].wc_bit) ? TRUE
6778                                                                  : FALSE;
6779     }
6780     return FALSE;
6781 }
6782 
6783 STATIC_OVL void
wc_set_font_name(opttype,fontname)6784 wc_set_font_name(opttype, fontname)
6785 int opttype;
6786 char *fontname;
6787 {
6788     char **fn = (char **) 0;
6789 
6790     if (!fontname)
6791         return;
6792     switch (opttype) {
6793     case MAP_OPTION:
6794         fn = &iflags.wc_font_map;
6795         break;
6796     case MESSAGE_OPTION:
6797         fn = &iflags.wc_font_message;
6798         break;
6799     case TEXT_OPTION:
6800         fn = &iflags.wc_font_text;
6801         break;
6802     case MENU_OPTION:
6803         fn = &iflags.wc_font_menu;
6804         break;
6805     case STATUS_OPTION:
6806         fn = &iflags.wc_font_status;
6807         break;
6808     default:
6809         return;
6810     }
6811     if (fn) {
6812         if (*fn)
6813             free((genericptr_t) *fn);
6814         *fn = dupstr(fontname);
6815     }
6816     return;
6817 }
6818 
6819 STATIC_OVL int
wc_set_window_colors(op)6820 wc_set_window_colors(op)
6821 char *op;
6822 {
6823     /* syntax:
6824      *  menu white/black message green/yellow status white/blue text
6825      * white/black
6826      */
6827     int j;
6828     char buf[BUFSZ];
6829     char *wn, *tfg, *tbg, *newop;
6830     static const char *wnames[] = { "menu", "message", "status", "text" };
6831     static const char *shortnames[] = { "mnu", "msg", "sts", "txt" };
6832     static char **fgp[] = { &iflags.wc_foregrnd_menu,
6833                             &iflags.wc_foregrnd_message,
6834                             &iflags.wc_foregrnd_status,
6835                             &iflags.wc_foregrnd_text };
6836     static char **bgp[] = { &iflags.wc_backgrnd_menu,
6837                             &iflags.wc_backgrnd_message,
6838                             &iflags.wc_backgrnd_status,
6839                             &iflags.wc_backgrnd_text };
6840 
6841     Strcpy(buf, op);
6842     newop = mungspaces(buf);
6843     while (newop && *newop) {
6844         wn = tfg = tbg = (char *) 0;
6845 
6846         /* until first non-space in case there's leading spaces - before
6847          * colorname*/
6848         if (*newop == ' ')
6849             newop++;
6850         if (*newop)
6851             wn = newop;
6852         else
6853             return 0;
6854 
6855         /* until first space - colorname*/
6856         while (*newop && *newop != ' ')
6857             newop++;
6858         if (*newop)
6859             *newop = '\0';
6860         else
6861             return 0;
6862         newop++;
6863 
6864         /* until first non-space - before foreground*/
6865         if (*newop == ' ')
6866             newop++;
6867         if (*newop)
6868             tfg = newop;
6869         else
6870             return 0;
6871 
6872         /* until slash - foreground */
6873         while (*newop && *newop != '/')
6874             newop++;
6875         if (*newop)
6876             *newop = '\0';
6877         else
6878             return 0;
6879         newop++;
6880 
6881         /* until first non-space (in case there's leading space after slash) -
6882          * before background */
6883         if (*newop == ' ')
6884             newop++;
6885         if (*newop)
6886             tbg = newop;
6887         else
6888             return 0;
6889 
6890         /* until first space - background */
6891         while (*newop && *newop != ' ')
6892             newop++;
6893         if (*newop)
6894             *newop++ = '\0';
6895 
6896         for (j = 0; j < 4; ++j) {
6897             if (!strcmpi(wn, wnames[j]) || !strcmpi(wn, shortnames[j])) {
6898                 if (tfg && !strstri(tfg, " ")) {
6899                     if (*fgp[j])
6900                         free((genericptr_t) *fgp[j]);
6901                     *fgp[j] = dupstr(tfg);
6902                 }
6903                 if (tbg && !strstri(tbg, " ")) {
6904                     if (*bgp[j])
6905                         free((genericptr_t) *bgp[j]);
6906                     *bgp[j] = dupstr(tbg);
6907                 }
6908                 break;
6909             }
6910         }
6911     }
6912     return 1;
6913 }
6914 
6915 /* set up for wizard mode if player or save file has requested to it;
6916    called from port-specific startup code to handle `nethack -D' or
6917    OPTIONS=playmode:debug, or from dorecover()'s restgamestate() if
6918    restoring a game which was saved in wizard mode */
6919 void
set_playmode()6920 set_playmode()
6921 {
6922     if (wizard) {
6923         if (authorize_wizard_mode())
6924             Strcpy(plname, "wizard");
6925         else
6926             wizard = FALSE; /* not allowed or not available */
6927         /* force explore mode if we didn't make it into wizard mode */
6928         discover = !wizard;
6929         iflags.deferred_X = FALSE;
6930     }
6931     /* don't need to do anything special for explore mode or normal play */
6932 }
6933 
6934 #endif /* OPTION_LISTS_ONLY */
6935 
6936 /*options.c*/
6937