Make note_skill_levels default to what it used to, make it a simple list option.
[crawl.git] / crawl-ref / source / initfile.cc
1 /**
2  * @file
3  * @brief Simple reading of an init file and system variables
4 **/
5
6 #include "AppHdr.h"
7
8 #include "initfile.h"
9 #include "options.h"
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string>
14 #include <ctype.h>
15
16 #include "chardump.h"
17 #include "clua.h"
18 #include "colour.h"
19 #include "dlua.h"
20 #include "delay.h"
21 #include "directn.h"
22 #include "errors.h"
23 #include "kills.h"
24 #include "files.h"
25 #include "defines.h"
26 #ifdef USE_TILE_LOCAL
27  #include "tilereg-map.h"
28 #endif
29 #ifdef USE_TILE_WEB
30  #include "tileweb.h"
31 #endif
32 #include "invent.h"
33 #include "libutil.h"
34 #include "macro.h"
35 #include "message.h"
36 #include "mon-util.h"
37 #include "jobs.h"
38 #include "player.h"
39 #include "species.h"
40 #include "spl-util.h"
41 #include "stash.h"
42 #include "state.h"
43 #include "stuff.h"
44 #include "syscalls.h"
45 #include "tags.h"
46 #include "throw.h"
47 #include "travel.h"
48 #include "items.h"
49 #include "view.h"
50 #include "viewchar.h"
51
52 // For finding the executable's path
53 #ifdef TARGET_OS_WINDOWS
54 #define WIN32_LEAN_AND_MEAN
55 #include <windows.h>
56 #include <shlwapi.h>
57 #include <shlobj.h>
58 #elif defined (__APPLE__)
59 extern char **NXArgv;
60 #elif defined (__linux__)
61 #include <unistd.h>
62 #endif
63
64 const std::string game_options::interrupt_prefix = "interrupt_";
65 system_environment SysEnv;
66 game_options Options;
67
68 object_class_type item_class_by_sym(ucs_t c)
69 {
70     switch (c)
71     {
72     case ')':
73         return OBJ_WEAPONS;
74     case '(':
75     case 0x27b9: // ➹
76         return OBJ_MISSILES;
77     case '[':
78         return OBJ_ARMOUR;
79     case '/':
80         return OBJ_WANDS;
81     case '%':
82         return OBJ_FOOD;
83     case '?':
84         return OBJ_SCROLLS;
85     case '"': // Make the amulet symbol equiv to ring -- bwross
86     case '=':
87     case 0x00B0: // °
88         return OBJ_JEWELLERY;
89     case '!':
90         return OBJ_POTIONS;
91     case ':':
92     case '+': // ??? -- was the only symbol working for tile order up to 0.10,
93               // so keeping it for compat purposes (user configs).
94     case 0x221e: // ∞
95         return OBJ_BOOKS;
96     case '|':
97         return OBJ_STAVES;
98     case '0':
99         return OBJ_ORBS;
100     case '}':
101         return OBJ_MISCELLANY;
102     case '&':
103     case 'X':
104     case 'x':
105         return OBJ_CORPSES;
106     case '$':
107     case 0x20ac: // €
108     case 0x00a3: // £
109     case 0x00a5: // ¥
110         return OBJ_GOLD;
111     case '\\': // Compat break: used to be staves (why not '|'?).
112         return OBJ_RODS;
113     default:
114         return NUM_OBJECT_CLASSES;
115     }
116
117 }
118
119 template<class A, class B> static void append_vector(A &dest, const B &src)
120 {
121     dest.insert(dest.end(), src.begin(), src.end());
122 }
123
124 // Returns -1 if unmatched else returns 0-15.
125 static msg_colour_type _str_to_channel_colour(const std::string &str)
126 {
127     int col = str_to_colour(str);
128     msg_colour_type ret = MSGCOL_NONE;
129     if (col == -1)
130     {
131         if (str == "mute")
132             ret = MSGCOL_MUTED;
133         else if (str == "plain" || str == "off")
134             ret = MSGCOL_PLAIN;
135         else if (str == "default" || str == "on")
136             ret = MSGCOL_DEFAULT;
137         else if (str == "alternate")
138             ret = MSGCOL_ALTERNATE;
139     }
140     else
141         ret = msg_colour(str_to_colour(str));
142
143     return ret;
144 }
145
146 static const std::string message_channel_names[] =
147 {
148     "plain", "friend_action", "prompt", "god", "pray", "duration", "danger",
149     "warning", "food", "recovery", "sound", "talk", "talk_visual",
150     "intrinsic_gain", "mutation", "monster_spell", "monster_enchant",
151     "friend_spell", "friend_enchant", "monster_damage", "monster_target",
152     "banishment", "rotten_meat", "equipment", "floor", "multiturn", "examine",
153     "examine_filter", "diagnostic", "error", "tutorial", "orb"
154 };
155
156 // returns -1 if unmatched else returns 0--(NUM_MESSAGE_CHANNELS-1)
157 int str_to_channel(const std::string &str)
158 {
159     COMPILE_CHECK(ARRAYSZ(message_channel_names) == NUM_MESSAGE_CHANNELS);
160
161     // widespread aliases
162     if (str == "visual")
163         return MSGCH_TALK_VISUAL;
164     else if (str == "spell")
165         return MSGCH_MONSTER_SPELL;
166
167     for (int ret = 0; ret < NUM_MESSAGE_CHANNELS; ret++)
168     {
169         if (str == message_channel_names[ret])
170             return ret;
171     }
172
173     return -1;
174 }
175
176 std::string channel_to_str(int channel)
177 {
178     if (channel < 0 || channel >= NUM_MESSAGE_CHANNELS)
179         return "";
180
181     return message_channel_names[channel];
182 }
183
184 weapon_type str_to_weapon(const std::string &str)
185 {
186     if (str == "shortsword" || str == "short sword")
187         return WPN_SHORT_SWORD;
188     else if (str == "falchion")
189         return WPN_FALCHION;
190     else if (str == "quarterstaff")
191         return WPN_QUARTERSTAFF;
192     else if (str == "mace")
193         return WPN_MACE;
194     else if (str == "spear")
195         return WPN_SPEAR;
196     else if (str == "trident")
197         return WPN_TRIDENT;
198     else if (str == "hand axe" || str == "handaxe")
199         return WPN_HAND_AXE;
200     else if (str == "unarmed" || str == "claws")
201         return WPN_UNARMED;
202     else if (str == "sling")
203         return WPN_SLING;
204     else if (str == "bow")
205         return WPN_BOW;
206     else if (str == "crossbow")
207         return WPN_CROSSBOW;
208     else if (str == "rocks")
209         return WPN_ROCKS;
210     else if (str == "javelins")
211         return WPN_JAVELINS;
212     else if (str == "darts")
213         return WPN_DARTS;
214     else if (str == "random")
215         return WPN_RANDOM;
216
217     return WPN_UNKNOWN;
218 }
219
220 static std::string _weapon_to_str(int weapon)
221 {
222     switch (weapon)
223     {
224     case WPN_SHORT_SWORD:
225         return "short sword";
226     case WPN_FALCHION:
227         return "falchion";
228     case WPN_QUARTERSTAFF:
229         return "quarterstaff";
230     case WPN_MACE:
231         return "mace";
232     case WPN_SPEAR:
233         return "spear";
234     case WPN_TRIDENT:
235         return "trident";
236     case WPN_HAND_AXE:
237         return "hand axe";
238     case WPN_UNARMED:
239         return "claws";
240     case WPN_SLING:
241         return "sling";
242     case WPN_BOW:
243         return "bow";
244     case WPN_CROSSBOW:
245         return "crossbow";
246     case WPN_ROCKS:
247         return "rocks";
248     case WPN_JAVELINS:
249         return "javelins";
250     case WPN_DARTS:
251         return "darts";
252     case WPN_RANDOM:
253     default:
254         return "random";
255     }
256 }
257
258 // Summon types can be any of mon_summon_type (enum.h), or a relevant summoning
259 // spell.
260 int str_to_summon_type(const std::string &str)
261 {
262     if (str == "clone")
263         return MON_SUMM_CLONE;
264     if (str == "animate")
265         return MON_SUMM_ANIMATE;
266     if (str == "chaos")
267         return MON_SUMM_CHAOS;
268     if (str == "miscast")
269         return MON_SUMM_MISCAST;
270     if (str == "zot")
271         return MON_SUMM_ZOT;
272     if (str == "wrath")
273         return MON_SUMM_WRATH;
274     if (str == "aid")
275         return MON_SUMM_AID;
276
277     return spell_by_name(str);
278 }
279
280 static fire_type _str_to_fire_types(const std::string &str)
281 {
282     if (str == "launcher")
283         return FIRE_LAUNCHER;
284     else if (str == "dart")
285         return FIRE_DART;
286     else if (str == "stone")
287         return FIRE_STONE;
288     else if (str == "rock")
289         return FIRE_ROCK;
290     else if (str == "dagger")
291         return FIRE_DAGGER;
292     else if (str == "spear")
293         return FIRE_SPEAR;
294     else if (str == "hand axe" || str == "handaxe" || str == "axe")
295         return FIRE_HAND_AXE;
296     else if (str == "club")
297         return FIRE_CLUB;
298     else if (str == "javelin")
299         return FIRE_JAVELIN;
300     else if (str == "net")
301         return FIRE_NET;
302     else if (str == "return" || str == "returning")
303         return FIRE_RETURNING;
304     else if (str == "inscribed")
305         return FIRE_INSCRIBED;
306
307     return FIRE_NONE;
308 }
309
310 std::string gametype_to_str(game_type type)
311 {
312     switch (type)
313     {
314     case GAME_TYPE_NORMAL:
315         return "normal";
316     case GAME_TYPE_TUTORIAL:
317         return "tutorial";
318     case GAME_TYPE_ARENA:
319         return "arena";
320     case GAME_TYPE_SPRINT:
321         return "sprint";
322     case GAME_TYPE_ZOTDEF:
323         return "zotdef";
324     default:
325         return "none";
326     }
327 }
328
329 #ifndef DGAMELAUNCH
330 static game_type _str_to_gametype(const std::string& s)
331 {
332     for (int i = 0; i < NUM_GAME_TYPE; ++i)
333     {
334         game_type t = static_cast<game_type>(i);
335         if (s == gametype_to_str(t))
336             return t;
337     }
338     return NUM_GAME_TYPE;
339 }
340 #endif
341
342 static std::string _species_to_str(species_type sp)
343 {
344     if (sp == SP_RANDOM)
345         return "random";
346     else if (sp == SP_VIABLE)
347         return "viable";
348     else
349         return species_name(sp);
350 }
351
352 static species_type _str_to_species(const std::string &str)
353 {
354     if (str == "random")
355         return SP_RANDOM;
356     else if (str == "viable")
357         return SP_VIABLE;
358
359     species_type ret = SP_UNKNOWN;
360     if (str.length() == 2) // scan abbreviations
361         ret = get_species_by_abbrev(str.c_str());
362
363     // if we don't have a match, scan the full names
364     if (ret == SP_UNKNOWN)
365         ret = str_to_species(str);
366
367     if (ret == SP_UNKNOWN)
368         fprintf(stderr, "Unknown species choice: %s\n", str.c_str());
369
370     return ret;
371 }
372
373 static std::string _job_to_str(job_type job)
374 {
375     if (job == JOB_RANDOM)
376         return "random";
377     else if (job == JOB_VIABLE)
378         return "viable";
379     else
380         return get_job_name(job);
381 }
382
383 static job_type _str_to_job(const std::string &str)
384 {
385     if (str == "random")
386         return JOB_RANDOM;
387     else if (str == "viable")
388         return JOB_VIABLE;
389
390     job_type job = JOB_UNKNOWN;
391
392     if (str.length() == 2) // scan abbreviations
393         job = get_job_by_abbrev(str.c_str());
394
395     // if we don't have a match, scan the full names
396     if (job == JOB_UNKNOWN)
397         job = get_job_by_name(str.c_str());
398
399     if (job == JOB_UNKNOWN)
400         fprintf(stderr, "Unknown background choice: %s\n", str.c_str());
401
402     return job;
403 }
404
405 static bool _read_bool(const std::string &field, bool def_value)
406 {
407     bool ret = def_value;
408
409     if (field == "true" || field == "1" || field == "yes")
410         ret = true;
411
412     if (field == "false" || field == "0" || field == "no")
413         ret = false;
414
415     return ret;
416 }
417
418 // read a value which can be either a boolean (in which case return
419 // 0 for true, -1 for false), or a string of the form PREFIX:NUMBER
420 // (e.g., auto:7), in which case return NUMBER as an int.
421 static int _read_bool_or_number(const std::string &field, int def_value,
422                                  const std::string& num_prefix)
423 {
424     int ret = def_value;
425
426     if (field == "true" || field == "1" || field == "yes")
427         ret = 0;
428
429     if (field == "false" || field == "0" || field == "no")
430         ret = -1;
431
432     if (field.find(num_prefix) == 0)
433         ret = atoi(field.c_str() + num_prefix.size());
434
435     return ret;
436 }
437
438
439 static unsigned curses_attribute(const std::string &field)
440 {
441     if (field == "standout")               // probably reverses
442         return CHATTR_STANDOUT;
443     else if (field == "bold")              // probably brightens fg
444         return CHATTR_BOLD;
445     else if (field == "blink")             // probably brightens bg
446         return CHATTR_BLINK;
447     else if (field == "underline")
448         return CHATTR_UNDERLINE;
449     else if (field == "reverse")
450         return CHATTR_REVERSE;
451     else if (field == "dim")
452         return CHATTR_DIM;
453     else if (field.find("hi:") == 0
454              || field.find("hilite:") == 0
455              || field.find("highlight:") == 0)
456     {
457         int col = field.find(":");
458         int colour = str_to_colour(field.substr(col + 1));
459         if (colour == -1)
460             Options.report_error("Bad highlight string -- %s\n", field.c_str());
461         else
462             return CHATTR_HILITE | (colour << 8);
463     }
464     else if (field != "none")
465         Options.report_error("Bad colour -- %s\n", field.c_str());
466     return CHATTR_NORMAL;
467 }
468
469 void game_options::str_to_enemy_hp_colour(const std::string &colours)
470 {
471     std::vector<std::string> colour_list = split_string(" ", colours, true, true);
472     for (int i = 0, csize = colour_list.size(); i < csize; i++)
473         enemy_hp_colour.push_back(str_to_colour(colour_list[i]));
474 }
475
476 #ifdef USE_TILE
477 static FixedVector<const char*, TAGPREF_MAX>
478     tag_prefs("none", "tutorial", "named", "enemy");
479
480 static tag_pref _str_to_tag_pref(const char *opt)
481 {
482     for (int i = 0; i < TAGPREF_MAX; i++)
483     {
484         if (!strcasecmp(opt, tag_prefs[i]))
485             return (tag_pref)i;
486     }
487
488     return TAGPREF_ENEMY;
489 }
490 #endif
491
492 void game_options::new_dump_fields(const std::string &text, bool add)
493 {
494     // Easy; chardump.cc has most of the intelligence.
495     std::vector<std::string> fields = split_string(",", text, true, true);
496     if (add)
497         append_vector(dump_order, fields);
498     else
499     {
500         for (int f = 0, size = fields.size(); f < size; ++f)
501             for (int i = 0, dsize = dump_order.size(); i < dsize; ++i)
502             {
503                 if (dump_order[i] == fields[f])
504                 {
505                     dump_order.erase(dump_order.begin() + i);
506                     break;
507                 }
508             }
509     }
510 }
511
512 void game_options::set_default_activity_interrupts()
513 {
514     for (int adelay = 0; adelay < NUM_DELAYS; ++adelay)
515         for (int aint = 0; aint < NUM_AINTERRUPTS; ++aint)
516         {
517             activity_interrupts[adelay][aint]
518                 = is_delay_interruptible(static_cast<delay_type>(adelay));
519         }
520
521     const char *default_activity_interrupts[] = {
522         "interrupt_armour_on = hp_loss, monster_attack",
523         "interrupt_armour_off = interrupt_armour_on",
524         "interrupt_drop_item = interrupt_armour_on",
525         "interrupt_jewellery_on = interrupt_armour_on",
526         "interrupt_memorise = interrupt_armour_on, stat",
527         "interrupt_butcher = interrupt_armour_on, teleport, stat, monster",
528         "interrupt_bottle_blood = interrupt_butcher",
529         "interrupt_vampire_feed = interrupt_butcher",
530         "interrupt_multidrop = interrupt_armour_on, teleport, stat",
531         "interrupt_macro = interrupt_multidrop",
532         "interrupt_travel = interrupt_butcher, statue, hungry, "
533                             "burden, hit_monster, sense_monster",
534         "interrupt_run = interrupt_travel, message",
535         "interrupt_rest = interrupt_run, full_hp, full_mp",
536
537         // Stair ascents/descents cannot be interrupted except by
538         // teleportation. Attempts to interrupt the delay will just
539         // trash all queued delays, including travel.
540         "interrupt_ascending_stairs = teleport",
541         "interrupt_descending_stairs = teleport",
542         "interrupt_recite = teleport",
543         "interrupt_uninterruptible =",
544         "interrupt_weapon_swap =",
545
546         NULL
547     };
548
549     for (int i = 0; default_activity_interrupts[i]; ++i)
550         read_option_line(default_activity_interrupts[i], false);
551 }
552
553 void game_options::clear_activity_interrupts(
554         FixedVector<bool, NUM_AINTERRUPTS> &eints)
555 {
556     for (int i = 0; i < NUM_AINTERRUPTS; ++i)
557         eints[i] = false;
558 }
559
560 void game_options::set_activity_interrupt(
561         FixedVector<bool, NUM_AINTERRUPTS> &eints,
562         const std::string &interrupt)
563 {
564     if (interrupt.find(interrupt_prefix) == 0)
565     {
566         std::string delay_name = interrupt.substr(interrupt_prefix.length());
567         delay_type delay = get_delay(delay_name);
568         if (delay == NUM_DELAYS)
569             return report_error("Unknown delay: %s\n", delay_name.c_str());
570
571         FixedVector<bool, NUM_AINTERRUPTS> &refints =
572             activity_interrupts[delay];
573
574         for (int i = 0; i < NUM_AINTERRUPTS; ++i)
575             if (refints[i])
576                 eints[i] = true;
577
578         return;
579     }
580
581     activity_interrupt_type ai = get_activity_interrupt(interrupt);
582     if (ai == NUM_AINTERRUPTS)
583     {
584         return report_error("Delay interrupt name \"%s\" not recognised.\n",
585                             interrupt.c_str());
586     }
587
588     eints[ai] = true;
589 }
590
591 void game_options::set_activity_interrupt(const std::string &activity_name,
592                                           const std::string &interrupt_names,
593                                           bool append_interrupts,
594                                           bool remove_interrupts)
595 {
596     const delay_type delay = get_delay(activity_name);
597     if (delay == NUM_DELAYS)
598         return report_error("Unknown delay: %s\n", activity_name.c_str());
599
600     std::vector<std::string> interrupts = split_string(",", interrupt_names);
601     FixedVector<bool, NUM_AINTERRUPTS> &eints = activity_interrupts[ delay ];
602
603     if (remove_interrupts)
604     {
605         FixedVector<bool, NUM_AINTERRUPTS> refints;
606         clear_activity_interrupts(refints);
607         for (int i = 0, size = interrupts.size(); i < size; ++i)
608             set_activity_interrupt(refints, interrupts[i]);
609
610         for (int i = 0; i < NUM_AINTERRUPTS; ++i)
611             if (refints[i])
612                 eints[i] = false;
613     }
614     else
615     {
616         if (!append_interrupts)
617             clear_activity_interrupts(eints);
618
619         for (int i = 0, size = interrupts.size(); i < size; ++i)
620             set_activity_interrupt(eints, interrupts[i]);
621     }
622
623     eints[AI_FORCE_INTERRUPT] = true;
624 }
625
626 #if defined(DGAMELAUNCH)
627 static std::string _resolve_dir(const char* path, const char* suffix)
628 {
629     return catpath(path, "");
630 }
631 #else
632
633 static std::string _user_home_dir()
634 {
635 #ifdef TARGET_OS_WINDOWS
636     wchar_t home[MAX_PATH];
637     if (SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, home))
638         return "./";
639     else
640         return utf16_to_8(home);
641 #else
642     const char *home = getenv("HOME");
643     if (!home || !*home)
644         return "./";
645     else
646         return mb_to_utf8(home);
647 #endif
648 }
649
650 static std::string _user_home_subpath(const std::string subpath)
651 {
652     return catpath(_user_home_dir(), subpath);
653 }
654
655 static std::string _resolve_dir(const char* path, const char* suffix)
656 {
657     if (path[0] != '~')
658         return catpath(std::string(path), suffix);
659     else
660         return _user_home_subpath(catpath(path + 1, suffix));
661 }
662 #endif
663
664 void game_options::reset_options()
665 {
666     filename     = "unknown";
667     basefilename = "unknown";
668     line_num     = -1;
669
670     set_default_activity_interrupts();
671
672 #ifdef DEBUG_DIAGNOSTICS
673     quiet_debug_messages.reset();
674 #endif
675
676 #if defined(USE_TILE_LOCAL)
677     restart_after_game = true;
678 #else
679     restart_after_game = false;
680 #endif
681
682     macro_dir = SysEnv.macro_dir;
683
684 #if !defined(DGAMELAUNCH)
685     if (macro_dir.empty())
686     {
687 #ifdef UNIX
688         macro_dir = _user_home_subpath(".crawl");
689 #else
690         macro_dir = "settings/";
691 #endif
692     }
693 #endif
694
695 #if defined(TARGET_OS_MACOSX)
696     const std::string tmp_path_base =
697         _user_home_subpath("Library/Application Support/" CRAWL);
698     save_dir   = tmp_path_base + "/saves/";
699     morgue_dir = tmp_path_base + "/morgue/";
700     if (SysEnv.macro_dir.empty())
701         macro_dir  = tmp_path_base;
702 #else
703         save_dir   = _resolve_dir(SysEnv.crawl_dir.c_str(), "saves/");
704 #endif
705
706 #if !defined(TARGET_OS_MACOSX)
707         morgue_dir = _resolve_dir(SysEnv.crawl_dir.c_str(), "morgue/");
708 #endif
709
710 #if defined(SHARED_DIR_PATH)
711     shared_dir = _resolve_dir(SHARED_DIR_PATH, "");
712 #else
713     shared_dir = save_dir;
714 #endif
715
716     additional_macro_files.clear();
717
718     seed = 0;
719 #ifdef DGL_SIMPLE_MESSAGING
720     messaging = true;
721 #endif
722
723     mouse_input = false;
724
725     view_max_width   = std::max(33, VIEW_MIN_WIDTH);
726     view_max_height  = std::max(21, VIEW_MIN_HEIGHT);
727     mlist_min_height = 4;
728     msg_min_height   = std::max(7, MSG_MIN_HEIGHT);
729     msg_max_height   = std::max(10, MSG_MIN_HEIGHT);
730     mlist_allow_alternate_layout = false;
731     messages_at_top  = false;
732     mlist_targetting = false;
733     msg_condense_repeats = true;
734     msg_condense_short = true;
735     show_no_ctele = true;
736
737     view_lock_x = true;
738     view_lock_y = true;
739
740     center_on_scroll = false;
741     symmetric_scroll = true;
742     scroll_margin_x  = 2;
743     scroll_margin_y  = 2;
744
745     autopickup_on    = 1;
746     default_friendly_pickup = FRIENDLY_PICKUP_FRIEND;
747     default_manual_training = false;
748
749     show_newturn_mark = true;
750     show_gold_turns = true;
751     show_game_turns = true;
752
753     game = newgame_def();
754
755     remember_name = true;
756
757     char_set      = CSET_DEFAULT;
758
759     // set it to the .crawlrc default
760     autopickups = ((1 << OBJ_GOLD)      |
761                    (1 << OBJ_SCROLLS)   |
762                    (1 << OBJ_POTIONS)   |
763                    (1 << OBJ_BOOKS)     |
764                    (1 << OBJ_JEWELLERY) |
765                    (1 << OBJ_WANDS)     |
766                    (1 << OBJ_FOOD));
767     auto_switch             = false;
768     suppress_startup_errors = false;
769
770     show_inventory_weights = false;
771     clean_map              = false;
772     show_uncursed          = true;
773     easy_open              = true;
774     easy_unequip           = true;
775     equip_unequip          = false;
776     confirm_butcher        = CONFIRM_AUTO;
777     chunks_autopickup      = true;
778     prompt_for_swap        = true;
779     list_rotten            = true;
780     auto_drop_chunks       = ADC_NEVER;
781     prefer_safe_chunks     = true;
782     easy_eat_chunks        = false;
783     easy_eat_gourmand      = false;
784     easy_eat_contaminated  = false;
785     auto_eat_chunks        = false;
786     easy_confirm           = CONFIRM_SAFE_EASY;
787     easy_quit_item_prompts = true;
788     allow_self_target      = CONFIRM_PROMPT;
789     hp_warning             = 30;
790     magic_point_warning    = 0;
791     default_target         = true;
792     autopickup_no_burden   = true;
793     skill_focus            = SKM_FOCUS_ON;
794
795     user_note_prefix       = "";
796     note_all_skill_levels  = false;
797     note_skill_max         = true;
798     note_xom_effects       = true;
799     note_chat_messages     = true;
800     note_hp_percent        = 5;
801
802     // [ds] Grumble grumble.
803     auto_list              = true;
804
805     clear_messages         = false;
806     show_more              = true;
807     small_more             = false;
808
809     pickup_thrown          = true;
810
811     travel_delay           = 20;
812     explore_delay          = -1;
813     show_travel_trail       = false;
814     travel_stair_cost      = 500;
815
816     arena_delay            = 600;
817     arena_dump_msgs        = false;
818     arena_dump_msgs_all    = false;
819     arena_list_eq          = false;
820
821     // Sort only pickup menus by default.
822     sort_menus.clear();
823     set_menu_sort("pickup: true");
824
825     tc_reachable           = BLUE;
826     tc_excluded            = LIGHTMAGENTA;
827     tc_exclude_circle      = RED;
828     tc_dangerous           = CYAN;
829     tc_disconnected        = DARKGREY;
830
831     show_waypoints         = true;
832
833     background_colour      = BLACK;
834     // [ds] Default to jazzy colours.
835     detected_item_colour   = GREEN;
836     detected_monster_colour= LIGHTRED;
837     status_caption_colour  = BROWN;
838
839     easy_exit_menu         = false;
840     dos_use_background_intensity = true;
841
842     level_map_title        = true;
843
844     assign_item_slot       = SS_FORWARD;
845
846     // 10 was the cursor step default on Linux.
847     level_map_cursor_step  = 7;
848 #ifdef UNIX
849     use_fake_cursor        = true;
850 #else
851     use_fake_cursor        = false;
852 #endif
853     use_fake_player_cursor = true;
854     show_player_species    = false;
855
856     stash_tracking         = STM_ALL;
857
858     explore_stop           = (ES_ITEM | ES_STAIR | ES_PORTAL | ES_BRANCH
859                               | ES_SHOP | ES_ALTAR | ES_GREEDY_PICKUP_SMART
860                               | ES_GREEDY_VISITED_ITEM_STACK
861                               | ES_GREEDY_SACRIFICIABLE);
862
863     // The prompt conditions will be combined into explore_stop after
864     // reading options.
865     explore_stop_prompt    = ES_NONE;
866
867     explore_stop_pickup_ignore.clear();
868
869     explore_item_greed     = 10;
870     explore_greedy         = true;
871
872     explore_wall_bias      = 0;
873     explore_improved       = false;
874     travel_key_stop        = true;
875
876     target_unshifted_dirs  = false;
877     darken_beyond_range    = true;
878
879     dump_kill_places       = KDO_ONE_PLACE;
880     dump_message_count     = 20;
881     dump_item_origins      = IODS_ARTEFACTS | IODS_RODS;
882     dump_item_origin_price = -1;
883     dump_book_spells       = true;
884
885     drop_mode              = DM_MULTI;
886     pickup_mode            = -1;
887
888     flush_input[ FLUSH_ON_FAILURE ]     = true;
889     flush_input[ FLUSH_BEFORE_COMMAND ] = false;
890     flush_input[ FLUSH_ON_MESSAGE ]     = false;
891     flush_input[ FLUSH_LUA ]            = true;
892
893     fire_items_start       = 0;           // start at slot 'a'
894
895     // Clear fire_order and set up the defaults.
896     set_fire_order("launcher, return, "
897                    "javelin / dart / stone / rock /"
898                    " spear / net / handaxe / dagger / club, inscribed",
899                    false);
900
901     item_stack_summary_minimum = 5;
902
903 #ifdef WIZARD
904     fsim_rounds = 4000L;
905     fsim_mons   = "";
906     fsim_scale.clear();
907     fsim_kit.clear();
908 #endif
909
910     // These are only used internally, and only from the commandline:
911     // XXX: These need a better place.
912     sc_entries             = 0;
913     sc_format              = -1;
914
915     friend_brand       = CHATTR_HILITE | (GREEN << 8);
916     neutral_brand      = CHATTR_HILITE | (LIGHTGREY << 8);
917     stab_brand         = CHATTR_HILITE | (BLUE << 8);
918     may_stab_brand     = CHATTR_HILITE | (YELLOW << 8);
919     heap_brand         = CHATTR_REVERSE;
920     feature_item_brand = CHATTR_REVERSE;
921     trap_item_brand    = CHATTR_REVERSE;
922
923     no_dark_brand      = true;
924
925 #ifdef WIZARD
926 #ifdef DGAMELAUNCH
927     if (wiz_mode != WIZ_NO)
928         wiz_mode         = WIZ_NEVER;
929 #else
930     wiz_mode         = WIZ_NO;
931 #endif
932     terp_files.clear();
933 #endif
934
935 #ifdef USE_TILE
936     tile_show_items      = "!?/%=([)x}:|\\";
937     tile_skip_title      = false;
938     tile_menu_icons      = true;
939 #endif
940
941 #ifdef USE_TILE_LOCAL
942     // minimap colours
943     tile_player_col      = MAP_WHITE;
944     tile_monster_col     = MAP_RED;
945     tile_neutral_col     = MAP_RED;
946     tile_peaceful_col    = MAP_LTRED;
947     tile_friendly_col    = MAP_LTRED;
948     tile_plant_col       = MAP_DKGREEN;
949     tile_item_col        = MAP_GREEN;
950     tile_unseen_col      = MAP_BLACK;
951     tile_floor_col       = MAP_LTGREY;
952     tile_wall_col        = MAP_DKGREY;
953     tile_mapped_wall_col = MAP_BLUE;
954     tile_door_col        = MAP_BROWN;
955     tile_downstairs_col  = MAP_MAGENTA;
956     tile_upstairs_col    = MAP_BLUE;
957     tile_feature_col     = MAP_CYAN;
958     tile_trap_col        = MAP_YELLOW;
959     tile_water_col       = MAP_MDGREY;
960     tile_lava_col        = MAP_MDGREY;
961     tile_excluded_col    = MAP_DKCYAN;
962     tile_excl_centre_col = MAP_DKBLUE;
963     tile_window_col      = MAP_YELLOW;
964
965     // font selection
966     tile_font_crt_file   = MONOSPACED_FONT;
967     tile_font_crt_size   = 0;
968     tile_font_stat_file  = MONOSPACED_FONT;
969     tile_font_stat_size  = 0;
970     tile_font_msg_file   = MONOSPACED_FONT;
971     tile_font_msg_size   = 0;
972     tile_font_tip_file   = MONOSPACED_FONT;
973     tile_font_tip_size   = 0;
974     tile_font_lbl_file   = PROPORTIONAL_FONT;
975     tile_font_lbl_size   = 0;
976 #ifdef USE_FT
977     // TODO: init this from system settings.  This would probably require
978     // using fontconfig, but that's planned.
979     tile_font_ft_light   = false;
980 #endif
981
982     // window layout
983     tile_full_screen      = SCREENMODE_AUTO;
984     tile_window_width     = -90;
985     tile_window_height    = -90;
986     tile_map_pixels       = 0;
987     tile_layout_priority = split_string(",", "minimap, inventory, gold_turn, "
988                                              "command, spell, monster");
989 #endif
990
991 #ifdef USE_TILE
992     tile_force_overlay    = false;
993     // delays
994     tile_update_rate      = 1000;
995     tile_runrest_rate     = 100;
996     tile_key_repeat_delay = 200;
997     tile_tooltip_ms       = 500;
998     // XXX: arena may now be chosen after options are read.
999     tile_tag_pref         = crawl_state.game_is_arena() ? TAGPREF_NAMED
1000                                                         : TAGPREF_ENEMY;
1001
1002     tile_show_minihealthbar  = true;
1003     tile_show_minimagicbar   = true;
1004     tile_show_demon_tier     = true;
1005     tile_force_regenerate_levels = false;
1006 #endif
1007
1008     // map each colour to itself as default
1009     // If USE_8_COLOUR_TERM_MAP is defined, then we force 8 colors.
1010     // Otherwise, do a check to see if we're using Apple_Terminal.
1011 #ifndef USE_8_COLOUR_TERM_MAP
1012     const char *term_program = getenv("TERM_PROGRAM");
1013     if (term_program && strcmp(term_program, "Apple_Terminal") == 0)
1014     {
1015 #endif
1016         for (int i = 0; i < 16; ++i)
1017             colour[i] = i % 8;
1018
1019         colour[ DARKGREY ] = COL_TO_REPLACE_DARKGREY;
1020 #ifndef USE_8_COLOUR_TERM_MAP
1021     }
1022     else
1023     {
1024         for (int i = 0; i < 16; ++i)
1025             colour[i] = i;
1026     }
1027 #endif
1028
1029     // map each channel to plain (well, default for now since I'm testing)
1030     for (int i = 0; i < NUM_MESSAGE_CHANNELS; ++i)
1031         channels[i] = MSGCOL_DEFAULT;
1032
1033     // Clear vector options.
1034     dump_order.clear();
1035     new_dump_fields("header,hiscore,stats,misc,inventory,"
1036                     "skills,spells,overview,mutations,messages,"
1037                     "screenshot,monlist,kills,notes");
1038
1039     hp_colour.clear();
1040     hp_colour.push_back(std::pair<int,int>(50, YELLOW));
1041     hp_colour.push_back(std::pair<int,int>(25, RED));
1042     mp_colour.clear();
1043     mp_colour.push_back(std::pair<int, int>(50, YELLOW));
1044     mp_colour.push_back(std::pair<int, int>(25, RED));
1045     stat_colour.clear();
1046     stat_colour.push_back(std::pair<int, int>(3, RED));
1047     enemy_hp_colour.clear();
1048     // I think these defaults are pretty ugly but apparently OS X has problems
1049     // with lighter colours
1050     enemy_hp_colour.push_back(GREEN);
1051     enemy_hp_colour.push_back(GREEN);
1052     enemy_hp_colour.push_back(BROWN);
1053     enemy_hp_colour.push_back(BROWN);
1054     enemy_hp_colour.push_back(MAGENTA);
1055     enemy_hp_colour.push_back(RED);
1056     enemy_hp_colour.push_back(LIGHTGREY);
1057     visual_monster_hp = false;
1058
1059     force_autopickup.clear();
1060     note_monsters.clear();
1061     note_messages.clear();
1062     autoinscriptions.clear();
1063     autoinscribe_artefacts = true;
1064     autoinscribe_cursed = true;
1065     note_items.clear();
1066     note_skill_levels.reset();
1067     note_skill_levels.set(1);
1068     note_skill_levels.set(5);
1069     note_skill_levels.set(10);
1070     note_skill_levels.set(15);
1071     note_skill_levels.set(27);
1072     auto_spell_letters.clear();
1073     force_more_message.clear();
1074     sound_mappings.clear();
1075     menu_colour_mappings.clear();
1076     menu_colour_prefix_class = true;
1077     menu_colour_shops = true;
1078     message_colour_mappings.clear();
1079     drop_filter.clear();
1080     map_file_name.clear();
1081     named_options.clear();
1082
1083     clear_cset_overrides();
1084
1085     clear_feature_overrides();
1086     mon_glyph_overrides.clear();
1087
1088     rest_wait_both = false;
1089
1090     // Map each category to itself. The user can override in init.txt
1091     kill_map[KC_YOU] = KC_YOU;
1092     kill_map[KC_FRIENDLY] = KC_FRIENDLY;
1093     kill_map[KC_OTHER] = KC_OTHER;
1094
1095     // Forget any files we remembered as included.
1096     included.clear();
1097
1098     // Forget variables and such.
1099     aliases.clear();
1100     variables.clear();
1101     constants.clear();
1102 }
1103
1104 void game_options::clear_cset_overrides()
1105 {
1106     memset(cset_override, 0, sizeof cset_override);
1107 }
1108
1109 void game_options::clear_feature_overrides()
1110 {
1111     feature_overrides.clear();
1112 }
1113
1114 ucs_t get_glyph_override(int c)
1115 {
1116     if (c < 0)
1117     {
1118         c = -c;
1119         if (Options.char_set == CSET_IBM)
1120             c = (c & ~0xff) ? 0 : charset_cp437[c & 0xff];
1121         else if (Options.char_set == CSET_DEC)
1122             c = (c & 0x80) ? charset_vt100[c & 0x7f] : c;
1123     }
1124     if (wcwidth(c) != 1)
1125     {
1126         mprf(MSGCH_ERROR, "Invalid glyph override: %X", c);
1127         c = 0;
1128     }
1129     return c;
1130 }
1131
1132 static int read_symbol(std::string s)
1133 {
1134     if (s.empty())
1135         return 0;
1136
1137     if (s.length() > 1 && s[0] == '\\')
1138         s = s.substr(1);
1139
1140     {
1141         ucs_t c;
1142         const char *nc = s.c_str();
1143         nc += utf8towc(&c, nc);
1144         // no control, combining or CJK characters, please
1145         if (!*nc && wcwidth(c) == 1)
1146             return c;
1147     }
1148
1149     int base = 10;
1150     if (s.length() > 1 && s[0] == 'x')
1151     {
1152         s = s.substr(1);
1153         base = 16;
1154     }
1155
1156     char *tail;
1157     return -strtoul(s.c_str(), &tail, base);
1158 }
1159
1160 void game_options::set_fire_order(const std::string &s, bool add)
1161 {
1162     if (!add)
1163         fire_order.clear();
1164     std::vector<std::string> slots = split_string(",", s);
1165     for (int i = 0, size = slots.size(); i < size; ++i)
1166         add_fire_order_slot(slots[i]);
1167 }
1168
1169 void game_options::add_fire_order_slot(const std::string &s)
1170 {
1171     unsigned flags = 0;
1172     std::vector<std::string> alts = split_string("/", s);
1173     for (int i = 0, size = alts.size(); i < size; ++i)
1174         flags |= _str_to_fire_types(alts[i]);
1175
1176     if (flags)
1177         fire_order.push_back(flags);
1178 }
1179
1180 void game_options::add_mon_glyph_overrides(const std::string &mons,
1181                                            mon_display &mdisp)
1182 {
1183     // If one character, this is a monster letter.
1184     int letter = -1;
1185     if (mons.length() == 1)
1186         letter = mons[0] == '_' ? ' ' : mons[0];
1187
1188     bool found = false;
1189     for (monster_type i = MONS_0; i < NUM_MONSTERS; ++i)
1190     {
1191         const monsterentry *me = get_monster_data(i);
1192         if (!me || me->mc == MONS_PROGRAM_BUG)
1193             continue;
1194
1195         if (me->basechar == letter || me->name == mons)
1196         {
1197             found = true;
1198             mon_glyph_overrides[i] = mdisp;
1199         }
1200     }
1201     if (!found)
1202         report_error("Unknown monster: \"%s\"", mons.c_str());
1203 }
1204
1205 mon_display game_options::parse_mon_glyph(const std::string &s) const
1206 {
1207     mon_display md;
1208     std::vector<std::string> phrases = split_string(" ", s);
1209     for (int i = 0, size = phrases.size(); i < size; ++i)
1210     {
1211         const std::string &p = phrases[i];
1212         const int col = str_to_colour(p, -1, false);
1213         if (col != -1 && colour)
1214             md.colour = col;
1215         else
1216             md.glyph = p == "_"? ' ' : read_symbol(p);
1217     }
1218     return md;
1219 }
1220
1221 void game_options::add_mon_glyph_override(const std::string &text)
1222 {
1223     std::vector<std::string> override = split_string(":", text);
1224     if (override.size() != 2u)
1225         return;
1226
1227     mon_display mdisp = parse_mon_glyph(override[1]);
1228     if (mdisp.glyph || mdisp.colour)
1229         add_mon_glyph_overrides(override[0], mdisp);
1230 }
1231
1232 void game_options::add_feature_override(const std::string &text)
1233 {
1234     std::string::size_type epos = text.rfind("}");
1235     if (epos == std::string::npos)
1236         return;
1237
1238     std::string::size_type spos = text.rfind("{", epos);
1239     if (spos == std::string::npos)
1240         return;
1241
1242     std::string fname = text.substr(0, spos);
1243     std::string props = text.substr(spos + 1, epos - spos - 1);
1244     std::vector<std::string> iprops = split_string(",", props, true, true);
1245
1246     if (iprops.size() < 1 || iprops.size() > 7)
1247         return;
1248
1249     if (iprops.size() < 7)
1250         iprops.resize(7);
1251
1252     trim_string(fname);
1253     std::vector<dungeon_feature_type> feats =
1254         features_by_desc(text_pattern(fname));
1255     if (feats.empty())
1256         return;
1257
1258     for (int i = 0, size = feats.size(); i < size; ++i)
1259     {
1260         if (feats[i] >= NUM_FEATURES)
1261             continue; // TODO: handle other object types.
1262         feature_override fov;
1263         fov.object.cls = SH_FEATURE;
1264         fov.object.feat = feats[i];
1265
1266         fov.override.symbol         = read_symbol(iprops[0]);
1267         fov.override.magic_symbol   = read_symbol(iprops[1]);
1268         fov.override.colour         = str_to_colour(iprops[2], BLACK);
1269         fov.override.map_colour     = str_to_colour(iprops[3], BLACK);
1270         fov.override.seen_colour    = str_to_colour(iprops[4], BLACK);
1271         fov.override.em_colour      = str_to_colour(iprops[5], BLACK);
1272         fov.override.seen_em_colour = str_to_colour(iprops[6], BLACK);
1273
1274         feature_overrides.push_back(fov);
1275     }
1276 }
1277
1278 void game_options::add_cset_override(
1279         char_set_type set, const std::string &overrides)
1280 {
1281     std::vector<std::string> overs = split_string(",", overrides);
1282     for (int i = 0, size = overs.size(); i < size; ++i)
1283     {
1284         std::vector<std::string> mapping = split_string(":", overs[i]);
1285         if (mapping.size() != 2)
1286             continue;
1287
1288         dungeon_char_type dc = dchar_by_name(mapping[0]);
1289         if (dc == NUM_DCHAR_TYPES)
1290             continue;
1291
1292         add_cset_override(set, dc, read_symbol(mapping[1]));
1293     }
1294 }
1295
1296 void game_options::add_cset_override(char_set_type set, dungeon_char_type dc,
1297                                      int symbol)
1298 {
1299     cset_override[dc] = get_glyph_override(symbol);
1300 }
1301
1302 static std::string _find_crawlrc()
1303 {
1304     const char* locations_data[][2] = {
1305         { SysEnv.crawl_dir.c_str(), "init.txt" },
1306 #ifdef UNIX
1307         { SysEnv.home.c_str(), ".crawl/init.txt" },
1308         { SysEnv.home.c_str(), ".crawlrc" },
1309         { SysEnv.home.c_str(), "init.txt" },
1310 #endif
1311 #ifndef DATA_DIR_PATH
1312         { "", "init.txt" },
1313         { "..", "init.txt" },
1314         { "../settings", "init.txt" },
1315 #endif
1316         { NULL, NULL }                // placeholder to mark end
1317     };
1318
1319     // We'll look for these files in any supplied -rcdirs.
1320     static const char *rc_dir_filenames[] = {
1321         ".crawlrc", "init.txt"
1322     };
1323
1324     // -rc option always wins.
1325     if (!SysEnv.crawl_rc.empty())
1326         return SysEnv.crawl_rc;
1327
1328     // If we have any rcdirs, look in them for files from the
1329     // rc_dir_names list.
1330     for (int i = 0, size = SysEnv.rcdirs.size(); i < size; ++i)
1331     {
1332         for (unsigned n = 0; n < ARRAYSZ(rc_dir_filenames); ++n)
1333         {
1334             const std::string rc(
1335                 catpath(SysEnv.rcdirs[i], rc_dir_filenames[n]));
1336             if (file_exists(rc))
1337                 return rc;
1338         }
1339     }
1340
1341     // Check all possibilities for init.txt
1342     for (int i = 0; locations_data[i][1] != NULL; ++i)
1343     {
1344         // Don't look at unset options
1345         if (locations_data[i][0] != NULL)
1346         {
1347             const std::string rc =
1348                 catpath(locations_data[i][0], locations_data[i][1]);
1349             if (file_exists(rc))
1350                 return rc;
1351         }
1352     }
1353
1354     // Last attempt: pick up init.txt from datafile_path, which will
1355     // also search the settings/ directory.
1356     return datafile_path("init.txt", false, false);
1357 }
1358
1359 static const char* lua_builtins[] =
1360 {
1361     "clua/stash.lua",
1362     "clua/wield.lua",
1363     "clua/runrest.lua",
1364     "clua/gearset.lua",
1365     "clua/trapwalk.lua",
1366     "clua/autofight.lua",
1367     "clua/kills.lua",
1368 };
1369
1370 static const char* config_defaults[] =
1371 {
1372     "defaults/autopickup_exceptions.txt",
1373     "defaults/runrest_messages.txt",
1374     "defaults/standard_colours.txt",
1375     "defaults/food_colouring.txt",
1376     "defaults/menu_colours.txt",
1377     "defaults/messages.txt",
1378 };
1379
1380 // Returns an error message if the init.txt was not found.
1381 std::string read_init_file(bool runscript)
1382 {
1383     Options.reset_options();
1384
1385 #ifdef CLUA_BINDINGS
1386     if (runscript)
1387     {
1388         for (unsigned int i = 0; i < ARRAYSZ(lua_builtins); ++i)
1389         {
1390             clua.execfile(lua_builtins[i], false, false);
1391             if (!clua.error.empty())
1392                 mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
1393         }
1394     }
1395
1396     for (unsigned int i = 0; i < ARRAYSZ(config_defaults); ++i)
1397         Options.include(datafile_path(config_defaults[i]), false, runscript);
1398 #endif
1399
1400     Options.filename     = "extra opts first";
1401     Options.basefilename = "extra opts first";
1402     Options.line_num     = 0;
1403     for (unsigned int i = 0; i < SysEnv.extra_opts_first.size(); i++)
1404     {
1405         Options.line_num++;
1406         Options.read_option_line(SysEnv.extra_opts_first[i], true);
1407     }
1408
1409     const std::string init_file_name(_find_crawlrc());
1410
1411     FileLineInput f(init_file_name.c_str());
1412     if (f.error())
1413     {
1414         if (!init_file_name.empty())
1415         {
1416             return make_stringf("(\"%s\" is not readable)",
1417                                 init_file_name.c_str());
1418         }
1419
1420 #ifdef UNIX
1421         return "(~/.crawlrc missing)";
1422 #else
1423         return "(no init.txt in current directory)";
1424 #endif
1425     }
1426
1427     Options.filename = init_file_name;
1428     Options.line_num = 0;
1429 #ifdef UNIX
1430     Options.basefilename = "~/.crawlrc";
1431 #else
1432     Options.basefilename = "init.txt";
1433 #endif
1434     Options.read_options(f, runscript);
1435
1436     Options.filename     = "extra opts last";
1437     Options.basefilename = "extra opts last";
1438     Options.line_num     = 0;
1439     for (unsigned int i = 0; i < SysEnv.extra_opts_last.size(); i++)
1440     {
1441         Options.line_num++;
1442         Options.read_option_line(SysEnv.extra_opts_last[i], false);
1443     }
1444
1445     Options.filename     = init_file_name;
1446     Options.basefilename = get_base_filename(init_file_name);
1447     Options.line_num     = -1;
1448
1449     return "";
1450 }
1451
1452 newgame_def read_startup_prefs()
1453 {
1454 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1455     FileLineInput fl(get_prefs_filename().c_str());
1456     if (fl.error())
1457         return newgame_def();
1458
1459     game_options temp;
1460     temp.read_options(fl, false);
1461
1462     return temp.game;
1463 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1464 }
1465
1466 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1467 static void write_newgame_options(const newgame_def& prefs, FILE *f)
1468 {
1469     if (prefs.type != NUM_GAME_TYPE)
1470         fprintf(f, "type = %s\n", gametype_to_str(prefs.type).c_str());
1471     if (!prefs.map.empty())
1472         fprintf(f, "map = %s\n", prefs.map.c_str());
1473     if (!prefs.arena_teams.empty())
1474         fprintf(f, "arena_teams = %s\n", prefs.arena_teams.c_str());
1475     fprintf(f, "name = %s\n", prefs.name.c_str());
1476     if (prefs.species != SP_UNKNOWN)
1477         fprintf(f, "species = %s\n", _species_to_str(prefs.species).c_str());
1478     if (prefs.job != JOB_UNKNOWN)
1479         fprintf(f, "background = %s\n", _job_to_str(prefs.job).c_str());
1480     if (prefs.weapon != WPN_UNKNOWN)
1481         fprintf(f, "weapon = %s\n", _weapon_to_str(prefs.weapon).c_str());
1482     fprintf(f, "fully_random = %s\n", prefs.fully_random ? "yes" : "no");
1483 }
1484 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1485
1486 void write_newgame_options_file(const newgame_def& prefs)
1487 {
1488 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1489     // [ds] Saving startup prefs should work like this:
1490     //
1491     // 1. If the game is started without specifying a game type, always
1492     //    save startup preferences in the base savedir.
1493     // 2. If the game is started with a game type (Sprint), save startup
1494     //    preferences in the game-specific savedir.
1495     //
1496     // The idea is that public servers can use one instance of Crawl
1497     // but present Crawl and Sprint as two separate games in the
1498     // server-specific game menu -- the startup prefs file for Crawl and
1499     // Sprint should never collide, because the public server config will
1500     // specify the game type on the command-line.
1501     //
1502     // For normal users, startup prefs should always be saved in the
1503     // same base savedir so that when they start Crawl with "./crawl"
1504     // or the equivalent, their last game choices will be remembered,
1505     // even if they chose a Sprint game.
1506     //
1507     // Yes, this is unnecessarily complex. Better ideas welcome.
1508     //
1509     unwind_var<game_type> gt(crawl_state.type, Options.game.type);
1510
1511     std::string fn = get_prefs_filename();
1512     FILE *f = fopen_u(fn.c_str(), "w");
1513     if (!f)
1514         return;
1515     write_newgame_options(prefs, f);
1516     fclose(f);
1517 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1518 }
1519
1520 void save_player_name()
1521 {
1522 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1523     if (!Options.remember_name)
1524         return ;
1525
1526     // Read other preferences
1527     newgame_def prefs = read_startup_prefs();
1528     prefs.name = you.your_name;
1529
1530     // And save
1531     write_newgame_options_file(prefs);
1532 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1533 }
1534
1535 void read_options(const std::string &s, bool runscript, bool clear_aliases)
1536 {
1537     StringLineInput st(s);
1538     Options.read_options(st, runscript, clear_aliases);
1539 }
1540
1541 game_options::game_options()
1542 {
1543     lang = LANG_EN; // FIXME: obtain from gettext
1544     lang_name = 0;
1545     reset_options();
1546 }
1547
1548 void game_options::read_options(LineInput &il, bool runscript,
1549                                 bool clear_aliases)
1550 {
1551     unsigned int line = 0;
1552
1553     bool inscriptblock = false;
1554     bool inscriptcond  = false;
1555     bool isconditional = false;
1556
1557     bool l_init        = false;
1558
1559     if (clear_aliases)
1560         aliases.clear();
1561
1562     dlua_chunk luacond(filename);
1563     dlua_chunk luacode(filename);
1564
1565     while (!il.eof())
1566     {
1567         line_num++;
1568         std::string s   = il.get_line();
1569         std::string str = s;
1570         line++;
1571
1572         trim_string(str);
1573
1574         // This is to make some efficient comments
1575         if ((str.empty() || str[0] == '#') && !inscriptcond && !inscriptblock)
1576             continue;
1577
1578         if (!inscriptcond && str[0] == ':')
1579         {
1580             // The init file is now forced into isconditional mode.
1581             isconditional = true;
1582             str = str.substr(1);
1583             if (!str.empty() && runscript)
1584             {
1585                 // If we're in the middle of an option block, close it.
1586                 if (!luacond.empty() && l_init)
1587                 {
1588                     luacond.add(line - 1, "]])");
1589                     l_init = false;
1590                 }
1591                 luacond.add(line, str);
1592             }
1593             continue;
1594         }
1595         if (!inscriptcond && (str.find("L<") == 0 || str.find("<") == 0))
1596         {
1597             // The init file is now forced into isconditional mode.
1598             isconditional = true;
1599             inscriptcond  = true;
1600
1601             str = str.substr(str.find("L<") == 0? 2 : 1);
1602             // Is this a one-liner?
1603             if (!str.empty() && str[ str.length() - 1 ] == '>')
1604             {
1605                 inscriptcond = false;
1606                 str = str.substr(0, str.length() - 1);
1607             }
1608
1609             if (!str.empty() && runscript)
1610             {
1611                 // If we're in the middle of an option block, close it.
1612                 if (!luacond.empty() && l_init)
1613                 {
1614                     luacond.add(line - 1, "]])");
1615                     l_init = false;
1616                 }
1617                 luacond.add(line, str);
1618             }
1619             continue;
1620         }
1621         else if (inscriptcond && !str.empty()
1622                  && (str.find(">") == str.length() - 1 || str == ">L"))
1623         {
1624             inscriptcond = false;
1625             str = str.substr(0, str.length() - 1);
1626             if (!str.empty() && runscript)
1627                 luacond.add(line, str);
1628             continue;
1629         }
1630         else if (inscriptcond)
1631         {
1632             if (runscript)
1633                 luacond.add(line, s);
1634             continue;
1635         }
1636
1637         // Handle blocks of Lua
1638         if (!inscriptblock && (str.find("Lua{") == 0 || str.find("{") == 0))
1639         {
1640             inscriptblock = true;
1641             luacode.clear();
1642             luacode.set_file(filename);
1643
1644             // Strip leading Lua[
1645             str = str.substr(str.find("Lua{") == 0? 4 : 1);
1646
1647             if (!str.empty() && str.find("}") == str.length() - 1)
1648             {
1649                 str = str.substr(0, str.length() - 1);
1650                 inscriptblock = false;
1651             }
1652
1653             if (!str.empty())
1654                 luacode.add(line, str);
1655
1656             if (!inscriptblock && runscript)
1657             {
1658 #ifdef CLUA_BINDINGS
1659                 if (luacode.run(clua))
1660                 {
1661                     mprf(MSGCH_ERROR, "Lua error: %s",
1662                          luacode.orig_error().c_str());
1663                 }
1664                 luacode.clear();
1665 #endif
1666             }
1667
1668             continue;
1669         }
1670         else if (inscriptblock && (str == "}Lua" || str == "}"))
1671         {
1672             inscriptblock = false;
1673 #ifdef CLUA_BINDINGS
1674             if (runscript)
1675             {
1676                 if (luacode.run(clua))
1677                     mprf(MSGCH_ERROR, "Lua error: %s",
1678                          luacode.orig_error().c_str());
1679             }
1680 #endif
1681             luacode.clear();
1682             continue;
1683         }
1684         else if (inscriptblock)
1685         {
1686             luacode.add(line, s);
1687             continue;
1688         }
1689
1690         if (isconditional && runscript)
1691         {
1692             if (!l_init)
1693             {
1694                 luacond.add(line, "crawl.setopt([[");
1695                 l_init = true;
1696             }
1697
1698             luacond.add(line, s);
1699             continue;
1700         }
1701
1702         read_option_line(str, runscript);
1703     }
1704
1705 #ifdef CLUA_BINDINGS
1706     if (runscript && !luacond.empty())
1707     {
1708         if (l_init)
1709             luacond.add(line, "]])");
1710         if (luacond.run(clua))
1711             mprf(MSGCH_ERROR, "Lua error: %s", luacond.orig_error().c_str());
1712     }
1713 #endif
1714
1715     Options.explore_stop |= Options.explore_stop_prompt;
1716
1717     evil_colour = str_to_colour(variables["evil"]);
1718 }
1719
1720 void game_options::fixup_options()
1721 {
1722     // Validate save_dir
1723     if (!check_mkdir("Save directory", &save_dir))
1724         end(1);
1725
1726     if (!SysEnv.morgue_dir.empty())
1727         morgue_dir = SysEnv.morgue_dir;
1728
1729     if (!check_mkdir("Morgue directory", &morgue_dir))
1730         end(1);
1731
1732     if (evil_colour == BLACK)
1733         evil_colour = MAGENTA;
1734 }
1735
1736 static int _str_to_killcategory(const std::string &s)
1737 {
1738     static const char *kc[] = {
1739         "you",
1740         "friend",
1741         "other",
1742     };
1743
1744     for (unsigned i = 0; i < ARRAYSZ(kc); ++i)
1745         if (s == kc[i])
1746             return i;
1747
1748     return -1;
1749 }
1750
1751 void game_options::do_kill_map(const std::string &from, const std::string &to)
1752 {
1753     int ifrom = _str_to_killcategory(from),
1754         ito   = _str_to_killcategory(to);
1755     if (ifrom != -1 && ito != -1)
1756         kill_map[ifrom] = ito;
1757 }
1758
1759 int game_options::read_explore_stop_conditions(const std::string &field) const
1760 {
1761     int conditions = 0;
1762     std::vector<std::string> stops = split_string(",", field);
1763     for (int i = 0, count = stops.size(); i < count; ++i)
1764     {
1765         const std::string c = replace_all_of(stops[i], " ", "_");
1766         if (c == "item" || c == "items")
1767             conditions |= ES_ITEM;
1768         else if (c == "greedy_pickup")
1769             conditions |= ES_GREEDY_PICKUP;
1770         else if (c == "greedy_pickup_gold")
1771             conditions |= ES_GREEDY_PICKUP_GOLD;
1772         else if (c == "greedy_pickup_smart")
1773             conditions |= ES_GREEDY_PICKUP_SMART;
1774         else if (c == "greedy_pickup_thrown")
1775             conditions |= ES_GREEDY_PICKUP_THROWN;
1776         else if (c == "shop" || c == "shops")
1777             conditions |= ES_SHOP;
1778         else if (c == "stair" || c == "stairs")
1779             conditions |= ES_STAIR;
1780         else if (c == "branch" || c == "branches")
1781             conditions |= ES_BRANCH;
1782         else if (c == "portal" || c == "portals")
1783             conditions |= ES_PORTAL;
1784         else if (c == "altar" || c == "altars")
1785             conditions |= ES_ALTAR;
1786         else if (c == "greedy_item" || c == "greedy_items")
1787             conditions |= ES_GREEDY_ITEM;
1788         else if (c == "greedy_visited_item_stack")
1789             conditions |= ES_GREEDY_VISITED_ITEM_STACK;
1790         else if (c == "glowing" || c == "glowing_item"
1791                  || c == "glowing_items")
1792             conditions |= ES_GLOWING_ITEM;
1793         else if (c == "artefact" || c == "artefacts"
1794                  || c == "artifact" || c == "artifacts")
1795             conditions |= ES_ARTEFACT;
1796         else if (c == "rune" || c == "runes")
1797             conditions |= ES_RUNE;
1798         else if (c == "greedy_sacrificiable" || c == "greedy_sacrificiables")
1799             conditions |= ES_GREEDY_SACRIFICIABLE;
1800     }
1801     return conditions;
1802 }
1803
1804 void game_options::add_alias(const std::string &key, const std::string &val)
1805 {
1806     if (key[0] == '$')
1807     {
1808         std::string name = key.substr(1);
1809         // Don't alter if it's a constant.
1810         if (constants.find(name) != constants.end())
1811             return;
1812         variables[name] = val;
1813     }
1814     else
1815         aliases[key] = val;
1816 }
1817
1818 std::string game_options::unalias(const std::string &key) const
1819 {
1820     string_map::const_iterator i = aliases.find(key);
1821     return (i == aliases.end()? key : i->second);
1822 }
1823
1824 #define IS_VAR_CHAR(c) (isaalpha(c) || c == '_' || c == '-')
1825
1826 std::string game_options::expand_vars(const std::string &field) const
1827 {
1828     std::string field_out = field;
1829
1830     std::string::size_type curr_pos = 0;
1831
1832     // Only try 100 times, so as to not get stuck in infinite recursion.
1833     for (int i = 0; i < 100; i++)
1834     {
1835         std::string::size_type dollar_pos = field_out.find("$", curr_pos);
1836
1837         if (dollar_pos == std::string::npos
1838             || field_out.size() == (dollar_pos + 1))
1839         {
1840             break;
1841         }
1842
1843         std::string::size_type start_pos = dollar_pos + 1;
1844
1845         if (!IS_VAR_CHAR(field_out[start_pos]))
1846             continue;
1847
1848         std::string::size_type end_pos;
1849         for (end_pos = start_pos; end_pos < field_out.size(); end_pos++)
1850         {
1851             if (!IS_VAR_CHAR(field_out[end_pos + 1]))
1852                 break;
1853         }
1854
1855         std::string var_name = field_out.substr(start_pos,
1856                                                 end_pos - start_pos + 1);
1857
1858         string_map::const_iterator x = variables.find(var_name);
1859
1860         if (x == variables.end())
1861         {
1862             curr_pos = end_pos + 1;
1863             continue;
1864         }
1865
1866         std::string dollar_plus_name = "$";
1867         dollar_plus_name += var_name;
1868
1869         field_out = replace_all(field_out, dollar_plus_name, x->second);
1870
1871         // Start over at beginning
1872         curr_pos = 0;
1873     }
1874
1875     return field_out;
1876 }
1877
1878 void game_options::add_message_colour_mappings(const std::string &field)
1879 {
1880     std::vector<std::string> fragments = split_string(",", field);
1881     for (int i = 0, count = fragments.size(); i < count; ++i)
1882         add_message_colour_mapping(fragments[i]);
1883 }
1884
1885 message_filter game_options::parse_message_filter(const std::string &filter)
1886 {
1887     std::string::size_type pos = filter.find(":");
1888     if (pos && pos != std::string::npos)
1889     {
1890         std::string prefix = filter.substr(0, pos);
1891         int channel = str_to_channel(prefix);
1892         if (channel != -1 || prefix == "any")
1893         {
1894             std::string s = filter.substr(pos + 1);
1895             trim_string(s);
1896             return message_filter(channel, s);
1897         }
1898     }
1899
1900     return message_filter(filter);
1901 }
1902
1903 void game_options::add_message_colour_mapping(const std::string &field)
1904 {
1905     std::vector<std::string> cmap = split_string(":", field, true, true, 1);
1906
1907     if (cmap.size() != 2)
1908         return;
1909
1910     const int col = str_to_colour(cmap[0]);
1911     msg_colour_type mcol;
1912     if (cmap[0] == "mute")
1913         mcol = MSGCOL_MUTED;
1914     else if (col == -1)
1915         return;
1916     else
1917         mcol = msg_colour(col);
1918
1919     message_colour_mapping m = { parse_message_filter(cmap[1]), mcol };
1920     message_colour_mappings.push_back(m);
1921 }
1922
1923 // Option syntax is:
1924 // sort_menu = [menu_type:]yes|no|auto:n[:sort_conditions]
1925 void game_options::set_menu_sort(std::string field)
1926 {
1927     if (field.empty())
1928         return;
1929
1930     menu_sort_condition cond(field);
1931
1932     // Overrides all previous settings.
1933     if (cond.mtype == MT_ANY)
1934         sort_menus.clear();
1935
1936     // Override existing values, if necessary.
1937     for (unsigned int i = 0; i < sort_menus.size(); i++)
1938         if (sort_menus[i].mtype == cond.mtype)
1939         {
1940             sort_menus[i].sort = cond.sort;
1941             sort_menus[i].cmp  = cond.cmp;
1942             return;
1943         }
1944
1945     sort_menus.push_back(cond);
1946 }
1947
1948 void game_options::split_parse(
1949     const std::string &s, const std::string &separator,
1950     void (game_options::*add)(const std::string &))
1951 {
1952     const std::vector<std::string> defs = split_string(separator, s);
1953     for (int i = 0, size = defs.size(); i < size; ++i)
1954         (this->*add)(defs[i]);
1955 }
1956
1957 void game_options::set_option_fragment(const std::string &s)
1958 {
1959     if (s.empty())
1960         return;
1961
1962     std::string::size_type st = s.find(':');
1963     if (st == std::string::npos)
1964     {
1965         // Boolean option.
1966         if (s[0] == '!')
1967             read_option_line(s.substr(1) + " = false");
1968         else
1969             read_option_line(s + " = true");
1970     }
1971     else
1972     {
1973         // key:val option.
1974         read_option_line(s.substr(0, st) + " = " + s.substr(st + 1));
1975     }
1976 }
1977
1978 // Not a method of the game_options class since keybindings aren't
1979 // stored in that class.
1980 static void _bindkey(std::string field)
1981 {
1982     const size_t start_bracket = field.find_first_of('[');
1983     const size_t end_bracket   = field.find_last_of(']');
1984
1985     if (start_bracket == std::string::npos
1986         || end_bracket == std::string::npos
1987         || start_bracket > end_bracket)
1988     {
1989         mprf(MSGCH_ERROR, "Bad bindkey bracketing in '%s'",
1990              field.c_str());
1991         return;
1992     }
1993
1994     const std::string key_str = field.substr(start_bracket + 1,
1995                                              end_bracket - start_bracket - 1);
1996
1997     int key;
1998
1999     // TODO: Function keys.
2000     if (key_str.length() == 0)
2001     {
2002         mprf(MSGCH_ERROR, "No key in bindkey directive '%s'",
2003              field.c_str());
2004         return;
2005     }
2006     else if (key_str.length() == 1)
2007         key = key_str[0];
2008     else if (key_str.length() == 2)
2009     {
2010         if (key_str[0] != '^')
2011         {
2012             mprf(MSGCH_ERROR, "Invalid key '%s' in bindkey directive '%s'",
2013                  key_str.c_str(), field.c_str());
2014             return;
2015         }
2016         key = CONTROL(key_str[1]);
2017     }
2018     else
2019     {
2020         mprf(MSGCH_ERROR, "Invalid key '%s' in bindkey directive '%s'",
2021              key_str.c_str(), field.c_str());
2022         return;
2023     }
2024
2025     const size_t start_name = field.find_first_not_of(' ', end_bracket + 1);
2026     if (start_name == std::string::npos)
2027     {
2028         mprf(MSGCH_ERROR, "No command name for bindkey directive '%s'",
2029              field.c_str());
2030         return;
2031     }
2032
2033     const std::string  name = field.substr(start_name);
2034     const command_type cmd  = name_to_command(name);
2035     if (cmd == CMD_NO_CMD)
2036     {
2037        mprf(MSGCH_ERROR, "No command named '%s'", name.c_str());
2038        return;
2039     }
2040
2041     bind_command_to_key(cmd, key);
2042 }
2043
2044 void game_options::read_option_line(const std::string &str, bool runscript)
2045 {
2046 #define BOOL_OPTION_NAMED(_opt_str, _opt_var)               \
2047     if (key == _opt_str) do {                               \
2048         _opt_var = _read_bool(field, _opt_var); \
2049     } while (false)
2050 #define BOOL_OPTION(_opt) BOOL_OPTION_NAMED(#_opt, _opt)
2051
2052 #define COLOUR_OPTION_NAMED(_opt_str, _opt_var)                         \
2053     if (key == _opt_str) do {                                           \
2054         const int col = str_to_colour(field);                           \
2055         if (col != -1) {                                                \
2056             _opt_var = col;                                       \
2057         } else {                                                        \
2058             /*fprintf(stderr, "Bad %s -- %s\n", key, field.c_str());*/  \
2059             report_error("Bad %s -- %s\n", key.c_str(), field.c_str()); \
2060         }                                                               \
2061     } while (false)
2062 #define COLOUR_OPTION(_opt) COLOUR_OPTION_NAMED(#_opt, _opt)
2063
2064 #define CURSES_OPTION_NAMED(_opt_str, _opt_var)     \
2065     if (key == _opt_str) do {                       \
2066         _opt_var = curses_attribute(field);   \
2067     } while (false)
2068 #define CURSES_OPTION(_opt) CURSES_OPTION_NAMED(#_opt, _opt)
2069
2070 #define INT_OPTION_NAMED(_opt_str, _opt_var, _min_val, _max_val)        \
2071     if (key == _opt_str) do {                                           \
2072         const int min_val = (_min_val);                                 \
2073         const int max_val = (_max_val);                                 \
2074         int val = _opt_var;                                             \
2075         if (!parse_int(field.c_str(), val))                             \
2076             report_error("Bad %s: \"%s\"", _opt_str, field.c_str());    \
2077         else if (val < min_val)                                         \
2078             report_error("Bad %s: %d < %d", _opt_str, val, min_val);    \
2079         else if (val > max_val)                                         \
2080             report_error("Bad %s: %d > %d", _opt_str, val, max_val);    \
2081         else                                                            \
2082             _opt_var = val;                                       \
2083     } while (false)
2084 #define INT_OPTION(_opt, _min_val, _max_val) \
2085     INT_OPTION_NAMED(#_opt, _opt, _min_val, _max_val)
2086
2087     std::string key    = "";
2088     std::string subkey = "";
2089     std::string field  = "";
2090
2091     bool plus_equal  = false;
2092     bool minus_equal = false;
2093
2094     const int first_equals = str.find('=');
2095
2096     // all lines with no equal-signs we ignore
2097     if (first_equals < 0)
2098         return;
2099
2100     field = str.substr(first_equals + 1);
2101     field = expand_vars(field);
2102
2103     std::string prequal = trimmed_string(str.substr(0, first_equals));
2104
2105     // Is this a case of key += val?
2106     if (prequal.length() && prequal[prequal.length() - 1] == '+')
2107     {
2108         plus_equal = true;
2109         prequal = prequal.substr(0, prequal.length() - 1);
2110         trim_string(prequal);
2111     }
2112     else if (prequal.length() && prequal[prequal.length() - 1] == '-')
2113     {
2114         minus_equal = true;
2115         prequal = prequal.substr(0, prequal.length() - 1);
2116         trim_string(prequal);
2117     }
2118     else if (prequal.length() && prequal[prequal.length() - 1] == ':')
2119     {
2120         prequal = prequal.substr(0, prequal.length() - 1);
2121         trim_string(prequal);
2122         trim_string(field);
2123
2124         add_alias(prequal, field);
2125         return;
2126     }
2127
2128     prequal = unalias(prequal);
2129
2130     const std::string::size_type first_dot = prequal.find('.');
2131     if (first_dot != std::string::npos)
2132     {
2133         key    = prequal.substr(0, first_dot);
2134         subkey = prequal.substr(first_dot + 1);
2135     }
2136     else
2137     {
2138         // no subkey (dots are okay in value field)
2139         key    = prequal;
2140     }
2141
2142     // Clean up our data...
2143     lowercase(trim_string(key));
2144     lowercase(trim_string(subkey));
2145
2146     // some fields want capitals... none care about external spaces
2147     trim_string(field);
2148
2149     // Keep unlowercased field around
2150     const std::string orig_field = field;
2151
2152     if (key != "name" && key != "crawl_dir" && key != "macro_dir"
2153         && key != "species" && key != "background" && key != "job"
2154         && key != "race" && key != "class" && key != "ban_pickup"
2155         && key != "autopickup_exceptions"
2156         && key != "explore_stop_pickup_ignore"
2157         && key != "stop_travel" && key != "sound"
2158         && key != "force_more_message"
2159         && key != "drop_filter" && key != "lua_file" && key != "terp_file"
2160         && key != "note_items" && key != "autoinscribe"
2161         && key != "note_monsters" && key != "note_messages"
2162         && key.find("cset") != 0 && key != "dungeon"
2163         && key != "feature" && key != "fire_items_start"
2164         && key != "mon_glyph" && key != "opt" && key != "option"
2165         && key != "menu_colour" && key != "menu_color"
2166         && key != "message_colour" && key != "message_color"
2167         && key != "levels" && key != "level" && key != "entries"
2168         && key != "include" && key != "bindkey"
2169         && key != "spell_slot"
2170         && key.find("font") == std::string::npos)
2171     {
2172         lowercase(field);
2173     }
2174
2175     if (key == "include")
2176         include(field, true, runscript);
2177     else if (key == "opt" || key == "option")
2178         split_parse(field, ",", &game_options::set_option_fragment);
2179     else if (key == "autopickup")
2180     {
2181         // clear out autopickup
2182         autopickups = 0;
2183
2184         ucs_t c;
2185         for (const char* tp = field.c_str(); int s = utf8towc(&c, tp); tp += s)
2186         {
2187             object_class_type type = item_class_by_sym(c);
2188
2189             if (type < NUM_OBJECT_CLASSES)
2190                 autopickups |= (1 << type);
2191             else
2192                 report_error("Bad object type '%*s' for autopickup.\n", s, tp);
2193         }
2194     }
2195 #if !defined(DGAMELAUNCH) || defined(DGL_REMEMBER_NAME)
2196     else if (key == "name")
2197     {
2198         // field is already cleaned up from trim_string()
2199         game.name = field;
2200     }
2201 #endif
2202     else if (key == "char_set")
2203     {
2204         if (field == "ascii")
2205             char_set = CSET_ASCII;
2206         else if (field == "ibm")
2207             char_set = CSET_IBM;
2208         else if (field == "dec")
2209             char_set = CSET_DEC;
2210         else if (field == "utf" || field == "unicode")
2211             char_set = CSET_OLD_UNICODE;
2212         else if (field == "default")
2213             char_set = CSET_DEFAULT;
2214         else
2215             fprintf(stderr, "Bad character set: %s\n", field.c_str());
2216     }
2217     else if (key == "language")
2218     {
2219         // FIXME: should talk to gettext/etc instead
2220         if (field == "en" || field == "english")
2221             lang = LANG_EN, lang_name = 0; // disable the db
2222         else if (field == "pl" || field == "polish" || field == "polski")
2223             lang = LANG_PL, lang_name = "pl";
2224         else if (field == "de" || field == "german" || field == "deutch")
2225             lang = LANG_DE, lang_name = "de";
2226         else if (field == "fr" || field == "french" || field == "français" || field == "francais")
2227             lang = LANG_FR, lang_name = "fr";
2228         else if (field == "es" || field == "spanish" || field == "español" || field == "espanol")
2229             lang = LANG_ES, lang_name = "es";
2230         else if (field == "el" || field == "greek" || field == "ελληνικά" || field == "ελληνικα")
2231             lang = LANG_EL, lang_name = "el";
2232         else if (field == "fi" || field == "finnish" || field == "suomi")
2233             lang = LANG_FI, lang_name = "fi";
2234         // Fake languages do not reset lang_name, allowing a translated
2235         // database in an actual language.  This is probably pointless for
2236         // most fake langs, though.
2237         else if (field == "dwarven" || field == "dwarf")
2238             lang = LANG_DWARVEN;
2239         else if (field == "jäger" || field == "jägerkin" || field == "jager" || field == "jagerkin"
2240                  || field == "jaeger" || field == "jaegerkin")
2241         {
2242             lang = LANG_JAGERKIN;
2243         }
2244         // Due to a conflict with actual "de", this uses slang names for the
2245         // option.  Let's try to keep to less rude ones, though.
2246         else if (field == "kraut" || field == "jerry" || field == "fritz")
2247             lang = LANG_KRAUT;
2248 /*
2249         else if (field == "cyr" || field == "cyrillic" || field == "commie" || field == "кириллица")
2250             lang = LANG_CYRILLIC;
2251 */
2252         else if (field == "wide" || field == "doublewidth" || field == "fullwidth")
2253             lang = LANG_WIDE;
2254         else
2255             report_error("No translations for language: %s\n", field.c_str());
2256     }
2257     else if (key == "default_autopickup")
2258     {
2259         if (_read_bool(field, true))
2260             autopickup_on = 1;
2261         else
2262             autopickup_on = 0;
2263     }
2264     else if (key == "default_friendly_pickup")
2265     {
2266         if (field == "none")
2267             default_friendly_pickup = FRIENDLY_PICKUP_NONE;
2268         else if (field == "friend")
2269             default_friendly_pickup = FRIENDLY_PICKUP_FRIEND;
2270         else if (field == "player")
2271             default_friendly_pickup = FRIENDLY_PICKUP_PLAYER;
2272         else if (field == "all")
2273             default_friendly_pickup = FRIENDLY_PICKUP_ALL;
2274     }
2275     else if (key == "default_manual_training")
2276     {
2277         if (_read_bool(field, true))
2278             default_manual_training = true;
2279         else
2280             default_manual_training = false;
2281     }
2282 #ifndef DGAMELAUNCH
2283     else BOOL_OPTION(restart_after_game);
2284 #endif
2285     else BOOL_OPTION(show_inventory_weights);
2286     else BOOL_OPTION(auto_switch);
2287     else BOOL_OPTION(suppress_startup_errors);
2288     else BOOL_OPTION(clean_map);
2289     else if (key == "easy_confirm")
2290     {
2291         // decide when to allow both 'Y'/'N' and 'y'/'n' on yesno() prompts
2292         if (field == "none")
2293             easy_confirm = CONFIRM_NONE_EASY;
2294         else if (field == "safe")
2295             easy_confirm = CONFIRM_SAFE_EASY;
2296         else if (field == "all")
2297             easy_confirm = CONFIRM_ALL_EASY;
2298     }
2299     else if (key == "allow_self_target")
2300     {
2301         if (field == "yes")
2302             allow_self_target = CONFIRM_NONE;
2303         else if (field == "no")
2304             allow_self_target = CONFIRM_CANCEL;
2305         else if (field == "prompt")
2306             allow_self_target = CONFIRM_PROMPT;
2307     }
2308     else BOOL_OPTION(easy_quit_item_prompts);
2309     else BOOL_OPTION_NAMED("easy_quit_item_lists", easy_quit_item_prompts);
2310     else BOOL_OPTION(easy_open);
2311     else BOOL_OPTION(easy_unequip);
2312     else BOOL_OPTION(equip_unequip);
2313     else BOOL_OPTION_NAMED("easy_armour", easy_unequip);
2314     else BOOL_OPTION_NAMED("easy_armor", easy_unequip);
2315     else if (key == "confirm_butcher")
2316     {
2317         if (field == "always")
2318             confirm_butcher = CONFIRM_ALWAYS;
2319         else if (field == "never")
2320             confirm_butcher = CONFIRM_NEVER;
2321         else if (field == "auto")
2322             confirm_butcher = CONFIRM_AUTO;
2323     }
2324     else BOOL_OPTION(chunks_autopickup);
2325     else BOOL_OPTION(prompt_for_swap);
2326     else BOOL_OPTION(list_rotten);
2327     else BOOL_OPTION(prefer_safe_chunks);
2328     else BOOL_OPTION(easy_eat_chunks);
2329     else BOOL_OPTION(easy_eat_gourmand);
2330     else BOOL_OPTION(easy_eat_contaminated);
2331     else BOOL_OPTION(auto_eat_chunks);
2332     else if (key == "auto_drop_chunks")
2333     {
2334         if (field == "never")
2335             auto_drop_chunks = ADC_NEVER;
2336         else if (field == "rotten")
2337             auto_drop_chunks = ADC_ROTTEN;
2338         else if (field == "yes" || field == "true")
2339             auto_drop_chunks = ADC_YES;
2340         else
2341             report_error("Invalid auto_drop_chunks: \"%s\"", field.c_str());
2342     }
2343     else if (key == "lua_file" && runscript)
2344     {
2345 #ifdef CLUA_BINDINGS
2346         clua.execfile(field.c_str(), false, false);
2347         if (!clua.error.empty())
2348             mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
2349 #endif
2350     }
2351     else if (key == "terp_file" && runscript)
2352     {
2353 #ifdef WIZARD
2354         terp_files.push_back(field);
2355 #endif
2356     }
2357     else if (key == "colour" || key == "color")
2358     {
2359         const int orig_col   = str_to_colour(subkey);
2360         const int result_col = str_to_colour(field);
2361
2362         if (orig_col != -1 && result_col != -1)
2363             colour[orig_col] = result_col;
2364         else
2365         {
2366             fprintf(stderr, "Bad colour -- %s=%d or %s=%d\n",
2367                      subkey.c_str(), orig_col, field.c_str(), result_col);
2368         }
2369     }
2370     else if (key == "channel")
2371     {
2372         const int chnl = str_to_channel(subkey);
2373         const msg_colour_type col  = _str_to_channel_colour(field);
2374
2375         if (chnl != -1 && col != -1)
2376             channels[chnl] = col;
2377         else if (chnl == -1)
2378             fprintf(stderr, "Bad channel -- %s\n", subkey.c_str());
2379         else if (col == MSGCOL_NONE)
2380             fprintf(stderr, "Bad colour -- %s\n", field.c_str());
2381     }
2382     else COLOUR_OPTION(background_colour);
2383     else COLOUR_OPTION(detected_item_colour);
2384     else COLOUR_OPTION(detected_monster_colour);
2385     else if (key.find(interrupt_prefix) == 0)
2386     {
2387         set_activity_interrupt(key.substr(interrupt_prefix.length()),
2388                                field,
2389                                plus_equal,
2390                                minus_equal);
2391     }
2392     else if (key.find("cset") == 0)
2393     {
2394         std::string cset = key.substr(4);
2395         if (!cset.empty() && cset[0] == '_')
2396             cset = cset.substr(1);
2397
2398         char_set_type cs = NUM_CSET;
2399         if (cset == "ibm")
2400             cs = CSET_IBM;
2401         else if (cset == "dec")
2402             cs = CSET_DEC;
2403
2404         add_cset_override(cs, field);
2405     }
2406     else if (key == "feature" || key == "dungeon")
2407         split_parse(field, ";", &game_options::add_feature_override);
2408     else if (key == "mon_glyph")
2409         split_parse(field, ",", &game_options::add_mon_glyph_override);
2410     else CURSES_OPTION(friend_brand);
2411     else CURSES_OPTION(neutral_brand);
2412     else CURSES_OPTION(stab_brand);
2413     else CURSES_OPTION(may_stab_brand);
2414     else CURSES_OPTION(feature_item_brand);
2415     else CURSES_OPTION(trap_item_brand);
2416     // This is useful for terms where dark grey does
2417     // not have standout modes (since it's black on black).
2418     // This option will use light-grey instead in these cases.
2419     else BOOL_OPTION(no_dark_brand);
2420     // no_dark_brand applies here as well.
2421     else CURSES_OPTION(heap_brand);
2422     else COLOUR_OPTION(status_caption_colour);
2423     else if (key == "arena_teams")
2424         game.arena_teams = field;
2425     // [ds] Allow changing map only if the map hasn't been set on the
2426     // command-line.
2427     else if (key == "map" && crawl_state.sprint_map.empty())
2428         game.map = field;
2429     // [ds] For dgamelaunch setups, the player should *not* be able to
2430     // set game type in their rc; the only way to set game type for
2431     // DGL builds should be the command-line options.
2432     else if (key == "type")
2433     {
2434 #if defined(DGAMELAUNCH)
2435         game.type = Options.game.type;
2436 #else
2437         game.type = _str_to_gametype(field);
2438 #endif
2439     }
2440     else if (key == "species" || key == "race")
2441         game.species = _str_to_species(field);
2442     else if (key == "background" || key == "job" || key == "class")
2443         game.job = _str_to_job(field);
2444     else if (key == "weapon")
2445     {
2446         // Choose this weapon for backgrounds that get choice.
2447         game.weapon = str_to_weapon(field);
2448     }
2449     BOOL_OPTION_NAMED("fully_random", game.fully_random);
2450     else if (key == "fire_items_start")
2451     {
2452         if (isaalpha(field[0]))
2453             fire_items_start = letter_to_index(field[0]);
2454         else
2455         {
2456             fprintf(stderr, "Bad fire item start index: %s\n",
2457                      field.c_str());
2458         }
2459     }
2460     else if (key == "assign_item_slot")
2461     {
2462         if (field == "forward")
2463             assign_item_slot = SS_FORWARD;
2464         else if (field == "backward")
2465             assign_item_slot = SS_BACKWARD;
2466     }
2467     else if (key == "fire_order")
2468         set_fire_order(field, plus_equal);
2469 #if !defined(DGAMELAUNCH) || defined(DGL_REMEMBER_NAME)
2470     else BOOL_OPTION(remember_name);
2471 #endif
2472 #ifndef DGAMELAUNCH
2473     else if (key == "save_dir")
2474         save_dir = field;
2475     else if (key == "morgue_dir")
2476         morgue_dir = field;
2477 #endif
2478     else BOOL_OPTION(show_newturn_mark);
2479     else BOOL_OPTION(show_gold_turns);
2480     else BOOL_OPTION(show_game_turns);
2481     else BOOL_OPTION(show_no_ctele);
2482     else INT_OPTION(hp_warning, 0, 100);
2483     else INT_OPTION_NAMED("mp_warning", magic_point_warning, 0, 100);
2484     else if (key == "note_monsters")
2485         append_vector(note_monsters, split_string(",", field));
2486     else if (key == "note_messages")
2487         append_vector(note_messages, split_string(",", field));
2488     else INT_OPTION(note_hp_percent, 0, 100);
2489 #ifndef DGAMELAUNCH
2490     // If DATA_DIR_PATH is set, don't set crawl_dir from .crawlrc.
2491 #ifndef DATA_DIR_PATH
2492     else if (key == "crawl_dir")
2493     {
2494         // We shouldn't bother to allocate this a second time
2495         // if the user puts two crawl_dir lines in the init file.
2496         SysEnv.crawl_dir = field;
2497     }
2498     else if (key == "macro_dir")
2499         macro_dir = field;
2500 #endif
2501 #endif
2502     else BOOL_OPTION(auto_list);
2503     else if (key == "default_target")
2504     {
2505         default_target = _read_bool(field, default_target);
2506         if (default_target)
2507             target_unshifted_dirs = false;
2508     }
2509     else BOOL_OPTION(autopickup_no_burden);
2510 #ifdef DGL_SIMPLE_MESSAGING
2511     else BOOL_OPTION(messaging);
2512 #endif
2513     else BOOL_OPTION(mouse_input);
2514     // These need to be odd, hence allow +1.
2515     else INT_OPTION(view_max_width, VIEW_MIN_WIDTH, GXM + 1);
2516     else INT_OPTION(view_max_height, VIEW_MIN_HEIGHT, GYM + 1);
2517     else INT_OPTION(mlist_min_height, 0, INT_MAX);
2518     else INT_OPTION(msg_min_height, MSG_MIN_HEIGHT, INT_MAX);
2519     else INT_OPTION(msg_max_height, MSG_MIN_HEIGHT, INT_MAX);
2520     else BOOL_OPTION(mlist_allow_alternate_layout);
2521     else BOOL_OPTION(messages_at_top);
2522 #ifndef USE_TILE
2523     else BOOL_OPTION(mlist_targetting);
2524 #endif
2525     else BOOL_OPTION(msg_condense_repeats);
2526     else BOOL_OPTION(msg_condense_short);
2527     else BOOL_OPTION(view_lock_x);
2528     else BOOL_OPTION(view_lock_y);
2529     else if (key == "view_lock")
2530     {
2531         const bool lock = _read_bool(field, true);
2532         view_lock_x = view_lock_y = lock;
2533     }
2534     else BOOL_OPTION(center_on_scroll);
2535     else BOOL_OPTION(symmetric_scroll);
2536     else if (key == "scroll_margin_x")
2537     {
2538         scroll_margin_x = atoi(field.c_str());
2539         if (scroll_margin_x < 0)
2540             scroll_margin_x = 0;
2541     }
2542     else if (key == "scroll_margin_y")
2543     {
2544         scroll_margin_y = atoi(field.c_str());
2545         if (scroll_margin_y < 0)
2546             scroll_margin_y = 0;
2547     }
2548     else if (key == "scroll_margin")
2549     {
2550         int scrollmarg = atoi(field.c_str());
2551         if (scrollmarg < 0)
2552             scrollmarg = 0;
2553         scroll_margin_x = scroll_margin_y = scrollmarg;
2554     }
2555     else if (key == "user_note_prefix")
2556     {
2557         // field is already cleaned up from trim_string()
2558         user_note_prefix = field;
2559     }
2560     else if (key == "skill_focus")
2561     {
2562         if (field == "toggle")
2563             skill_focus = SKM_FOCUS_TOGGLE;
2564         else if (_read_bool(field, true))
2565             skill_focus = SKM_FOCUS_ON;
2566         else
2567             skill_focus = SKM_FOCUS_OFF;
2568     }
2569     else BOOL_OPTION(note_all_skill_levels);
2570     else BOOL_OPTION(note_skill_max);
2571     else BOOL_OPTION(note_xom_effects);
2572     else BOOL_OPTION(note_chat_messages);
2573     else BOOL_OPTION(clear_messages);
2574     else BOOL_OPTION(show_more);
2575     else BOOL_OPTION(small_more);
2576     else if (key == "flush")
2577     {
2578         if (subkey == "failure")
2579         {
2580             flush_input[FLUSH_ON_FAILURE]
2581                 = _read_bool(field, flush_input[FLUSH_ON_FAILURE]);
2582         }
2583         else if (subkey == "command")
2584         {
2585             flush_input[FLUSH_BEFORE_COMMAND]
2586                 = _read_bool(field, flush_input[FLUSH_BEFORE_COMMAND]);
2587         }
2588         else if (subkey == "message")
2589         {
2590             flush_input[FLUSH_ON_MESSAGE]
2591                 = _read_bool(field, flush_input[FLUSH_ON_MESSAGE]);
2592         }
2593         else if (subkey == "lua")
2594         {
2595             flush_input[FLUSH_LUA]
2596                 = _read_bool(field, flush_input[FLUSH_LUA]);
2597         }
2598     }
2599     else if (key == "wiz_mode")
2600     {
2601         // wiz_mode is recognised as a legal key in all compiles -- bwr
2602 #ifdef WIZARD
2603     #ifndef DGAMELAUNCH
2604         if (field == "never")
2605             wiz_mode = WIZ_NEVER;
2606         else if (field == "no")
2607             wiz_mode = WIZ_NO;
2608         else if (field == "yes")
2609             wiz_mode = WIZ_YES;
2610         else
2611             report_error("Unknown wiz_mode option: %s\n", field.c_str());
2612     #endif
2613 #endif
2614     }
2615     else if (key == "ban_pickup")
2616     {
2617         std::vector<std::string> args = split_string(",", field);
2618         for (int i = 0, size = args.size(); i < size; ++i)
2619         {
2620             const std::string &s = args[i];
2621             if (s.empty())
2622                 continue;
2623             force_autopickup.push_back(std::make_pair(s, false));
2624         }
2625     }
2626     else if (key == "autopickup_exceptions")
2627     {
2628         std::vector<std::string> args = split_string(",", field);
2629         for (int i = 0, size = args.size(); i < size; ++i)
2630         {
2631             const std::string &s = args[i];
2632             if (s.empty())
2633                 continue;
2634
2635             if (s[0] == '>')
2636                 force_autopickup.push_back(std::make_pair(s.substr(1), false));
2637             else if (s[0] == '<')
2638                 force_autopickup.push_back(std::make_pair(s.substr(1), true));
2639             else
2640                 force_autopickup.push_back(std::make_pair(s, false));
2641         }
2642     }
2643     else if (key == "note_items")
2644         append_vector(note_items, split_string(",", field));
2645
2646 #ifndef _MSC_VER
2647     // break if-else chain on broken Microsoft compilers with stupid nesting limits
2648     else
2649 #endif
2650
2651     if (key == "autoinscribe")
2652     {
2653         if (field.empty())
2654             return report_error("Autoinscribe string is empty");
2655
2656         const size_t first = field.find_first_of(':');
2657         const size_t last  = field.find_last_of(':');
2658         if (first == std::string::npos || first != last)
2659         {
2660             return report_error("Autoinscribe string must have exactly "
2661                                 "one colon: %s\n", field.c_str());
2662         }
2663
2664         if (first == 0)
2665         {
2666             report_error("Autoinscribe pattern is empty: %s\n", field.c_str());
2667             return;
2668         }
2669
2670         if (last == field.length() - 1)
2671             return report_error("Autoinscribe result is empty: %s\n", field.c_str());
2672
2673         std::vector<std::string> thesplit = split_string(":", field);
2674
2675         if (thesplit.size() != 2)
2676         {
2677             report_error("Error parsing autoinscribe string: %s\n", field.c_str());
2678             return;
2679         }
2680
2681         autoinscriptions.push_back(
2682             std::pair<text_pattern,std::string>(thesplit[0], thesplit[1]));
2683     }
2684     else BOOL_OPTION(autoinscribe_artefacts);
2685     else BOOL_OPTION(autoinscribe_cursed);
2686 #ifndef DGAMELAUNCH
2687     else if (key == "map_file_name")
2688         map_file_name = field;
2689 #endif
2690     else if (key == "hp_colour" || key == "hp_color")
2691     {
2692         hp_colour.clear();
2693         std::vector<std::string> thesplit = split_string(",", field);
2694         for (unsigned i = 0; i < thesplit.size(); ++i)
2695         {
2696             std::vector<std::string> insplit = split_string(":", thesplit[i]);
2697             int hp_percent = 100;
2698
2699             if (insplit.empty() || insplit.size() > 2
2700                  || insplit.size() == 1 && i != 0)
2701             {
2702                 report_error("Bad hp_colour string: %s\n", field.c_str());
2703                 break;
2704             }
2705
2706             if (insplit.size() == 2)
2707                 hp_percent = atoi(insplit[0].c_str());
2708
2709             int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2710             hp_colour.push_back(std::pair<int, int>(hp_percent, scolour));
2711         }
2712     }
2713     else if (key == "mp_color" || key == "mp_colour")
2714     {
2715         mp_colour.clear();
2716         std::vector<std::string> thesplit = split_string(",", field);
2717         for (unsigned i = 0; i < thesplit.size(); ++i)
2718         {
2719             std::vector<std::string> insplit = split_string(":", thesplit[i]);
2720             int mp_percent = 100;
2721
2722             if (insplit.empty() || insplit.size() > 2
2723                  || insplit.size() == 1 && i != 0)
2724             {
2725                 report_error("Bad mp_colour string: %s\n", field.c_str());
2726                 break;
2727             }
2728
2729             if (insplit.size() == 2)
2730                 mp_percent = atoi(insplit[0].c_str());
2731
2732             int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2733             mp_colour.push_back(std::pair<int, int>(mp_percent, scolour));
2734         }
2735     }
2736     else if (key == "stat_colour" || key == "stat_color")
2737     {
2738         stat_colour.clear();
2739         std::vector<std::string> thesplit = split_string(",", field);
2740         for (unsigned i = 0; i < thesplit.size(); ++i)
2741         {
2742             std::vector<std::string> insplit = split_string(":", thesplit[i]);
2743
2744             if (insplit.empty() || insplit.size() > 2
2745                 || insplit.size() == 1 && i != 0)
2746             {
2747                 report_error("Bad stat_colour string: %s\n", field.c_str());
2748                 break;
2749             }
2750
2751             int stat_limit = 1;
2752             if (insplit.size() == 2)
2753                 stat_limit = atoi(insplit[0].c_str());
2754
2755             int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2756             stat_colour.push_back(std::pair<int, int>(stat_limit, scolour));
2757         }
2758     }
2759
2760     else if (key == "enemy_hp_colour" || key == "enemy_hp_color")
2761     {
2762         enemy_hp_colour.clear();
2763         str_to_enemy_hp_colour(field);
2764     }
2765
2766     else if (key == "note_skill_levels")
2767     {
2768         if (!plus_equal && !minus_equal)
2769             note_skill_levels.reset();
2770         std::vector<std::string> thesplit = split_string(",", field);
2771         for (unsigned i = 0; i < thesplit.size(); ++i)
2772         {
2773             int num = atoi(thesplit[i].c_str());
2774             if (num > 0 && num <= 27)
2775                 note_skill_levels.set(num, !minus_equal);
2776             else
2777             {
2778                 report_error("Bad skill level to note -- %s\n",
2779                              thesplit[i].c_str());
2780                 continue;
2781             }
2782         }
2783     }
2784     else if (key == "spell_slot")
2785     {
2786         std::vector<std::string> thesplit = split_string(":", field);
2787         if (thesplit.size() != 2)
2788         {
2789             return report_error("Error parsing spell lettering string: %s\n",
2790                                 field.c_str());
2791         }
2792         lowercase(thesplit[0]);
2793         auto_spell_letters.push_back(
2794             std::pair<text_pattern,std::string>(thesplit[0], thesplit[1]));
2795     }
2796     else BOOL_OPTION(pickup_thrown);
2797 #ifdef WIZARD
2798     else if (key == "fsim_mode")
2799         fsim_mode = field;
2800     else if (key == "fsim_scale")
2801         append_vector(fsim_scale, split_string(",", field));
2802     else if (key == "fsim_kit")
2803         append_vector(fsim_kit, split_string(",", field));
2804     else if (key == "fsim_rounds")
2805     {
2806         fsim_rounds = atol(field.c_str());
2807         if (fsim_rounds < 1000)
2808             fsim_rounds = 1000;
2809         if (fsim_rounds > 500000L)
2810             fsim_rounds = 500000L;
2811     }
2812     else if (key == "fsim_mons")
2813         fsim_mons = field;
2814 #endif // WIZARD
2815     else if (key == "sort_menus")
2816     {
2817         std::vector<std::string> frags = split_string(";", field);
2818         for (int i = 0, size = frags.size(); i < size; ++i)
2819         {
2820             if (frags[i].empty())
2821                 continue;
2822             set_menu_sort(frags[i]);
2823         }
2824     }
2825     else if (key == "travel_delay")
2826     {
2827         // Read travel delay in milliseconds.
2828         travel_delay = atoi(field.c_str());
2829         if (travel_delay < -1)
2830             travel_delay = -1;
2831         if (travel_delay > 2000)
2832             travel_delay = 2000;
2833     }
2834     else if (key == "explore_delay")
2835     {
2836         // Read explore delay in milliseconds.
2837         explore_delay = atoi(field.c_str());
2838         if (explore_delay < -1)
2839             explore_delay = -1;
2840         if (explore_delay > 2000)
2841             explore_delay = 2000;
2842     }
2843     else BOOL_OPTION(show_travel_trail);
2844     else if (key == "level_map_cursor_step")
2845     {
2846         level_map_cursor_step = atoi(field.c_str());
2847         if (level_map_cursor_step < 1)
2848             level_map_cursor_step = 1;
2849         if (level_map_cursor_step > 50)
2850             level_map_cursor_step = 50;
2851     }
2852     else BOOL_OPTION(use_fake_cursor);
2853     else BOOL_OPTION(use_fake_player_cursor);
2854     else BOOL_OPTION(show_player_species);
2855     else if (key == "force_more_message")
2856     {
2857         std::vector<std::string> fragments = split_string(",", field);
2858         for (int i = 0, count = fragments.size(); i < count; ++i)
2859         {
2860             if (fragments[i].length() == 0)
2861                 continue;
2862
2863             std::string::size_type pos = fragments[i].find(":");
2864             if (pos && pos != std::string::npos)
2865             {
2866                 std::string prefix = fragments[i].substr(0, pos);
2867                 int channel = str_to_channel(prefix);
2868                 if (channel != -1 || prefix == "any")
2869                 {
2870                     std::string s = fragments[i].substr(pos + 1);
2871                     trim_string(s);
2872                     force_more_message.push_back(
2873                         message_filter(channel, s));
2874                     continue;
2875                 }
2876             }
2877
2878             force_more_message.push_back(
2879                     message_filter(fragments[i]));
2880         }
2881     }
2882     else if (key == "drop_filter")
2883         append_vector(drop_filter, split_string(",", field));
2884     else if (key == "travel_avoid_terrain")
2885     {
2886         std::vector<std::string> seg = split_string(",", field);
2887         for (int i = 0, count = seg.size(); i < count; ++i)
2888             prevent_travel_to(seg[i]);
2889     }
2890     else if (key == "tc_reachable")
2891         tc_reachable = str_to_colour(field, tc_reachable);
2892     else if (key == "tc_excluded")
2893         tc_excluded = str_to_colour(field, tc_excluded);
2894     else if (key == "tc_exclude_circle")
2895     {
2896         tc_exclude_circle =
2897             str_to_colour(field, tc_exclude_circle);
2898     }
2899     else if (key == "tc_dangerous")
2900         tc_dangerous = str_to_colour(field, tc_dangerous);
2901     else if (key == "tc_disconnected")
2902         tc_disconnected = str_to_colour(field, tc_disconnected);
2903     else if (key == "auto_exclude")
2904         append_vector(auto_exclude, split_string(",", field));
2905     else BOOL_OPTION(easy_exit_menu);
2906     else BOOL_OPTION(dos_use_background_intensity);
2907     else if (key == "item_stack_summary_minimum")
2908         item_stack_summary_minimum = atoi(field.c_str());
2909     else if (key == "explore_stop")
2910     {
2911         if (!plus_equal && !minus_equal)
2912             explore_stop = ES_NONE;
2913
2914         const int new_conditions = read_explore_stop_conditions(field);
2915         if (minus_equal)
2916             explore_stop &= ~new_conditions;
2917         else
2918             explore_stop |= new_conditions;
2919     }
2920     else if (key == "explore_stop_prompt")
2921     {
2922         if (!plus_equal && !minus_equal)
2923             explore_stop_prompt = ES_NONE;
2924         const int new_conditions = read_explore_stop_conditions(field);
2925         if (minus_equal)
2926             explore_stop_prompt &= ~new_conditions;
2927         else
2928             explore_stop_prompt |= new_conditions;
2929     }
2930     else if (key == "explore_stop_pickup_ignore")
2931         append_vector(explore_stop_pickup_ignore, split_string(",", field));
2932     else if (key == "explore_item_greed")
2933     {
2934         explore_item_greed = atoi(field.c_str());
2935         if (explore_item_greed > 1000)
2936             explore_item_greed = 1000;
2937         else if (explore_item_greed < -1000)
2938             explore_item_greed = -1000;
2939     }
2940     else BOOL_OPTION(explore_greedy);
2941     else if (key == "explore_wall_bias")
2942     {
2943         explore_wall_bias = atoi(field.c_str());
2944         if (explore_wall_bias > 1000)
2945             explore_wall_bias = 1000;
2946         else if (explore_wall_bias < 0)
2947             explore_wall_bias = 0;
2948     }
2949     else BOOL_OPTION(explore_improved);
2950     else BOOL_OPTION(travel_key_stop);
2951     else if (key == "sound")
2952     {
2953         std::vector<std::string> seg = split_string(",", field);
2954         for (int i = 0, count = seg.size(); i < count; ++i)
2955         {
2956             const std::string &sub = seg[i];
2957             std::string::size_type cpos = sub.find(":", 0);
2958             if (cpos != std::string::npos)
2959             {
2960                 sound_mapping mapping;
2961                 mapping.pattern = sub.substr(0, cpos);
2962                 mapping.soundfile = sub.substr(cpos + 1);
2963                 sound_mappings.push_back(mapping);
2964             }
2965         }
2966     }
2967 #ifndef TARGET_COMPILER_VC
2968     // MSVC has a limit on how many if/else if can be chained together.
2969     else
2970 #endif
2971     if (key == "menu_colour" || key == "menu_color")
2972     {
2973         std::vector<std::string> seg = split_string(",", field);
2974         for (int i = 0, count = seg.size(); i < count; ++i)
2975         {
2976             // Format is "tag:colour:pattern" or "colour:pattern" (default tag).
2977             // FIXME: arrange so that you can use ':' inside a pattern
2978             std::vector<std::string> subseg = split_string(":", seg[i], false);
2979             std::string tagname, patname, colname;
2980             if (subseg.size() < 2)
2981                 continue;
2982             if (subseg.size() >= 3)
2983             {
2984                 tagname = subseg[0];
2985                 colname = subseg[1];
2986                 patname = subseg[2];
2987             }
2988             else
2989             {
2990                 colname = subseg[0];
2991                 patname = subseg[1];
2992             }
2993
2994             colour_mapping mapping;
2995             mapping.tag     = tagname;
2996             mapping.pattern = patname;
2997             mapping.colour  = str_to_colour(colname);
2998
2999             if (mapping.colour != -1)
3000                 menu_colour_mappings.push_back(mapping);
3001         }
3002     }
3003     else BOOL_OPTION(menu_colour_prefix_class);
3004     else BOOL_OPTION_NAMED("menu_color_prefix_class", menu_colour_prefix_class);
3005     else BOOL_OPTION(menu_colour_shops);
3006     else BOOL_OPTION_NAMED("menu_color_shops", menu_colour_shops);
3007     else if (key == "message_colour" || key == "message_color")
3008         add_message_colour_mappings(field);
3009     else if (key == "dump_order")
3010     {
3011         if (!plus_equal)
3012             dump_order.clear();
3013
3014         new_dump_fields(field, !minus_equal);
3015     }
3016     else if (key == "dump_kill_places")
3017     {
3018         dump_kill_places = (field == "none" ? KDO_NO_PLACES :
3019                             field == "all"  ? KDO_ALL_PLACES
3020                                             : KDO_ONE_PLACE);
3021     }
3022     else if (key == "kill_map")
3023     {
3024         std::vector<std::string> seg = split_string(",", field);
3025         for (int i = 0, count = seg.size(); i < count; ++i)
3026         {
3027             const std::string &s = seg[i];
3028             std::string::size_type cpos = s.find(":", 0);
3029             if (cpos != std::string::npos)
3030             {
3031                 std::string from = s.substr(0, cpos);
3032                 std::string to   = s.substr(cpos + 1);
3033                 do_kill_map(from, to);
3034             }
3035         }
3036     }
3037     else BOOL_OPTION(rest_wait_both);
3038     else if (key == "dump_message_count")
3039     {
3040         // Capping is implicit
3041         dump_message_count = atoi(field.c_str());
3042     }
3043     else if (key == "dump_item_origins")
3044     {
3045         dump_item_origins = IODS_PRICE;
3046         std::vector<std::string> choices = split_string(",", field);
3047         for (int i = 0, count = choices.size(); i < count; ++i)
3048         {
3049             const std::string &ch = choices[i];
3050             if (ch == "artefacts" || ch == "artifacts"
3051                 || ch == "artefact" || ch == "artifact")
3052             {
3053                 dump_item_origins |= IODS_ARTEFACTS;
3054             }
3055             else if (ch == "ego_arm" || ch == "ego armour"
3056                      || ch == "ego_armour" || ch == "ego armor"
3057                      || ch == "ego_armor")
3058             {
3059                 dump_item_origins |= IODS_EGO_ARMOUR;
3060             }
3061             else if (ch == "ego_weap" || ch == "ego weapon"
3062                      || ch == "ego_weapon" || ch == "ego weapons"
3063                      || ch == "ego_weapons")
3064             {
3065                 dump_item_origins |= IODS_EGO_WEAPON;
3066             }
3067             else if (ch == "jewellery" || ch == "jewelry")
3068                 dump_item_origins |= IODS_JEWELLERY;
3069             else if (ch == "runes")
3070                 dump_item_origins |= IODS_RUNES;
3071             else if (ch == "rods")
3072                 dump_item_origins |= IODS_RODS;
3073             else if (ch == "staves")
3074                 dump_item_origins |= IODS_STAVES;
3075             else if (ch == "books")
3076                 dump_item_origins |= IODS_BOOKS;
3077             else if (ch == "all" || ch == "everything")
3078                 dump_item_origins = IODS_EVERYTHING;
3079         }
3080     }
3081     else if (key == "dump_item_origin_price")
3082     {
3083         dump_item_origin_price = atoi(field.c_str());
3084         if (dump_item_origin_price < -1)
3085             dump_item_origin_price = -1;
3086     }
3087     else BOOL_OPTION(dump_book_spells);
3088     else BOOL_OPTION(level_map_title);
3089     else if (key == "target_unshifted_dirs")
3090     {
3091         target_unshifted_dirs = _read_bool(field, target_unshifted_dirs);
3092         if (target_unshifted_dirs)
3093             default_target = false;
3094     }
3095     else if (key == "darken_beyond_range")
3096         darken_beyond_range = _read_bool(field, darken_beyond_range);
3097     else if (key == "drop_mode")
3098     {
3099         if (field.find("multi") != std::string::npos)
3100             drop_mode = DM_MULTI;
3101         else
3102             drop_mode = DM_SINGLE;
3103     }
3104     else if (key == "pickup_mode")
3105     {
3106         if (field.find("multi") != std::string::npos)
3107             pickup_mode = 0;
3108         else if (field.find("single") != std::string::npos)
3109             pickup_mode = -1;
3110         else
3111             pickup_mode = _read_bool_or_number(field, pickup_mode, "auto:");
3112     }
3113     else if (key == "additional_macro_file")
3114     {
3115         const std::string resolved = resolve_include(orig_field, "macro ");
3116         if (!resolved.empty())
3117             additional_macro_files.push_back(resolved);
3118     }
3119 #ifdef USE_TILE
3120     else if (key == "tile_show_items")
3121         tile_show_items = field;
3122     else BOOL_OPTION(tile_skip_title);
3123     else BOOL_OPTION(tile_menu_icons);
3124 #endif
3125 #ifdef USE_TILE_LOCAL
3126     else if (key == "tile_player_col")
3127         tile_player_col = str_to_tile_colour(field);
3128     else if (key == "tile_monster_col")
3129         tile_monster_col = str_to_tile_colour(field);
3130     else if (key == "tile_neutral_col")
3131         tile_neutral_col = str_to_tile_colour(field);
3132     else if (key == "tile_friendly_col")
3133         tile_friendly_col = str_to_tile_colour(field);
3134     else if (key == "tile_plant_col")
3135         tile_plant_col = str_to_tile_colour(field);
3136     else if (key == "tile_item_col")
3137         tile_item_col = str_to_tile_colour(field);
3138     else if (key == "tile_unseen_col")
3139         tile_unseen_col = str_to_tile_colour(field);
3140     else if (key == "tile_floor_col")
3141         tile_floor_col = str_to_tile_colour(field);
3142     else if (key == "tile_wall_col")
3143         tile_wall_col = str_to_tile_colour(field);
3144     else if (key == "tile_mapped_wall_col")
3145         tile_mapped_wall_col = str_to_tile_colour(field);
3146     else if (key == "tile_door_col")
3147         tile_door_col = str_to_tile_colour(field);
3148     else if (key == "tile_downstairs_col")
3149         tile_downstairs_col = str_to_tile_colour(field);
3150     else if (key == "tile_upstairs_col")
3151         tile_upstairs_col = str_to_tile_colour(field);
3152     else if (key == "tile_feature_col")
3153         tile_feature_col = str_to_tile_colour(field);
3154     else if (key == "tile_trap_col")
3155         tile_trap_col = str_to_tile_colour(field);
3156     else if (key == "tile_water_col")
3157         tile_water_col = str_to_tile_colour(field);
3158     else if (key == "tile_lava_col")
3159         tile_lava_col = str_to_tile_colour(field);
3160     else if (key == "tile_excluded_col")
3161         tile_excluded_col = str_to_tile_colour(field);
3162     else if (key == "tile_excl_centre_col" || key == "tile_excl_center_col")
3163         tile_excl_centre_col = str_to_tile_colour(field);
3164     else if (key == "tile_window_col")
3165         tile_window_col = str_to_tile_colour(field);
3166     else if (key == "tile_font_crt_file")
3167         tile_font_crt_file = field;
3168     else INT_OPTION(tile_font_crt_size, 1, INT_MAX);
3169     else if (key == "tile_font_msg_file")
3170         tile_font_msg_file = field;
3171     else INT_OPTION(tile_font_msg_size, 1, INT_MAX);
3172     else if (key == "tile_font_stat_file")
3173         tile_font_stat_file = field;
3174     else INT_OPTION(tile_font_stat_size, 1, INT_MAX);
3175     else if (key == "tile_font_tip_file")
3176         tile_font_tip_file = field;
3177     else INT_OPTION(tile_font_tip_size, 1, INT_MAX);
3178     else if (key == "tile_font_lbl_file")
3179         tile_font_lbl_file = field;
3180     else INT_OPTION(tile_font_lbl_size, 1, INT_MAX);
3181 #ifdef USE_FT
3182     else BOOL_OPTION(tile_font_ft_light);
3183 #endif
3184     else INT_OPTION(tile_key_repeat_delay, 0, INT_MAX);
3185     else if (key == "tile_full_screen")
3186         tile_full_screen = (screen_mode)_read_bool(field, tile_full_screen);
3187     else INT_OPTION(tile_window_width, INT_MIN, INT_MAX);
3188     else INT_OPTION(tile_window_height, INT_MIN, INT_MAX);
3189     else INT_OPTION(tile_map_pixels, 1, INT_MAX);
3190 #endif // USE_TILE_LOCAL
3191 #ifdef USE_TILE
3192     else BOOL_OPTION(tile_force_overlay);
3193     else INT_OPTION(tile_tooltip_ms, 0, INT_MAX);
3194     else INT_OPTION(tile_update_rate, 50, INT_MAX);
3195     else INT_OPTION(tile_runrest_rate, 0, INT_MAX);
3196     else BOOL_OPTION(tile_show_minihealthbar);
3197     else BOOL_OPTION(tile_show_minimagicbar);
3198     else BOOL_OPTION(tile_show_demon_tier);
3199     else BOOL_OPTION(tile_force_regenerate_levels);
3200     else if (key == "tile_layout_priority")
3201         tile_layout_priority = split_string(",", field.c_str());
3202     else if (key == "tile_tag_pref")
3203         tile_tag_pref = _str_to_tag_pref(field.c_str());
3204 #endif
3205
3206     else if (key == "bindkey")
3207         _bindkey(field);
3208     else if (key == "constant")
3209     {
3210         if (variables.find(field) == variables.end())
3211             report_error("No variable named '%s' to make constant", field.c_str());
3212         else if (constants.find(field) != constants.end())
3213             report_error("'%s' is already a constant", field.c_str());
3214         else
3215             constants.insert(field);
3216     }
3217     else INT_OPTION(arena_delay, 0, INT_MAX);
3218     else BOOL_OPTION(arena_dump_msgs);
3219     else BOOL_OPTION(arena_dump_msgs_all);
3220     else BOOL_OPTION(arena_list_eq);
3221
3222     // Catch-all else, copies option into map
3223     else if (runscript)
3224     {
3225 #ifdef CLUA_BINDINGS
3226         if (!clua.callbooleanfn(false, "c_process_lua_option", "ss",
3227                         key.c_str(), orig_field.c_str()))
3228 #endif
3229         {
3230 #ifdef CLUA_BINDINGS
3231             if (!clua.error.empty())
3232                 mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
3233 #endif
3234             named_options[key] = orig_field;
3235         }
3236     }
3237 }
3238
3239 // Checks an include file name for safety and resolves it to a readable path.
3240 // If safety check fails, throws a string with the reason for failure.
3241 // If file cannot be resolved, returns the empty string (this does not throw!)
3242 // If file can be resolved, returns the resolved path.
3243 std::string game_options::resolve_include(
3244     std::string parent_file,
3245     std::string included_file,
3246     const std::vector<std::string> *rcdirs)
3247
3248     throw (std::string)
3249 {
3250     // Before we start, make sure we convert forward slashes to the platform's
3251     // favoured file separator.
3252     parent_file   = canonicalise_file_separator(parent_file);
3253     included_file = canonicalise_file_separator(included_file);
3254
3255     // How we resolve include paths:
3256     // 1. If it's an absolute path, use it directly.
3257     // 2. Try the name relative to the parent filename, if supplied.
3258     // 3. Try the name relative to any of the provided rcdirs.
3259     // 4. Try locating the name as a regular data file (also checks for the
3260     //    file name relative to the current directory).
3261     // 5. Fail, and return empty string.
3262
3263     assert_read_safe_path(included_file);
3264
3265     // There's only so much you can do with an absolute path.
3266     // Note: absolute paths can only get here if we're not on a
3267     // multiuser system. On multiuser systems assert_read_safe_path()
3268     // will throw an exception if it sees absolute paths.
3269     if (is_absolute_path(included_file))
3270         return file_exists(included_file)? included_file : "";
3271
3272     if (!parent_file.empty())
3273     {
3274         const std::string candidate =
3275             get_path_relative_to(parent_file, included_file);
3276         if (file_exists(candidate))
3277             return candidate;
3278     }
3279
3280     if (rcdirs)
3281     {
3282         const std::vector<std::string> &dirs(*rcdirs);
3283         for (int i = 0, size = dirs.size(); i < size; ++i)
3284         {
3285             const std::string candidate(catpath(dirs[i], included_file));
3286             if (file_exists(candidate))
3287                 return candidate;
3288         }
3289     }
3290
3291     return datafile_path(included_file, false, true);
3292 }
3293
3294 std::string game_options::resolve_include(const std::string &file,
3295                                            const char *type)
3296 {
3297     try
3298     {
3299         const std::string resolved =
3300             resolve_include(filename, file, &SysEnv.rcdirs);
3301
3302         if (resolved.empty())
3303             report_error("Cannot find %sfile \"%s\".", type, file.c_str());
3304         return resolved;
3305     }
3306     catch (const std::string &err)
3307     {
3308         report_error("Cannot include %sfile: %s", type, err.c_str());
3309         return "";
3310     }
3311 }
3312
3313 bool game_options::was_included(const std::string &file) const
3314 {
3315     return (included.find(file) != included.end());
3316 }
3317
3318 void game_options::include(const std::string &rawfilename,
3319                            bool resolve,
3320                            bool runscript)
3321 {
3322     const std::string include_file =
3323         resolve ? resolve_include(rawfilename) : rawfilename;
3324
3325     if (was_included(include_file))
3326         return;
3327
3328     included.insert(include_file);
3329
3330     // Change filename to the included filename while we're reading it.
3331     unwind_var<std::string> optfile(filename, include_file);
3332     unwind_var<std::string> basefile(basefilename, rawfilename);
3333
3334     // Save current line number
3335     unwind_var<int> currlinenum(line_num, 0);
3336
3337     // Also unwind any aliases defined in included files.
3338     unwind_var<string_map> unwalias(aliases);
3339
3340     FileLineInput fl(include_file.c_str());
3341     if (!fl.error())
3342         read_options(fl, runscript, false);
3343 }
3344
3345 void game_options::report_error(const char* format, ...)
3346 {
3347     va_list args;
3348     va_start(args, format);
3349     std::string error = vmake_stringf(format, args);
3350     va_end(args);
3351
3352     // If called before game starts, log a startup error,
3353     // otherwise spam the warning channel.
3354     if (crawl_state.need_save)
3355     {
3356         mprf(MSGCH_ERROR, "Warning: %s (%s:%d)", error.c_str(),
3357              basefilename.c_str(), line_num);
3358     }
3359     else
3360     {
3361         crawl_state.add_startup_error(make_stringf("%s (%s:%d)",
3362                                                    error.c_str(),
3363                                                    basefilename.c_str(),
3364                                                    line_num));
3365     }
3366 }
3367
3368 static std::string check_string(const char *s)
3369 {
3370     return s? s : "";
3371 }
3372
3373 void get_system_environment(void)
3374 {
3375     // The player's name
3376     SysEnv.crawl_name = check_string(getenv("CRAWL_NAME"));
3377
3378     // The directory which contians init.txt, macro.txt, morgue.txt
3379     // This should end with the appropriate path delimiter.
3380     SysEnv.crawl_dir = check_string(getenv("CRAWL_DIR"));
3381
3382 #ifdef SAVE_DIR_PATH
3383     if (SysEnv.crawl_dir == "")
3384         SysEnv.crawl_dir = SAVE_DIR_PATH;
3385 #endif
3386
3387 #ifdef DGL_SIMPLE_MESSAGING
3388     // Enable DGL_SIMPLE_MESSAGING only if SIMPLEMAIL and MAIL are set.
3389     const char *simplemail = getenv("SIMPLEMAIL");
3390     if (simplemail && strcmp(simplemail, "0"))
3391     {
3392         const char *mail = getenv("MAIL");
3393         SysEnv.messagefile = mail? mail : "";
3394     }
3395 #endif
3396
3397     // The full path to the init file -- this overrides CRAWL_DIR.
3398     SysEnv.crawl_rc = check_string(getenv("CRAWL_RC"));
3399
3400 #ifdef UNIX
3401     // The user's home directory (used to look for ~/.crawlrc file)
3402     SysEnv.home = check_string(getenv("HOME"));
3403 #endif
3404 }
3405
3406 static void set_crawl_base_dir(const char *arg)
3407 {
3408     if (!arg)
3409         return;
3410
3411     SysEnv.crawl_base = get_parent_directory(arg);
3412 }
3413
3414 // parse args, filling in Options and game environment as we go.
3415 // returns true if no unknown or malformed arguments were found.
3416
3417 // Keep this in sync with the option names.
3418 enum commandline_option_type
3419 {
3420     CLO_SCORES,
3421     CLO_NAME,
3422     CLO_RACE,
3423     CLO_CLASS,
3424     CLO_PLAIN,
3425     CLO_DIR,
3426     CLO_RC,
3427     CLO_RCDIR,
3428     CLO_TSCORES,
3429     CLO_VSCORES,
3430     CLO_SCOREFILE,
3431     CLO_MORGUE,
3432     CLO_MACRO,
3433     CLO_MAPSTAT,
3434     CLO_ARENA,
3435     CLO_DUMP_MAPS,
3436     CLO_TEST,
3437     CLO_SCRIPT,
3438     CLO_BUILDDB,
3439     CLO_HELP,
3440     CLO_VERSION,
3441     CLO_SEED,
3442     CLO_SAVE_VERSION,
3443     CLO_SPRINT,
3444     CLO_EXTRA_OPT_FIRST,
3445     CLO_EXTRA_OPT_LAST,
3446     CLO_SPRINT_MAP,
3447     CLO_EDIT_SAVE,
3448     CLO_PRINT_CHARSET,
3449     CLO_ZOTDEF,
3450     CLO_TUTORIAL,
3451     CLO_WIZARD,
3452     CLO_NO_SAVE,
3453 #ifdef USE_TILE_WEB
3454     CLO_WEBTILES_SOCKET,
3455     CLO_AWAIT_CONNECTION,
3456 #endif
3457
3458     CLO_NOPS
3459 };
3460
3461 static const char *cmd_ops[] = {
3462     "scores", "name", "species", "background", "plain", "dir", "rc",
3463     "rcdir", "tscores", "vscores", "scorefile", "morgue", "macro",
3464     "mapstat", "arena", "dump-maps", "test", "script", "builddb",
3465     "help", "version", "seed", "save-version", "sprint",
3466     "extra-opt-first", "extra-opt-last", "sprint-map", "edit-save",
3467     "print-charset", "zotdef", "tutorial", "wizard", "no-save",
3468 #ifdef USE_TILE_WEB
3469     "webtiles-socket", "await-connection",
3470 #endif
3471 };
3472
3473 static const int num_cmd_ops = CLO_NOPS;
3474 static bool arg_seen[num_cmd_ops];
3475
3476 static std::string _find_executable_path()
3477 {
3478     // A lot of OSes give ways to find the location of the running app's
3479     // binary executable. This is useful, because argv[0] can be relative
3480     // when we really need an absolute path in order to locate the game's
3481     // resources.
3482 #if defined ( TARGET_OS_WINDOWS )
3483     wchar_t tempPath[MAX_PATH];
3484     if (GetModuleFileNameW(NULL, tempPath, MAX_PATH))
3485         return utf16_to_8(tempPath);
3486     else
3487         return "";
3488 #elif defined ( TARGET_OS_LINUX )
3489     char tempPath[2048];
3490     const ssize_t rsize =
3491         readlink("/proc/self/exe", tempPath, sizeof(tempPath) - 1);
3492     if (rsize > 0)
3493     {
3494         tempPath[rsize] = 0;
3495         return mb_to_utf8(tempPath);
3496     }
3497     return "";
3498 #elif defined ( TARGET_OS_MACOSX )
3499     return mb_to_utf8(NXArgv[0]);
3500 #else
3501     // We don't know how to find the executable's path on this OS.
3502     return "";
3503 #endif
3504 }
3505
3506 static void _print_version()
3507 {
3508     printf("Crawl version %s%s", Version::Long().c_str(), "\n");
3509     printf("Save file version %d.%d%s", TAG_MAJOR_VERSION, TAG_MINOR_VERSION, "\n");
3510     printf("%s", compilation_info().c_str());
3511 }
3512
3513 static void _print_save_version(char *name)
3514 {
3515     try
3516     {
3517         std::string filename = name;
3518         // Check for the exact filename first, then go by char name.
3519         if (!file_exists(filename))
3520             filename = get_savedir_filename(filename);
3521         package save(filename.c_str(), false);
3522         reader chrf(&save, "chr");
3523
3524         int major, minor;
3525         if (!get_save_version(chrf, major, minor))
3526             fail("Save file is invalid.");
3