aed1e7d1d2d9dfb274a3a81cf69e9fda57b70090
[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.clear();
1067     auto_spell_letters.clear();
1068     force_more_message.clear();
1069     sound_mappings.clear();
1070     menu_colour_mappings.clear();
1071     menu_colour_prefix_class = true;
1072     menu_colour_shops = true;
1073     message_colour_mappings.clear();
1074     drop_filter.clear();
1075     map_file_name.clear();
1076     named_options.clear();
1077
1078     clear_cset_overrides();
1079
1080     clear_feature_overrides();
1081     mon_glyph_overrides.clear();
1082
1083     rest_wait_both = false;
1084
1085     // Map each category to itself. The user can override in init.txt
1086     kill_map[KC_YOU] = KC_YOU;
1087     kill_map[KC_FRIENDLY] = KC_FRIENDLY;
1088     kill_map[KC_OTHER] = KC_OTHER;
1089
1090     // Forget any files we remembered as included.
1091     included.clear();
1092
1093     // Forget variables and such.
1094     aliases.clear();
1095     variables.clear();
1096     constants.clear();
1097 }
1098
1099 void game_options::clear_cset_overrides()
1100 {
1101     memset(cset_override, 0, sizeof cset_override);
1102 }
1103
1104 void game_options::clear_feature_overrides()
1105 {
1106     feature_overrides.clear();
1107 }
1108
1109 ucs_t get_glyph_override(int c)
1110 {
1111     if (c < 0)
1112     {
1113         c = -c;
1114         if (Options.char_set == CSET_IBM)
1115             c = (c & ~0xff) ? 0 : charset_cp437[c & 0xff];
1116         else if (Options.char_set == CSET_DEC)
1117             c = (c & 0x80) ? charset_vt100[c & 0x7f] : c;
1118     }
1119     if (wcwidth(c) != 1)
1120     {
1121         mprf(MSGCH_ERROR, "Invalid glyph override: %X", c);
1122         c = 0;
1123     }
1124     return c;
1125 }
1126
1127 static int read_symbol(std::string s)
1128 {
1129     if (s.empty())
1130         return 0;
1131
1132     if (s.length() > 1 && s[0] == '\\')
1133         s = s.substr(1);
1134
1135     {
1136         ucs_t c;
1137         const char *nc = s.c_str();
1138         nc += utf8towc(&c, nc);
1139         // no control, combining or CJK characters, please
1140         if (!*nc && wcwidth(c) == 1)
1141             return c;
1142     }
1143
1144     int base = 10;
1145     if (s.length() > 1 && s[0] == 'x')
1146     {
1147         s = s.substr(1);
1148         base = 16;
1149     }
1150
1151     char *tail;
1152     return -strtoul(s.c_str(), &tail, base);
1153 }
1154
1155 void game_options::set_fire_order(const std::string &s, bool add)
1156 {
1157     if (!add)
1158         fire_order.clear();
1159     std::vector<std::string> slots = split_string(",", s);
1160     for (int i = 0, size = slots.size(); i < size; ++i)
1161         add_fire_order_slot(slots[i]);
1162 }
1163
1164 void game_options::add_fire_order_slot(const std::string &s)
1165 {
1166     unsigned flags = 0;
1167     std::vector<std::string> alts = split_string("/", s);
1168     for (int i = 0, size = alts.size(); i < size; ++i)
1169         flags |= _str_to_fire_types(alts[i]);
1170
1171     if (flags)
1172         fire_order.push_back(flags);
1173 }
1174
1175 void game_options::add_mon_glyph_overrides(const std::string &mons,
1176                                            mon_display &mdisp)
1177 {
1178     // If one character, this is a monster letter.
1179     int letter = -1;
1180     if (mons.length() == 1)
1181         letter = mons[0] == '_' ? ' ' : mons[0];
1182
1183     bool found = false;
1184     for (monster_type i = MONS_0; i < NUM_MONSTERS; ++i)
1185     {
1186         const monsterentry *me = get_monster_data(i);
1187         if (!me || me->mc == MONS_PROGRAM_BUG)
1188             continue;
1189
1190         if (me->basechar == letter || me->name == mons)
1191         {
1192             found = true;
1193             mon_glyph_overrides[i] = mdisp;
1194         }
1195     }
1196     if (!found)
1197         report_error("Unknown monster: \"%s\"", mons.c_str());
1198 }
1199
1200 mon_display game_options::parse_mon_glyph(const std::string &s) const
1201 {
1202     mon_display md;
1203     std::vector<std::string> phrases = split_string(" ", s);
1204     for (int i = 0, size = phrases.size(); i < size; ++i)
1205     {
1206         const std::string &p = phrases[i];
1207         const int col = str_to_colour(p, -1, false);
1208         if (col != -1 && colour)
1209             md.colour = col;
1210         else
1211             md.glyph = p == "_"? ' ' : read_symbol(p);
1212     }
1213     return md;
1214 }
1215
1216 void game_options::add_mon_glyph_override(const std::string &text)
1217 {
1218     std::vector<std::string> override = split_string(":", text);
1219     if (override.size() != 2u)
1220         return;
1221
1222     mon_display mdisp = parse_mon_glyph(override[1]);
1223     if (mdisp.glyph || mdisp.colour)
1224         add_mon_glyph_overrides(override[0], mdisp);
1225 }
1226
1227 void game_options::add_feature_override(const std::string &text)
1228 {
1229     std::string::size_type epos = text.rfind("}");
1230     if (epos == std::string::npos)
1231         return;
1232
1233     std::string::size_type spos = text.rfind("{", epos);
1234     if (spos == std::string::npos)
1235         return;
1236
1237     std::string fname = text.substr(0, spos);
1238     std::string props = text.substr(spos + 1, epos - spos - 1);
1239     std::vector<std::string> iprops = split_string(",", props, true, true);
1240
1241     if (iprops.size() < 1 || iprops.size() > 7)
1242         return;
1243
1244     if (iprops.size() < 7)
1245         iprops.resize(7);
1246
1247     trim_string(fname);
1248     std::vector<dungeon_feature_type> feats =
1249         features_by_desc(text_pattern(fname));
1250     if (feats.empty())
1251         return;
1252
1253     for (int i = 0, size = feats.size(); i < size; ++i)
1254     {
1255         if (feats[i] >= NUM_FEATURES)
1256             continue; // TODO: handle other object types.
1257         feature_override fov;
1258         fov.object.cls = SH_FEATURE;
1259         fov.object.feat = feats[i];
1260
1261         fov.override.symbol         = read_symbol(iprops[0]);
1262         fov.override.magic_symbol   = read_symbol(iprops[1]);
1263         fov.override.colour         = str_to_colour(iprops[2], BLACK);
1264         fov.override.map_colour     = str_to_colour(iprops[3], BLACK);
1265         fov.override.seen_colour    = str_to_colour(iprops[4], BLACK);
1266         fov.override.em_colour      = str_to_colour(iprops[5], BLACK);
1267         fov.override.seen_em_colour = str_to_colour(iprops[6], BLACK);
1268
1269         feature_overrides.push_back(fov);
1270     }
1271 }
1272
1273 void game_options::add_cset_override(
1274         char_set_type set, const std::string &overrides)
1275 {
1276     std::vector<std::string> overs = split_string(",", overrides);
1277     for (int i = 0, size = overs.size(); i < size; ++i)
1278     {
1279         std::vector<std::string> mapping = split_string(":", overs[i]);
1280         if (mapping.size() != 2)
1281             continue;
1282
1283         dungeon_char_type dc = dchar_by_name(mapping[0]);
1284         if (dc == NUM_DCHAR_TYPES)
1285             continue;
1286
1287         add_cset_override(set, dc, read_symbol(mapping[1]));
1288     }
1289 }
1290
1291 void game_options::add_cset_override(char_set_type set, dungeon_char_type dc,
1292                                      int symbol)
1293 {
1294     cset_override[dc] = get_glyph_override(symbol);
1295 }
1296
1297 static std::string _find_crawlrc()
1298 {
1299     const char* locations_data[][2] = {
1300         { SysEnv.crawl_dir.c_str(), "init.txt" },
1301 #ifdef UNIX
1302         { SysEnv.home.c_str(), ".crawl/init.txt" },
1303         { SysEnv.home.c_str(), ".crawlrc" },
1304         { SysEnv.home.c_str(), "init.txt" },
1305 #endif
1306 #ifndef DATA_DIR_PATH
1307         { "", "init.txt" },
1308         { "..", "init.txt" },
1309         { "../settings", "init.txt" },
1310 #endif
1311         { NULL, NULL }                // placeholder to mark end
1312     };
1313
1314     // We'll look for these files in any supplied -rcdirs.
1315     static const char *rc_dir_filenames[] = {
1316         ".crawlrc", "init.txt"
1317     };
1318
1319     // -rc option always wins.
1320     if (!SysEnv.crawl_rc.empty())
1321         return SysEnv.crawl_rc;
1322
1323     // If we have any rcdirs, look in them for files from the
1324     // rc_dir_names list.
1325     for (int i = 0, size = SysEnv.rcdirs.size(); i < size; ++i)
1326     {
1327         for (unsigned n = 0; n < ARRAYSZ(rc_dir_filenames); ++n)
1328         {
1329             const std::string rc(
1330                 catpath(SysEnv.rcdirs[i], rc_dir_filenames[n]));
1331             if (file_exists(rc))
1332                 return rc;
1333         }
1334     }
1335
1336     // Check all possibilities for init.txt
1337     for (int i = 0; locations_data[i][1] != NULL; ++i)
1338     {
1339         // Don't look at unset options
1340         if (locations_data[i][0] != NULL)
1341         {
1342             const std::string rc =
1343                 catpath(locations_data[i][0], locations_data[i][1]);
1344             if (file_exists(rc))
1345                 return rc;
1346         }
1347     }
1348
1349     // Last attempt: pick up init.txt from datafile_path, which will
1350     // also search the settings/ directory.
1351     return datafile_path("init.txt", false, false);
1352 }
1353
1354 static const char* lua_builtins[] =
1355 {
1356     "clua/stash.lua",
1357     "clua/wield.lua",
1358     "clua/runrest.lua",
1359     "clua/gearset.lua",
1360     "clua/trapwalk.lua",
1361     "clua/autofight.lua",
1362     "clua/kills.lua",
1363 };
1364
1365 static const char* config_defaults[] =
1366 {
1367     "defaults/autopickup_exceptions.txt",
1368     "defaults/runrest_messages.txt",
1369     "defaults/standard_colours.txt",
1370     "defaults/food_colouring.txt",
1371     "defaults/menu_colours.txt",
1372     "defaults/messages.txt",
1373 };
1374
1375 // Returns an error message if the init.txt was not found.
1376 std::string read_init_file(bool runscript)
1377 {
1378     Options.reset_options();
1379
1380 #ifdef CLUA_BINDINGS
1381     if (runscript)
1382     {
1383         for (unsigned int i = 0; i < ARRAYSZ(lua_builtins); ++i)
1384         {
1385             clua.execfile(lua_builtins[i], false, false);
1386             if (!clua.error.empty())
1387                 mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
1388         }
1389     }
1390
1391     for (unsigned int i = 0; i < ARRAYSZ(config_defaults); ++i)
1392         Options.include(datafile_path(config_defaults[i]), false, runscript);
1393 #endif
1394
1395     Options.filename     = "extra opts first";
1396     Options.basefilename = "extra opts first";
1397     Options.line_num     = 0;
1398     for (unsigned int i = 0; i < SysEnv.extra_opts_first.size(); i++)
1399     {
1400         Options.line_num++;
1401         Options.read_option_line(SysEnv.extra_opts_first[i], true);
1402     }
1403
1404     const std::string init_file_name(_find_crawlrc());
1405
1406     FileLineInput f(init_file_name.c_str());
1407     if (f.error())
1408     {
1409         if (!init_file_name.empty())
1410         {
1411             return make_stringf("(\"%s\" is not readable)",
1412                                 init_file_name.c_str());
1413         }
1414
1415 #ifdef UNIX
1416         return "(~/.crawlrc missing)";
1417 #else
1418         return "(no init.txt in current directory)";
1419 #endif
1420     }
1421
1422     Options.filename = init_file_name;
1423     Options.line_num = 0;
1424 #ifdef UNIX
1425     Options.basefilename = "~/.crawlrc";
1426 #else
1427     Options.basefilename = "init.txt";
1428 #endif
1429     Options.read_options(f, runscript);
1430
1431     Options.filename     = "extra opts last";
1432     Options.basefilename = "extra opts last";
1433     Options.line_num     = 0;
1434     for (unsigned int i = 0; i < SysEnv.extra_opts_last.size(); i++)
1435     {
1436         Options.line_num++;
1437         Options.read_option_line(SysEnv.extra_opts_last[i], false);
1438     }
1439
1440     Options.filename     = init_file_name;
1441     Options.basefilename = get_base_filename(init_file_name);
1442     Options.line_num     = -1;
1443
1444     return "";
1445 }
1446
1447 newgame_def read_startup_prefs()
1448 {
1449 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1450     FileLineInput fl(get_prefs_filename().c_str());
1451     if (fl.error())
1452         return newgame_def();
1453
1454     game_options temp;
1455     temp.read_options(fl, false);
1456
1457     return temp.game;
1458 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1459 }
1460
1461 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1462 static void write_newgame_options(const newgame_def& prefs, FILE *f)
1463 {
1464     if (prefs.type != NUM_GAME_TYPE)
1465         fprintf(f, "type = %s\n", gametype_to_str(prefs.type).c_str());
1466     if (!prefs.map.empty())
1467         fprintf(f, "map = %s\n", prefs.map.c_str());
1468     if (!prefs.arena_teams.empty())
1469         fprintf(f, "arena_teams = %s\n", prefs.arena_teams.c_str());
1470     fprintf(f, "name = %s\n", prefs.name.c_str());
1471     if (prefs.species != SP_UNKNOWN)
1472         fprintf(f, "species = %s\n", _species_to_str(prefs.species).c_str());
1473     if (prefs.job != JOB_UNKNOWN)
1474         fprintf(f, "background = %s\n", _job_to_str(prefs.job).c_str());
1475     if (prefs.weapon != WPN_UNKNOWN)
1476         fprintf(f, "weapon = %s\n", _weapon_to_str(prefs.weapon).c_str());
1477     fprintf(f, "fully_random = %s\n", prefs.fully_random ? "yes" : "no");
1478 }
1479 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1480
1481 void write_newgame_options_file(const newgame_def& prefs)
1482 {
1483 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1484     // [ds] Saving startup prefs should work like this:
1485     //
1486     // 1. If the game is started without specifying a game type, always
1487     //    save startup preferences in the base savedir.
1488     // 2. If the game is started with a game type (Sprint), save startup
1489     //    preferences in the game-specific savedir.
1490     //
1491     // The idea is that public servers can use one instance of Crawl
1492     // but present Crawl and Sprint as two separate games in the
1493     // server-specific game menu -- the startup prefs file for Crawl and
1494     // Sprint should never collide, because the public server config will
1495     // specify the game type on the command-line.
1496     //
1497     // For normal users, startup prefs should always be saved in the
1498     // same base savedir so that when they start Crawl with "./crawl"
1499     // or the equivalent, their last game choices will be remembered,
1500     // even if they chose a Sprint game.
1501     //
1502     // Yes, this is unnecessarily complex. Better ideas welcome.
1503     //
1504     unwind_var<game_type> gt(crawl_state.type, Options.game.type);
1505
1506     std::string fn = get_prefs_filename();
1507     FILE *f = fopen_u(fn.c_str(), "w");
1508     if (!f)
1509         return;
1510     write_newgame_options(prefs, f);
1511     fclose(f);
1512 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1513 }
1514
1515 void save_player_name()
1516 {
1517 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1518     if (!Options.remember_name)
1519         return ;
1520
1521     // Read other preferences
1522     newgame_def prefs = read_startup_prefs();
1523     prefs.name = you.your_name;
1524
1525     // And save
1526     write_newgame_options_file(prefs);
1527 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1528 }
1529
1530 void read_options(const std::string &s, bool runscript, bool clear_aliases)
1531 {
1532     StringLineInput st(s);
1533     Options.read_options(st, runscript, clear_aliases);
1534 }
1535
1536 game_options::game_options()
1537 {
1538     lang = LANG_EN; // FIXME: obtain from gettext
1539     lang_name = 0;
1540     reset_options();
1541 }
1542
1543 void game_options::read_options(LineInput &il, bool runscript,
1544                                 bool clear_aliases)
1545 {
1546     unsigned int line = 0;
1547
1548     bool inscriptblock = false;
1549     bool inscriptcond  = false;
1550     bool isconditional = false;
1551
1552     bool l_init        = false;
1553
1554     if (clear_aliases)
1555         aliases.clear();
1556
1557     dlua_chunk luacond(filename);
1558     dlua_chunk luacode(filename);
1559
1560     while (!il.eof())
1561     {
1562         line_num++;
1563         std::string s   = il.get_line();
1564         std::string str = s;
1565         line++;
1566
1567         trim_string(str);
1568
1569         // This is to make some efficient comments
1570         if ((str.empty() || str[0] == '#') && !inscriptcond && !inscriptblock)
1571             continue;
1572
1573         if (!inscriptcond && str[0] == ':')
1574         {
1575             // The init file is now forced into isconditional mode.
1576             isconditional = true;
1577             str = str.substr(1);
1578             if (!str.empty() && runscript)
1579             {
1580                 // If we're in the middle of an option block, close it.
1581                 if (!luacond.empty() && l_init)
1582                 {
1583                     luacond.add(line - 1, "]])");
1584                     l_init = false;
1585                 }
1586                 luacond.add(line, str);
1587             }
1588             continue;
1589         }
1590         if (!inscriptcond && (str.find("L<") == 0 || str.find("<") == 0))
1591         {
1592             // The init file is now forced into isconditional mode.
1593             isconditional = true;
1594             inscriptcond  = true;
1595
1596             str = str.substr(str.find("L<") == 0? 2 : 1);
1597             // Is this a one-liner?
1598             if (!str.empty() && str[ str.length() - 1 ] == '>')
1599             {
1600                 inscriptcond = false;
1601                 str = str.substr(0, str.length() - 1);
1602             }
1603
1604             if (!str.empty() && runscript)
1605             {
1606                 // If we're in the middle of an option block, close it.
1607                 if (!luacond.empty() && l_init)
1608                 {
1609                     luacond.add(line - 1, "]])");
1610                     l_init = false;
1611                 }
1612                 luacond.add(line, str);
1613             }
1614             continue;
1615         }
1616         else if (inscriptcond && !str.empty()
1617                  && (str.find(">") == str.length() - 1 || str == ">L"))
1618         {
1619             inscriptcond = false;
1620             str = str.substr(0, str.length() - 1);
1621             if (!str.empty() && runscript)
1622                 luacond.add(line, str);
1623             continue;
1624         }
1625         else if (inscriptcond)
1626         {
1627             if (runscript)
1628                 luacond.add(line, s);
1629             continue;
1630         }
1631
1632         // Handle blocks of Lua
1633         if (!inscriptblock && (str.find("Lua{") == 0 || str.find("{") == 0))
1634         {
1635             inscriptblock = true;
1636             luacode.clear();
1637             luacode.set_file(filename);
1638
1639             // Strip leading Lua[
1640             str = str.substr(str.find("Lua{") == 0? 4 : 1);
1641
1642             if (!str.empty() && str.find("}") == str.length() - 1)
1643             {
1644                 str = str.substr(0, str.length() - 1);
1645                 inscriptblock = false;
1646             }
1647
1648             if (!str.empty())
1649                 luacode.add(line, str);
1650
1651             if (!inscriptblock && runscript)
1652             {
1653 #ifdef CLUA_BINDINGS
1654                 if (luacode.run(clua))
1655                 {
1656                     mprf(MSGCH_ERROR, "Lua error: %s",
1657                          luacode.orig_error().c_str());
1658                 }
1659                 luacode.clear();
1660 #endif
1661             }
1662
1663             continue;
1664         }
1665         else if (inscriptblock && (str == "}Lua" || str == "}"))
1666         {
1667             inscriptblock = false;
1668 #ifdef CLUA_BINDINGS
1669             if (runscript)
1670             {
1671                 if (luacode.run(clua))
1672                     mprf(MSGCH_ERROR, "Lua error: %s",
1673                          luacode.orig_error().c_str());
1674             }
1675 #endif
1676             luacode.clear();
1677             continue;
1678         }
1679         else if (inscriptblock)
1680         {
1681             luacode.add(line, s);
1682             continue;
1683         }
1684
1685         if (isconditional && runscript)
1686         {
1687             if (!l_init)
1688             {
1689                 luacond.add(line, "crawl.setopt([[");
1690                 l_init = true;
1691             }
1692
1693             luacond.add(line, s);
1694             continue;
1695         }
1696
1697         read_option_line(str, runscript);
1698     }
1699
1700 #ifdef CLUA_BINDINGS
1701     if (runscript && !luacond.empty())
1702     {
1703         if (l_init)
1704             luacond.add(line, "]])");
1705         if (luacond.run(clua))
1706             mprf(MSGCH_ERROR, "Lua error: %s", luacond.orig_error().c_str());
1707     }
1708 #endif
1709
1710     Options.explore_stop |= Options.explore_stop_prompt;
1711
1712     evil_colour = str_to_colour(variables["evil"]);
1713 }
1714
1715 void game_options::fixup_options()
1716 {
1717     // Validate save_dir
1718     if (!check_mkdir("Save directory", &save_dir))
1719         end(1);
1720
1721     if (!SysEnv.morgue_dir.empty())
1722         morgue_dir = SysEnv.morgue_dir;
1723
1724     if (!check_mkdir("Morgue directory", &morgue_dir))
1725         end(1);
1726
1727     if (evil_colour == BLACK)
1728         evil_colour = MAGENTA;
1729 }
1730
1731 static int _str_to_killcategory(const std::string &s)
1732 {
1733     static const char *kc[] = {
1734         "you",
1735         "friend",
1736         "other",
1737     };
1738
1739     for (unsigned i = 0; i < ARRAYSZ(kc); ++i)
1740         if (s == kc[i])
1741             return i;
1742
1743     return -1;
1744 }
1745
1746 void game_options::do_kill_map(const std::string &from, const std::string &to)
1747 {
1748     int ifrom = _str_to_killcategory(from),
1749         ito   = _str_to_killcategory(to);
1750     if (ifrom != -1 && ito != -1)
1751         kill_map[ifrom] = ito;
1752 }
1753
1754 int game_options::read_explore_stop_conditions(const std::string &field) const
1755 {
1756     int conditions = 0;
1757     std::vector<std::string> stops = split_string(",", field);
1758     for (int i = 0, count = stops.size(); i < count; ++i)
1759     {
1760         const std::string c = replace_all_of(stops[i], " ", "_");
1761         if (c == "item" || c == "items")
1762             conditions |= ES_ITEM;
1763         else if (c == "greedy_pickup")
1764             conditions |= ES_GREEDY_PICKUP;
1765         else if (c == "greedy_pickup_gold")
1766             conditions |= ES_GREEDY_PICKUP_GOLD;
1767         else if (c == "greedy_pickup_smart")
1768             conditions |= ES_GREEDY_PICKUP_SMART;
1769         else if (c == "greedy_pickup_thrown")
1770             conditions |= ES_GREEDY_PICKUP_THROWN;
1771         else if (c == "shop" || c == "shops")
1772             conditions |= ES_SHOP;
1773         else if (c == "stair" || c == "stairs")
1774             conditions |= ES_STAIR;
1775         else if (c == "branch" || c == "branches")
1776             conditions |= ES_BRANCH;
1777         else if (c == "portal" || c == "portals")
1778             conditions |= ES_PORTAL;
1779         else if (c == "altar" || c == "altars")
1780             conditions |= ES_ALTAR;
1781         else if (c == "greedy_item" || c == "greedy_items")
1782             conditions |= ES_GREEDY_ITEM;
1783         else if (c == "greedy_visited_item_stack")
1784             conditions |= ES_GREEDY_VISITED_ITEM_STACK;
1785         else if (c == "glowing" || c == "glowing_item"
1786                  || c == "glowing_items")
1787             conditions |= ES_GLOWING_ITEM;
1788         else if (c == "artefact" || c == "artefacts"
1789                  || c == "artifact" || c == "artifacts")
1790             conditions |= ES_ARTEFACT;
1791         else if (c == "rune" || c == "runes")
1792             conditions |= ES_RUNE;
1793         else if (c == "greedy_sacrificiable" || c == "greedy_sacrificiables")
1794             conditions |= ES_GREEDY_SACRIFICIABLE;
1795     }
1796     return conditions;
1797 }
1798
1799 void game_options::add_alias(const std::string &key, const std::string &val)
1800 {
1801     if (key[0] == '$')
1802     {
1803         std::string name = key.substr(1);
1804         // Don't alter if it's a constant.
1805         if (constants.find(name) != constants.end())
1806             return;
1807         variables[name] = val;
1808     }
1809     else
1810         aliases[key] = val;
1811 }
1812
1813 std::string game_options::unalias(const std::string &key) const
1814 {
1815     string_map::const_iterator i = aliases.find(key);
1816     return (i == aliases.end()? key : i->second);
1817 }
1818
1819 #define IS_VAR_CHAR(c) (isaalpha(c) || c == '_' || c == '-')
1820
1821 std::string game_options::expand_vars(const std::string &field) const
1822 {
1823     std::string field_out = field;
1824
1825     std::string::size_type curr_pos = 0;
1826
1827     // Only try 100 times, so as to not get stuck in infinite recursion.
1828     for (int i = 0; i < 100; i++)
1829     {
1830         std::string::size_type dollar_pos = field_out.find("$", curr_pos);
1831
1832         if (dollar_pos == std::string::npos
1833             || field_out.size() == (dollar_pos + 1))
1834         {
1835             break;
1836         }
1837
1838         std::string::size_type start_pos = dollar_pos + 1;
1839
1840         if (!IS_VAR_CHAR(field_out[start_pos]))
1841             continue;
1842
1843         std::string::size_type end_pos;
1844         for (end_pos = start_pos; end_pos < field_out.size(); end_pos++)
1845         {
1846             if (!IS_VAR_CHAR(field_out[end_pos + 1]))
1847                 break;
1848         }
1849
1850         std::string var_name = field_out.substr(start_pos,
1851                                                 end_pos - start_pos + 1);
1852
1853         string_map::const_iterator x = variables.find(var_name);
1854
1855         if (x == variables.end())
1856         {
1857             curr_pos = end_pos + 1;
1858             continue;
1859         }
1860
1861         std::string dollar_plus_name = "$";
1862         dollar_plus_name += var_name;
1863
1864         field_out = replace_all(field_out, dollar_plus_name, x->second);
1865
1866         // Start over at beginning
1867         curr_pos = 0;
1868     }
1869
1870     return field_out;
1871 }
1872
1873 void game_options::add_message_colour_mappings(const std::string &field)
1874 {
1875     std::vector<std::string> fragments = split_string(",", field);
1876     for (int i = 0, count = fragments.size(); i < count; ++i)
1877         add_message_colour_mapping(fragments[i]);
1878 }
1879
1880 message_filter game_options::parse_message_filter(const std::string &filter)
1881 {
1882     std::string::size_type pos = filter.find(":");
1883     if (pos && pos != std::string::npos)
1884     {
1885         std::string prefix = filter.substr(0, pos);
1886         int channel = str_to_channel(prefix);
1887         if (channel != -1 || prefix == "any")
1888         {
1889             std::string s = filter.substr(pos + 1);
1890             trim_string(s);
1891             return message_filter(channel, s);
1892         }
1893     }
1894
1895     return message_filter(filter);
1896 }
1897
1898 void game_options::add_message_colour_mapping(const std::string &field)
1899 {
1900     std::vector<std::string> cmap = split_string(":", field, true, true, 1);
1901
1902     if (cmap.size() != 2)
1903         return;
1904
1905     const int col = str_to_colour(cmap[0]);
1906     msg_colour_type mcol;
1907     if (cmap[0] == "mute")
1908         mcol = MSGCOL_MUTED;
1909     else if (col == -1)
1910         return;
1911     else
1912         mcol = msg_colour(col);
1913
1914     message_colour_mapping m = { parse_message_filter(cmap[1]), mcol };
1915     message_colour_mappings.push_back(m);
1916 }
1917
1918 // Option syntax is:
1919 // sort_menu = [menu_type:]yes|no|auto:n[:sort_conditions]
1920 void game_options::set_menu_sort(std::string field)
1921 {
1922     if (field.empty())
1923         return;
1924
1925     menu_sort_condition cond(field);
1926
1927     // Overrides all previous settings.
1928     if (cond.mtype == MT_ANY)
1929         sort_menus.clear();
1930
1931     // Override existing values, if necessary.
1932     for (unsigned int i = 0; i < sort_menus.size(); i++)
1933         if (sort_menus[i].mtype == cond.mtype)
1934         {
1935             sort_menus[i].sort = cond.sort;
1936             sort_menus[i].cmp  = cond.cmp;
1937             return;
1938         }
1939
1940     sort_menus.push_back(cond);
1941 }
1942
1943 void game_options::split_parse(
1944     const std::string &s, const std::string &separator,
1945     void (game_options::*add)(const std::string &))
1946 {
1947     const std::vector<std::string> defs = split_string(separator, s);
1948     for (int i = 0, size = defs.size(); i < size; ++i)
1949         (this->*add)(defs[i]);
1950 }
1951
1952 void game_options::set_option_fragment(const std::string &s)
1953 {
1954     if (s.empty())
1955         return;
1956
1957     std::string::size_type st = s.find(':');
1958     if (st == std::string::npos)
1959     {
1960         // Boolean option.
1961         if (s[0] == '!')
1962             read_option_line(s.substr(1) + " = false");
1963         else
1964             read_option_line(s + " = true");
1965     }
1966     else
1967     {
1968         // key:val option.
1969         read_option_line(s.substr(0, st) + " = " + s.substr(st + 1));
1970     }
1971 }
1972
1973 // Not a method of the game_options class since keybindings aren't
1974 // stored in that class.
1975 static void _bindkey(std::string field)
1976 {
1977     const size_t start_bracket = field.find_first_of('[');
1978     const size_t end_bracket   = field.find_last_of(']');
1979
1980     if (start_bracket == std::string::npos
1981         || end_bracket == std::string::npos
1982         || start_bracket > end_bracket)
1983     {
1984         mprf(MSGCH_ERROR, "Bad bindkey bracketing in '%s'",
1985              field.c_str());
1986         return;
1987     }
1988
1989     const std::string key_str = field.substr(start_bracket + 1,
1990                                              end_bracket - start_bracket - 1);
1991
1992     int key;
1993
1994     // TODO: Function keys.
1995     if (key_str.length() == 0)
1996     {
1997         mprf(MSGCH_ERROR, "No key in bindkey directive '%s'",
1998              field.c_str());
1999         return;
2000     }
2001     else if (key_str.length() == 1)
2002         key = key_str[0];
2003     else if (key_str.length() == 2)
2004     {
2005         if (key_str[0] != '^')
2006         {
2007             mprf(MSGCH_ERROR, "Invalid key '%s' in bindkey directive '%s'",
2008                  key_str.c_str(), field.c_str());
2009             return;
2010         }
2011         key = CONTROL(key_str[1]);
2012     }
2013     else
2014     {
2015         mprf(MSGCH_ERROR, "Invalid key '%s' in bindkey directive '%s'",
2016              key_str.c_str(), field.c_str());
2017         return;
2018     }
2019
2020     const size_t start_name = field.find_first_not_of(' ', end_bracket + 1);
2021     if (start_name == std::string::npos)
2022     {
2023         mprf(MSGCH_ERROR, "No command name for bindkey directive '%s'",
2024              field.c_str());
2025         return;
2026     }
2027
2028     const std::string  name = field.substr(start_name);
2029     const command_type cmd  = name_to_command(name);
2030     if (cmd == CMD_NO_CMD)
2031     {
2032        mprf(MSGCH_ERROR, "No command named '%s'", name.c_str());
2033        return;
2034     }
2035
2036     bind_command_to_key(cmd, key);
2037 }
2038
2039 void game_options::read_option_line(const std::string &str, bool runscript)
2040 {
2041 #define BOOL_OPTION_NAMED(_opt_str, _opt_var)               \
2042     if (key == _opt_str) do {                               \
2043         _opt_var = _read_bool(field, _opt_var); \
2044     } while (false)
2045 #define BOOL_OPTION(_opt) BOOL_OPTION_NAMED(#_opt, _opt)
2046
2047 #define COLOUR_OPTION_NAMED(_opt_str, _opt_var)                         \
2048     if (key == _opt_str) do {                                           \
2049         const int col = str_to_colour(field);                           \
2050         if (col != -1) {                                                \
2051             _opt_var = col;                                       \
2052         } else {                                                        \
2053             /*fprintf(stderr, "Bad %s -- %s\n", key, field.c_str());*/  \
2054             report_error("Bad %s -- %s\n", key.c_str(), field.c_str()); \
2055         }                                                               \
2056     } while (false)
2057 #define COLOUR_OPTION(_opt) COLOUR_OPTION_NAMED(#_opt, _opt)
2058
2059 #define CURSES_OPTION_NAMED(_opt_str, _opt_var)     \
2060     if (key == _opt_str) do {                       \
2061         _opt_var = curses_attribute(field);   \
2062     } while (false)
2063 #define CURSES_OPTION(_opt) CURSES_OPTION_NAMED(#_opt, _opt)
2064
2065 #define INT_OPTION_NAMED(_opt_str, _opt_var, _min_val, _max_val)        \
2066     if (key == _opt_str) do {                                           \
2067         const int min_val = (_min_val);                                 \
2068         const int max_val = (_max_val);                                 \
2069         int val = _opt_var;                                             \
2070         if (!parse_int(field.c_str(), val))                             \
2071             report_error("Bad %s: \"%s\"", _opt_str, field.c_str());    \
2072         else if (val < min_val)                                         \
2073             report_error("Bad %s: %d < %d", _opt_str, val, min_val);    \
2074         else if (val > max_val)                                         \
2075             report_error("Bad %s: %d > %d", _opt_str, val, max_val);    \
2076         else                                                            \
2077             _opt_var = val;                                       \
2078     } while (false)
2079 #define INT_OPTION(_opt, _min_val, _max_val) \
2080     INT_OPTION_NAMED(#_opt, _opt, _min_val, _max_val)
2081
2082     std::string key    = "";
2083     std::string subkey = "";
2084     std::string field  = "";
2085
2086     bool plus_equal  = false;
2087     bool minus_equal = false;
2088
2089     const int first_equals = str.find('=');
2090
2091     // all lines with no equal-signs we ignore
2092     if (first_equals < 0)
2093         return;
2094
2095     field = str.substr(first_equals + 1);
2096     field = expand_vars(field);
2097
2098     std::string prequal = trimmed_string(str.substr(0, first_equals));
2099
2100     // Is this a case of key += val?
2101     if (prequal.length() && prequal[prequal.length() - 1] == '+')
2102     {
2103         plus_equal = true;
2104         prequal = prequal.substr(0, prequal.length() - 1);
2105         trim_string(prequal);
2106     }
2107     else if (prequal.length() && prequal[prequal.length() - 1] == '-')
2108     {
2109         minus_equal = true;
2110         prequal = prequal.substr(0, prequal.length() - 1);
2111         trim_string(prequal);
2112     }
2113     else if (prequal.length() && prequal[prequal.length() - 1] == ':')
2114     {
2115         prequal = prequal.substr(0, prequal.length() - 1);
2116         trim_string(prequal);
2117         trim_string(field);
2118
2119         add_alias(prequal, field);
2120         return;
2121     }
2122
2123     prequal = unalias(prequal);
2124
2125     const std::string::size_type first_dot = prequal.find('.');
2126     if (first_dot != std::string::npos)
2127     {
2128         key    = prequal.substr(0, first_dot);
2129         subkey = prequal.substr(first_dot + 1);
2130     }
2131     else
2132     {
2133         // no subkey (dots are okay in value field)
2134         key    = prequal;
2135     }
2136
2137     // Clean up our data...
2138     lowercase(trim_string(key));
2139     lowercase(trim_string(subkey));
2140
2141     // some fields want capitals... none care about external spaces
2142     trim_string(field);
2143
2144     // Keep unlowercased field around
2145     const std::string orig_field = field;
2146
2147     if (key != "name" && key != "crawl_dir" && key != "macro_dir"
2148         && key != "species" && key != "background" && key != "job"
2149         && key != "race" && key != "class" && key != "ban_pickup"
2150         && key != "autopickup_exceptions"
2151         && key != "explore_stop_pickup_ignore"
2152         && key != "stop_travel" && key != "sound"
2153         && key != "force_more_message"
2154         && key != "drop_filter" && key != "lua_file" && key != "terp_file"
2155         && key != "note_items" && key != "autoinscribe"
2156         && key != "note_monsters" && key != "note_messages"
2157         && key.find("cset") != 0 && key != "dungeon"
2158         && key != "feature" && key != "fire_items_start"
2159         && key != "mon_glyph" && key != "opt" && key != "option"
2160         && key != "menu_colour" && key != "menu_color"
2161         && key != "message_colour" && key != "message_color"
2162         && key != "levels" && key != "level" && key != "entries"
2163         && key != "include" && key != "bindkey"
2164         && key != "spell_slot"
2165         && key.find("font") == std::string::npos)
2166     {
2167         lowercase(field);
2168     }
2169
2170     if (key == "include")
2171         include(field, true, runscript);
2172     else if (key == "opt" || key == "option")
2173         split_parse(field, ",", &game_options::set_option_fragment);
2174     else if (key == "autopickup")
2175     {
2176         // clear out autopickup
2177         autopickups = 0;
2178
2179         ucs_t c;
2180         for (const char* tp = field.c_str(); int s = utf8towc(&c, tp); tp += s)
2181         {
2182             object_class_type type = item_class_by_sym(c);
2183
2184             if (type < NUM_OBJECT_CLASSES)
2185                 autopickups |= (1 << type);
2186             else
2187                 report_error("Bad object type '%*s' for autopickup.\n", s, tp);
2188         }
2189     }
2190 #if !defined(DGAMELAUNCH) || defined(DGL_REMEMBER_NAME)
2191     else if (key == "name")
2192     {
2193         // field is already cleaned up from trim_string()
2194         game.name = field;
2195     }
2196 #endif
2197     else if (key == "char_set")
2198     {
2199         if (field == "ascii")
2200             char_set = CSET_ASCII;
2201         else if (field == "ibm")
2202             char_set = CSET_IBM;
2203         else if (field == "dec")
2204             char_set = CSET_DEC;
2205         else if (field == "utf" || field == "unicode")
2206             char_set = CSET_OLD_UNICODE;
2207         else if (field == "default")
2208             char_set = CSET_DEFAULT;
2209         else
2210             fprintf(stderr, "Bad character set: %s\n", field.c_str());
2211     }
2212     else if (key == "language")
2213     {
2214         // FIXME: should talk to gettext/etc instead
2215         if (field == "en" || field == "english")
2216             lang = LANG_EN, lang_name = 0; // disable the db
2217         else if (field == "pl" || field == "polish" || field == "polski")
2218             lang = LANG_PL, lang_name = "pl";
2219         else if (field == "de" || field == "german" || field == "deutch")
2220             lang = LANG_DE, lang_name = "de";
2221         else if (field == "fr" || field == "french" || field == "français" || field == "francais")
2222             lang = LANG_FR, lang_name = "fr";
2223         else if (field == "es" || field == "spanish" || field == "español" || field == "espanol")
2224             lang = LANG_ES, lang_name = "es";
2225         else if (field == "el" || field == "greek" || field == "ελληνικά" || field == "ελληνικα")
2226             lang = LANG_EL, lang_name = "el";
2227         else if (field == "fi" || field == "finnish" || field == "suomi")
2228             lang = LANG_FI, lang_name = "fi";
2229         // Fake languages do not reset lang_name, allowing a translated
2230         // database in an actual language.  This is probably pointless for
2231         // most fake langs, though.
2232         else if (field == "dwarven" || field == "dwarf")
2233             lang = LANG_DWARVEN;
2234         else if (field == "jäger" || field == "jägerkin" || field == "jager" || field == "jagerkin"
2235                  || field == "jaeger" || field == "jaegerkin")
2236         {
2237             lang = LANG_JAGERKIN;
2238         }
2239         // Due to a conflict with actual "de", this uses slang names for the
2240         // option.  Let's try to keep to less rude ones, though.
2241         else if (field == "kraut" || field == "jerry" || field == "fritz")
2242             lang = LANG_KRAUT;
2243 /*
2244         else if (field == "cyr" || field == "cyrillic" || field == "commie" || field == "кириллица")
2245             lang = LANG_CYRILLIC;
2246 */
2247         else if (field == "wide" || field == "doublewidth" || field == "fullwidth")
2248             lang = LANG_WIDE;
2249         else
2250             report_error("No translations for language: %s\n", field.c_str());
2251     }
2252     else if (key == "default_autopickup")
2253     {
2254         if (_read_bool(field, true))
2255             autopickup_on = 1;
2256         else
2257             autopickup_on = 0;
2258     }
2259     else if (key == "default_friendly_pickup")
2260     {
2261         if (field == "none")
2262             default_friendly_pickup = FRIENDLY_PICKUP_NONE;
2263         else if (field == "friend")
2264             default_friendly_pickup = FRIENDLY_PICKUP_FRIEND;
2265         else if (field == "player")
2266             default_friendly_pickup = FRIENDLY_PICKUP_PLAYER;
2267         else if (field == "all")
2268             default_friendly_pickup = FRIENDLY_PICKUP_ALL;
2269     }
2270     else if (key == "default_manual_training")
2271     {
2272         if (_read_bool(field, true))
2273             default_manual_training = true;
2274         else
2275             default_manual_training = false;
2276     }
2277 #ifndef DGAMELAUNCH
2278     else BOOL_OPTION(restart_after_game);
2279 #endif
2280     else BOOL_OPTION(show_inventory_weights);
2281     else BOOL_OPTION(auto_switch);
2282     else BOOL_OPTION(suppress_startup_errors);
2283     else BOOL_OPTION(clean_map);
2284     else if (key == "easy_confirm")
2285     {
2286         // decide when to allow both 'Y'/'N' and 'y'/'n' on yesno() prompts
2287         if (field == "none")
2288             easy_confirm = CONFIRM_NONE_EASY;
2289         else if (field == "safe")
2290             easy_confirm = CONFIRM_SAFE_EASY;
2291         else if (field == "all")
2292             easy_confirm = CONFIRM_ALL_EASY;
2293     }
2294     else if (key == "allow_self_target")
2295     {
2296         if (field == "yes")
2297             allow_self_target = CONFIRM_NONE;
2298         else if (field == "no")
2299             allow_self_target = CONFIRM_CANCEL;
2300         else if (field == "prompt")
2301             allow_self_target = CONFIRM_PROMPT;
2302     }
2303     else BOOL_OPTION(easy_quit_item_prompts);
2304     else BOOL_OPTION_NAMED("easy_quit_item_lists", easy_quit_item_prompts);
2305     else BOOL_OPTION(easy_open);
2306     else BOOL_OPTION(easy_unequip);
2307     else BOOL_OPTION(equip_unequip);
2308     else BOOL_OPTION_NAMED("easy_armour", easy_unequip);
2309     else BOOL_OPTION_NAMED("easy_armor", easy_unequip);
2310     else if (key == "confirm_butcher")
2311     {
2312         if (field == "always")
2313             confirm_butcher = CONFIRM_ALWAYS;
2314         else if (field == "never")
2315             confirm_butcher = CONFIRM_NEVER;
2316         else if (field == "auto")
2317             confirm_butcher = CONFIRM_AUTO;
2318     }
2319     else BOOL_OPTION(chunks_autopickup);
2320     else BOOL_OPTION(prompt_for_swap);
2321     else BOOL_OPTION(list_rotten);
2322     else BOOL_OPTION(prefer_safe_chunks);
2323     else BOOL_OPTION(easy_eat_chunks);
2324     else BOOL_OPTION(easy_eat_gourmand);
2325     else BOOL_OPTION(easy_eat_contaminated);
2326     else BOOL_OPTION(auto_eat_chunks);
2327     else if (key == "auto_drop_chunks")
2328     {
2329         if (field == "never")
2330             auto_drop_chunks = ADC_NEVER;
2331         else if (field == "rotten")
2332             auto_drop_chunks = ADC_ROTTEN;
2333         else if (field == "yes" || field == "true")
2334             auto_drop_chunks = ADC_YES;
2335         else
2336             report_error("Invalid auto_drop_chunks: \"%s\"", field.c_str());
2337     }
2338     else if (key == "lua_file" && runscript)
2339     {
2340 #ifdef CLUA_BINDINGS
2341         clua.execfile(field.c_str(), false, false);
2342         if (!clua.error.empty())
2343             mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
2344 #endif
2345     }
2346     else if (key == "terp_file" && runscript)
2347     {
2348 #ifdef WIZARD
2349         terp_files.push_back(field);
2350 #endif
2351     }
2352     else if (key == "colour" || key == "color")
2353     {
2354         const int orig_col   = str_to_colour(subkey);
2355         const int result_col = str_to_colour(field);
2356
2357         if (orig_col != -1 && result_col != -1)
2358             colour[orig_col] = result_col;
2359         else
2360         {
2361             fprintf(stderr, "Bad colour -- %s=%d or %s=%d\n",
2362                      subkey.c_str(), orig_col, field.c_str(), result_col);
2363         }
2364     }
2365     else if (key == "channel")
2366     {
2367         const int chnl = str_to_channel(subkey);
2368         const msg_colour_type col  = _str_to_channel_colour(field);
2369
2370         if (chnl != -1 && col != -1)
2371             channels[chnl] = col;
2372         else if (chnl == -1)
2373             fprintf(stderr, "Bad channel -- %s\n", subkey.c_str());
2374         else if (col == MSGCOL_NONE)
2375             fprintf(stderr, "Bad colour -- %s\n", field.c_str());
2376     }
2377     else COLOUR_OPTION(background_colour);
2378     else COLOUR_OPTION(detected_item_colour);
2379     else COLOUR_OPTION(detected_monster_colour);
2380     else if (key.find(interrupt_prefix) == 0)
2381     {
2382         set_activity_interrupt(key.substr(interrupt_prefix.length()),
2383                                field,
2384                                plus_equal,
2385                                minus_equal);
2386     }
2387     else if (key.find("cset") == 0)
2388     {
2389         std::string cset = key.substr(4);
2390         if (!cset.empty() && cset[0] == '_')
2391             cset = cset.substr(1);
2392
2393         char_set_type cs = NUM_CSET;
2394         if (cset == "ibm")
2395             cs = CSET_IBM;
2396         else if (cset == "dec")
2397             cs = CSET_DEC;
2398
2399         add_cset_override(cs, field);
2400     }
2401     else if (key == "feature" || key == "dungeon")
2402         split_parse(field, ";", &game_options::add_feature_override);
2403     else if (key == "mon_glyph")
2404         split_parse(field, ",", &game_options::add_mon_glyph_override);
2405     else CURSES_OPTION(friend_brand);
2406     else CURSES_OPTION(neutral_brand);
2407     else CURSES_OPTION(stab_brand);
2408     else CURSES_OPTION(may_stab_brand);
2409     else CURSES_OPTION(feature_item_brand);
2410     else CURSES_OPTION(trap_item_brand);
2411     // This is useful for terms where dark grey does
2412     // not have standout modes (since it's black on black).
2413     // This option will use light-grey instead in these cases.
2414     else BOOL_OPTION(no_dark_brand);
2415     // no_dark_brand applies here as well.
2416     else CURSES_OPTION(heap_brand);
2417     else COLOUR_OPTION(status_caption_colour);
2418     else if (key == "arena_teams")
2419         game.arena_teams = field;
2420     // [ds] Allow changing map only if the map hasn't been set on the
2421     // command-line.
2422     else if (key == "map" && crawl_state.sprint_map.empty())
2423         game.map = field;
2424     // [ds] For dgamelaunch setups, the player should *not* be able to
2425     // set game type in their rc; the only way to set game type for
2426     // DGL builds should be the command-line options.
2427     else if (key == "type")
2428     {
2429 #if defined(DGAMELAUNCH)
2430         game.type = Options.game.type;
2431 #else
2432         game.type = _str_to_gametype(field);
2433 #endif
2434     }
2435     else if (key == "species" || key == "race")
2436         game.species = _str_to_species(field);
2437     else if (key == "background" || key == "job" || key == "class")
2438         game.job = _str_to_job(field);
2439     else if (key == "weapon")
2440     {
2441         // Choose this weapon for backgrounds that get choice.
2442         game.weapon = str_to_weapon(field);
2443     }
2444     BOOL_OPTION_NAMED("fully_random", game.fully_random);
2445     else if (key == "fire_items_start")
2446     {
2447         if (isaalpha(field[0]))
2448             fire_items_start = letter_to_index(field[0]);
2449         else
2450         {
2451             fprintf(stderr, "Bad fire item start index: %s\n",
2452                      field.c_str());
2453         }
2454     }
2455     else if (key == "assign_item_slot")
2456     {
2457         if (field == "forward")
2458             assign_item_slot = SS_FORWARD;
2459         else if (field == "backward")
2460             assign_item_slot = SS_BACKWARD;
2461     }
2462     else if (key == "fire_order")
2463         set_fire_order(field, plus_equal);
2464 #if !defined(DGAMELAUNCH) || defined(DGL_REMEMBER_NAME)
2465     else BOOL_OPTION(remember_name);
2466 #endif
2467 #ifndef DGAMELAUNCH
2468     else if (key == "save_dir")
2469         save_dir = field;
2470     else if (key == "morgue_dir")
2471         morgue_dir = field;
2472 #endif
2473     else BOOL_OPTION(show_newturn_mark);
2474     else BOOL_OPTION(show_gold_turns);
2475     else BOOL_OPTION(show_game_turns);
2476     else BOOL_OPTION(show_no_ctele);
2477     else INT_OPTION(hp_warning, 0, 100);
2478     else INT_OPTION_NAMED("mp_warning", magic_point_warning, 0, 100);
2479     else if (key == "note_monsters")
2480         append_vector(note_monsters, split_string(",", field));
2481     else if (key == "note_messages")
2482         append_vector(note_messages, split_string(",", field));
2483     else INT_OPTION(note_hp_percent, 0, 100);
2484 #ifndef DGAMELAUNCH
2485     // If DATA_DIR_PATH is set, don't set crawl_dir from .crawlrc.
2486 #ifndef DATA_DIR_PATH
2487     else if (key == "crawl_dir")
2488     {
2489         // We shouldn't bother to allocate this a second time
2490         // if the user puts two crawl_dir lines in the init file.
2491         SysEnv.crawl_dir = field;
2492     }
2493     else if (key == "macro_dir")
2494         macro_dir = field;
2495 #endif
2496 #endif
2497     else BOOL_OPTION(auto_list);
2498     else if (key == "default_target")
2499     {
2500         default_target = _read_bool(field, default_target);
2501         if (default_target)
2502             target_unshifted_dirs = false;
2503     }
2504     else BOOL_OPTION(autopickup_no_burden);
2505 #ifdef DGL_SIMPLE_MESSAGING
2506     else BOOL_OPTION(messaging);
2507 #endif
2508     else BOOL_OPTION(mouse_input);
2509     // These need to be odd, hence allow +1.
2510     else INT_OPTION(view_max_width, VIEW_MIN_WIDTH, GXM + 1);
2511     else INT_OPTION(view_max_height, VIEW_MIN_HEIGHT, GYM + 1);
2512     else INT_OPTION(mlist_min_height, 0, INT_MAX);
2513     else INT_OPTION(msg_min_height, MSG_MIN_HEIGHT, INT_MAX);
2514     else INT_OPTION(msg_max_height, MSG_MIN_HEIGHT, INT_MAX);
2515     else BOOL_OPTION(mlist_allow_alternate_layout);
2516     else BOOL_OPTION(messages_at_top);
2517 #ifndef USE_TILE
2518     else BOOL_OPTION(mlist_targetting);
2519 #endif
2520     else BOOL_OPTION(msg_condense_repeats);
2521     else BOOL_OPTION(msg_condense_short);
2522     else BOOL_OPTION(view_lock_x);
2523     else BOOL_OPTION(view_lock_y);
2524     else if (key == "view_lock")
2525     {
2526         const bool lock = _read_bool(field, true);
2527         view_lock_x = view_lock_y = lock;
2528     }
2529     else BOOL_OPTION(center_on_scroll);
2530     else BOOL_OPTION(symmetric_scroll);
2531     else if (key == "scroll_margin_x")
2532     {
2533         scroll_margin_x = atoi(field.c_str());
2534         if (scroll_margin_x < 0)
2535             scroll_margin_x = 0;
2536     }
2537     else if (key == "scroll_margin_y")
2538     {
2539         scroll_margin_y = atoi(field.c_str());
2540         if (scroll_margin_y < 0)
2541             scroll_margin_y = 0;
2542     }
2543     else if (key == "scroll_margin")
2544     {
2545         int scrollmarg = atoi(field.c_str());
2546         if (scrollmarg < 0)
2547             scrollmarg = 0;
2548         scroll_margin_x = scroll_margin_y = scrollmarg;
2549     }
2550     else if (key == "user_note_prefix")
2551     {
2552         // field is already cleaned up from trim_string()
2553         user_note_prefix = field;
2554     }
2555     else if (key == "skill_focus")
2556     {
2557         if (field == "toggle")
2558             skill_focus = SKM_FOCUS_TOGGLE;
2559         else if (_read_bool(field, true))
2560             skill_focus = SKM_FOCUS_ON;
2561         else
2562             skill_focus = SKM_FOCUS_OFF;
2563     }
2564     else BOOL_OPTION(note_all_skill_levels);
2565     else BOOL_OPTION(note_skill_max);
2566     else BOOL_OPTION(note_xom_effects);
2567     else BOOL_OPTION(note_chat_messages);
2568     else BOOL_OPTION(clear_messages);
2569     else BOOL_OPTION(show_more);
2570     else BOOL_OPTION(small_more);
2571     else if (key == "flush")
2572     {
2573         if (subkey == "failure")
2574         {
2575             flush_input[FLUSH_ON_FAILURE]
2576                 = _read_bool(field, flush_input[FLUSH_ON_FAILURE]);
2577         }
2578         else if (subkey == "command")
2579         {
2580             flush_input[FLUSH_BEFORE_COMMAND]
2581                 = _read_bool(field, flush_input[FLUSH_BEFORE_COMMAND]);
2582         }
2583         else if (subkey == "message")
2584         {
2585             flush_input[FLUSH_ON_MESSAGE]
2586                 = _read_bool(field, flush_input[FLUSH_ON_MESSAGE]);
2587         }
2588         else if (subkey == "lua")
2589         {
2590             flush_input[FLUSH_LUA]
2591                 = _read_bool(field, flush_input[FLUSH_LUA]);
2592         }
2593     }
2594     else if (key == "wiz_mode")
2595     {
2596         // wiz_mode is recognised as a legal key in all compiles -- bwr
2597 #ifdef WIZARD
2598     #ifndef DGAMELAUNCH
2599         if (field == "never")
2600             wiz_mode = WIZ_NEVER;
2601         else if (field == "no")
2602             wiz_mode = WIZ_NO;
2603         else if (field == "yes")
2604             wiz_mode = WIZ_YES;
2605         else
2606             report_error("Unknown wiz_mode option: %s\n", field.c_str());
2607     #endif
2608 #endif
2609     }
2610     else if (key == "ban_pickup")
2611     {
2612         std::vector<std::string> args = split_string(",", field);
2613         for (int i = 0, size = args.size(); i < size; ++i)
2614         {
2615             const std::string &s = args[i];
2616             if (s.empty())
2617                 continue;
2618             force_autopickup.push_back(std::make_pair(s, false));
2619         }
2620     }
2621     else if (key == "autopickup_exceptions")
2622     {
2623         std::vector<std::string> args = split_string(",", field);
2624         for (int i = 0, size = args.size(); i < size; ++i)
2625         {
2626             const std::string &s = args[i];
2627             if (s.empty())
2628                 continue;
2629
2630             if (s[0] == '>')
2631                 force_autopickup.push_back(std::make_pair(s.substr(1), false));
2632             else if (s[0] == '<')
2633                 force_autopickup.push_back(std::make_pair(s.substr(1), true));
2634             else
2635                 force_autopickup.push_back(std::make_pair(s, false));
2636         }
2637     }
2638     else if (key == "note_items")
2639         append_vector(note_items, split_string(",", field));
2640
2641 #ifndef _MSC_VER
2642     // break if-else chain on broken Microsoft compilers with stupid nesting limits
2643     else
2644 #endif
2645
2646     if (key == "autoinscribe")
2647     {
2648         if (field.empty())
2649             return report_error("Autoinscribe string is empty");
2650
2651         const size_t first = field.find_first_of(':');
2652         const size_t last  = field.find_last_of(':');
2653         if (first == std::string::npos || first != last)
2654         {
2655             return report_error("Autoinscribe string must have exactly "
2656                                 "one colon: %s\n", field.c_str());
2657         }
2658
2659         if (first == 0)
2660         {
2661             report_error("Autoinscribe pattern is empty: %s\n", field.c_str());
2662             return;
2663         }
2664
2665         if (last == field.length() - 1)
2666             return report_error("Autoinscribe result is empty: %s\n", field.c_str());
2667
2668         std::vector<std::string> thesplit = split_string(":", field);
2669
2670         if (thesplit.size() != 2)
2671         {
2672             report_error("Error parsing autoinscribe string: %s\n", field.c_str());
2673             return;
2674         }
2675
2676         autoinscriptions.push_back(
2677             std::pair<text_pattern,std::string>(thesplit[0], thesplit[1]));
2678     }
2679     else BOOL_OPTION(autoinscribe_artefacts);
2680     else BOOL_OPTION(autoinscribe_cursed);
2681 #ifndef DGAMELAUNCH
2682     else if (key == "map_file_name")
2683         map_file_name = field;
2684 #endif
2685     else if (key == "hp_colour" || key == "hp_color")
2686     {
2687         hp_colour.clear();
2688         std::vector<std::string> thesplit = split_string(",", field);
2689         for (unsigned i = 0; i < thesplit.size(); ++i)
2690         {
2691             std::vector<std::string> insplit = split_string(":", thesplit[i]);
2692             int hp_percent = 100;
2693
2694             if (insplit.empty() || insplit.size() > 2
2695                  || insplit.size() == 1 && i != 0)
2696             {
2697                 report_error("Bad hp_colour string: %s\n", field.c_str());
2698                 break;
2699             }
2700
2701             if (insplit.size() == 2)
2702                 hp_percent = atoi(insplit[0].c_str());
2703
2704             int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2705             hp_colour.push_back(std::pair<int, int>(hp_percent, scolour));
2706         }
2707     }
2708     else if (key == "mp_color" || key == "mp_colour")
2709     {
2710         mp_colour.clear();
2711         std::vector<std::string> thesplit = split_string(",", field);
2712         for (unsigned i = 0; i < thesplit.size(); ++i)
2713         {
2714             std::vector<std::string> insplit = split_string(":", thesplit[i]);
2715             int mp_percent = 100;
2716
2717             if (insplit.empty() || insplit.size() > 2
2718                  || insplit.size() == 1 && i != 0)
2719             {
2720                 report_error("Bad mp_colour string: %s\n", field.c_str());
2721                 break;
2722             }
2723
2724             if (insplit.size() == 2)
2725                 mp_percent = atoi(insplit[0].c_str());
2726
2727             int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2728             mp_colour.push_back(std::pair<int, int>(mp_percent, scolour));
2729         }
2730     }
2731     else if (key == "stat_colour" || key == "stat_color")
2732     {
2733         stat_colour.clear();
2734         std::vector<std::string> thesplit = split_string(",", field);
2735         for (unsigned i = 0; i < thesplit.size(); ++i)
2736         {
2737             std::vector<std::string> insplit = split_string(":", thesplit[i]);
2738
2739             if (insplit.empty() || insplit.size() > 2
2740                 || insplit.size() == 1 && i != 0)
2741             {
2742                 report_error("Bad stat_colour string: %s\n", field.c_str());
2743                 break;
2744             }
2745
2746             int stat_limit = 1;
2747             if (insplit.size() == 2)
2748                 stat_limit = atoi(insplit[0].c_str());
2749
2750             int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2751             stat_colour.push_back(std::pair<int, int>(stat_limit, scolour));
2752         }
2753     }
2754
2755     else if (key == "enemy_hp_colour" || key == "enemy_hp_color")
2756     {
2757         enemy_hp_colour.clear();
2758         str_to_enemy_hp_colour(field);
2759     }
2760
2761     else if (key == "note_skill_levels")
2762     {
2763         std::vector<std::string> thesplit = split_string(",", field);
2764         for (unsigned i = 0; i < thesplit.size(); ++i)
2765         {
2766             int num = atoi(thesplit[i].c_str());
2767             if (num > 0 && num <= 27)
2768                 note_skill_levels.push_back(num);
2769             else
2770             {
2771                 report_error("Bad skill level to note -- %s\n",
2772                              thesplit[i].c_str());
2773                 continue;
2774             }
2775         }
2776     }
2777     else if (key == "spell_slot")
2778     {
2779         std::vector<std::string> thesplit = split_string(":", field);
2780         if (thesplit.size() != 2)
2781         {
2782             return report_error("Error parsing spell lettering string: %s\n",
2783                                 field.c_str());
2784         }
2785         lowercase(thesplit[0]);
2786         auto_spell_letters.push_back(
2787             std::pair<text_pattern,std::string>(thesplit[0], thesplit[1]));
2788     }
2789     else BOOL_OPTION(pickup_thrown);
2790 #ifdef WIZARD
2791     else if (key == "fsim_mode")
2792         fsim_mode = field;
2793     else if (key == "fsim_scale")
2794         append_vector(fsim_scale, split_string(",", field));
2795     else if (key == "fsim_kit")
2796         append_vector(fsim_kit, split_string(",", field));
2797     else if (key == "fsim_rounds")
2798     {
2799         fsim_rounds = atol(field.c_str());
2800         if (fsim_rounds < 1000)
2801             fsim_rounds = 1000;
2802         if (fsim_rounds > 500000L)
2803             fsim_rounds = 500000L;
2804     }
2805     else if (key == "fsim_mons")
2806         fsim_mons = field;
2807 #endif // WIZARD
2808     else if (key == "sort_menus")
2809     {
2810         std::vector<std::string> frags = split_string(";", field);
2811         for (int i = 0, size = frags.size(); i < size; ++i)
2812         {
2813             if (frags[i].empty())
2814                 continue;
2815             set_menu_sort(frags[i]);
2816         }
2817     }
2818     else if (key == "travel_delay")
2819     {
2820         // Read travel delay in milliseconds.
2821         travel_delay = atoi(field.c_str());
2822         if (travel_delay < -1)
2823             travel_delay = -1;
2824         if (travel_delay > 2000)
2825             travel_delay = 2000;
2826     }
2827     else if (key == "explore_delay")
2828     {
2829         // Read explore delay in milliseconds.
2830         explore_delay = atoi(field.c_str());
2831         if (explore_delay < -1)
2832             explore_delay = -1;
2833         if (explore_delay > 2000)
2834             explore_delay = 2000;
2835     }
2836     else BOOL_OPTION(show_travel_trail);
2837     else if (key == "level_map_cursor_step")
2838     {
2839         level_map_cursor_step = atoi(field.c_str());
2840         if (level_map_cursor_step < 1)
2841             level_map_cursor_step = 1;
2842         if (level_map_cursor_step > 50)
2843             level_map_cursor_step = 50;
2844     }
2845     else BOOL_OPTION(use_fake_cursor);
2846     else BOOL_OPTION(use_fake_player_cursor);
2847     else BOOL_OPTION(show_player_species);
2848     else if (key == "force_more_message")
2849     {
2850         std::vector<std::string> fragments = split_string(",", field);
2851         for (int i = 0, count = fragments.size(); i < count; ++i)
2852         {
2853             if (fragments[i].length() == 0)
2854                 continue;
2855
2856             std::string::size_type pos = fragments[i].find(":");
2857             if (pos && pos != std::string::npos)
2858             {
2859                 std::string prefix = fragments[i].substr(0, pos);
2860                 int channel = str_to_channel(prefix);
2861                 if (channel != -1 || prefix == "any")
2862                 {
2863                     std::string s = fragments[i].substr(pos + 1);
2864                     trim_string(s);
2865                     force_more_message.push_back(
2866                         message_filter(channel, s));
2867                     continue;
2868                 }
2869             }
2870
2871             force_more_message.push_back(
2872                     message_filter(fragments[i]));
2873         }
2874     }
2875     else if (key == "drop_filter")
2876         append_vector(drop_filter, split_string(",", field));
2877     else if (key == "travel_avoid_terrain")
2878     {
2879         std::vector<std::string> seg = split_string(",", field);
2880         for (int i = 0, count = seg.size(); i < count; ++i)
2881             prevent_travel_to(seg[i]);
2882     }
2883     else if (key == "tc_reachable")
2884         tc_reachable = str_to_colour(field, tc_reachable);
2885     else if (key == "tc_excluded")
2886         tc_excluded = str_to_colour(field, tc_excluded);
2887     else if (key == "tc_exclude_circle")
2888     {
2889         tc_exclude_circle =
2890             str_to_colour(field, tc_exclude_circle);
2891     }
2892     else if (key == "tc_dangerous")
2893         tc_dangerous = str_to_colour(field, tc_dangerous);
2894     else if (key == "tc_disconnected")
2895         tc_disconnected = str_to_colour(field, tc_disconnected);
2896     else if (key == "auto_exclude")
2897         append_vector(auto_exclude, split_string(",", field));
2898     else BOOL_OPTION(easy_exit_menu);
2899     else BOOL_OPTION(dos_use_background_intensity);
2900     else if (key == "item_stack_summary_minimum")
2901         item_stack_summary_minimum = atoi(field.c_str());
2902     else if (key == "explore_stop")
2903     {
2904         if (!plus_equal && !minus_equal)
2905             explore_stop = ES_NONE;
2906
2907         const int new_conditions = read_explore_stop_conditions(field);
2908         if (minus_equal)
2909             explore_stop &= ~new_conditions;
2910         else
2911             explore_stop |= new_conditions;
2912     }
2913     else if (key == "explore_stop_prompt")
2914     {
2915         if (!plus_equal && !minus_equal)
2916             explore_stop_prompt = ES_NONE;
2917         const int new_conditions = read_explore_stop_conditions(field);
2918         if (minus_equal)
2919             explore_stop_prompt &= ~new_conditions;
2920         else
2921             explore_stop_prompt |= new_conditions;
2922     }
2923     else if (key == "explore_stop_pickup_ignore")
2924         append_vector(explore_stop_pickup_ignore, split_string(",", field));
2925     else if (key == "explore_item_greed")
2926     {
2927         explore_item_greed = atoi(field.c_str());
2928         if (explore_item_greed > 1000)
2929             explore_item_greed = 1000;
2930         else if (explore_item_greed < -1000)
2931             explore_item_greed = -1000;
2932     }
2933     else BOOL_OPTION(explore_greedy);
2934     else if (key == "explore_wall_bias")
2935     {
2936         explore_wall_bias = atoi(field.c_str());
2937         if (explore_wall_bias > 1000)
2938             explore_wall_bias = 1000;
2939         else if (explore_wall_bias < 0)
2940             explore_wall_bias = 0;
2941     }
2942     else BOOL_OPTION(explore_improved);
2943     else BOOL_OPTION(travel_key_stop);
2944     else if (key == "sound")
2945     {
2946         std::vector<std::string> seg = split_string(",", field);
2947         for (int i = 0, count = seg.size(); i < count; ++i)
2948         {
2949             const std::string &sub = seg[i];
2950             std::string::size_type cpos = sub.find(":", 0);
2951             if (cpos != std::string::npos)
2952             {
2953                 sound_mapping mapping;
2954                 mapping.pattern = sub.substr(0, cpos);
2955                 mapping.soundfile = sub.substr(cpos + 1);
2956                 sound_mappings.push_back(mapping);
2957             }
2958         }
2959     }
2960 #ifndef TARGET_COMPILER_VC
2961     // MSVC has a limit on how many if/else if can be chained together.
2962     else
2963 #endif
2964     if (key == "menu_colour" || key == "menu_color")
2965     {
2966         std::vector<std::string> seg = split_string(",", field);
2967         for (int i = 0, count = seg.size(); i < count; ++i)
2968         {
2969             // Format is "tag:colour:pattern" or "colour:pattern" (default tag).
2970             // FIXME: arrange so that you can use ':' inside a pattern
2971             std::vector<std::string> subseg = split_string(":", seg[i], false);
2972             std::string tagname, patname, colname;
2973             if (subseg.size() < 2)
2974                 continue;
2975             if (subseg.size() >= 3)
2976             {
2977                 tagname = subseg[0];
2978                 colname = subseg[1];
2979                 patname = subseg[2];
2980             }
2981             else
2982             {
2983                 colname = subseg[0];
2984                 patname = subseg[1];
2985             }
2986
2987             colour_mapping mapping;
2988             mapping.tag     = tagname;
2989             mapping.pattern = patname;
2990             mapping.colour  = str_to_colour(colname);
2991
2992             if (mapping.colour != -1)
2993                 menu_colour_mappings.push_back(mapping);
2994         }
2995     }
2996     else BOOL_OPTION(menu_colour_prefix_class);
2997     else BOOL_OPTION_NAMED("menu_color_prefix_class", menu_colour_prefix_class);
2998     else BOOL_OPTION(menu_colour_shops);
2999     else BOOL_OPTION_NAMED("menu_color_shops", menu_colour_shops);
3000     else if (key == "message_colour" || key == "message_color")
3001         add_message_colour_mappings(field);
3002     else if (key == "dump_order")
3003     {
3004         if (!plus_equal)
3005             dump_order.clear();
3006
3007         new_dump_fields(field, !minus_equal);
3008     }
3009     else if (key == "dump_kill_places")
3010     {
3011         dump_kill_places = (field == "none" ? KDO_NO_PLACES :
3012                             field == "all"  ? KDO_ALL_PLACES
3013                                             : KDO_ONE_PLACE);
3014     }
3015     else if (key == "kill_map")
3016     {
3017         std::vector<std::string> seg = split_string(",", field);
3018         for (int i = 0, count = seg.size(); i < count; ++i)
3019         {
3020             const std::string &s = seg[i];
3021             std::string::size_type cpos = s.find(":", 0);
3022             if (cpos != std::string::npos)
3023             {
3024                 std::string from = s.substr(0, cpos);
3025                 std::string to   = s.substr(cpos + 1);
3026                 do_kill_map(from, to);
3027             }
3028         }
3029     }
3030     else BOOL_OPTION(rest_wait_both);
3031     else if (key == "dump_message_count")
3032     {
3033         // Capping is implicit
3034         dump_message_count = atoi(field.c_str());
3035     }
3036     else if (key == "dump_item_origins")
3037     {
3038         dump_item_origins = IODS_PRICE;
3039         std::vector<std::string> choices = split_string(",", field);
3040         for (int i = 0, count = choices.size(); i < count; ++i)
3041         {
3042             const std::string &ch = choices[i];
3043             if (ch == "artefacts" || ch == "artifacts"
3044                 || ch == "artefact" || ch == "artifact")
3045             {
3046                 dump_item_origins |= IODS_ARTEFACTS;
3047             }
3048             else if (ch == "ego_arm" || ch == "ego armour"
3049                      || ch == "ego_armour" || ch == "ego armor"
3050                      || ch == "ego_armor")
3051             {
3052                 dump_item_origins |= IODS_EGO_ARMOUR;
3053             }
3054             else if (ch == "ego_weap" || ch == "ego weapon"
3055                      || ch == "ego_weapon" || ch == "ego weapons"
3056                      || ch == "ego_weapons")
3057             {
3058                 dump_item_origins |= IODS_EGO_WEAPON;
3059             }
3060             else if (ch == "jewellery" || ch == "jewelry")
3061                 dump_item_origins |= IODS_JEWELLERY;
3062             else if (ch == "runes")
3063                 dump_item_origins |= IODS_RUNES;
3064             else if (ch == "rods")
3065                 dump_item_origins |= IODS_RODS;
3066             else if (ch == "staves")
3067                 dump_item_origins |= IODS_STAVES;
3068             else if (ch == "books")
3069                 dump_item_origins |= IODS_BOOKS;
3070             else if (ch == "all" || ch == "everything")
3071                 dump_item_origins = IODS_EVERYTHING;
3072         }
3073     }
3074     else if (key == "dump_item_origin_price")
3075     {
3076         dump_item_origin_price = atoi(field.c_str());
3077         if (dump_item_origin_price < -1)
3078             dump_item_origin_price = -1;
3079     }
3080     else BOOL_OPTION(dump_book_spells);
3081     else BOOL_OPTION(level_map_title);
3082     else if (key == "target_unshifted_dirs")
3083     {
3084         target_unshifted_dirs = _read_bool(field, target_unshifted_dirs);
3085         if (target_unshifted_dirs)
3086             default_target = false;
3087     }
3088     else if (key == "darken_beyond_range")
3089         darken_beyond_range = _read_bool(field, darken_beyond_range);
3090     else if (key == "drop_mode")
3091     {
3092         if (field.find("multi") != std::string::npos)
3093             drop_mode = DM_MULTI;
3094         else
3095             drop_mode = DM_SINGLE;
3096     }
3097     else if (key == "pickup_mode")
3098     {
3099         if (field.find("multi") != std::string::npos)
3100             pickup_mode = 0;
3101         else if (field.find("single") != std::string::npos)
3102             pickup_mode = -1;
3103         else
3104             pickup_mode = _read_bool_or_number(field, pickup_mode, "auto:");
3105     }
3106     else if (key == "additional_macro_file")
3107     {
3108         const std::string resolved = resolve_include(orig_field, "macro ");
3109         if (!resolved.empty())
3110             additional_macro_files.push_back(resolved);
3111     }
3112 #ifdef USE_TILE
3113     else if (key == "tile_show_items")
3114         tile_show_items = field;
3115     else BOOL_OPTION(tile_skip_title);
3116     else BOOL_OPTION(tile_menu_icons);
3117 #endif
3118 #ifdef USE_TILE_LOCAL
3119     else if (key == "tile_player_col")
3120         tile_player_col = str_to_tile_colour(field);
3121     else if (key == "tile_monster_col")
3122         tile_monster_col = str_to_tile_colour(field);
3123     else if (key == "tile_neutral_col")
3124         tile_neutral_col = str_to_tile_colour(field);
3125     else if (key == "tile_friendly_col")
3126         tile_friendly_col = str_to_tile_colour(field);
3127     else if (key == "tile_plant_col")
3128         tile_plant_col = str_to_tile_colour(field);
3129     else if (key == "tile_item_col")
3130         tile_item_col = str_to_tile_colour(field);
3131     else if (key == "tile_unseen_col")
3132         tile_unseen_col = str_to_tile_colour(field);
3133     else if (key == "tile_floor_col")
3134         tile_floor_col = str_to_tile_colour(field);
3135     else if (key == "tile_wall_col")
3136         tile_wall_col = str_to_tile_colour(field);
3137     else if (key == "tile_mapped_wall_col")
3138         tile_mapped_wall_col = str_to_tile_colour(field);
3139     else if (key == "tile_door_col")
3140         tile_door_col = str_to_tile_colour(field);
3141     else if (key == "tile_downstairs_col")
3142         tile_downstairs_col = str_to_tile_colour(field);
3143     else if (key == "tile_upstairs_col")
3144         tile_upstairs_col = str_to_tile_colour(field);
3145     else if (key == "tile_feature_col")
3146         tile_feature_col = str_to_tile_colour(field);
3147     else if (key == "tile_trap_col")
3148         tile_trap_col = str_to_tile_colour(field);
3149     else if (key == "tile_water_col")
3150         tile_water_col = str_to_tile_colour(field);
3151     else if (key == "tile_lava_col")
3152         tile_lava_col = str_to_tile_colour(field);
3153     else if (key == "tile_excluded_col")
3154         tile_excluded_col = str_to_tile_colour(field);
3155     else if (key == "tile_excl_centre_col" || key == "tile_excl_center_col")
3156         tile_excl_centre_col = str_to_tile_colour(field);
3157     else if (key == "tile_window_col")
3158         tile_window_col = str_to_tile_colour(field);
3159     else if (key == "tile_font_crt_file")
3160         tile_font_crt_file = field;
3161     else INT_OPTION(tile_font_crt_size, 1, INT_MAX);
3162     else if (key == "tile_font_msg_file")
3163         tile_font_msg_file = field;
3164     else INT_OPTION(tile_font_msg_size, 1, INT_MAX);
3165     else if (key == "tile_font_stat_file")
3166         tile_font_stat_file = field;
3167     else INT_OPTION(tile_font_stat_size, 1, INT_MAX);
3168     else if (key == "tile_font_tip_file")
3169         tile_font_tip_file = field;
3170     else INT_OPTION(tile_font_tip_size, 1, INT_MAX);
3171     else if (key == "tile_font_lbl_file")
3172         tile_font_lbl_file = field;
3173     else INT_OPTION(tile_font_lbl_size, 1, INT_MAX);
3174 #ifdef USE_FT
3175     else BOOL_OPTION(tile_font_ft_light);
3176 #endif
3177     else INT_OPTION(tile_key_repeat_delay, 0, INT_MAX);
3178     else if (key == "tile_full_screen")
3179         tile_full_screen = (screen_mode)_read_bool(field, tile_full_screen);
3180     else INT_OPTION(tile_window_width, INT_MIN, INT_MAX);
3181     else INT_OPTION(tile_window_height, INT_MIN, INT_MAX);
3182     else INT_OPTION(tile_map_pixels, 1, INT_MAX);
3183 #endif // USE_TILE_LOCAL
3184 #ifdef USE_TILE
3185     else BOOL_OPTION(tile_force_overlay);
3186     else INT_OPTION(tile_tooltip_ms, 0, INT_MAX);
3187     else INT_OPTION(tile_update_rate, 50, INT_MAX);
3188     else INT_OPTION(tile_runrest_rate, 0, INT_MAX);
3189     else BOOL_OPTION(tile_show_minihealthbar);
3190     else BOOL_OPTION(tile_show_minimagicbar);
3191     else BOOL_OPTION(tile_show_demon_tier);
3192     else BOOL_OPTION(tile_force_regenerate_levels);
3193     else if (key == "tile_layout_priority")
3194         tile_layout_priority = split_string(",", field.c_str());
3195     else if (key == "tile_tag_pref")
3196         tile_tag_pref = _str_to_tag_pref(field.c_str());
3197 #endif
3198
3199     else if (key == "bindkey")
3200         _bindkey(field);
3201     else if (key == "constant")
3202     {
3203         if (variables.find(field) == variables.end())
3204             report_error("No variable named '%s' to make constant", field.c_str());
3205         else if (constants.find(field) != constants.end())
3206             report_error("'%s' is already a constant", field.c_str());
3207         else
3208             constants.insert(field);
3209     }
3210     else INT_OPTION(arena_delay, 0, INT_MAX);
3211     else BOOL_OPTION(arena_dump_msgs);
3212     else BOOL_OPTION(arena_dump_msgs_all);
3213     else BOOL_OPTION(arena_list_eq);
3214
3215     // Catch-all else, copies option into map
3216     else if (runscript)
3217     {
3218 #ifdef CLUA_BINDINGS
3219         if (!clua.callbooleanfn(false, "c_process_lua_option", "ss",
3220                         key.c_str(), orig_field.c_str()))
3221 #endif
3222         {
3223 #ifdef CLUA_BINDINGS
3224             if (!clua.error.empty())
3225                 mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
3226 #endif
3227             named_options[key] = orig_field;
3228         }
3229     }
3230 }
3231
3232 // Checks an include file name for safety and resolves it to a readable path.
3233 // If safety check fails, throws a string with the reason for failure.
3234 // If file cannot be resolved, returns the empty string (this does not throw!)
3235 // If file can be resolved, returns the resolved path.
3236 std::string game_options::resolve_include(
3237     std::string parent_file,
3238     std::string included_file,
3239     const std::vector<std::string> *rcdirs)
3240
3241     throw (std::string)
3242 {
3243     // Before we start, make sure we convert forward slashes to the platform's
3244     // favoured file separator.
3245     parent_file   = canonicalise_file_separator(parent_file);
3246     included_file = canonicalise_file_separator(included_file);
3247
3248     // How we resolve include paths:
3249     // 1. If it's an absolute path, use it directly.
3250     // 2. Try the name relative to the parent filename, if supplied.
3251     // 3. Try the name relative to any of the provided rcdirs.
3252     // 4. Try locating the name as a regular data file (also checks for the
3253     //    file name relative to the current directory).
3254     // 5. Fail, and return empty string.
3255
3256     assert_read_safe_path(included_file);
3257
3258     // There's only so much you can do with an absolute path.
3259     // Note: absolute paths can only get here if we're not on a
3260     // multiuser system. On multiuser systems assert_read_safe_path()
3261     // will throw an exception if it sees absolute paths.
3262     if (is_absolute_path(included_file))
3263         return file_exists(included_file)? included_file : "";
3264
3265     if (!parent_file.empty())
3266     {
3267         const std::string candidate =
3268             get_path_relative_to(parent_file, included_file);
3269         if (file_exists(candidate))
3270             return candidate;
3271     }
3272
3273     if (rcdirs)
3274     {
3275         const std::vector<std::string> &dirs(*rcdirs);
3276         for (int i = 0, size = dirs.size(); i < size; ++i)
3277         {
3278             const std::string candidate(catpath(dirs[i], included_file));
3279             if (file_exists(candidate))
3280                 return candidate;
3281         }
3282     }
3283
3284     return datafile_path(included_file, false, true);
3285 }
3286
3287 std::string game_options::resolve_include(const std::string &file,
3288                                            const char *type)
3289 {
3290     try
3291     {
3292         const std::string resolved =
3293             resolve_include(filename, file, &SysEnv.rcdirs);
3294
3295         if (resolved.empty())
3296             report_error("Cannot find %sfile \"%s\".", type, file.c_str());
3297         return resolved;
3298     }
3299     catch (const std::string &err)
3300     {
3301         report_error("Cannot include %sfile: %s", type, err.c_str());
3302         return "";
3303     }
3304 }
3305
3306 bool game_options::was_included(const std::string &file) const
3307 {
3308     return (included.find(file) != included.end());
3309 }
3310
3311 void game_options::include(const std::string &rawfilename,
3312                            bool resolve,
3313                            bool runscript)
3314 {
3315     const std::string include_file =
3316         resolve ? resolve_include(rawfilename) : rawfilename;
3317
3318     if (was_included(include_file))
3319         return;
3320
3321     included.insert(include_file);
3322
3323     // Change filename to the included filename while we're reading it.
3324     unwind_var<std::string> optfile(filename, include_file);
3325     unwind_var<std::string> basefile(basefilename, rawfilename);
3326
3327     // Save current line number
3328     unwind_var<int> currlinenum(line_num, 0);
3329
3330     // Also unwind any aliases defined in included files.
3331     unwind_var<string_map> unwalias(aliases);
3332
3333     FileLineInput fl(include_file.c_str());
3334     if (!fl.error())
3335         read_options(fl, runscript, false);
3336 }
3337
3338 void game_options::report_error(const char* format, ...)
3339 {
3340     va_list args;
3341     va_start(args, format);
3342     std::string error = vmake_stringf(format, args);
3343     va_end(args);
3344
3345     // If called before game starts, log a startup error,
3346     // otherwise spam the warning channel.
3347     if (crawl_state.need_save)
3348     {
3349         mprf(MSGCH_ERROR, "Warning: %s (%s:%d)", error.c_str(),
3350              basefilename.c_str(), line_num);
3351     }
3352     else
3353     {
3354         crawl_state.add_startup_error(make_stringf("%s (%s:%d)",
3355                                                    error.c_str(),
3356                                                    basefilename.c_str(),
3357                                                    line_num));
3358     }
3359 }
3360
3361 static std::string check_string(const char *s)
3362 {
3363     return s? s : "";
3364 }
3365
3366 void get_system_environment(void)
3367 {
3368     // The player's name
3369     SysEnv.crawl_name = check_string(getenv("CRAWL_NAME"));
3370
3371     // The directory which contians init.txt, macro.txt, morgue.txt
3372     // This should end with the appropriate path delimiter.
3373     SysEnv.crawl_dir = check_string(getenv("CRAWL_DIR"));
3374
3375 #ifdef SAVE_DIR_PATH
3376     if (SysEnv.crawl_dir == "")
3377         SysEnv.crawl_dir = SAVE_DIR_PATH;
3378 #endif
3379
3380 #ifdef DGL_SIMPLE_MESSAGING
3381     // Enable DGL_SIMPLE_MESSAGING only if SIMPLEMAIL and MAIL are set.
3382     const char *simplemail = getenv("SIMPLEMAIL");
3383     if (simplemail && strcmp(simplemail, "0"))
3384     {
3385         const char *mail = getenv("MAIL");
3386         SysEnv.messagefile = mail? mail : "";
3387     }
3388 #endif
3389
3390     // The full path to the init file -- this overrides CRAWL_DIR.
3391     SysEnv.crawl_rc = check_string(getenv("CRAWL_RC"));
3392
3393 #ifdef UNIX
3394     // The user's home directory (used to look for ~/.crawlrc file)
3395     SysEnv.home = check_string(getenv("HOME"));
3396 #endif
3397 }
3398
3399 static void set_crawl_base_dir(const char *arg)
3400 {
3401     if (!arg)
3402         return;
3403
3404     SysEnv.crawl_base = get_parent_directory(arg);
3405 }
3406
3407 // parse args, filling in Options and game environment as we go.
3408 // returns true if no unknown or malformed arguments were found.
3409
3410 // Keep this in sync with the option names.
3411 enum commandline_option_type
3412 {
3413     CLO_SCORES,
3414     CLO_NAME,
3415     CLO_RACE,
3416     CLO_CLASS,
3417     CLO_PLAIN,
3418     CLO_DIR,
3419     CLO_RC,
3420     CLO_RCDIR,
3421     CLO_TSCORES,
3422     CLO_VSCORES,
3423     CLO_SCOREFILE,
3424     CLO_MORGUE,
3425     CLO_MACRO,
3426     CLO_MAPSTAT,
3427     CLO_ARENA,
3428     CLO_DUMP_MAPS,
3429     CLO_TEST,
3430     CLO_SCRIPT,
3431     CLO_BUILDDB,
3432     CLO_HELP,
3433     CLO_VERSION,
3434     CLO_SEED,
3435     CLO_SAVE_VERSION,
3436     CLO_SPRINT,
3437     CLO_EXTRA_OPT_FIRST,
3438     CLO_EXTRA_OPT_LAST,
3439     CLO_SPRINT_MAP,
3440     CLO_EDIT_SAVE,
3441     CLO_PRINT_CHARSET,
3442     CLO_ZOTDEF,
3443     CLO_TUTORIAL,
3444     CLO_WIZARD,
3445     CLO_NO_SAVE,
3446 #ifdef USE_TILE_WEB
3447     CLO_WEBTILES_SOCKET,
3448     CLO_AWAIT_CONNECTION,
3449 #endif
3450
3451     CLO_NOPS
3452 };
3453
3454 static const char *cmd_ops[] = {
3455     "scores", "name", "species", "background", "plain", "dir", "rc",
3456     "rcdir", "tscores", "vscores", "scorefile", "morgue", "macro",
3457     "mapstat", "arena", "dump-maps", "test", "script", "builddb",
3458     "help", "version", "seed", "save-version", "sprint",
3459     "extra-opt-first", "extra-opt-last", "sprint-map", "edit-save",
3460     "print-charset", "zotdef", "tutorial", "wizard", "no-save",
3461 #ifdef USE_TILE_WEB
3462     "webtiles-socket", "await-connection",
3463 #endif
3464 };
3465
3466 static const int num_cmd_ops = CLO_NOPS;
3467 static bool arg_seen[num_cmd_ops];
3468
3469 static std::string _find_executable_path()
3470 {
3471     // A lot of OSes give ways to find the location of the running app's
3472     // binary executable. This is useful, because argv[0] can be relative
3473     // when we really need an absolute path in order to locate the game's
3474     // resources.
3475 #if defined ( TARGET_OS_WINDOWS )
3476     wchar_t tempPath[MAX_PATH];
3477     if (GetModuleFileNameW(NULL, tempPath, MAX_PATH))
3478         return utf16_to_8(tempPath);
3479     else
3480         return "";
3481 #elif defined ( TARGET_OS_LINUX )
3482     char tempPath[2048];
3483     const ssize_t rsize =
3484         readlink("/proc/self/exe", tempPath, sizeof(tempPath) - 1);
3485     if (rsize > 0)
3486     {
3487         tempPath[rsize] = 0;
3488         return mb_to_utf8(tempPath);
3489     }
3490     return "";
3491 #elif defined ( TARGET_OS_MACOSX )
3492     return mb_to_utf8(NXArgv[0]);
3493 #else
3494     // We don't know how to find the executable's path on this OS.
3495     return "";
3496 #endif
3497 }
3498
3499 static void _print_version()
3500 {
3501     printf("Crawl version %s%s", Version::Long().c_str(), "\n");
3502     printf("Save file version %d.%d%s", TAG_MAJOR_VERSION, TAG_MINOR_VERSION, "\n");
3503     printf("%s", compilation_info().c_str());
3504 }
3505
3506 static void _print_save_version(char *name)
3507 {
3508     try
3509     {
3510         std::string filename = name;
3511         // Check for the exact filename first, then go by char name.
3512         if (!file_exists(filename))
3513             filename = get_savedir_filename(filename);
3514         package save(filename.c_str(), false);
3515         reader chrf(&save, "chr");
3516
3517         int major, minor;
3518         if (!get_save_version(chrf, major, minor))
3519             fail("Save file is invalid.");
3520         else
3521             printf("Save file version for %s is %d.%d\n", name, major, minor);
3522     }
3523     catch (ext_fail_exception &fe)
3524     {
3525         fprintf(stderr, "Error: %s\n", fe.msg.c_str());
3526     }
3527 }
3528
3529 enum es_command_type
3530 {
3531     ES_LS,
3532     ES_RM,
3533     ES_GET,
3534     ES_PUT,
3535     ES_REPACK,
3536     ES_INFO,
3537     NUM_ES
3538 };
3539
3540 static struct es_command
3541 {
3542     es_command_type cmd;
3543     const char* name;
3544     bool rw;
3545     int min_args, max_args;
3546 } es_commands[] =
3547 {
3548     { ES_LS,      "ls",      false, 0, 0, },
3549     { ES_GET,     "get",     false, 1, 2, },
3550     { ES_PUT,     "put",     true,  1, 2, },
3551     { ES_RM,      "rm",      true,  1, 1, },
3552     { ES_REPACK,  "repack",  false, 0, 0, },
3553     { ES_INFO,    "info",    false, 0, 0, },
3554 };
3555
3556 #define ERR(...) do { fprintf(stderr, __VA_ARGS__); return; } while (0)
3557 static void _edit_save(int argc, char **argv)
3558 {
3559     if (argc <= 1 || !strcmp(argv[1], "help"))
3560     {
3561         printf("Usage: crawl --edit-save <name> <command>, where <command> may be:\n"
3562                "  ls                        list the chunks\n"
3563                "  get <chunk> [<file>]      write a chunk from <file> (default \"chunk\", \"-\" for stdout)\n"
3564                "  put <chunk> [<file>]      extract a chunk to <file> (default \"chunk\", \"-\" for stdin)\n"
3565                "  rm <chunk>                delete a chunk\n"
3566                "  repack                    defrag and reclaim unused space\n"
3567              );
3568         return;
3569     }
3570     const char *name = argv[0];
3571     const char *cmdn = argv[1];
3572
3573     es_command_type cmd = NUM_ES;
3574     bool rw;
3575
3576     for (unsigned int nc = 0; nc < ARRAYSZ(es_commands); nc++)
3577         if (!strcmp(es_commands[nc].name, cmdn))
3578         {
3579             if (argc < es_commands[nc].min_args + 2)
3580                 ERR("Too few arguments for %s.\n", cmdn);
3581             else if (argc > es_commands[nc].max_args + 2)
3582                 ERR("Too many arguments for %s.\n", cmdn);
3583             cmd = es_commands[nc].cmd;
3584             rw = es_commands[nc].rw;
3585             break;
3586         }
3587     if (cmd == NUM_ES)
3588         ERR("Unknown command: %s.\n", cmdn);
3589
3590     try
3591     {
3592         std::string filename = name;
3593         // Check for the exact filename first, then go by char name.
3594         if (!file_exists(filename))
3595             filename = get_savedir_filename(filename);
3596         package save(filename.c_str(), rw);
3597
3598         if (cmd == ES_LS)
3599         {
3600             std::vector<std::string> list = save.list_chunks();
3601             std::sort(list.begin(), list.end(), numcmpstr);
3602             for (size_t i = 0; i < list.size(); i++)
3603                 printf("%s\n", list[i].c_str());
3604         }
3605         else if (cmd == ES_GET)
3606         {
3607             const char *chunk = argv[2];
3608             if (!*chunk || strlen(chunk) > MAX_CHUNK_NAME_LENGTH)
3609                 ERR("Invalid chunk name \"%s\".\n", chunk);
3610             if (!save.has_chunk(chunk))
3611                 ERR("No such chunk in the save file.\n");
3612             chunk_reader inc(&save, chunk);
3613
3614             const char *file = (argc == 4) ? argv[3] : "chunk";
3615             FILE *f;
3616             if (strcmp(file, "-"))
3617                 f = fopen_u(file, "wb");
3618             else
3619                 f = stdout;
3620             if (!f)
3621                 sysfail("Can't open \"%s\" for writing", file);
3622
3623             char buf[16384];
3624             while (size_t s = inc.read(buf, sizeof(buf)))
3625                 if (fwrite(buf, 1, s, f) != s)
3626                     sysfail("Error writing \"%s\"", file);
3627
3628             if (f != stdout)
3629                 if (fclose(f))
3630                     sysfail("Write error on close of \"%s\"", file);
3631         }
3632         else if (cmd == ES_PUT)
3633         {
3634             const char *chunk = argv[2];
3635             if (!*chunk || strlen(chunk) > MAX_CHUNK_NAME_LENGTH)
3636                 ERR("Invalid chunk name \"%s\".\n", chunk);
3637
3638             const char *file = (argc == 4) ? argv[3] : "chunk";
3639             FILE *f;
3640             if (strcmp(file, "-"))
3641                 f = fopen_u(file, "rb");
3642             else
3643                 f = stdin;
3644             if (!f)
3645                 sysfail("Can't read \"%s\"", file);
3646             chunk_writer outc(&save, chunk);
3647
3648             char buf[16384];
3649             while (size_t s = fread(buf, 1, sizeof(buf), f))
3650                 outc.write(buf, s);
3651             if (ferror(f))
3652                 sysfail("Error reading \"%s\"", file);
3653
3654             if (f != stdin)
3655                 fclose(f);
3656         }
3657         else if (cmd == ES_RM)
3658         {
3659             const char *chunk = argv[2];
3660             if (!*chunk || strlen(chunk) > MAX_CHUNK_NAME_LENGTH)
3661                 ERR("Invalid chunk name \"%s\".\n", chunk);
3662             if (!save.has_chunk(chunk))
3663                 ERR("No such chunk in the save file.\n");
3664
3665             save.delete_chunk(chunk);
3666         }
3667         else if (cmd == ES_REPACK)
3668         {
3669             package save2((filename + ".tmp").c_str(), true, true);
3670             std::vector<std::string> list = save.list_chunks();
3671             for (size_t i = 0; i < list.size(); i++)
3672             {
3673                 char buf[16384];
3674
3675                 chunk_reader in(&save, list[i]);
3676                 chunk_writer out(&save2, list[i]);
3677
3678                 while (len_t s = in.read(buf, sizeof(buf)))
3679                     out.write(buf, s);
3680             }
3681             save2.commit();
3682             save.unlink();
3683             rename_u((filename + ".tmp").c_str(), filename.c_str());
3684         }
3685         else if (cmd == ES_INFO)
3686         {
3687             std::vector<std::string> list = save.list_chunks();
3688             std::sort(list.begin(), list.end(), numcmpstr);
3689             len_t nchunks = list.size();
3690             len_t frag = save.get_chunk_fragmentation("");
3691             len_t flen = save.get_size();
3692             len_t slack = save.get_slack();
3693             printf("Chunks: (size compressed/uncompressed, fragments, name)\n");
3694             for (size_t i = 0; i < list.size(); i++)
3695             {
3696                 int cfrag = save.get_chunk_fragmentation(list[i]);
3697                 frag += cfrag;
3698                 int cclen = save.get_chunk_compressed_length(list[i]);
3699
3700                 char buf[16384];
3701                 chunk_reader in(&save, list[i]);
3702                 len_t clen = 0;
3703                 while (len_t s = in.read(buf, sizeof(buf)))
3704                     clen += s;
3705                 printf("%7u/%7u %3u %s\n", cclen, clen, cfrag, list[i].c_str());
3706             }
3707             // the directory is not a chunk visible from the outside
3708             printf("Fragmentation:    %u/%u (%4.2f)\n", frag, nchunks + 1,
3709                    ((float)frag) / (nchunks + 1));
3710             printf("Unused space:     %u/%u (%u%%)\n", slack, flen,
3711                    100 - (100 * (flen - slack) / flen));
3712             // there's also wasted space due to fragmentation, but since
3713             // it's linear, there's no need to print it
3714         }
3715     }
3716     catch (ext_fail_exception &fe)
3717     {
3718         fprintf(stderr, "Error: %s\n", fe.msg.c_str());
3719     }
3720 }
3721 #undef ERR
3722
3723 static bool _check_extra_opt(char* _opt)
3724 {
3725     std::string opt(_opt);
3726     trim_string(opt);
3727
3728     if (opt[0] == ':' || opt[0] == '<' || opt[0] == '{'
3729         || starts_with(opt, "L<") || starts_with(opt, "Lua{"))
3730     {
3731         fprintf(stderr, "An extra option can't use Lua (%s)\n",
3732                 _opt);
3733         return false;
3734     }
3735
3736     if (opt[0] == '#')
3737     {
3738         fprintf(stderr, "An extra option can't be a comment (%s)\n",
3739                 _opt);
3740         return false;
3741     }
3742
3743     if (opt.find_first_of('=') == std::string::npos)
3744     {
3745         fprintf(stderr, "An extra opt must contain a '=' (%s)\n",
3746                 _opt);
3747         return false;
3748     }
3749
3750     std::vector<std::string> parts = split_string(opt, "=");
3751     if (opt.find_first_of('=') == 0 || parts[0].length() == 0)
3752     {
3753         fprintf(stderr, "An extra opt must have an option name (%s)\n",
3754                 _opt);
3755         return false;
3756     }
3757
3758     return true;
3759 }
3760
3761 bool parse_args(int argc, char **argv, bool rc_only)
3762 {
3763     COMPILE_CHECK(ARRAYSZ(cmd_ops) == CLO_NOPS);
3764
3765     if (crawl_state.command_line_arguments.empty())
3766     {
3767         crawl_state.command_line_arguments.insert(
3768             crawl_state.command_line_arguments.end(),
3769             argv, argv + argc);
3770     }
3771
3772     std::string exe_path = _find_executable_path();
3773
3774     if (!exe_path.empty())
3775         set_crawl_base_dir(exe_path.c_str());
3776     else
3777         set_crawl_base_dir(argv[0]);
3778
3779     SysEnv.crawl_exe = get_base_filename(argv[0]);
3780
3781     SysEnv.rcdirs.clear();
3782
3783     if (argc < 2)           // no args!
3784         return true;
3785
3786     char *arg, *next_arg;
3787     int current = 1;
3788     bool nextUsed = false;
3789     int ecount;
3790
3791     // initialise
3792     for (int i = 0; i < num_cmd_ops; i++)
3793          arg_seen[i] = false;
3794
3795     if (SysEnv.cmd_args.empty())
3796     {
3797         for (int i = 1; i < argc; ++i)
3798             SysEnv.cmd_args.push_back(argv[i]);
3799     }
3800
3801     while (current < argc)
3802     {
3803         // get argument
3804         arg = argv[current];
3805
3806         // next argument (if there is one)
3807         if (current+1 < argc)
3808             next_arg = argv[current+1];
3809         else
3810             next_arg = NULL;
3811
3812         nextUsed = false;
3813
3814         // arg MUST begin with '-'
3815         char c = arg[0];
3816         if (c != '-')
3817         {
3818             fprintf(stderr,
3819                     "Option '%s' is invalid; options must be prefixed "
3820                     "with -\n\n", arg);
3821             return false;
3822         }
3823
3824         // Look for match (we accept both -option and --option).
3825         if (arg[1] == '-')
3826             arg = &arg[2];
3827         else
3828             arg = &arg[1];
3829
3830         int o;
3831         for (o = 0; o < num_cmd_ops; o++)
3832             if (strcasecmp(cmd_ops[o], arg) == 0)
3833                 break;
3834
3835         // Print the list of commandline options for "--help".
3836         if (o == CLO_HELP)
3837             return false;
3838
3839         if (o == num_cmd_ops)
3840         {
3841             fprintf(stderr,
3842                     "Unknown option: %s\n\n", argv[current]);
3843             return false;
3844         }
3845
3846         // Disallow options specified more than once.
3847         if (arg_seen[o] == true)
3848             return false;
3849
3850         // Set arg to 'seen'.
3851         arg_seen[o] = true;
3852
3853         // Partially parse next argument.
3854         bool next_is_param = false;
3855         if (next_arg != NULL
3856             && (next_arg[0] != '-' || strlen(next_arg) == 1))
3857         {
3858             next_is_param = true;
3859         }
3860
3861         // Take action according to the cmd chosen.
3862         switch (o)
3863         {
3864         case CLO_SCORES:
3865         case CLO_TSCORES:
3866         case CLO_VSCORES:
3867             if (!next_is_param)
3868                 ecount = -1;            // default
3869             else // optional number given
3870             {
3871                 ecount = atoi(next_arg);
3872                 if (ecount < 1)
3873                     ecount = 1;
3874
3875                 if (ecount > SCORE_FILE_ENTRIES)
3876                     ecount = SCORE_FILE_ENTRIES;
3877
3878                 nextUsed = true;
3879             }
3880
3881             if (!rc_only)
3882             {
3883                 Options.sc_entries = ecount;
3884
3885                 if (o == CLO_TSCORES)
3886                     Options.sc_format = SCORE_TERSE;
3887                 else if (o == CLO_VSCORES)
3888                     Options.sc_format = SCORE_VERBOSE;
3889                 else if (o == CLO_SCORES)
3890                     Options.sc_format = SCORE_REGULAR;
3891             }
3892             break;
3893
3894         case CLO_MAPSTAT:
3895             crawl_state.map_stat_gen = true;
3896             if (next_is_param)
3897             {
3898                 SysEnv.map_gen_iters = atoi(next_arg);
3899                 if (SysEnv.map_gen_iters < 1)
3900                     SysEnv.map_gen_iters = 1;
3901                 else if (SysEnv.map_gen_iters > 10000)
3902                     SysEnv.map_gen_iters = 10000;
3903                 nextUsed = true;
3904             }
3905             else
3906                 SysEnv.map_gen_iters = 100;
3907             break;
3908
3909         case CLO_ARENA:
3910             if (!rc_only)
3911             {
3912                 Options.game.type = GAME_TYPE_ARENA;
3913                 Options.restart_after_game = false;
3914             }
3915             if (next_is_param)
3916             {
3917                 if (!rc_only)
3918                     Options.game.arena_teams = next_arg;
3919                 nextUsed = true;
3920             }
3921             break;
3922
3923         case CLO_DUMP_MAPS:
3924             crawl_state.dump_maps = true;
3925             break;
3926
3927         case CLO_TEST:
3928             crawl_state.test = true;
3929             if (next_is_param)
3930             {
3931                 crawl_state.tests_selected = split_string(",", next_arg);
3932                 nextUsed = true;
3933             }
3934             break;
3935
3936         case CLO_SCRIPT:
3937             crawl_state.test   = true;
3938             crawl_state.script = true;
3939             if (current < argc - 1)
3940             {
3941                 crawl_state.tests_selected = split_string(",", next_arg);
3942                 for (int extra = current + 2; extra < argc; ++extra)
3943                     crawl_state.script_args.push_back(argv[extra]);
3944                 current = argc;
3945             }
3946             else
3947                 end(1, false,
3948                     "-script must specify comma-separated script names");
3949             break;
3950
3951         case CLO_BUILDDB:
3952             if (next_is_param)
3953                 return false;
3954             crawl_state.build_db = true;
3955             break;
3956
3957         case CLO_MACRO:
3958             if (!next_is_param)
3959                 return false;
3960             SysEnv.macro_dir = next_arg;
3961             nextUsed = true;
3962             break;
3963
3964         case CLO_MORGUE:
3965             if (!next_is_param)
3966                 return false;
3967             if (!rc_only)
3968                 SysEnv.morgue_dir = next_arg;
3969             nextUsed = true;
3970             break;
3971
3972         case CLO_SCOREFILE:
3973             if (!next_is_param)
3974                 return false;
3975             if (!rc_only)
3976                 SysEnv.scorefile = next_arg;
3977             nextUsed = true;
3978             break;
3979
3980         case CLO_NAME:
3981             if (!next_is_param)
3982                 return false;
3983             if (!rc_only)
3984                 Options.game.name = next_arg;
3985             nextUsed = true;
3986             break;
3987
3988         case CLO_RACE:
3989         case CLO_CLASS:
3990             if (!next_is_param)
3991                 return false;
3992
3993             if (!rc_only)
3994             {
3995                 if (o == 2)
3996                     Options.game.species = _str_to_species(std::string(next_arg));
3997
3998                 if (o == 3)
3999                     Options.game.job = _str_to_job(std::string(next_arg));
4000             }
4001             nextUsed = true;
4002             break;
4003
4004         case CLO_PLAIN:
4005             if (next_is_param)
4006                 return false;
4007
4008             if (!rc_only)
4009             {
4010                 Options.char_set = CSET_ASCII;
4011                 init_char_table(Options.char_set);
4012             }
4013             break;
4014
4015         case CLO_RCDIR:
4016             // Always parse.
4017             if (!next_is_param)
4018                 return false;
4019
4020             SysEnv.add_rcdir(next_arg);
4021             nextUsed = true;
4022             break;
4023
4024         case CLO_DIR:
4025             // Always parse.
4026             if (!next_is_param)
4027                 return false;
4028
4029             SysEnv.crawl_dir = next_arg;
4030             nextUsed = true;
4031             break;
4032
4033         case CLO_RC:
4034             // Always parse.
4035             if (!next_is_param)
4036                 return false;
4037
4038             SysEnv.crawl_rc = next_arg;
4039             nextUsed = true;
4040             break;
4041
4042         case CLO_HELP:
4043             // Shouldn't happen.
4044             return false;
4045
4046         case CLO_VERSION:
4047             _print_version();
4048             end(0);
4049
4050         case CLO_SAVE_VERSION:
4051             // Always parse.
4052             if (!next_is_param)
4053                 return false;
4054
4055             _print_save_version(next_arg);
4056             end(0);
4057
4058         case CLO_EDIT_SAVE:
4059             // Always parse.
4060             if (!next_is_param)
4061                 return false;
4062
4063             _edit_save(argc - current - 1, argv + current + 1);
4064             end(0);
4065
4066         case CLO_SEED:
4067             if (!next_is_param)
4068                 return false;
4069
4070             if (!sscanf(next_arg, "%x", &Options.seed))
4071                 return false;
4072             nextUsed = true;
4073             break;
4074
4075         case CLO_SPRINT:
4076             if (!rc_only)
4077                 Options.game.type = GAME_TYPE_SPRINT;
4078             break;
4079
4080         case CLO_SPRINT_MAP:
4081             if (!next_is_param)
4082                 return false;
4083
4084             nextUsed               = true;
4085             crawl_state.sprint_map = next_arg;
4086             Options.game.map       = next_arg;
4087             break;
4088
4089         case CLO_ZOTDEF:
4090             if (!rc_only)
4091                 Options.game.type = GAME_TYPE_ZOTDEF;
4092             break;
4093
4094         case CLO_TUTORIAL:
4095             if (!rc_only)
4096                 Options.game.type = GAME_TYPE_TUTORIAL;
4097             break;
4098
4099         case CLO_WIZARD:
4100 #ifdef WIZARD
4101             if (!rc_only)
4102                 Options.wiz_mode = WIZ_NO;
4103 #endif
4104             break;
4105
4106         case CLO_NO_SAVE:
4107 #ifdef WIZARD
4108             if (!rc_only)
4109                 Options.no_save = true;
4110 #endif
4111             break;
4112
4113 #ifdef USE_TILE_WEB
4114         case CLO_WEBTILES_SOCKET:
4115             nextUsed          = true;
4116             tiles.m_sock_name = next_arg;
4117             break;
4118
4119         case CLO_AWAIT_CONNECTION:
4120             tiles.m_await_connection = true;