3 * @brief Simple reading of an init file and system variables
27 #include "tilereg-map.h"
52 // For finding the executable's path
53 #ifdef TARGET_OS_WINDOWS
54 #define WIN32_LEAN_AND_MEAN
58 #elif defined (__APPLE__)
60 #elif defined (__linux__)
64 const std::string game_options::interrupt_prefix = "interrupt_";
65 system_environment SysEnv;
68 object_class_type item_class_by_sym(ucs_t c)
85 case '"': // Make the amulet symbol equiv to ring -- bwross
92 case '+': // ??? -- was the only symbol working for tile order up to 0.10,
93 // so keeping it for compat purposes (user configs).
101 return OBJ_MISCELLANY;
111 case '\\': // Compat break: used to be staves (why not '|'?).
114 return NUM_OBJECT_CLASSES;
119 template<class A, class B> static void append_vector(A &dest, const B &src)
121 dest.insert(dest.end(), src.begin(), src.end());
124 // Returns -1 if unmatched else returns 0-15.
125 static msg_colour_type _str_to_channel_colour(const std::string &str)
127 int col = str_to_colour(str);
128 msg_colour_type ret = MSGCOL_NONE;
133 else if (str == "plain" || str == "off")
135 else if (str == "default" || str == "on")
136 ret = MSGCOL_DEFAULT;
137 else if (str == "alternate")
138 ret = MSGCOL_ALTERNATE;
141 ret = msg_colour(str_to_colour(str));
146 static const std::string message_channel_names[] =
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"
156 // returns -1 if unmatched else returns 0--(NUM_MESSAGE_CHANNELS-1)
157 int str_to_channel(const std::string &str)
159 COMPILE_CHECK(ARRAYSZ(message_channel_names) == NUM_MESSAGE_CHANNELS);
161 // widespread aliases
163 return MSGCH_TALK_VISUAL;
164 else if (str == "spell")
165 return MSGCH_MONSTER_SPELL;
167 for (int ret = 0; ret < NUM_MESSAGE_CHANNELS; ret++)
169 if (str == message_channel_names[ret])
176 std::string channel_to_str(int channel)
178 if (channel < 0 || channel >= NUM_MESSAGE_CHANNELS)
181 return message_channel_names[channel];
184 weapon_type str_to_weapon(const std::string &str)
186 if (str == "shortsword" || str == "short sword")
187 return WPN_SHORT_SWORD;
188 else if (str == "falchion")
190 else if (str == "quarterstaff")
191 return WPN_QUARTERSTAFF;
192 else if (str == "mace")
194 else if (str == "spear")
196 else if (str == "trident")
198 else if (str == "hand axe" || str == "handaxe")
200 else if (str == "unarmed" || str == "claws")
202 else if (str == "sling")
204 else if (str == "bow")
206 else if (str == "crossbow")
208 else if (str == "rocks")
210 else if (str == "javelins")
212 else if (str == "darts")
214 else if (str == "random")
220 static std::string _weapon_to_str(int weapon)
224 case WPN_SHORT_SWORD:
225 return "short sword";
228 case WPN_QUARTERSTAFF:
229 return "quarterstaff";
258 // Summon types can be any of mon_summon_type (enum.h), or a relevant summoning
260 int str_to_summon_type(const std::string &str)
263 return MON_SUMM_CLONE;
264 if (str == "animate")
265 return MON_SUMM_ANIMATE;
267 return MON_SUMM_CHAOS;
268 if (str == "miscast")
269 return MON_SUMM_MISCAST;
273 return MON_SUMM_WRATH;
277 return spell_by_name(str);
280 static fire_type _str_to_fire_types(const std::string &str)
282 if (str == "launcher")
283 return FIRE_LAUNCHER;
284 else if (str == "dart")
286 else if (str == "stone")
288 else if (str == "rock")
290 else if (str == "dagger")
292 else if (str == "spear")
294 else if (str == "hand axe" || str == "handaxe" || str == "axe")
295 return FIRE_HAND_AXE;
296 else if (str == "club")
298 else if (str == "javelin")
300 else if (str == "net")
302 else if (str == "return" || str == "returning")
303 return FIRE_RETURNING;
304 else if (str == "inscribed")
305 return FIRE_INSCRIBED;
310 std::string gametype_to_str(game_type type)
314 case GAME_TYPE_NORMAL:
316 case GAME_TYPE_TUTORIAL:
318 case GAME_TYPE_ARENA:
320 case GAME_TYPE_SPRINT:
322 case GAME_TYPE_ZOTDEF:
330 static game_type _str_to_gametype(const std::string& s)
332 for (int i = 0; i < NUM_GAME_TYPE; ++i)
334 game_type t = static_cast<game_type>(i);
335 if (s == gametype_to_str(t))
338 return NUM_GAME_TYPE;
342 static std::string _species_to_str(species_type sp)
346 else if (sp == SP_VIABLE)
349 return species_name(sp);
352 static species_type _str_to_species(const std::string &str)
356 else if (str == "viable")
359 species_type ret = SP_UNKNOWN;
360 if (str.length() == 2) // scan abbreviations
361 ret = get_species_by_abbrev(str.c_str());
363 // if we don't have a match, scan the full names
364 if (ret == SP_UNKNOWN)
365 ret = str_to_species(str);
367 if (ret == SP_UNKNOWN)
368 fprintf(stderr, "Unknown species choice: %s\n", str.c_str());
373 static std::string _job_to_str(job_type job)
375 if (job == JOB_RANDOM)
377 else if (job == JOB_VIABLE)
380 return get_job_name(job);
383 static job_type _str_to_job(const std::string &str)
387 else if (str == "viable")
390 job_type job = JOB_UNKNOWN;
392 if (str.length() == 2) // scan abbreviations
393 job = get_job_by_abbrev(str.c_str());
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());
399 if (job == JOB_UNKNOWN)
400 fprintf(stderr, "Unknown background choice: %s\n", str.c_str());
405 static bool _read_bool(const std::string &field, bool def_value)
407 bool ret = def_value;
409 if (field == "true" || field == "1" || field == "yes")
412 if (field == "false" || field == "0" || field == "no")
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)
426 if (field == "true" || field == "1" || field == "yes")
429 if (field == "false" || field == "0" || field == "no")
432 if (field.find(num_prefix) == 0)
433 ret = atoi(field.c_str() + num_prefix.size());
439 static unsigned curses_attribute(const std::string &field)
441 if (field == "standout") // probably reverses
442 return CHATTR_STANDOUT;
443 else if (field == "bold") // probably brightens fg
445 else if (field == "blink") // probably brightens bg
447 else if (field == "underline")
448 return CHATTR_UNDERLINE;
449 else if (field == "reverse")
450 return CHATTR_REVERSE;
451 else if (field == "dim")
453 else if (field.find("hi:") == 0
454 || field.find("hilite:") == 0
455 || field.find("highlight:") == 0)
457 int col = field.find(":");
458 int colour = str_to_colour(field.substr(col + 1));
460 Options.report_error("Bad highlight string -- %s\n", field.c_str());
462 return CHATTR_HILITE | (colour << 8);
464 else if (field != "none")
465 Options.report_error("Bad colour -- %s\n", field.c_str());
466 return CHATTR_NORMAL;
469 void game_options::str_to_enemy_hp_colour(const std::string &colours)
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]));
477 static FixedVector<const char*, TAGPREF_MAX>
478 tag_prefs("none", "tutorial", "named", "enemy");
480 static tag_pref _str_to_tag_pref(const char *opt)
482 for (int i = 0; i < TAGPREF_MAX; i++)
484 if (!strcasecmp(opt, tag_prefs[i]))
488 return TAGPREF_ENEMY;
492 void game_options::new_dump_fields(const std::string &text, bool add)
494 // Easy; chardump.cc has most of the intelligence.
495 std::vector<std::string> fields = split_string(",", text, true, true);
497 append_vector(dump_order, fields);
500 for (int f = 0, size = fields.size(); f < size; ++f)
501 for (int i = 0, dsize = dump_order.size(); i < dsize; ++i)
503 if (dump_order[i] == fields[f])
505 dump_order.erase(dump_order.begin() + i);
512 void game_options::set_default_activity_interrupts()
514 for (int adelay = 0; adelay < NUM_DELAYS; ++adelay)
515 for (int aint = 0; aint < NUM_AINTERRUPTS; ++aint)
517 activity_interrupts[adelay][aint]
518 = is_delay_interruptible(static_cast<delay_type>(adelay));
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",
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 =",
549 for (int i = 0; default_activity_interrupts[i]; ++i)
550 read_option_line(default_activity_interrupts[i], false);
553 void game_options::clear_activity_interrupts(
554 FixedVector<bool, NUM_AINTERRUPTS> &eints)
556 for (int i = 0; i < NUM_AINTERRUPTS; ++i)
560 void game_options::set_activity_interrupt(
561 FixedVector<bool, NUM_AINTERRUPTS> &eints,
562 const std::string &interrupt)
564 if (interrupt.find(interrupt_prefix) == 0)
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());
571 FixedVector<bool, NUM_AINTERRUPTS> &refints =
572 activity_interrupts[delay];
574 for (int i = 0; i < NUM_AINTERRUPTS; ++i)
581 activity_interrupt_type ai = get_activity_interrupt(interrupt);
582 if (ai == NUM_AINTERRUPTS)
584 return report_error("Delay interrupt name \"%s\" not recognised.\n",
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)
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());
600 std::vector<std::string> interrupts = split_string(",", interrupt_names);
601 FixedVector<bool, NUM_AINTERRUPTS> &eints = activity_interrupts[ delay ];
603 if (remove_interrupts)
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]);
610 for (int i = 0; i < NUM_AINTERRUPTS; ++i)
616 if (!append_interrupts)
617 clear_activity_interrupts(eints);
619 for (int i = 0, size = interrupts.size(); i < size; ++i)
620 set_activity_interrupt(eints, interrupts[i]);
623 eints[AI_FORCE_INTERRUPT] = true;
626 #if defined(DGAMELAUNCH)
627 static std::string _resolve_dir(const char* path, const char* suffix)
629 return catpath(path, "");
633 static std::string _user_home_dir()
635 #ifdef TARGET_OS_WINDOWS
636 wchar_t home[MAX_PATH];
637 if (SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, home))
640 return utf16_to_8(home);
642 const char *home = getenv("HOME");
646 return mb_to_utf8(home);
650 static std::string _user_home_subpath(const std::string subpath)
652 return catpath(_user_home_dir(), subpath);
655 static std::string _resolve_dir(const char* path, const char* suffix)
658 return catpath(std::string(path), suffix);
660 return _user_home_subpath(catpath(path + 1, suffix));
664 void game_options::reset_options()
666 filename = "unknown";
667 basefilename = "unknown";
670 set_default_activity_interrupts();
672 #ifdef DEBUG_DIAGNOSTICS
673 quiet_debug_messages.reset();
676 #if defined(USE_TILE_LOCAL)
677 restart_after_game = true;
679 restart_after_game = false;
682 macro_dir = SysEnv.macro_dir;
684 #if !defined(DGAMELAUNCH)
685 if (macro_dir.empty())
688 macro_dir = _user_home_subpath(".crawl");
690 macro_dir = "settings/";
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;
703 save_dir = _resolve_dir(SysEnv.crawl_dir.c_str(), "saves/");
706 #if !defined(TARGET_OS_MACOSX)
707 morgue_dir = _resolve_dir(SysEnv.crawl_dir.c_str(), "morgue/");
710 #if defined(SHARED_DIR_PATH)
711 shared_dir = _resolve_dir(SHARED_DIR_PATH, "");
713 shared_dir = save_dir;
716 additional_macro_files.clear();
719 #ifdef DGL_SIMPLE_MESSAGING
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;
740 center_on_scroll = false;
741 symmetric_scroll = true;
746 default_friendly_pickup = FRIENDLY_PICKUP_FRIEND;
747 default_manual_training = false;
749 show_newturn_mark = true;
750 show_gold_turns = true;
751 show_game_turns = true;
753 game = newgame_def();
755 remember_name = true;
757 char_set = CSET_DEFAULT;
759 // set it to the .crawlrc default
760 autopickups = ((1 << OBJ_GOLD) |
764 (1 << OBJ_JEWELLERY) |
768 suppress_startup_errors = false;
770 show_inventory_weights = false;
772 show_uncursed = true;
775 equip_unequip = false;
776 confirm_butcher = CONFIRM_AUTO;
777 chunks_autopickup = true;
778 prompt_for_swap = 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;
790 magic_point_warning = 0;
791 default_target = true;
792 autopickup_no_burden = true;
793 skill_focus = SKM_FOCUS_ON;
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;
802 // [ds] Grumble grumble.
805 clear_messages = false;
809 pickup_thrown = true;
813 show_travel_trail = false;
814 travel_stair_cost = 500;
817 arena_dump_msgs = false;
818 arena_dump_msgs_all = false;
819 arena_list_eq = false;
821 // Sort only pickup menus by default.
823 set_menu_sort("pickup: true");
826 tc_excluded = LIGHTMAGENTA;
827 tc_exclude_circle = RED;
829 tc_disconnected = DARKGREY;
831 show_waypoints = true;
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;
839 easy_exit_menu = false;
840 dos_use_background_intensity = true;
842 level_map_title = true;
844 assign_item_slot = SS_FORWARD;
846 // 10 was the cursor step default on Linux.
847 level_map_cursor_step = 7;
849 use_fake_cursor = true;
851 use_fake_cursor = false;
853 use_fake_player_cursor = true;
854 show_player_species = false;
856 stash_tracking = STM_ALL;
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);
863 // The prompt conditions will be combined into explore_stop after
865 explore_stop_prompt = ES_NONE;
867 explore_stop_pickup_ignore.clear();
869 explore_item_greed = 10;
870 explore_greedy = true;
872 explore_wall_bias = 0;
873 explore_improved = false;
874 travel_key_stop = true;
876 target_unshifted_dirs = false;
877 darken_beyond_range = true;
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;
885 drop_mode = DM_MULTI;
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;
893 fire_items_start = 0; // start at slot 'a'
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",
901 item_stack_summary_minimum = 5;
910 // These are only used internally, and only from the commandline:
911 // XXX: These need a better place.
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;
923 no_dark_brand = true;
927 if (wiz_mode != WIZ_NO)
928 wiz_mode = WIZ_NEVER;
936 tile_show_items = "!?/%=([)x}:|\\";
937 tile_skip_title = false;
938 tile_menu_icons = true;
941 #ifdef USE_TILE_LOCAL
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;
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;
977 // TODO: init this from system settings. This would probably require
978 // using fontconfig, but that's planned.
979 tile_font_ft_light = false;
983 tile_full_screen = SCREENMODE_AUTO;
984 tile_window_width = -90;
985 tile_window_height = -90;
987 tile_layout_priority = split_string(",", "minimap, inventory, gold_turn, "
988 "command, spell, monster");
992 tile_force_overlay = false;
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
1002 tile_show_minihealthbar = true;
1003 tile_show_minimagicbar = true;
1004 tile_show_demon_tier = true;
1005 tile_force_regenerate_levels = false;
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)
1016 for (int i = 0; i < 16; ++i)
1019 colour[ DARKGREY ] = COL_TO_REPLACE_DARKGREY;
1020 #ifndef USE_8_COLOUR_TERM_MAP
1024 for (int i = 0; i < 16; ++i)
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;
1033 // Clear vector options.
1035 new_dump_fields("header,hiscore,stats,misc,inventory,"
1036 "skills,spells,overview,mutations,messages,"
1037 "screenshot,monlist,kills,notes");
1040 hp_colour.push_back(std::pair<int,int>(50, YELLOW));
1041 hp_colour.push_back(std::pair<int,int>(25, RED));
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;
1059 force_autopickup.clear();
1060 note_monsters.clear();
1061 note_messages.clear();
1062 autoinscriptions.clear();
1063 autoinscribe_artefacts = true;
1064 autoinscribe_cursed = true;
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();
1078 clear_cset_overrides();
1080 clear_feature_overrides();
1081 mon_glyph_overrides.clear();
1083 rest_wait_both = false;
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;
1090 // Forget any files we remembered as included.
1093 // Forget variables and such.
1099 void game_options::clear_cset_overrides()
1101 memset(cset_override, 0, sizeof cset_override);
1104 void game_options::clear_feature_overrides()
1106 feature_overrides.clear();
1109 ucs_t get_glyph_override(int 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;
1119 if (wcwidth(c) != 1)
1121 mprf(MSGCH_ERROR, "Invalid glyph override: %X", c);
1127 static int read_symbol(std::string s)
1132 if (s.length() > 1 && s[0] == '\\')
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)
1145 if (s.length() > 1 && s[0] == 'x')
1152 return -strtoul(s.c_str(), &tail, base);
1155 void game_options::set_fire_order(const std::string &s, bool add)
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]);
1164 void game_options::add_fire_order_slot(const std::string &s)
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]);
1172 fire_order.push_back(flags);
1175 void game_options::add_mon_glyph_overrides(const std::string &mons,
1178 // If one character, this is a monster letter.
1180 if (mons.length() == 1)
1181 letter = mons[0] == '_' ? ' ' : mons[0];
1184 for (monster_type i = MONS_0; i < NUM_MONSTERS; ++i)
1186 const monsterentry *me = get_monster_data(i);
1187 if (!me || me->mc == MONS_PROGRAM_BUG)
1190 if (me->basechar == letter || me->name == mons)
1193 mon_glyph_overrides[i] = mdisp;
1197 report_error("Unknown monster: \"%s\"", mons.c_str());
1200 mon_display game_options::parse_mon_glyph(const std::string &s) const
1203 std::vector<std::string> phrases = split_string(" ", s);
1204 for (int i = 0, size = phrases.size(); i < size; ++i)
1206 const std::string &p = phrases[i];
1207 const int col = str_to_colour(p, -1, false);
1208 if (col != -1 && colour)
1211 md.glyph = p == "_"? ' ' : read_symbol(p);
1216 void game_options::add_mon_glyph_override(const std::string &text)
1218 std::vector<std::string> override = split_string(":", text);
1219 if (override.size() != 2u)
1222 mon_display mdisp = parse_mon_glyph(override[1]);
1223 if (mdisp.glyph || mdisp.colour)
1224 add_mon_glyph_overrides(override[0], mdisp);
1227 void game_options::add_feature_override(const std::string &text)
1229 std::string::size_type epos = text.rfind("}");
1230 if (epos == std::string::npos)
1233 std::string::size_type spos = text.rfind("{", epos);
1234 if (spos == std::string::npos)
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);
1241 if (iprops.size() < 1 || iprops.size() > 7)
1244 if (iprops.size() < 7)
1248 std::vector<dungeon_feature_type> feats =
1249 features_by_desc(text_pattern(fname));
1253 for (int i = 0, size = feats.size(); i < size; ++i)
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];
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);
1269 feature_overrides.push_back(fov);
1273 void game_options::add_cset_override(
1274 char_set_type set, const std::string &overrides)
1276 std::vector<std::string> overs = split_string(",", overrides);
1277 for (int i = 0, size = overs.size(); i < size; ++i)
1279 std::vector<std::string> mapping = split_string(":", overs[i]);
1280 if (mapping.size() != 2)
1283 dungeon_char_type dc = dchar_by_name(mapping[0]);
1284 if (dc == NUM_DCHAR_TYPES)
1287 add_cset_override(set, dc, read_symbol(mapping[1]));
1291 void game_options::add_cset_override(char_set_type set, dungeon_char_type dc,
1294 cset_override[dc] = get_glyph_override(symbol);
1297 static std::string _find_crawlrc()
1299 const char* locations_data[][2] = {
1300 { SysEnv.crawl_dir.c_str(), "init.txt" },
1302 { SysEnv.home.c_str(), ".crawl/init.txt" },
1303 { SysEnv.home.c_str(), ".crawlrc" },
1304 { SysEnv.home.c_str(), "init.txt" },
1306 #ifndef DATA_DIR_PATH
1308 { "..", "init.txt" },
1309 { "../settings", "init.txt" },
1311 { NULL, NULL } // placeholder to mark end
1314 // We'll look for these files in any supplied -rcdirs.
1315 static const char *rc_dir_filenames[] = {
1316 ".crawlrc", "init.txt"
1319 // -rc option always wins.
1320 if (!SysEnv.crawl_rc.empty())
1321 return SysEnv.crawl_rc;
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)
1327 for (unsigned n = 0; n < ARRAYSZ(rc_dir_filenames); ++n)
1329 const std::string rc(
1330 catpath(SysEnv.rcdirs[i], rc_dir_filenames[n]));
1331 if (file_exists(rc))
1336 // Check all possibilities for init.txt
1337 for (int i = 0; locations_data[i][1] != NULL; ++i)
1339 // Don't look at unset options
1340 if (locations_data[i][0] != NULL)
1342 const std::string rc =
1343 catpath(locations_data[i][0], locations_data[i][1]);
1344 if (file_exists(rc))
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);
1354 static const char* lua_builtins[] =
1360 "clua/trapwalk.lua",
1361 "clua/autofight.lua",
1365 static const char* config_defaults[] =
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",
1375 // Returns an error message if the init.txt was not found.
1376 std::string read_init_file(bool runscript)
1378 Options.reset_options();
1380 #ifdef CLUA_BINDINGS
1383 for (unsigned int i = 0; i < ARRAYSZ(lua_builtins); ++i)
1385 clua.execfile(lua_builtins[i], false, false);
1386 if (!clua.error.empty())
1387 mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
1391 for (unsigned int i = 0; i < ARRAYSZ(config_defaults); ++i)
1392 Options.include(datafile_path(config_defaults[i]), false, runscript);
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++)
1401 Options.read_option_line(SysEnv.extra_opts_first[i], true);
1404 const std::string init_file_name(_find_crawlrc());
1406 FileLineInput f(init_file_name.c_str());
1409 if (!init_file_name.empty())
1411 return make_stringf("(\"%s\" is not readable)",
1412 init_file_name.c_str());
1416 return "(~/.crawlrc missing)";
1418 return "(no init.txt in current directory)";
1422 Options.filename = init_file_name;
1423 Options.line_num = 0;
1425 Options.basefilename = "~/.crawlrc";
1427 Options.basefilename = "init.txt";
1429 Options.read_options(f, runscript);
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++)
1437 Options.read_option_line(SysEnv.extra_opts_last[i], false);
1440 Options.filename = init_file_name;
1441 Options.basefilename = get_base_filename(init_file_name);
1442 Options.line_num = -1;
1447 newgame_def read_startup_prefs()
1449 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1450 FileLineInput fl(get_prefs_filename().c_str());
1452 return newgame_def();
1455 temp.read_options(fl, false);
1458 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1461 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1462 static void write_newgame_options(const newgame_def& prefs, FILE *f)
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");
1479 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1481 void write_newgame_options_file(const newgame_def& prefs)
1483 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1484 // [ds] Saving startup prefs should work like this:
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.
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.
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.
1502 // Yes, this is unnecessarily complex. Better ideas welcome.
1504 unwind_var<game_type> gt(crawl_state.type, Options.game.type);
1506 std::string fn = get_prefs_filename();
1507 FILE *f = fopen_u(fn.c_str(), "w");
1510 write_newgame_options(prefs, f);
1512 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1515 void save_player_name()
1517 #ifndef DISABLE_STICKY_STARTUP_OPTIONS
1518 if (!Options.remember_name)
1521 // Read other preferences
1522 newgame_def prefs = read_startup_prefs();
1523 prefs.name = you.your_name;
1526 write_newgame_options_file(prefs);
1527 #endif // !DISABLE_STICKY_STARTUP_OPTIONS
1530 void read_options(const std::string &s, bool runscript, bool clear_aliases)
1532 StringLineInput st(s);
1533 Options.read_options(st, runscript, clear_aliases);
1536 game_options::game_options()
1538 lang = LANG_EN; // FIXME: obtain from gettext
1543 void game_options::read_options(LineInput &il, bool runscript,
1546 unsigned int line = 0;
1548 bool inscriptblock = false;
1549 bool inscriptcond = false;
1550 bool isconditional = false;
1552 bool l_init = false;
1557 dlua_chunk luacond(filename);
1558 dlua_chunk luacode(filename);
1563 std::string s = il.get_line();
1564 std::string str = s;
1569 // This is to make some efficient comments
1570 if ((str.empty() || str[0] == '#') && !inscriptcond && !inscriptblock)
1573 if (!inscriptcond && str[0] == ':')
1575 // The init file is now forced into isconditional mode.
1576 isconditional = true;
1577 str = str.substr(1);
1578 if (!str.empty() && runscript)
1580 // If we're in the middle of an option block, close it.
1581 if (!luacond.empty() && l_init)
1583 luacond.add(line - 1, "]])");
1586 luacond.add(line, str);
1590 if (!inscriptcond && (str.find("L<") == 0 || str.find("<") == 0))
1592 // The init file is now forced into isconditional mode.
1593 isconditional = true;
1594 inscriptcond = true;
1596 str = str.substr(str.find("L<") == 0? 2 : 1);
1597 // Is this a one-liner?
1598 if (!str.empty() && str[ str.length() - 1 ] == '>')
1600 inscriptcond = false;
1601 str = str.substr(0, str.length() - 1);
1604 if (!str.empty() && runscript)
1606 // If we're in the middle of an option block, close it.
1607 if (!luacond.empty() && l_init)
1609 luacond.add(line - 1, "]])");
1612 luacond.add(line, str);
1616 else if (inscriptcond && !str.empty()
1617 && (str.find(">") == str.length() - 1 || str == ">L"))
1619 inscriptcond = false;
1620 str = str.substr(0, str.length() - 1);
1621 if (!str.empty() && runscript)
1622 luacond.add(line, str);
1625 else if (inscriptcond)
1628 luacond.add(line, s);
1632 // Handle blocks of Lua
1633 if (!inscriptblock && (str.find("Lua{") == 0 || str.find("{") == 0))
1635 inscriptblock = true;
1637 luacode.set_file(filename);
1639 // Strip leading Lua[
1640 str = str.substr(str.find("Lua{") == 0? 4 : 1);
1642 if (!str.empty() && str.find("}") == str.length() - 1)
1644 str = str.substr(0, str.length() - 1);
1645 inscriptblock = false;
1649 luacode.add(line, str);
1651 if (!inscriptblock && runscript)
1653 #ifdef CLUA_BINDINGS
1654 if (luacode.run(clua))
1656 mprf(MSGCH_ERROR, "Lua error: %s",
1657 luacode.orig_error().c_str());
1665 else if (inscriptblock && (str == "}Lua" || str == "}"))
1667 inscriptblock = false;
1668 #ifdef CLUA_BINDINGS
1671 if (luacode.run(clua))
1672 mprf(MSGCH_ERROR, "Lua error: %s",
1673 luacode.orig_error().c_str());
1679 else if (inscriptblock)
1681 luacode.add(line, s);
1685 if (isconditional && runscript)
1689 luacond.add(line, "crawl.setopt([[");
1693 luacond.add(line, s);
1697 read_option_line(str, runscript);
1700 #ifdef CLUA_BINDINGS
1701 if (runscript && !luacond.empty())
1704 luacond.add(line, "]])");
1705 if (luacond.run(clua))
1706 mprf(MSGCH_ERROR, "Lua error: %s", luacond.orig_error().c_str());
1710 Options.explore_stop |= Options.explore_stop_prompt;
1712 evil_colour = str_to_colour(variables["evil"]);
1715 void game_options::fixup_options()
1717 // Validate save_dir
1718 if (!check_mkdir("Save directory", &save_dir))
1721 if (!SysEnv.morgue_dir.empty())
1722 morgue_dir = SysEnv.morgue_dir;
1724 if (!check_mkdir("Morgue directory", &morgue_dir))
1727 if (evil_colour == BLACK)
1728 evil_colour = MAGENTA;
1731 static int _str_to_killcategory(const std::string &s)
1733 static const char *kc[] = {
1739 for (unsigned i = 0; i < ARRAYSZ(kc); ++i)
1746 void game_options::do_kill_map(const std::string &from, const std::string &to)
1748 int ifrom = _str_to_killcategory(from),
1749 ito = _str_to_killcategory(to);
1750 if (ifrom != -1 && ito != -1)
1751 kill_map[ifrom] = ito;
1754 int game_options::read_explore_stop_conditions(const std::string &field) const
1757 std::vector<std::string> stops = split_string(",", field);
1758 for (int i = 0, count = stops.size(); i < count; ++i)
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;
1799 void game_options::add_alias(const std::string &key, const std::string &val)
1803 std::string name = key.substr(1);
1804 // Don't alter if it's a constant.
1805 if (constants.find(name) != constants.end())
1807 variables[name] = val;
1813 std::string game_options::unalias(const std::string &key) const
1815 string_map::const_iterator i = aliases.find(key);
1816 return (i == aliases.end()? key : i->second);
1819 #define IS_VAR_CHAR(c) (isaalpha(c) || c == '_' || c == '-')
1821 std::string game_options::expand_vars(const std::string &field) const
1823 std::string field_out = field;
1825 std::string::size_type curr_pos = 0;
1827 // Only try 100 times, so as to not get stuck in infinite recursion.
1828 for (int i = 0; i < 100; i++)
1830 std::string::size_type dollar_pos = field_out.find("$", curr_pos);
1832 if (dollar_pos == std::string::npos
1833 || field_out.size() == (dollar_pos + 1))
1838 std::string::size_type start_pos = dollar_pos + 1;
1840 if (!IS_VAR_CHAR(field_out[start_pos]))
1843 std::string::size_type end_pos;
1844 for (end_pos = start_pos; end_pos < field_out.size(); end_pos++)
1846 if (!IS_VAR_CHAR(field_out[end_pos + 1]))
1850 std::string var_name = field_out.substr(start_pos,
1851 end_pos - start_pos + 1);
1853 string_map::const_iterator x = variables.find(var_name);
1855 if (x == variables.end())
1857 curr_pos = end_pos + 1;
1861 std::string dollar_plus_name = "$";
1862 dollar_plus_name += var_name;
1864 field_out = replace_all(field_out, dollar_plus_name, x->second);
1866 // Start over at beginning
1873 void game_options::add_message_colour_mappings(const std::string &field)
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]);
1880 message_filter game_options::parse_message_filter(const std::string &filter)
1882 std::string::size_type pos = filter.find(":");
1883 if (pos && pos != std::string::npos)
1885 std::string prefix = filter.substr(0, pos);
1886 int channel = str_to_channel(prefix);
1887 if (channel != -1 || prefix == "any")
1889 std::string s = filter.substr(pos + 1);
1891 return message_filter(channel, s);
1895 return message_filter(filter);
1898 void game_options::add_message_colour_mapping(const std::string &field)
1900 std::vector<std::string> cmap = split_string(":", field, true, true, 1);
1902 if (cmap.size() != 2)
1905 const int col = str_to_colour(cmap[0]);
1906 msg_colour_type mcol;
1907 if (cmap[0] == "mute")
1908 mcol = MSGCOL_MUTED;
1912 mcol = msg_colour(col);
1914 message_colour_mapping m = { parse_message_filter(cmap[1]), mcol };
1915 message_colour_mappings.push_back(m);
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)
1925 menu_sort_condition cond(field);
1927 // Overrides all previous settings.
1928 if (cond.mtype == MT_ANY)
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)
1935 sort_menus[i].sort = cond.sort;
1936 sort_menus[i].cmp = cond.cmp;
1940 sort_menus.push_back(cond);
1943 void game_options::split_parse(
1944 const std::string &s, const std::string &separator,
1945 void (game_options::*add)(const std::string &))
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]);
1952 void game_options::set_option_fragment(const std::string &s)
1957 std::string::size_type st = s.find(':');
1958 if (st == std::string::npos)
1962 read_option_line(s.substr(1) + " = false");
1964 read_option_line(s + " = true");
1969 read_option_line(s.substr(0, st) + " = " + s.substr(st + 1));
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)
1977 const size_t start_bracket = field.find_first_of('[');
1978 const size_t end_bracket = field.find_last_of(']');
1980 if (start_bracket == std::string::npos
1981 || end_bracket == std::string::npos
1982 || start_bracket > end_bracket)
1984 mprf(MSGCH_ERROR, "Bad bindkey bracketing in '%s'",
1989 const std::string key_str = field.substr(start_bracket + 1,
1990 end_bracket - start_bracket - 1);
1994 // TODO: Function keys.
1995 if (key_str.length() == 0)
1997 mprf(MSGCH_ERROR, "No key in bindkey directive '%s'",
2001 else if (key_str.length() == 1)
2003 else if (key_str.length() == 2)
2005 if (key_str[0] != '^')
2007 mprf(MSGCH_ERROR, "Invalid key '%s' in bindkey directive '%s'",
2008 key_str.c_str(), field.c_str());
2011 key = CONTROL(key_str[1]);
2015 mprf(MSGCH_ERROR, "Invalid key '%s' in bindkey directive '%s'",
2016 key_str.c_str(), field.c_str());
2020 const size_t start_name = field.find_first_not_of(' ', end_bracket + 1);
2021 if (start_name == std::string::npos)
2023 mprf(MSGCH_ERROR, "No command name for bindkey directive '%s'",
2028 const std::string name = field.substr(start_name);
2029 const command_type cmd = name_to_command(name);
2030 if (cmd == CMD_NO_CMD)
2032 mprf(MSGCH_ERROR, "No command named '%s'", name.c_str());
2036 bind_command_to_key(cmd, key);
2039 void game_options::read_option_line(const std::string &str, bool runscript)
2041 #define BOOL_OPTION_NAMED(_opt_str, _opt_var) \
2042 if (key == _opt_str) do { \
2043 _opt_var = _read_bool(field, _opt_var); \
2045 #define BOOL_OPTION(_opt) BOOL_OPTION_NAMED(#_opt, _opt)
2047 #define COLOUR_OPTION_NAMED(_opt_str, _opt_var) \
2048 if (key == _opt_str) do { \
2049 const int col = str_to_colour(field); \
2053 /*fprintf(stderr, "Bad %s -- %s\n", key, field.c_str());*/ \
2054 report_error("Bad %s -- %s\n", key.c_str(), field.c_str()); \
2057 #define COLOUR_OPTION(_opt) COLOUR_OPTION_NAMED(#_opt, _opt)
2059 #define CURSES_OPTION_NAMED(_opt_str, _opt_var) \
2060 if (key == _opt_str) do { \
2061 _opt_var = curses_attribute(field); \
2063 #define CURSES_OPTION(_opt) CURSES_OPTION_NAMED(#_opt, _opt)
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); \
2079 #define INT_OPTION(_opt, _min_val, _max_val) \
2080 INT_OPTION_NAMED(#_opt, _opt, _min_val, _max_val)
2082 std::string key = "";
2083 std::string subkey = "";
2084 std::string field = "";
2086 bool plus_equal = false;
2087 bool minus_equal = false;
2089 const int first_equals = str.find('=');
2091 // all lines with no equal-signs we ignore
2092 if (first_equals < 0)
2095 field = str.substr(first_equals + 1);
2096 field = expand_vars(field);
2098 std::string prequal = trimmed_string(str.substr(0, first_equals));
2100 // Is this a case of key += val?
2101 if (prequal.length() && prequal[prequal.length() - 1] == '+')
2104 prequal = prequal.substr(0, prequal.length() - 1);
2105 trim_string(prequal);
2107 else if (prequal.length() && prequal[prequal.length() - 1] == '-')
2110 prequal = prequal.substr(0, prequal.length() - 1);
2111 trim_string(prequal);
2113 else if (prequal.length() && prequal[prequal.length() - 1] == ':')
2115 prequal = prequal.substr(0, prequal.length() - 1);
2116 trim_string(prequal);
2119 add_alias(prequal, field);
2123 prequal = unalias(prequal);
2125 const std::string::size_type first_dot = prequal.find('.');
2126 if (first_dot != std::string::npos)
2128 key = prequal.substr(0, first_dot);
2129 subkey = prequal.substr(first_dot + 1);
2133 // no subkey (dots are okay in value field)
2137 // Clean up our data...
2138 lowercase(trim_string(key));
2139 lowercase(trim_string(subkey));
2141 // some fields want capitals... none care about external spaces
2144 // Keep unlowercased field around
2145 const std::string orig_field = field;
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)
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")
2176 // clear out autopickup
2180 for (const char* tp = field.c_str(); int s = utf8towc(&c, tp); tp += s)
2182 object_class_type type = item_class_by_sym(c);
2184 if (type < NUM_OBJECT_CLASSES)
2185 autopickups |= (1 << type);
2187 report_error("Bad object type '%*s' for autopickup.\n", s, tp);
2190 #if !defined(DGAMELAUNCH) || defined(DGL_REMEMBER_NAME)
2191 else if (key == "name")
2193 // field is already cleaned up from trim_string()
2197 else if (key == "char_set")
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;
2210 fprintf(stderr, "Bad character set: %s\n", field.c_str());
2212 else if (key == "language")
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")
2237 lang = LANG_JAGERKIN;
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")
2244 else if (field == "cyr" || field == "cyrillic" || field == "commie" || field == "кириллица")
2245 lang = LANG_CYRILLIC;
2247 else if (field == "wide" || field == "doublewidth" || field == "fullwidth")
2250 report_error("No translations for language: %s\n", field.c_str());
2252 else if (key == "default_autopickup")
2254 if (_read_bool(field, true))
2259 else if (key == "default_friendly_pickup")
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;
2270 else if (key == "default_manual_training")
2272 if (_read_bool(field, true))
2273 default_manual_training = true;
2275 default_manual_training = false;
2278 else BOOL_OPTION(restart_after_game);
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")
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;
2294 else if (key == "allow_self_target")
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;
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")
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;
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")
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;
2336 report_error("Invalid auto_drop_chunks: \"%s\"", field.c_str());
2338 else if (key == "lua_file" && runscript)
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());
2346 else if (key == "terp_file" && runscript)
2349 terp_files.push_back(field);
2352 else if (key == "colour" || key == "color")
2354 const int orig_col = str_to_colour(subkey);
2355 const int result_col = str_to_colour(field);
2357 if (orig_col != -1 && result_col != -1)
2358 colour[orig_col] = result_col;
2361 fprintf(stderr, "Bad colour -- %s=%d or %s=%d\n",
2362 subkey.c_str(), orig_col, field.c_str(), result_col);
2365 else if (key == "channel")
2367 const int chnl = str_to_channel(subkey);
2368 const msg_colour_type col = _str_to_channel_colour(field);
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());
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)
2382 set_activity_interrupt(key.substr(interrupt_prefix.length()),
2387 else if (key.find("cset") == 0)
2389 std::string cset = key.substr(4);
2390 if (!cset.empty() && cset[0] == '_')
2391 cset = cset.substr(1);
2393 char_set_type cs = NUM_CSET;
2396 else if (cset == "dec")
2399 add_cset_override(cs, field);
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
2422 else if (key == "map" && crawl_state.sprint_map.empty())
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")
2429 #if defined(DGAMELAUNCH)
2430 game.type = Options.game.type;
2432 game.type = _str_to_gametype(field);
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")
2441 // Choose this weapon for backgrounds that get choice.
2442 game.weapon = str_to_weapon(field);
2444 BOOL_OPTION_NAMED("fully_random", game.fully_random);
2445 else if (key == "fire_items_start")
2447 if (isaalpha(field[0]))
2448 fire_items_start = letter_to_index(field[0]);
2451 fprintf(stderr, "Bad fire item start index: %s\n",
2455 else if (key == "assign_item_slot")
2457 if (field == "forward")
2458 assign_item_slot = SS_FORWARD;
2459 else if (field == "backward")
2460 assign_item_slot = SS_BACKWARD;
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);
2468 else if (key == "save_dir")
2470 else if (key == "morgue_dir")
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);
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")
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;
2493 else if (key == "macro_dir")
2497 else BOOL_OPTION(auto_list);
2498 else if (key == "default_target")
2500 default_target = _read_bool(field, default_target);
2502 target_unshifted_dirs = false;
2504 else BOOL_OPTION(autopickup_no_burden);
2505 #ifdef DGL_SIMPLE_MESSAGING
2506 else BOOL_OPTION(messaging);
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);
2518 else BOOL_OPTION(mlist_targetting);
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")
2526 const bool lock = _read_bool(field, true);
2527 view_lock_x = view_lock_y = lock;
2529 else BOOL_OPTION(center_on_scroll);
2530 else BOOL_OPTION(symmetric_scroll);
2531 else if (key == "scroll_margin_x")
2533 scroll_margin_x = atoi(field.c_str());
2534 if (scroll_margin_x < 0)
2535 scroll_margin_x = 0;
2537 else if (key == "scroll_margin_y")
2539 scroll_margin_y = atoi(field.c_str());
2540 if (scroll_margin_y < 0)
2541 scroll_margin_y = 0;
2543 else if (key == "scroll_margin")
2545 int scrollmarg = atoi(field.c_str());
2548 scroll_margin_x = scroll_margin_y = scrollmarg;
2550 else if (key == "user_note_prefix")
2552 // field is already cleaned up from trim_string()
2553 user_note_prefix = field;
2555 else if (key == "skill_focus")
2557 if (field == "toggle")
2558 skill_focus = SKM_FOCUS_TOGGLE;
2559 else if (_read_bool(field, true))
2560 skill_focus = SKM_FOCUS_ON;
2562 skill_focus = SKM_FOCUS_OFF;
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")
2573 if (subkey == "failure")
2575 flush_input[FLUSH_ON_FAILURE]
2576 = _read_bool(field, flush_input[FLUSH_ON_FAILURE]);
2578 else if (subkey == "command")
2580 flush_input[FLUSH_BEFORE_COMMAND]
2581 = _read_bool(field, flush_input[FLUSH_BEFORE_COMMAND]);
2583 else if (subkey == "message")
2585 flush_input[FLUSH_ON_MESSAGE]
2586 = _read_bool(field, flush_input[FLUSH_ON_MESSAGE]);
2588 else if (subkey == "lua")
2590 flush_input[FLUSH_LUA]
2591 = _read_bool(field, flush_input[FLUSH_LUA]);
2594 else if (key == "wiz_mode")
2596 // wiz_mode is recognised as a legal key in all compiles -- bwr
2599 if (field == "never")
2600 wiz_mode = WIZ_NEVER;
2601 else if (field == "no")
2603 else if (field == "yes")
2606 report_error("Unknown wiz_mode option: %s\n", field.c_str());
2610 else if (key == "ban_pickup")
2612 std::vector<std::string> args = split_string(",", field);
2613 for (int i = 0, size = args.size(); i < size; ++i)
2615 const std::string &s = args[i];
2618 force_autopickup.push_back(std::make_pair(s, false));
2621 else if (key == "autopickup_exceptions")
2623 std::vector<std::string> args = split_string(",", field);
2624 for (int i = 0, size = args.size(); i < size; ++i)
2626 const std::string &s = args[i];
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));
2635 force_autopickup.push_back(std::make_pair(s, false));
2638 else if (key == "note_items")
2639 append_vector(note_items, split_string(",", field));
2642 // break if-else chain on broken Microsoft compilers with stupid nesting limits
2646 if (key == "autoinscribe")
2649 return report_error("Autoinscribe string is empty");
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)
2655 return report_error("Autoinscribe string must have exactly "
2656 "one colon: %s\n", field.c_str());
2661 report_error("Autoinscribe pattern is empty: %s\n", field.c_str());
2665 if (last == field.length() - 1)
2666 return report_error("Autoinscribe result is empty: %s\n", field.c_str());
2668 std::vector<std::string> thesplit = split_string(":", field);
2670 if (thesplit.size() != 2)
2672 report_error("Error parsing autoinscribe string: %s\n", field.c_str());
2676 autoinscriptions.push_back(
2677 std::pair<text_pattern,std::string>(thesplit[0], thesplit[1]));
2679 else BOOL_OPTION(autoinscribe_artefacts);
2680 else BOOL_OPTION(autoinscribe_cursed);
2682 else if (key == "map_file_name")
2683 map_file_name = field;
2685 else if (key == "hp_colour" || key == "hp_color")
2688 std::vector<std::string> thesplit = split_string(",", field);
2689 for (unsigned i = 0; i < thesplit.size(); ++i)
2691 std::vector<std::string> insplit = split_string(":", thesplit[i]);
2692 int hp_percent = 100;
2694 if (insplit.empty() || insplit.size() > 2
2695 || insplit.size() == 1 && i != 0)
2697 report_error("Bad hp_colour string: %s\n", field.c_str());
2701 if (insplit.size() == 2)
2702 hp_percent = atoi(insplit[0].c_str());
2704 int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2705 hp_colour.push_back(std::pair<int, int>(hp_percent, scolour));
2708 else if (key == "mp_color" || key == "mp_colour")
2711 std::vector<std::string> thesplit = split_string(",", field);
2712 for (unsigned i = 0; i < thesplit.size(); ++i)
2714 std::vector<std::string> insplit = split_string(":", thesplit[i]);
2715 int mp_percent = 100;
2717 if (insplit.empty() || insplit.size() > 2
2718 || insplit.size() == 1 && i != 0)
2720 report_error("Bad mp_colour string: %s\n", field.c_str());
2724 if (insplit.size() == 2)
2725 mp_percent = atoi(insplit[0].c_str());
2727 int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2728 mp_colour.push_back(std::pair<int, int>(mp_percent, scolour));
2731 else if (key == "stat_colour" || key == "stat_color")
2733 stat_colour.clear();
2734 std::vector<std::string> thesplit = split_string(",", field);
2735 for (unsigned i = 0; i < thesplit.size(); ++i)
2737 std::vector<std::string> insplit = split_string(":", thesplit[i]);
2739 if (insplit.empty() || insplit.size() > 2
2740 || insplit.size() == 1 && i != 0)
2742 report_error("Bad stat_colour string: %s\n", field.c_str());
2747 if (insplit.size() == 2)
2748 stat_limit = atoi(insplit[0].c_str());
2750 int scolour = str_to_colour(insplit[(insplit.size() == 1) ? 0 : 1]);
2751 stat_colour.push_back(std::pair<int, int>(stat_limit, scolour));
2755 else if (key == "enemy_hp_colour" || key == "enemy_hp_color")
2757 enemy_hp_colour.clear();
2758 str_to_enemy_hp_colour(field);
2761 else if (key == "note_skill_levels")
2763 std::vector<std::string> thesplit = split_string(",", field);
2764 for (unsigned i = 0; i < thesplit.size(); ++i)
2766 int num = atoi(thesplit[i].c_str());
2767 if (num > 0 && num <= 27)
2768 note_skill_levels.push_back(num);
2771 report_error("Bad skill level to note -- %s\n",
2772 thesplit[i].c_str());
2777 else if (key == "spell_slot")
2779 std::vector<std::string> thesplit = split_string(":", field);
2780 if (thesplit.size() != 2)
2782 return report_error("Error parsing spell lettering string: %s\n",
2785 lowercase(thesplit[0]);
2786 auto_spell_letters.push_back(
2787 std::pair<text_pattern,std::string>(thesplit[0], thesplit[1]));
2789 else BOOL_OPTION(pickup_thrown);
2791 else if (key == "fsim_mode")
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")
2799 fsim_rounds = atol(field.c_str());
2800 if (fsim_rounds < 1000)
2802 if (fsim_rounds > 500000L)
2803 fsim_rounds = 500000L;
2805 else if (key == "fsim_mons")
2808 else if (key == "sort_menus")
2810 std::vector<std::string> frags = split_string(";", field);
2811 for (int i = 0, size = frags.size(); i < size; ++i)
2813 if (frags[i].empty())
2815 set_menu_sort(frags[i]);
2818 else if (key == "travel_delay")
2820 // Read travel delay in milliseconds.
2821 travel_delay = atoi(field.c_str());
2822 if (travel_delay < -1)
2824 if (travel_delay > 2000)
2825 travel_delay = 2000;
2827 else if (key == "explore_delay")
2829 // Read explore delay in milliseconds.
2830 explore_delay = atoi(field.c_str());
2831 if (explore_delay < -1)
2833 if (explore_delay > 2000)
2834 explore_delay = 2000;
2836 else BOOL_OPTION(show_travel_trail);
2837 else if (key == "level_map_cursor_step")
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;
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")
2850 std::vector<std::string> fragments = split_string(",", field);
2851 for (int i = 0, count = fragments.size(); i < count; ++i)
2853 if (fragments[i].length() == 0)
2856 std::string::size_type pos = fragments[i].find(":");
2857 if (pos && pos != std::string::npos)
2859 std::string prefix = fragments[i].substr(0, pos);
2860 int channel = str_to_channel(prefix);
2861 if (channel != -1 || prefix == "any")
2863 std::string s = fragments[i].substr(pos + 1);
2865 force_more_message.push_back(
2866 message_filter(channel, s));
2871 force_more_message.push_back(
2872 message_filter(fragments[i]));
2875 else if (key == "drop_filter")
2876 append_vector(drop_filter, split_string(",", field));
2877 else if (key == "travel_avoid_terrain")
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]);
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")
2890 str_to_colour(field, tc_exclude_circle);
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")
2904 if (!plus_equal && !minus_equal)
2905 explore_stop = ES_NONE;
2907 const int new_conditions = read_explore_stop_conditions(field);
2909 explore_stop &= ~new_conditions;
2911 explore_stop |= new_conditions;
2913 else if (key == "explore_stop_prompt")
2915 if (!plus_equal && !minus_equal)
2916 explore_stop_prompt = ES_NONE;
2917 const int new_conditions = read_explore_stop_conditions(field);
2919 explore_stop_prompt &= ~new_conditions;
2921 explore_stop_prompt |= new_conditions;
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")
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;
2933 else BOOL_OPTION(explore_greedy);
2934 else if (key == "explore_wall_bias")
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;
2942 else BOOL_OPTION(explore_improved);
2943 else BOOL_OPTION(travel_key_stop);
2944 else if (key == "sound")
2946 std::vector<std::string> seg = split_string(",", field);
2947 for (int i = 0, count = seg.size(); i < count; ++i)
2949 const std::string &sub = seg[i];
2950 std::string::size_type cpos = sub.find(":", 0);
2951 if (cpos != std::string::npos)
2953 sound_mapping mapping;
2954 mapping.pattern = sub.substr(0, cpos);
2955 mapping.soundfile = sub.substr(cpos + 1);
2956 sound_mappings.push_back(mapping);
2960 #ifndef TARGET_COMPILER_VC
2961 // MSVC has a limit on how many if/else if can be chained together.
2964 if (key == "menu_colour" || key == "menu_color")
2966 std::vector<std::string> seg = split_string(",", field);
2967 for (int i = 0, count = seg.size(); i < count; ++i)
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)
2975 if (subseg.size() >= 3)
2977 tagname = subseg[0];
2978 colname = subseg[1];
2979 patname = subseg[2];
2983 colname = subseg[0];
2984 patname = subseg[1];
2987 colour_mapping mapping;
2988 mapping.tag = tagname;
2989 mapping.pattern = patname;
2990 mapping.colour = str_to_colour(colname);
2992 if (mapping.colour != -1)
2993 menu_colour_mappings.push_back(mapping);
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")
3007 new_dump_fields(field, !minus_equal);
3009 else if (key == "dump_kill_places")
3011 dump_kill_places = (field == "none" ? KDO_NO_PLACES :
3012 field == "all" ? KDO_ALL_PLACES
3015 else if (key == "kill_map")
3017 std::vector<std::string> seg = split_string(",", field);
3018 for (int i = 0, count = seg.size(); i < count; ++i)
3020 const std::string &s = seg[i];
3021 std::string::size_type cpos = s.find(":", 0);
3022 if (cpos != std::string::npos)
3024 std::string from = s.substr(0, cpos);
3025 std::string to = s.substr(cpos + 1);
3026 do_kill_map(from, to);
3030 else BOOL_OPTION(rest_wait_both);
3031 else if (key == "dump_message_count")
3033 // Capping is implicit
3034 dump_message_count = atoi(field.c_str());
3036 else if (key == "dump_item_origins")
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)
3042 const std::string &ch = choices[i];
3043 if (ch == "artefacts" || ch == "artifacts"
3044 || ch == "artefact" || ch == "artifact")
3046 dump_item_origins |= IODS_ARTEFACTS;
3048 else if (ch == "ego_arm" || ch == "ego armour"
3049 || ch == "ego_armour" || ch == "ego armor"
3050 || ch == "ego_armor")
3052 dump_item_origins |= IODS_EGO_ARMOUR;
3054 else if (ch == "ego_weap" || ch == "ego weapon"
3055 || ch == "ego_weapon" || ch == "ego weapons"
3056 || ch == "ego_weapons")
3058 dump_item_origins |= IODS_EGO_WEAPON;
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;
3074 else if (key == "dump_item_origin_price")
3076 dump_item_origin_price = atoi(field.c_str());
3077 if (dump_item_origin_price < -1)
3078 dump_item_origin_price = -1;
3080 else BOOL_OPTION(dump_book_spells);
3081 else BOOL_OPTION(level_map_title);
3082 else if (key == "target_unshifted_dirs")
3084 target_unshifted_dirs = _read_bool(field, target_unshifted_dirs);
3085 if (target_unshifted_dirs)
3086 default_target = false;
3088 else if (key == "darken_beyond_range")
3089 darken_beyond_range = _read_bool(field, darken_beyond_range);
3090 else if (key == "drop_mode")
3092 if (field.find("multi") != std::string::npos)
3093 drop_mode = DM_MULTI;
3095 drop_mode = DM_SINGLE;
3097 else if (key == "pickup_mode")
3099 if (field.find("multi") != std::string::npos)
3101 else if (field.find("single") != std::string::npos)
3104 pickup_mode = _read_bool_or_number(field, pickup_mode, "auto:");
3106 else if (key == "additional_macro_file")
3108 const std::string resolved = resolve_include(orig_field, "macro ");
3109 if (!resolved.empty())
3110 additional_macro_files.push_back(resolved);
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);
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);
3175 else BOOL_OPTION(tile_font_ft_light);
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
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());
3199 else if (key == "bindkey")
3201 else if (key == "constant")
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());
3208 constants.insert(field);
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);
3215 // Catch-all else, copies option into map
3218 #ifdef CLUA_BINDINGS
3219 if (!clua.callbooleanfn(false, "c_process_lua_option", "ss",
3220 key.c_str(), orig_field.c_str()))
3223 #ifdef CLUA_BINDINGS
3224 if (!clua.error.empty())
3225 mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
3227 named_options[key] = orig_field;
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)
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);
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.
3256 assert_read_safe_path(included_file);
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 : "";
3265 if (!parent_file.empty())
3267 const std::string candidate =
3268 get_path_relative_to(parent_file, included_file);
3269 if (file_exists(candidate))
3275 const std::vector<std::string> &dirs(*rcdirs);
3276 for (int i = 0, size = dirs.size(); i < size; ++i)
3278 const std::string candidate(catpath(dirs[i], included_file));
3279 if (file_exists(candidate))
3284 return datafile_path(included_file, false, true);
3287 std::string game_options::resolve_include(const std::string &file,
3292 const std::string resolved =
3293 resolve_include(filename, file, &SysEnv.rcdirs);
3295 if (resolved.empty())
3296 report_error("Cannot find %sfile \"%s\".", type, file.c_str());
3299 catch (const std::string &err)
3301 report_error("Cannot include %sfile: %s", type, err.c_str());
3306 bool game_options::was_included(const std::string &file) const
3308 return (included.find(file) != included.end());
3311 void game_options::include(const std::string &rawfilename,
3315 const std::string include_file =
3316 resolve ? resolve_include(rawfilename) : rawfilename;
3318 if (was_included(include_file))
3321 included.insert(include_file);
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);
3327 // Save current line number
3328 unwind_var<int> currlinenum(line_num, 0);
3330 // Also unwind any aliases defined in included files.
3331 unwind_var<string_map> unwalias(aliases);
3333 FileLineInput fl(include_file.c_str());
3335 read_options(fl, runscript, false);
3338 void game_options::report_error(const char* format, ...)
3341 va_start(args, format);
3342 std::string error = vmake_stringf(format, args);
3345 // If called before game starts, log a startup error,
3346 // otherwise spam the warning channel.
3347 if (crawl_state.need_save)
3349 mprf(MSGCH_ERROR, "Warning: %s (%s:%d)", error.c_str(),
3350 basefilename.c_str(), line_num);
3354 crawl_state.add_startup_error(make_stringf("%s (%s:%d)",
3356 basefilename.c_str(),
3361 static std::string check_string(const char *s)
3366 void get_system_environment(void)
3368 // The player's name
3369 SysEnv.crawl_name = check_string(getenv("CRAWL_NAME"));
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"));
3375 #ifdef SAVE_DIR_PATH
3376 if (SysEnv.crawl_dir == "")
3377 SysEnv.crawl_dir = SAVE_DIR_PATH;
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"))
3385 const char *mail = getenv("MAIL");
3386 SysEnv.messagefile = mail? mail : "";
3390 // The full path to the init file -- this overrides CRAWL_DIR.
3391 SysEnv.crawl_rc = check_string(getenv("CRAWL_RC"));
3394 // The user's home directory (used to look for ~/.crawlrc file)
3395 SysEnv.home = check_string(getenv("HOME"));
3399 static void set_crawl_base_dir(const char *arg)
3404 SysEnv.crawl_base = get_parent_directory(arg);
3407 // parse args, filling in Options and game environment as we go.
3408 // returns true if no unknown or malformed arguments were found.
3410 // Keep this in sync with the option names.
3411 enum commandline_option_type
3437 CLO_EXTRA_OPT_FIRST,
3447 CLO_WEBTILES_SOCKET,
3448 CLO_AWAIT_CONNECTION,
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",
3462 "webtiles-socket", "await-connection",
3466 static const int num_cmd_ops = CLO_NOPS;
3467 static bool arg_seen[num_cmd_ops];
3469 static std::string _find_executable_path()
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
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);
3481 #elif defined ( TARGET_OS_LINUX )
3482 char tempPath[2048];
3483 const ssize_t rsize =
3484 readlink("/proc/self/exe", tempPath, sizeof(tempPath) - 1);
3487 tempPath[rsize] = 0;
3488 return mb_to_utf8(tempPath);
3491 #elif defined ( TARGET_OS_MACOSX )
3492 return mb_to_utf8(NXArgv[0]);
3494 // We don't know how to find the executable's path on this OS.
3499 static void _print_version()
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());
3506 static void _print_save_version(char *name)
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");
3518 if (!get_save_version(chrf, major, minor))
3519 fail("Save file is invalid.");
3521 printf("Save file version for %s is %d.%d\n", name, major, minor);
3523 catch (ext_fail_exception &fe)
3525 fprintf(stderr, "Error: %s\n", fe.msg.c_str());
3529 enum es_command_type
3540 static struct es_command
3542 es_command_type cmd;
3545 int min_args, max_args;
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, },
3556 #define ERR(...) do { fprintf(stderr, __VA_ARGS__); return; } while (0)
3557 static void _edit_save(int argc, char **argv)
3559 if (argc <= 1 || !strcmp(argv[1], "help"))
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"
3570 const char *name = argv[0];
3571 const char *cmdn = argv[1];
3573 es_command_type cmd = NUM_ES;
3576 for (unsigned int nc = 0; nc < ARRAYSZ(es_commands); nc++)
3577 if (!strcmp(es_commands[nc].name, cmdn))
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;
3588 ERR("Unknown command: %s.\n", cmdn);
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);
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());
3605 else if (cmd == ES_GET)
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);
3614 const char *file = (argc == 4) ? argv[3] : "chunk";
3616 if (strcmp(file, "-"))
3617 f = fopen_u(file, "wb");
3621 sysfail("Can't open \"%s\" for writing", file);
3624 while (size_t s = inc.read(buf, sizeof(buf)))
3625 if (fwrite(buf, 1, s, f) != s)
3626 sysfail("Error writing \"%s\"", file);
3630 sysfail("Write error on close of \"%s\"", file);
3632 else if (cmd == ES_PUT)
3634 const char *chunk = argv[2];
3635 if (!*chunk || strlen(chunk) > MAX_CHUNK_NAME_LENGTH)
3636 ERR("Invalid chunk name \"%s\".\n", chunk);
3638 const char *file = (argc == 4) ? argv[3] : "chunk";
3640 if (strcmp(file, "-"))
3641 f = fopen_u(file, "rb");
3645 sysfail("Can't read \"%s\"", file);
3646 chunk_writer outc(&save, chunk);
3649 while (size_t s = fread(buf, 1, sizeof(buf), f))
3652 sysfail("Error reading \"%s\"", file);
3657 else if (cmd == ES_RM)
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");
3665 save.delete_chunk(chunk);
3667 else if (cmd == ES_REPACK)
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++)
3675 chunk_reader in(&save, list[i]);
3676 chunk_writer out(&save2, list[i]);
3678 while (len_t s = in.read(buf, sizeof(buf)))
3683 rename_u((filename + ".tmp").c_str(), filename.c_str());
3685 else if (cmd == ES_INFO)
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++)
3696 int cfrag = save.get_chunk_fragmentation(list[i]);
3698 int cclen = save.get_chunk_compressed_length(list[i]);
3701 chunk_reader in(&save, list[i]);
3703 while (len_t s = in.read(buf, sizeof(buf)))
3705 printf("%7u/%7u %3u %s\n", cclen, clen, cfrag, list[i].c_str());
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
3716 catch (ext_fail_exception &fe)
3718 fprintf(stderr, "Error: %s\n", fe.msg.c_str());
3723 static bool _check_extra_opt(char* _opt)
3725 std::string opt(_opt);
3728 if (opt[0] == ':' || opt[0] == '<' || opt[0] == '{'
3729 || starts_with(opt, "L<") || starts_with(opt, "Lua{"))
3731 fprintf(stderr, "An extra option can't use Lua (%s)\n",
3738 fprintf(stderr, "An extra option can't be a comment (%s)\n",
3743 if (opt.find_first_of('=') == std::string::npos)
3745 fprintf(stderr, "An extra opt must contain a '=' (%s)\n",
3750 std::vector<std::string> parts = split_string(opt, "=");
3751 if (opt.find_first_of('=') == 0 || parts[0].length() == 0)
3753 fprintf(stderr, "An extra opt must have an option name (%s)\n",
3761 bool parse_args(int argc, char **argv, bool rc_only)
3763 COMPILE_CHECK(ARRAYSZ(cmd_ops) == CLO_NOPS);
3765 if (crawl_state.command_line_arguments.empty())
3767 crawl_state.command_line_arguments.insert(
3768 crawl_state.command_line_arguments.end(),
3772 std::string exe_path = _find_executable_path();
3774 if (!exe_path.empty())
3775 set_crawl_base_dir(exe_path.c_str());
3777 set_crawl_base_dir(argv[0]);
3779 SysEnv.crawl_exe = get_base_filename(argv[0]);
3781 SysEnv.rcdirs.clear();
3783 if (argc < 2) // no args!
3786 char *arg, *next_arg;
3788 bool nextUsed = false;
3792 for (int i = 0; i < num_cmd_ops; i++)
3793 arg_seen[i] = false;
3795 if (SysEnv.cmd_args.empty())
3797 for (int i = 1; i < argc; ++i)
3798 SysEnv.cmd_args.push_back(argv[i]);
3801 while (current < argc)
3804 arg = argv[current];
3806 // next argument (if there is one)
3807 if (current+1 < argc)
3808 next_arg = argv[current+1];
3814 // arg MUST begin with '-'
3819 "Option '%s' is invalid; options must be prefixed "
3824 // Look for match (we accept both -option and --option).
3831 for (o = 0; o < num_cmd_ops; o++)
3832 if (strcasecmp(cmd_ops[o], arg) == 0)
3835 // Print the list of commandline options for "--help".
3839 if (o == num_cmd_ops)
3842 "Unknown option: %s\n\n", argv[current]);
3846 // Disallow options specified more than once.
3847 if (arg_seen[o] == true)
3850 // Set arg to 'seen'.
3853 // Partially parse next argument.
3854 bool next_is_param = false;
3855 if (next_arg != NULL
3856 && (next_arg[0] != '-' || strlen(next_arg) == 1))
3858 next_is_param = true;
3861 // Take action according to the cmd chosen.
3868 ecount = -1; // default
3869 else // optional number given
3871 ecount = atoi(next_arg);
3875 if (ecount > SCORE_FILE_ENTRIES)
3876 ecount = SCORE_FILE_ENTRIES;
3883 Options.sc_entries = ecount;
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;
3895 crawl_state.map_stat_gen = true;
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;
3906 SysEnv.map_gen_iters = 100;
3912 Options.game.type = GAME_TYPE_ARENA;
3913 Options.restart_after_game = false;
3918 Options.game.arena_teams = next_arg;
3924 crawl_state.dump_maps = true;
3928 crawl_state.test = true;
3931 crawl_state.tests_selected = split_string(",", next_arg);
3937 crawl_state.test = true;
3938 crawl_state.script = true;
3939 if (current < argc - 1)
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]);
3948 "-script must specify comma-separated script names");
3954 crawl_state.build_db = true;
3960 SysEnv.macro_dir = next_arg;
3968 SysEnv.morgue_dir = next_arg;
3976 SysEnv.scorefile = next_arg;
3984 Options.game.name = next_arg;
3996 Options.game.species = _str_to_species(std::string(next_arg));
3999 Options.game.job = _str_to_job(std::string(next_arg));
4010 Options.char_set = CSET_ASCII;
4011 init_char_table(Options.char_set);
4020 SysEnv.add_rcdir(next_arg);
4029 SysEnv.crawl_dir = next_arg;
4038 SysEnv.crawl_rc = next_arg;
4043 // Shouldn't happen.
4050 case CLO_SAVE_VERSION:
4055 _print_save_version(next_arg);
4063 _edit_save(argc - current - 1, argv + current + 1);
4070 if (!sscanf(next_arg, "%x", &Options.seed))
4077 Options.game.type = GAME_TYPE_SPRINT;
4080 case CLO_SPRINT_MAP:
4085 crawl_state.sprint_map = next_arg;
4086 Options.game.map = next_arg;
4091 Options.game.type = GAME_TYPE_ZOTDEF;
4096 Options.game.type = GAME_TYPE_TUTORIAL;
4102 Options.wiz_mode = WIZ_NO;
4109 Options.no_save = true;
4114 case CLO_WEBTILES_SOCKET:
4116 tiles.m_sock_name = next_arg;
4119 case CLO_AWAIT_CONNECTION:
4120 tiles.m_await_connection = true;