/** * @file * @brief Simple reading of an init file and system variables * @detailed read_init_file is the main function, but read_option_line does * most of the work though. Read through read_init_file to get an overview of * how Crawl loads options. This file also contains a large number of utility * functions for setting particular options and converting between human * readable strings and internal values. (E.g. str_to_enemy_hp_colour, * _weapon_to_str). There is also some code dealing with sorting menus. **/ #include "AppHdr.h" #include "initfile.h" #include "options.h" #include #include #include #include #include #include "chardump.h" #include "clua.h" #include "colour.h" #include "dlua.h" #include "delay.h" #include "directn.h" #include "end.h" #include "errors.h" #include "kills.h" #include "files.h" #include "defines.h" #ifdef USE_TILE_WEB #include "tileweb.h" #endif #include "invent.h" #include "itemprop.h" #include "libutil.h" #include "macro.h" #include "mapdef.h" #include "message.h" #include "mon-util.h" #include "jobs.h" #include "player.h" #include "prompt.h" #include "species.h" #include "spl-util.h" #include "stash.h" #include "state.h" #include "strings.h" #include "syscalls.h" #include "tags.h" #include "throw.h" #include "travel.h" #include "items.h" #include "unwind.h" #include "version.h" #include "view.h" #include "viewchar.h" // For finding the executable's path #ifdef TARGET_OS_WINDOWS #define WIN32_LEAN_AND_MEAN #include #include #include #elif defined (__APPLE__) extern char **NXArgv; #elif defined (__linux__) #include #endif const string game_options::interrupt_prefix = "interrupt_"; system_environment SysEnv; game_options Options; template static L& remove_matching(L& lis, const E& entry) { lis.erase(remove(lis.begin(), lis.end(), entry), lis.end()); return lis; } object_class_type item_class_by_sym(ucs_t c) { switch (c) { case ')': return OBJ_WEAPONS; case '(': case 0x27b9: // ➹ return OBJ_MISSILES; case '[': return OBJ_ARMOUR; case '/': return OBJ_WANDS; case '%': return OBJ_FOOD; case '?': return OBJ_SCROLLS; case '"': // Make the amulet symbol equiv to ring -- bwross case '=': case 0x00B0: // ° return OBJ_JEWELLERY; case '!': return OBJ_POTIONS; case ':': case '+': // ??? -- was the only symbol working for tile order up to 0.10, // so keeping it for compat purposes (user configs). case 0x221e: // ∞ return OBJ_BOOKS; case '|': return OBJ_STAVES; case '0': return OBJ_ORBS; case '}': return OBJ_MISCELLANY; case '&': case 'X': case 'x': return OBJ_CORPSES; case '$': case 0x20ac: // € case 0x00a3: // £ case 0x00a5: // ¥ return OBJ_GOLD; case '\\': // Compat break: used to be staves (why not '|'?). return OBJ_RODS; default: return NUM_OBJECT_CLASSES; } } template static void _merge_lists(A &dest, const B &src, bool prepend) { dest.insert(prepend ? dest.begin() : dest.end(), src.begin(), src.end()); } // Returns MSGCOL_NONE if unmatched else returns 0-15. static msg_colour_type _str_to_channel_colour(const string &str) { int col = str_to_colour(str); msg_colour_type ret = MSGCOL_NONE; if (col == -1) { if (str == "mute") ret = MSGCOL_MUTED; else if (str == "plain" || str == "off") ret = MSGCOL_PLAIN; else if (str == "default" || str == "on") ret = MSGCOL_DEFAULT; else if (str == "alternate") ret = MSGCOL_ALTERNATE; } else ret = msg_colour(col); return ret; } static const string message_channel_names[] = { "plain", "friend_action", "prompt", "god", "pray", "duration", "danger", "warning", "food", "recovery", "sound", "talk", "talk_visual", "intrinsic_gain", "mutation", "monster_spell", "monster_enchant", "friend_spell", "friend_enchant", "monster_damage", "monster_target", "banishment", "rotten_meat", "equipment", "floor", "multiturn", "examine", "examine_filter", "diagnostic", "error", "tutorial", "orb", "timed_portal", "hell_effect", "monster_warning", "dgl_message", }; // returns -1 if unmatched else returns 0--(NUM_MESSAGE_CHANNELS-1) int str_to_channel(const string &str) { COMPILE_CHECK(ARRAYSZ(message_channel_names) == NUM_MESSAGE_CHANNELS); // widespread aliases if (str == "visual") return MSGCH_TALK_VISUAL; else if (str == "spell") return MSGCH_MONSTER_SPELL; for (int ret = 0; ret < NUM_MESSAGE_CHANNELS; ret++) { if (str == message_channel_names[ret]) return ret; } return -1; } string channel_to_str(int channel) { if (channel < 0 || channel >= NUM_MESSAGE_CHANNELS) return ""; return message_channel_names[channel]; } /** * Populate the map used to interpret a crawlrc entry as a starting weapon * type. For most entries, we can just look up which weapon has the entry as * its name; this map contains the exceptions. * * @return The special starting weapon type map. */ static map _create_special_weapon_map() { map weapon_map; // "staff" normally refers to a magical staff, but here we want to // interpret it as a quarterstaff. weapon_map["staff"] = WPN_QUARTERSTAFF; // These weapons' base names have changed; we want to interpret the old // names correctly. weapon_map["sling"] = WPN_HUNTING_SLING; weapon_map["crossbow"] = WPN_HAND_CROSSBOW; // Pseudo-weapons. weapon_map["unarmed"] = WPN_UNARMED; weapon_map["claws"] = WPN_UNARMED; weapon_map["thrown"] = WPN_THROWN; weapon_map["rocks"] = WPN_THROWN; weapon_map["javelins"] = WPN_THROWN; weapon_map["tomahawks"] = WPN_THROWN; weapon_map["random"] = WPN_RANDOM; weapon_map["viable"] = WPN_VIABLE; return weapon_map; } // This should be const, but operator[] on maps isn't const. static map _special_weapon_map = _create_special_weapon_map(); /** * Interpret a crawlrc entry as a starting weapon type. * * @param str The value of the crawlrc entry. * @return The weapon the string refers to, or WPN_UNKNOWN if invalid */ weapon_type str_to_weapon(const string &str) { string str_nospace = str; remove_whitespace(str_nospace); // Synonyms and pseudo-weapons. if (_special_weapon_map.count(str_nospace)) return _special_weapon_map[str_nospace]; // Real weapons referred to by their standard names. return name_nospace_to_weapon(str_nospace); } static string _weapon_to_str(weapon_type wpn_type) { if (wpn_type >= 0 && wpn_type < NUM_WEAPONS) return weapon_base_name(wpn_type); switch (wpn_type) { case WPN_UNARMED: return "claws"; case WPN_THROWN: return "thrown"; case WPN_VIABLE: return "viable"; case WPN_RANDOM: default: return "random"; } } // Summon types can be any of mon_summon_type (enum.h), or a relevant summoning // spell. int str_to_summon_type(const string &str) { if (str == "clone") return MON_SUMM_CLONE; if (str == "animate") return MON_SUMM_ANIMATE; if (str == "chaos") return MON_SUMM_CHAOS; if (str == "miscast") return MON_SUMM_MISCAST; if (str == "zot") return MON_SUMM_ZOT; if (str == "wrath") return MON_SUMM_WRATH; if (str == "aid") return MON_SUMM_AID; return spell_by_name(str); } static fire_type _str_to_fire_types(const string &str) { if (str == "launcher") return FIRE_LAUNCHER; else if (str == "stone") return FIRE_STONE; else if (str == "rock") return FIRE_ROCK; else if (str == "javelin") return FIRE_JAVELIN; else if (str == "tomahawk") return FIRE_TOMAHAWK; else if (str == "net") return FIRE_NET; else if (str == "return" || str == "returning") return FIRE_RETURNING; else if (str == "inscribed") return FIRE_INSCRIBED; return FIRE_NONE; } string gametype_to_str(game_type type) { switch (type) { case GAME_TYPE_NORMAL: return "normal"; case GAME_TYPE_TUTORIAL: return "tutorial"; case GAME_TYPE_ARENA: return "arena"; case GAME_TYPE_SPRINT: return "sprint"; case GAME_TYPE_ZOTDEF: return "zotdef"; default: return "none"; } } #ifndef DGAMELAUNCH static game_type _str_to_gametype(const string& s) { for (int i = 0; i < NUM_GAME_TYPE; ++i) { game_type t = static_cast(i); if (s == gametype_to_str(t)) return t; } return NUM_GAME_TYPE; } #endif static string _species_to_str(species_type sp) { if (sp == SP_RANDOM) return "random"; else if (sp == SP_VIABLE) return "viable"; else return species_name(sp); } static species_type _str_to_species(const string &str) { if (str == "random") return SP_RANDOM; else if (str == "viable") return SP_VIABLE; species_type ret = SP_UNKNOWN; if (str.length() == 2) // scan abbreviations ret = get_species_by_abbrev(str.c_str()); // if we don't have a match, scan the full names if (ret == SP_UNKNOWN) ret = str_to_species(str); if (!is_species_valid_choice(ret)) ret = SP_UNKNOWN; if (ret == SP_UNKNOWN) fprintf(stderr, "Unknown species choice: %s\n", str.c_str()); return ret; } static string _job_to_str(job_type job) { if (job == JOB_RANDOM) return "random"; else if (job == JOB_VIABLE) return "viable"; else return get_job_name(job); } static job_type _str_to_job(const string &str) { if (str == "random") return JOB_RANDOM; else if (str == "viable") return JOB_VIABLE; job_type job = JOB_UNKNOWN; if (str.length() == 2) // scan abbreviations job = get_job_by_abbrev(str.c_str()); // if we don't have a match, scan the full names if (job == JOB_UNKNOWN) job = get_job_by_name(str.c_str()); if (!is_job_valid_choice(job)) job = JOB_UNKNOWN; if (job == JOB_UNKNOWN) fprintf(stderr, "Unknown background choice: %s\n", str.c_str()); return job; } static bool _read_bool(const string &field, bool def_value) { bool ret = def_value; if (field == "true" || field == "1" || field == "yes") ret = true; if (field == "false" || field == "0" || field == "no") ret = false; return ret; } // read a value which can be either a boolean (in which case return // 0 for true, -1 for false), or a string of the form PREFIX:NUMBER // (e.g., auto:7), in which case return NUMBER as an int. static int _read_bool_or_number(const string &field, int def_value, const string& num_prefix) { int ret = def_value; if (field == "true" || field == "1" || field == "yes") ret = 0; if (field == "false" || field == "0" || field == "no") ret = -1; if (field.find(num_prefix) == 0) ret = atoi(field.c_str() + num_prefix.size()); return ret; } static unsigned curses_attribute(const string &field) { if (field == "standout") // probably reverses return CHATTR_STANDOUT; else if (field == "bold") // probably brightens fg return CHATTR_BOLD; else if (field == "blink") // probably brightens bg return CHATTR_BLINK; else if (field == "underline") return CHATTR_UNDERLINE; else if (field == "reverse") return CHATTR_REVERSE; else if (field == "dim") return CHATTR_DIM; else if (field.find("hi:") == 0 || field.find("hilite:") == 0 || field.find("highlight:") == 0) { int col = field.find(":"); int colour = str_to_colour(field.substr(col + 1)); if (colour == -1) Options.report_error("Bad highlight string -- %s\n", field.c_str()); else return CHATTR_HILITE | (colour << 8); } else if (field != "none") Options.report_error("Bad colour -- %s\n", field.c_str()); return CHATTR_NORMAL; } void game_options::str_to_enemy_hp_colour(const string &colours, bool prepend) { vector colour_list = split_string(" ", colours, true, true); if (prepend) reverse(colour_list.begin(), colour_list.end()); for (int i = 0, csize = colour_list.size(); i < csize; i++) { const int col = str_to_colour(colour_list[i]); if (col < 0) { Options.report_error("Bad enemy_hp_colour: %s\n", colour_list[i].c_str()); return; } else if (prepend) enemy_hp_colour.insert(enemy_hp_colour.begin(), col); else enemy_hp_colour.push_back(col); } } #ifdef USE_TILE static FixedVector tag_prefs("none", "tutorial", "named", "enemy"); static tag_pref _str_to_tag_pref(const char *opt) { for (int i = 0; i < TAGPREF_MAX; i++) { if (!strcasecmp(opt, tag_prefs[i])) return (tag_pref)i; } return TAGPREF_ENEMY; } #endif void game_options::new_dump_fields(const string &text, bool add, bool prepend) { // Easy; chardump.cc has most of the intelligence. vector fields = split_string(",", text, true, true); if (add) _merge_lists(dump_order, fields, prepend); else { for (int f = 0, size = fields.size(); f < size; ++f) for (int i = 0, dsize = dump_order.size(); i < dsize; ++i) { if (dump_order[i] == fields[f]) { dump_order.erase(dump_order.begin() + i); break; } } } } void game_options::set_default_activity_interrupts() { for (int adelay = 0; adelay < NUM_DELAYS; ++adelay) for (int aint = 0; aint < NUM_AINTERRUPTS; ++aint) { activity_interrupts[adelay].set(aint, is_delay_interruptible(static_cast(adelay))); } const char *default_activity_interrupts[] = { "interrupt_armour_on = hp_loss, monster_attack, monster", "interrupt_armour_off = interrupt_armour_on", "interrupt_drop_item = interrupt_armour_on", "interrupt_jewellery_on = interrupt_armour_on", "interrupt_memorise = hp_loss, monster_attack, stat", "interrupt_butcher = interrupt_armour_on, teleport, stat", "interrupt_bottle_blood = interrupt_butcher", "interrupt_vampire_feed = interrupt_butcher", "interrupt_multidrop = hp_loss, monster_attack, teleport, stat", "interrupt_macro = interrupt_multidrop", "interrupt_travel = interrupt_butcher, statue, hungry, hit_monster, " "sense_monster", "interrupt_run = interrupt_travel, message", "interrupt_rest = interrupt_run, full_hp, full_mp", // Stair ascents/descents cannot be interrupted except by // teleportation. Attempts to interrupt the delay will just // trash all queued delays, including travel. "interrupt_ascending_stairs = teleport", "interrupt_descending_stairs = teleport", "interrupt_uninterruptible =", "interrupt_weapon_swap =", NULL }; for (int i = 0; default_activity_interrupts[i]; ++i) read_option_line(default_activity_interrupts[i], false); } void game_options::set_activity_interrupt( FixedBitVector &eints, const string &interrupt) { if (interrupt.find(interrupt_prefix) == 0) { string delay_name = interrupt.substr(interrupt_prefix.length()); delay_type delay = get_delay(delay_name); if (delay == NUM_DELAYS) return report_error("Unknown delay: %s\n", delay_name.c_str()); FixedBitVector &refints = activity_interrupts[delay]; eints |= refints; return; } activity_interrupt_type ai = get_activity_interrupt(interrupt); if (ai == NUM_AINTERRUPTS) { return report_error("Delay interrupt name \"%s\" not recognised.\n", interrupt.c_str()); } eints.set(ai); } void game_options::set_activity_interrupt(const string &activity_name, const string &interrupt_names, bool append_interrupts, bool remove_interrupts) { const delay_type delay = get_delay(activity_name); if (delay == NUM_DELAYS) return report_error("Unknown delay: %s\n", activity_name.c_str()); vector interrupts = split_string(",", interrupt_names); FixedBitVector &eints = activity_interrupts[ delay ]; if (remove_interrupts) { FixedBitVector refints; for (int i = 0, size = interrupts.size(); i < size; ++i) set_activity_interrupt(refints, interrupts[i]); for (int i = 0; i < NUM_AINTERRUPTS; ++i) if (refints[i]) eints.set(i, false); } else { if (!append_interrupts) eints.reset(); for (int i = 0, size = interrupts.size(); i < size; ++i) set_activity_interrupt(eints, interrupts[i]); } eints.set(AI_FORCE_INTERRUPT); } #if defined(DGAMELAUNCH) static string _resolve_dir(const char* path, const char* suffix) { return catpath(path, ""); } #else static string _user_home_dir() { #ifdef TARGET_OS_WINDOWS wchar_t home[MAX_PATH]; if (SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, home)) return "./"; else return utf16_to_8(home); #else const char *home = getenv("HOME"); if (!home || !*home) return "./"; else return mb_to_utf8(home); #endif } static string _user_home_subpath(const string subpath) { return catpath(_user_home_dir(), subpath); } static string _resolve_dir(const char* path, const char* suffix) { if (path[0] != '~') return catpath(string(path), suffix); else return _user_home_subpath(catpath(path + 1, suffix)); } #endif void game_options::reset_options() { filename = "unknown"; basefilename = "unknown"; line_num = -1; set_default_activity_interrupts(); #ifdef DEBUG_DIAGNOSTICS quiet_debug_messages.reset(); #endif #if defined(USE_TILE_LOCAL) restart_after_game = true; #else restart_after_game = false; #endif macro_dir = SysEnv.macro_dir; #if !defined(DGAMELAUNCH) if (macro_dir.empty()) { #ifdef UNIX macro_dir = _user_home_subpath(".crawl"); #else macro_dir = "settings/"; #endif } #endif #if defined(TARGET_OS_MACOSX) UNUSED(_resolve_dir); const string tmp_path_base = _user_home_subpath("Library/Application Support/" CRAWL); save_dir = tmp_path_base + "/saves/"; morgue_dir = tmp_path_base + "/morgue/"; if (SysEnv.macro_dir.empty()) macro_dir = tmp_path_base; #else save_dir = _resolve_dir(SysEnv.crawl_dir.c_str(), "saves/"); morgue_dir = _resolve_dir(SysEnv.crawl_dir.c_str(), "morgue/"); #endif #if defined(SHARED_DIR_PATH) shared_dir = _resolve_dir(SHARED_DIR_PATH, ""); #else shared_dir = save_dir; #endif additional_macro_files.clear(); #ifdef DGL_SIMPLE_MESSAGING messaging = true; #endif mouse_input = false; view_max_width = max(33, VIEW_MIN_WIDTH); view_max_height = max(21, VIEW_MIN_HEIGHT); mlist_min_height = 4; msg_min_height = max(7, MSG_MIN_HEIGHT); msg_max_height = max(10, MSG_MIN_HEIGHT); mlist_allow_alternate_layout = false; messages_at_top = false; mlist_targeting = false; msg_condense_repeats = true; msg_condense_short = true; view_lock_x = true; view_lock_y = true; center_on_scroll = false; symmetric_scroll = true; scroll_margin_x = 2; scroll_margin_y = 2; autopickup_on = 1; autopickup_starting_ammo = true; default_manual_training = false; show_newturn_mark = true; show_game_turns = true; game = newgame_def(); remember_name = true; char_set = CSET_DEFAULT; // set it to the .crawlrc default autopickups.reset(); autopickups.set(OBJ_GOLD); autopickups.set(OBJ_SCROLLS); autopickups.set(OBJ_POTIONS); autopickups.set(OBJ_BOOKS); autopickups.set(OBJ_JEWELLERY); autopickups.set(OBJ_WANDS); autopickups.set(OBJ_FOOD); autopickups.set(OBJ_RODS); auto_switch = false; suppress_startup_errors = false; show_uncursed = true; travel_open_doors = true; easy_unequip = true; equip_unequip = false; jewellery_prompt = false; confirm_butcher = CONFIRM_AUTO; chunks_autopickup = true; prompt_for_swap = true; auto_drop_chunks = ADC_NEVER; easy_eat_chunks = false; auto_eat_chunks = false; easy_confirm = CONFIRM_SAFE_EASY; easy_quit_item_prompts = true; allow_self_target = CONFIRM_PROMPT; hp_warning = 30; magic_point_warning = 0; skill_focus = SKM_FOCUS_ON; user_note_prefix = ""; note_all_skill_levels = false; note_skill_max = true; note_xom_effects = true; note_chat_messages = false; note_dgl_messages = true; note_hp_percent = 5; clear_messages = false; #ifdef TOUCH_UI show_more = false; #else show_more = true; #endif small_more = false; pickup_thrown = true; #ifdef DGAMELAUNCH travel_delay = -1; explore_delay = -1; rest_delay = -1; show_travel_trail = true; #else travel_delay = 20; explore_delay = -1; rest_delay = 0; show_travel_trail = false; #endif travel_stair_cost = 500; view_delay = DEFAULT_VIEW_DELAY; arena_dump_msgs = false; arena_dump_msgs_all = false; arena_list_eq = false; // Sort only pickup menus by default. sort_menus.clear(); set_menu_sort("pickup: true"); tc_reachable = BLUE; tc_excluded = LIGHTMAGENTA; tc_exclude_circle = RED; tc_dangerous = CYAN; tc_disconnected = DARKGREY; show_waypoints = true; background_colour = BLACK; // [ds] Default to jazzy colours. detected_item_colour = GREEN; detected_monster_colour = LIGHTRED; remembered_monster_colour = DARKGREY; status_caption_colour = BROWN; easy_exit_menu = false; dos_use_background_intensity = true; assign_item_slot = SS_FORWARD; show_god_gift = MB_MAYBE; // 10 was the cursor step default on Linux. level_map_cursor_step = 7; #ifdef UNIX use_fake_cursor = true; #else use_fake_cursor = false; #endif use_fake_player_cursor = true; show_player_species = false; explore_stop = (ES_ITEM | ES_STAIR | ES_PORTAL | ES_BRANCH | ES_SHOP | ES_ALTAR | ES_RUNED_DOOR | ES_GREEDY_PICKUP_SMART | ES_GREEDY_VISITED_ITEM_STACK | ES_GREEDY_SACRIFICEABLE); // The prompt conditions will be combined into explore_stop after // reading options. explore_stop_prompt = ES_NONE; explore_stop_pickup_ignore.clear(); explore_item_greed = 10; explore_greedy = true; explore_wall_bias = 0; explore_improved = false; travel_key_stop = true; auto_sacrifice = AS_NO; darken_beyond_range = true; dump_on_save = true; dump_kill_places = KDO_ONE_PLACE; dump_message_count = 20; dump_item_origins = IODS_ARTEFACTS | IODS_RODS; dump_item_origin_price = -1; dump_book_spells = true; pickup_menu_limit = 1; flush_input[ FLUSH_ON_FAILURE ] = true; flush_input[ FLUSH_BEFORE_COMMAND ] = false; flush_input[ FLUSH_ON_MESSAGE ] = false; flush_input[ FLUSH_LUA ] = true; fire_items_start = 0; // start at slot 'a' // Clear fire_order and set up the defaults. set_fire_order("launcher, return, " "javelin / tomahawk / stone / rock / net, " "inscribed", false, false); item_stack_summary_minimum = 4; #ifdef WIZARD fsim_rounds = 4000L; fsim_csv = false; fsim_mons = ""; fsim_scale.clear(); fsim_kit.clear(); #endif // These are only used internally, and only from the commandline: // XXX: These need a better place. sc_entries = 0; sc_format = -1; friend_brand = CHATTR_HILITE | (GREEN << 8); neutral_brand = CHATTR_HILITE | (LIGHTGREY << 8); stab_brand = CHATTR_HILITE | (BLUE << 8); may_stab_brand = CHATTR_HILITE | (YELLOW << 8); heap_brand = CHATTR_REVERSE; feature_item_brand = CHATTR_REVERSE; trap_item_brand = CHATTR_REVERSE; no_dark_brand = true; #ifdef WIZARD #ifdef DGAMELAUNCH if (wiz_mode != WIZ_NO) wiz_mode = WIZ_NEVER; #else wiz_mode = WIZ_NO; #endif #endif terp_files.clear(); no_save = false; #ifdef USE_TILE tile_show_items = "!?/%=([)x}:|\\"; tile_skip_title = false; tile_menu_icons = true; // minimap colours tile_player_col = str_to_tile_colour("white"); tile_monster_col = str_to_tile_colour("#660000"); tile_neutral_col = str_to_tile_colour("#660000"); tile_peaceful_col = str_to_tile_colour("#664400"); tile_friendly_col = str_to_tile_colour("#664400"); tile_plant_col = str_to_tile_colour("#446633"); tile_item_col = str_to_tile_colour("#005544"); tile_unseen_col = str_to_tile_colour("black"); tile_floor_col = str_to_tile_colour("#333333"); tile_wall_col = str_to_tile_colour("#666666"); tile_mapped_floor_col = str_to_tile_colour("#222266"); tile_mapped_wall_col = str_to_tile_colour("#444499"); tile_door_col = str_to_tile_colour("#775544"); tile_downstairs_col = str_to_tile_colour("#ff00ff"); tile_upstairs_col = str_to_tile_colour("cyan"); tile_branchstairs_col = str_to_tile_colour("#ff7788"); tile_portal_col = str_to_tile_colour("#ffdd00"); tile_feature_col = str_to_tile_colour("#997700"); tile_trap_col = str_to_tile_colour("#aa6644"); tile_water_col = str_to_tile_colour("#114455"); tile_deep_water_col = str_to_tile_colour("#001122"); tile_lava_col = str_to_tile_colour("#552211"); tile_excluded_col = str_to_tile_colour("#552266"); tile_excl_centre_col = str_to_tile_colour("#552266"); tile_window_col = str_to_tile_colour("#558855"); #endif #ifdef USE_TILE_LOCAL // font selection tile_font_crt_file = MONOSPACED_FONT; tile_font_stat_file = MONOSPACED_FONT; tile_font_msg_file = MONOSPACED_FONT; tile_font_tip_file = MONOSPACED_FONT; tile_font_lbl_file = PROPORTIONAL_FONT; #endif #ifdef USE_TILE_WEB tile_font_crt_family = "monospace"; tile_font_stat_family = "monospace"; tile_font_msg_family = "monospace"; tile_font_lbl_family = "monospace"; #endif #ifdef USE_TILE tile_font_crt_size = 0; tile_font_stat_size = 0; tile_font_msg_size = 0; tile_font_tip_size = 0; tile_font_lbl_size = 0; #endif #ifdef USE_TILE_LOCAL #ifdef USE_FT // TODO: init this from system settings. This would probably require // using fontconfig, but that's planned. tile_font_ft_light = false; #endif // window layout tile_full_screen = SCREENMODE_AUTO; tile_window_width = -90; tile_window_height = -90; # ifdef TOUCH_UI tile_layout_priority = split_string(",", "minimap, command, gold_turn, " "inventory, command2, spell, " "ability, monster"); # else tile_layout_priority = split_string(",", "minimap, inventory, gold_turn, " "command, spell, ability, " "monster"); # endif tile_use_small_layout = MB_MAYBE; #endif #ifdef USE_TILE tile_cell_pixels = 32; tile_filter_scaling = false; tile_map_pixels = 0; tile_force_overlay = false; // delays tile_update_rate = 1000; tile_runrest_rate = 100; tile_key_repeat_delay = 200; tile_tooltip_ms = 500; // XXX: arena may now be chosen after options are read. tile_tag_pref = crawl_state.game_is_arena() ? TAGPREF_NAMED : TAGPREF_ENEMY; tile_show_minihealthbar = true; tile_show_minimagicbar = true; tile_show_demon_tier = true; #ifdef USE_TILE_WEB // disabled by default due to performance issues tile_water_anim = false; #else tile_water_anim = true; #endif tile_misc_anim = true; tile_show_player_species = false; #endif #ifdef USE_TILE_WEB tile_realtime_anim = false; tile_display_mode = "tiles"; tile_level_map_hide_messages = true; tile_level_map_hide_sidebar = false; #endif // map each colour to itself as default for (int i = 0; i < (int)ARRAYSZ(colour); ++i) colour[i] = i; // map each channel to plain (well, default for now since I'm testing) for (int i = 0; i < NUM_MESSAGE_CHANNELS; ++i) channels[i] = MSGCOL_DEFAULT; // Clear vector options. dump_order.clear(); new_dump_fields("header,hiscore,stats,misc,inventory," "skills,spells,overview,mutations,messages," "screenshot,monlist,kills,notes,action_counts"); hp_colour.clear(); hp_colour.push_back(pair(50, YELLOW)); hp_colour.push_back(pair(25, RED)); mp_colour.clear(); mp_colour.push_back(pair(50, YELLOW)); mp_colour.push_back(pair(25, RED)); stat_colour.clear(); stat_colour.push_back(pair(3, RED)); enemy_hp_colour.clear(); // I think these defaults are pretty ugly but apparently OS X has problems // with lighter colours enemy_hp_colour.push_back(GREEN); enemy_hp_colour.push_back(GREEN); enemy_hp_colour.push_back(BROWN); enemy_hp_colour.push_back(BROWN); enemy_hp_colour.push_back(MAGENTA); enemy_hp_colour.push_back(RED); enemy_hp_colour.push_back(LIGHTGREY); visual_monster_hp = false; force_autopickup.clear(); note_monsters.clear(); note_messages.clear(); autoinscriptions.clear(); autoinscribe_cursed = true; note_items.clear(); note_skill_levels.reset(); note_skill_levels.set(1); note_skill_levels.set(5); note_skill_levels.set(10); note_skill_levels.set(15); note_skill_levels.set(27); auto_spell_letters.clear(); force_more_message.clear(); sound_mappings.clear(); menu_colour_mappings.clear(); message_colour_mappings.clear(); drop_filter.clear(); map_file_name.clear(); named_options.clear(); clear_cset_overrides(); clear_feature_overrides(); mon_glyph_overrides.clear(); item_glyph_overrides.clear(); item_glyph_cache.clear(); rest_wait_both = false; // Map each category to itself. The user can override in init.txt kill_map[KC_YOU] = KC_YOU; kill_map[KC_FRIENDLY] = KC_FRIENDLY; kill_map[KC_OTHER] = KC_OTHER; // Forget any files we remembered as included. included.clear(); // Forget variables and such. aliases.clear(); variables.clear(); constants.clear(); } void game_options::clear_cset_overrides() { memset(cset_override, 0, sizeof cset_override); } void game_options::clear_feature_overrides() { feature_overrides.clear(); } ucs_t get_glyph_override(int c) { if (c < 0) { c = -c; if (Options.char_set == CSET_IBM) c = (c & ~0xff) ? 0 : charset_cp437[c & 0xff]; else if (Options.char_set == CSET_DEC) c = (c & 0x80) ? charset_vt100[c & 0x7f] : c; } if (wcwidth(c) != 1) { mprf(MSGCH_ERROR, "Invalid glyph override: %X", c); c = 0; } return c; } static int read_symbol(string s) { if (s.empty()) return 0; if (s.length() > 1 && s[0] == '\\') s = s.substr(1); { ucs_t c; const char *nc = s.c_str(); nc += utf8towc(&c, nc); // no control, combining or CJK characters, please if (!*nc && wcwidth(c) == 1) return c; } int base = 10; if (s.length() > 1 && s[0] == 'x') { s = s.substr(1); base = 16; } char *tail; return -strtoul(s.c_str(), &tail, base); } void game_options::set_fire_order(const string &s, bool append, bool prepend) { if (!append && !prepend) fire_order.clear(); vector slots = split_string(",", s); if (prepend) reverse(slots.begin(), slots.end()); for (int i = 0, size = slots.size(); i < size; ++i) add_fire_order_slot(slots[i], prepend); } void game_options::add_fire_order_slot(const string &s, bool prepend) { unsigned flags = 0; vector alts = split_string("/", s); for (int i = 0, size = alts.size(); i < size; ++i) flags |= _str_to_fire_types(alts[i]); if (flags) { if (prepend) fire_order.insert(fire_order.begin(), flags); else fire_order.push_back(flags); } } void game_options::add_mon_glyph_overrides(const string &mons, cglyph_t &mdisp) { // If one character, this is a monster letter. int letter = -1; if (mons.length() == 1) letter = mons[0] == '_' ? ' ' : mons[0]; bool found = false; for (monster_type i = MONS_0; i < NUM_MONSTERS; ++i) { const monsterentry *me = get_monster_data(i); if (!me || me->mc == MONS_PROGRAM_BUG) continue; if (me->basechar == letter || me->name == mons) { found = true; mon_glyph_overrides[i] = mdisp; } } if (!found) report_error("Unknown monster: \"%s\"", mons.c_str()); } cglyph_t game_options::parse_mon_glyph(const string &s) const { cglyph_t md; md.col = 0; vector phrases = split_string(" ", s); for (int i = 0, size = phrases.size(); i < size; ++i) { const string &p = phrases[i]; const int col = str_to_colour(p, -1, false); if (col != -1 && colour) md.col = col; else md.ch = p == "_"? ' ' : read_symbol(p); } return md; } void game_options::add_mon_glyph_override(const string &text) { vector override = split_string(":", text); if (override.size() != 2u) return; cglyph_t mdisp = parse_mon_glyph(override[1]); if (mdisp.ch || mdisp.col) add_mon_glyph_overrides(override[0], mdisp); } void game_options::add_item_glyph_override(const string &text) { vector override = split_string(":", text); if (override.size() != 2u) return; cglyph_t mdisp = parse_mon_glyph(override[1]); if (mdisp.ch || mdisp.col) item_glyph_overrides.push_back(pair(override[0], mdisp)); } void game_options::add_feature_override(const string &text) { string::size_type epos = text.rfind("}"); if (epos == string::npos) return; string::size_type spos = text.rfind("{", epos); if (spos == string::npos) return; string fname = text.substr(0, spos); string props = text.substr(spos + 1, epos - spos - 1); vector iprops = split_string(",", props, true, true); if (iprops.size() < 1 || iprops.size() > 7) return; if (iprops.size() < 7) iprops.resize(7); trim_string(fname); vector feats = features_by_desc(text_pattern(fname)); if (feats.empty()) return; for (int i = 0, size = feats.size(); i < size; ++i) { if (feats[i] >= NUM_FEATURES) continue; // TODO: handle other object types. feature_def &fov(feature_overrides[feats[i]]); init_fd(fov); #define SYM(n, field) if (ucs_t s = read_symbol(iprops[n])) \ fov.field = s; #define COL(n, field) if (unsigned short c = str_to_colour(iprops[n], BLACK)) \ fov.field = c; SYM(0, symbol); SYM(1, magic_symbol); COL(2, colour); COL(3, map_colour); COL(4, seen_colour); COL(5, em_colour); COL(6, seen_em_colour); #undef SYM #undef COL } } void game_options::add_cset_override(char_set_type set, const string &overrides) { vector overs = split_string(",", overrides); for (int i = 0, size = overs.size(); i < size; ++i) { vector mapping = split_string(":", overs[i]); if (mapping.size() != 2) continue; dungeon_char_type dc = dchar_by_name(mapping[0]); if (dc == NUM_DCHAR_TYPES) continue; add_cset_override(set, dc, read_symbol(mapping[1])); } } void game_options::add_cset_override(char_set_type set, dungeon_char_type dc, int symbol) { cset_override[dc] = get_glyph_override(symbol); } static string _find_crawlrc() { const char* locations_data[][2] = { { SysEnv.crawl_dir.c_str(), "init.txt" }, #ifdef UNIX { SysEnv.home.c_str(), ".crawl/init.txt" }, { SysEnv.home.c_str(), ".crawlrc" }, { SysEnv.home.c_str(), "init.txt" }, #endif #ifndef DATA_DIR_PATH { "", "init.txt" }, { "..", "init.txt" }, { "../settings", "init.txt" }, #endif { NULL, NULL } // placeholder to mark end }; // We'll look for these files in any supplied -rcdirs. static const char *rc_dir_filenames[] = { ".crawlrc", "init.txt", }; // -rc option always wins. if (!SysEnv.crawl_rc.empty()) return SysEnv.crawl_rc; // If we have any rcdirs, look in them for files from the // rc_dir_names list. for (int i = 0, size = SysEnv.rcdirs.size(); i < size; ++i) { for (unsigned n = 0; n < ARRAYSZ(rc_dir_filenames); ++n) { const string rc(catpath(SysEnv.rcdirs[i], rc_dir_filenames[n])); if (file_exists(rc)) return rc; } } // Check all possibilities for init.txt for (int i = 0; locations_data[i][1] != NULL; ++i) { // Don't look at unset options if (locations_data[i][0] != NULL) { const string rc = catpath(locations_data[i][0], locations_data[i][1]); if (file_exists(rc)) return rc; } } // Last attempt: pick up init.txt from datafile_path, which will // also search the settings/ directory. return datafile_path("init.txt", false, false); } static const char* lua_builtins[] = { "clua/stash.lua", "clua/wield.lua", "clua/runrest.lua", "clua/gearset.lua", "clua/trapwalk.lua", "clua/autofight.lua", "clua/automagic.lua", "clua/kills.lua", }; static const char* config_defaults[] = { "defaults/autopickup_exceptions.txt", "defaults/runrest_messages.txt", "defaults/standard_colours.txt", "defaults/food_colouring.txt", "defaults/menu_colours.txt", "defaults/messages.txt", "defaults/misc.txt", }; // Returns an error message if the init.txt was not found. string read_init_file(bool runscript) { Options.reset_options(); // Load Lua builtins. #ifdef CLUA_BINDINGS if (runscript) { for (unsigned int i = 0; i < ARRAYSZ(lua_builtins); ++i) { clua.execfile(lua_builtins[i], false, false); if (!clua.error.empty()) mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str()); } } // Load default options. for (unsigned int i = 0; i < ARRAYSZ(config_defaults); ++i) Options.include(datafile_path(config_defaults[i]), false, runscript); #else UNUSED(lua_builtins); UNUSED(config_defaults); #endif // Load early binding extra options from the command line BEFORE init.txt. Options.filename = "extra opts first"; Options.basefilename = "extra opts first"; Options.line_num = 0; for (unsigned int i = 0; i < SysEnv.extra_opts_first.size(); i++) { Options.line_num++; Options.read_option_line(SysEnv.extra_opts_first[i], true); } // Load init.txt. const string init_file_name(_find_crawlrc()); FileLineInput f(init_file_name.c_str()); if (f.error()) { if (!init_file_name.empty()) { return make_stringf("(\"%s\" is not readable)", init_file_name.c_str()); } #ifdef UNIX return "(~/.crawlrc missing)"; #else return "(no init.txt in current directory)"; #endif } Options.filename = init_file_name; Options.line_num = 0; #ifdef UNIX Options.basefilename = "~/.crawlrc"; #else Options.basefilename = "init.txt"; #endif Options.read_options(f, runscript); // Load late binding extra options from the command line AFTER init.txt. Options.filename = "extra opts last"; Options.basefilename = "extra opts last"; Options.line_num = 0; for (unsigned int i = 0; i < SysEnv.extra_opts_last.size(); i++) { Options.line_num++; Options.read_option_line(SysEnv.extra_opts_last[i], false); } Options.filename = init_file_name; Options.basefilename = get_base_filename(init_file_name); Options.line_num = -1; return ""; } newgame_def read_startup_prefs() { #ifndef DISABLE_STICKY_STARTUP_OPTIONS FileLineInput fl(get_prefs_filename().c_str()); if (fl.error()) return newgame_def(); game_options temp; temp.read_options(fl, false); return temp.game; #endif // !DISABLE_STICKY_STARTUP_OPTIONS } #ifndef DISABLE_STICKY_STARTUP_OPTIONS static void write_newgame_options(const newgame_def& prefs, FILE *f) { if (prefs.type != NUM_GAME_TYPE) fprintf(f, "type = %s\n", gametype_to_str(prefs.type).c_str()); if (!prefs.map.empty()) fprintf(f, "map = %s\n", prefs.map.c_str()); if (!prefs.arena_teams.empty()) fprintf(f, "arena_teams = %s\n", prefs.arena_teams.c_str()); fprintf(f, "name = %s\n", prefs.name.c_str()); if (prefs.species != SP_UNKNOWN) fprintf(f, "species = %s\n", _species_to_str(prefs.species).c_str()); if (prefs.job != JOB_UNKNOWN) fprintf(f, "background = %s\n", _job_to_str(prefs.job).c_str()); if (prefs.weapon != WPN_UNKNOWN) fprintf(f, "weapon = %s\n", _weapon_to_str(prefs.weapon).c_str()); fprintf(f, "fully_random = %s\n", prefs.fully_random ? "yes" : "no"); } #endif // !DISABLE_STICKY_STARTUP_OPTIONS void write_newgame_options_file(const newgame_def& prefs) { #ifndef DISABLE_STICKY_STARTUP_OPTIONS // [ds] Saving startup prefs should work like this: // // 1. If the game is started without specifying a game type, always // save startup preferences in the base savedir. // 2. If the game is started with a game type (Sprint), save startup // preferences in the game-specific savedir. // // The idea is that public servers can use one instance of Crawl // but present Crawl and Sprint as two separate games in the // server-specific game menu -- the startup prefs file for Crawl and // Sprint should never collide, because the public server config will // specify the game type on the command-line. // // For normal users, startup prefs should always be saved in the // same base savedir so that when they start Crawl with "./crawl" // or the equivalent, their last game choices will be remembered, // even if they chose a Sprint game. // // Yes, this is unnecessarily complex. Better ideas welcome. // unwind_var gt(crawl_state.type, Options.game.type); string fn = get_prefs_filename(); FILE *f = fopen_u(fn.c_str(), "w"); if (!f) return; write_newgame_options(prefs, f); fclose(f); #endif // !DISABLE_STICKY_STARTUP_OPTIONS } void save_player_name() { #ifndef DISABLE_STICKY_STARTUP_OPTIONS if (!Options.remember_name) return ; // Read other preferences newgame_def prefs = read_startup_prefs(); prefs.name = you.your_name; // And save write_newgame_options_file(prefs); #endif // !DISABLE_STICKY_STARTUP_OPTIONS } void read_options(const string &s, bool runscript, bool clear_aliases) { StringLineInput st(s); Options.read_options(st, runscript, clear_aliases); } game_options::game_options() { lang = LANG_EN; lang_name = 0; #ifndef TARGET_OS_WINDOWS if (Version::ReleaseType == VER_ALPHA) { set_lang(getenv("LC_ALL")) || set_lang(getenv("LC_MESSAGES")) || set_lang(getenv("LANG")); } #elif defined USE_TILE_LOCAL if (Version::ReleaseType == VER_ALPHA) { char ln[30]; if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SENGLANGUAGE, ln, sizeof(ln))) set_lang(ln); } #endif seed = 0; reset_options(); } void game_options::read_options(LineInput &il, bool runscript, bool clear_aliases) { unsigned int line = 0; bool inscriptblock = false; bool inscriptcond = false; bool isconditional = false; bool l_init = false; if (clear_aliases) aliases.clear(); dlua_chunk luacond(filename); dlua_chunk luacode(filename); while (!il.eof()) { line_num++; string s = il.get_line(); string str = s; line++; trim_string(str); // This is to make some efficient comments if ((str.empty() || str[0] == '#') && !inscriptcond && !inscriptblock) continue; if (!inscriptcond && str[0] == ':') { // The init file is now forced into isconditional mode. isconditional = true; str = str.substr(1); if (!str.empty() && runscript) { // If we're in the middle of an option block, close it. if (!luacond.empty() && l_init) { luacond.add(line - 1, "]])"); l_init = false; } luacond.add(line, str); } continue; } if (!inscriptcond && (str.find("L<") == 0 || str.find("<") == 0)) { // The init file is now forced into isconditional mode. isconditional = true; inscriptcond = true; str = str.substr(str.find("L<") == 0? 2 : 1); // Is this a one-liner? if (!str.empty() && str[ str.length() - 1 ] == '>') { inscriptcond = false; str = str.substr(0, str.length() - 1); } if (!str.empty() && runscript) { // If we're in the middle of an option block, close it. if (!luacond.empty() && l_init) { luacond.add(line - 1, "]])"); l_init = false; } luacond.add(line, str); } continue; } else if (inscriptcond && !str.empty() && (str.find(">") == str.length() - 1 || str == ">L")) { inscriptcond = false; str = str.substr(0, str.length() - 1); if (!str.empty() && runscript) luacond.add(line, str); continue; } else if (inscriptcond) { if (runscript) luacond.add(line, s); continue; } // Handle blocks of Lua if (!inscriptblock && (str.find("Lua{") == 0 || str.find("{") == 0)) { inscriptblock = true; luacode.clear(); luacode.set_file(filename); // Strip leading Lua[ str = str.substr(str.find("Lua{") == 0? 4 : 1); if (!str.empty() && str.find("}") == str.length() - 1) { str = str.substr(0, str.length() - 1); inscriptblock = false; } if (!str.empty()) luacode.add(line, str); if (!inscriptblock && runscript) { #ifdef CLUA_BINDINGS if (luacode.run(clua)) { mprf(MSGCH_ERROR, "Lua error: %s", luacode.orig_error().c_str()); } luacode.clear(); #endif } continue; } else if (inscriptblock && (str == "}Lua" || str == "}")) { inscriptblock = false; #ifdef CLUA_BINDINGS if (runscript) { if (luacode.run(clua)) { mprf(MSGCH_ERROR, "Lua error: %s", luacode.orig_error().c_str()); } } #endif luacode.clear(); continue; } else if (inscriptblock) { luacode.add(line, s); continue; } if (isconditional && runscript) { if (!l_init) { luacond.add(line, "crawl.setopt([["); l_init = true; } luacond.add(line, s); continue; } read_option_line(str, runscript); } #ifdef CLUA_BINDINGS if (runscript && !luacond.empty()) { if (l_init) luacond.add(line, "]])"); if (luacond.run(clua)) mprf(MSGCH_ERROR, "Lua error: %s", luacond.orig_error().c_str()); } #endif Options.explore_stop |= Options.explore_stop_prompt; evil_colour = str_to_colour(variables["evil"]); } void game_options::fixup_options() { // Validate save_dir if (!check_mkdir("Save directory", &save_dir)) end(1); if (!SysEnv.morgue_dir.empty()) morgue_dir = SysEnv.morgue_dir; if (!check_mkdir("Morgue directory", &morgue_dir)) end(1); if (evil_colour == BLACK) evil_colour = MAGENTA; } static int _str_to_killcategory(const string &s) { static const char *kc[] = { "you", "friend", "other", }; for (unsigned i = 0; i < ARRAYSZ(kc); ++i) if (s == kc[i]) return i; return -1; } void game_options::do_kill_map(const string &from, const string &to) { int ifrom = _str_to_killcategory(from), ito = _str_to_killcategory(to); if (ifrom != -1 && ito != -1) kill_map[ifrom] = ito; } int game_options::read_explore_stop_conditions(const string &field) const { int conditions = 0; vector stops = split_string(",", field); for (int i = 0, count = stops.size(); i < count; ++i) { const string c = replace_all_of(stops[i], " ", "_"); if (c == "item" || c == "items") conditions |= ES_ITEM; else if (c == "greedy_pickup") conditions |= ES_GREEDY_PICKUP; else if (c == "greedy_pickup_gold") conditions |= ES_GREEDY_PICKUP_GOLD; else if (c == "greedy_pickup_smart") conditions |= ES_GREEDY_PICKUP_SMART; else if (c == "greedy_pickup_thrown") conditions |= ES_GREEDY_PICKUP_THROWN; else if (c == "shop" || c == "shops") conditions |= ES_SHOP; else if (c == "stair" || c == "stairs") conditions |= ES_STAIR; else if (c == "branch" || c == "branches") conditions |= ES_BRANCH; else if (c == "portal" || c == "portals") conditions |= ES_PORTAL; else if (c == "altar" || c == "altars") conditions |= ES_ALTAR; else if (c == "runed_door") conditions |= ES_RUNED_DOOR; else if (c == "greedy_item" || c == "greedy_items") conditions |= ES_GREEDY_ITEM; else if (c == "greedy_visited_item_stack") conditions |= ES_GREEDY_VISITED_ITEM_STACK; else if (c == "glowing" || c == "glowing_item" || c == "glowing_items") conditions |= ES_GLOWING_ITEM; else if (c == "artefact" || c == "artefacts" || c == "artifact" || c == "artifacts") conditions |= ES_ARTEFACT; else if (c == "rune" || c == "runes") conditions |= ES_RUNE; else if (c == "greedy_sacrificeable" || c == "greedy_sacrificeables" || c == "greedy_sacrificable" || c == "greedy_sacrificables" || c == "greedy_sacrificiable" || c == "greedy_sacrificiables") { conditions |= ES_GREEDY_SACRIFICEABLE; } } return conditions; } // Note the distinction between: // 1. aliases "ae := autopickup_exception" "ae += useless_item" // stored in game_options.aliases. // 2. variables "$slots := abc" "spell_slots += Dispel undead:$slots" // stored in game_options.variables. // 3. constant variables "$slots = abc", "constant = slots". // stored in game_options.variables, but with an extra entry in // game_options.constants. void game_options::add_alias(const string &key, const string &val) { if (key[0] == '$') { string name = key.substr(1); // Don't alter if it's a constant. if (constants.count(name)) return; variables[name] = val; } else aliases[key] = val; } string game_options::unalias(const string &key) const { string_map::const_iterator i = aliases.find(key); return i == aliases.end()? key : i->second; } #define IS_VAR_CHAR(c) (isaalpha(c) || c == '_' || c == '-') string game_options::expand_vars(const string &field) const { string field_out = field; string::size_type curr_pos = 0; // Only try 100 times, so as to not get stuck in infinite recursion. for (int i = 0; i < 100; i++) { string::size_type dollar_pos = field_out.find("$", curr_pos); if (dollar_pos == string::npos || field_out.size() == (dollar_pos + 1)) break; string::size_type start_pos = dollar_pos + 1; if (!IS_VAR_CHAR(field_out[start_pos])) continue; string::size_type end_pos; for (end_pos = start_pos; end_pos < field_out.size(); end_pos++) { if (!IS_VAR_CHAR(field_out[end_pos + 1])) break; } string var_name = field_out.substr(start_pos, end_pos - start_pos + 1); string_map::const_iterator x = variables.find(var_name); if (x == variables.end()) { curr_pos = end_pos + 1; continue; } string dollar_plus_name = "$"; dollar_plus_name += var_name; field_out = replace_all(field_out, dollar_plus_name, x->second); // Start over at beginning curr_pos = 0; } return field_out; } void game_options::add_message_colour_mappings(const string &field, bool prepend, bool subtract) { vector fragments = split_string(",", field); if (prepend) reverse(fragments.begin(), fragments.end()); for (int i = 0, count = fragments.size(); i < count; ++i) add_message_colour_mapping(fragments[i], prepend, subtract); } message_filter game_options::parse_message_filter(const string &filter) { string::size_type pos = filter.find(":"); if (pos && pos != string::npos) { string prefix = filter.substr(0, pos); int channel = str_to_channel(prefix); if (channel != -1 || prefix == "any") { string s = filter.substr(pos + 1); trim_string(s); return message_filter(channel, s); } } return message_filter(filter); } void game_options::add_message_colour_mapping(const string &field, bool prepend, bool subtract) { vector cmap = split_string(":", field, true, true, 1); if (cmap.size() != 2) return; const int col = str_to_colour(cmap[0]); msg_colour_type mcol; if (cmap[0] == "mute") mcol = MSGCOL_MUTED; else if (col == -1) return; else mcol = msg_colour(col); message_colour_mapping m = { parse_message_filter(cmap[1]), mcol }; if (subtract) remove_matching(message_colour_mappings, m); else if (prepend) message_colour_mappings.insert(message_colour_mappings.begin(), m); else message_colour_mappings.push_back(m); } // Option syntax is: // sort_menu = [menu_type:]yes|no|auto:n[:sort_conditions] void game_options::set_menu_sort(string field) { if (field.empty()) return; menu_sort_condition cond(field); // Overrides all previous settings. if (cond.mtype == MT_ANY) sort_menus.clear(); // Override existing values, if necessary. for (unsigned int i = 0; i < sort_menus.size(); i++) if (sort_menus[i].mtype == cond.mtype) { sort_menus[i].sort = cond.sort; sort_menus[i].cmp = cond.cmp; return; } sort_menus.push_back(cond); } void game_options::split_parse(const string &s, const string &separator, void (game_options::*add)(const string &)) { const vector defs = split_string(separator, s); for (int i = 0, size = defs.size(); i < size; ++i) (this->*add)(defs[i]); } void game_options::set_option_fragment(const string &s) { if (s.empty()) return; string::size_type st = s.find(':'); if (st == string::npos) { // Boolean option. if (s[0] == '!') read_option_line(s.substr(1) + " = false"); else read_option_line(s + " = true"); } else { // key:val option. read_option_line(s.substr(0, st) + " = " + s.substr(st + 1)); } } // Not a method of the game_options class since keybindings aren't // stored in that class. static void _bindkey(string field) { const size_t start_bracket = field.find_first_of('['); const size_t end_bracket = field.find_last_of(']'); if (start_bracket == string::npos || end_bracket == string::npos || start_bracket > end_bracket) { mprf(MSGCH_ERROR, "Bad bindkey bracketing in '%s'", field.c_str()); return; } const string key_str = field.substr(start_bracket + 1, end_bracket - start_bracket - 1); int key; // TODO: Function keys. if (key_str.length() == 0) { mprf(MSGCH_ERROR, "No key in bindkey directive '%s'", field.c_str()); return; } else if (key_str.length() == 1) key = key_str[0]; else if (key_str.length() == 2) { if (key_str[0] != '^') { mprf(MSGCH_ERROR, "Invalid key '%s' in bindkey directive '%s'", key_str.c_str(), field.c_str()); return; } key = CONTROL(key_str[1]); } else { mprf(MSGCH_ERROR, "Invalid key '%s' in bindkey directive '%s'", key_str.c_str(), field.c_str()); return; } const size_t start_name = field.find_first_not_of(' ', end_bracket + 1); if (start_name == string::npos) { mprf(MSGCH_ERROR, "No command name for bindkey directive '%s'", field.c_str()); return; } const string name = field.substr(start_name); const command_type cmd = name_to_command(name); if (cmd == CMD_NO_CMD) { mprf(MSGCH_ERROR, "No command named '%s'", name.c_str()); return; } bind_command_to_key(cmd, key); } static bool _is_autopickup_ban(pair entry) { return !entry.second; } static bool _first_less(const pair &l, const pair &r) { return l.first < r.first; } static bool _first_greater(const pair &l, const pair &r) { return l.first > r.first; } // T must be convertible to from a string. template static void _handle_list(vector &value_list, string field, bool append, bool prepend, bool subtract) { if (!append && !prepend && !subtract) value_list.clear(); vector new_entries; vector parts = split_string(",", field); for (vector::iterator part = parts.begin(); part != parts.end(); ++part) { if (part->empty()) continue; if (subtract) remove_matching(value_list, *part); else new_entries.push_back(*part); } _merge_lists(value_list, new_entries, prepend); } void game_options::read_option_line(const string &str, bool runscript) { #define BOOL_OPTION_NAMED(_opt_str, _opt_var) \ if (key == _opt_str) do { \ _opt_var = _read_bool(field, _opt_var); \ } while (false) #define BOOL_OPTION(_opt) BOOL_OPTION_NAMED(#_opt, _opt) #define COLOUR_OPTION_NAMED(_opt_str, _opt_var) \ if (key == _opt_str) do { \ const int col = str_to_colour(field); \ if (col != -1) { \ _opt_var = col; \ } else { \ /*fprintf(stderr, "Bad %s -- %s\n", key, field.c_str());*/ \ report_error("Bad %s -- %s\n", key.c_str(), field.c_str()); \ } \ } while (false) #define COLOUR_OPTION(_opt) COLOUR_OPTION_NAMED(#_opt, _opt) #define CURSES_OPTION_NAMED(_opt_str, _opt_var) \ if (key == _opt_str) do { \ _opt_var = curses_attribute(field); \ } while (false) #define CURSES_OPTION(_opt) CURSES_OPTION_NAMED(#_opt, _opt) #define INT_OPTION_NAMED(_opt_str, _opt_var, _min_val, _max_val) \ if (key == _opt_str) do { \ const int min_val = (_min_val); \ const int max_val = (_max_val); \ int val = _opt_var; \ if (!parse_int(field.c_str(), val)) \ report_error("Bad %s: \"%s\"", _opt_str, field.c_str()); \ else if (val < min_val) \ report_error("Bad %s: %d < %d", _opt_str, val, min_val); \ else if (val > max_val) \ report_error("Bad %s: %d > %d", _opt_str, val, max_val); \ else \ _opt_var = val; \ } while (false) #define INT_OPTION(_opt, _min_val, _max_val) \ INT_OPTION_NAMED(#_opt, _opt, _min_val, _max_val) #define LIST_OPTION_NAMED(_opt_str, _opt_var) \ if (key == _opt_str) do { \ _handle_list(_opt_var, field, plus_equal, caret_equal, minus_equal); \ } while (false) #define LIST_OPTION(_opt) LIST_OPTION_NAMED(#_opt, _opt) string key = ""; string subkey = ""; string field = ""; bool plus_equal = false; bool caret_equal = false; bool minus_equal = false; const int first_equals = str.find('='); // all lines with no equal-signs we ignore if (first_equals < 0) return; field = str.substr(first_equals + 1); field = expand_vars(field); string prequal = trimmed_string(str.substr(0, first_equals)); // Is this a case of key += val? if (prequal.length() && prequal[prequal.length() - 1] == '+') { plus_equal = true; prequal = prequal.substr(0, prequal.length() - 1); trim_string(prequal); } else if (prequal.length() && prequal[prequal.length() - 1] == '-') { minus_equal = true; prequal = prequal.substr(0, prequal.length() - 1); trim_string(prequal); } else if (prequal.length() && prequal[prequal.length() - 1] == '^') { caret_equal = true; prequal = prequal.substr(0, prequal.length() - 1); trim_string(prequal); } else if (prequal.length() && prequal[prequal.length() - 1] == ':') { prequal = prequal.substr(0, prequal.length() - 1); trim_string(prequal); trim_string(field); add_alias(prequal, field); return; } bool plain = !plus_equal && !minus_equal && !caret_equal; prequal = unalias(prequal); const string::size_type first_dot = prequal.find('.'); if (first_dot != string::npos) { key = prequal.substr(0, first_dot); subkey = prequal.substr(first_dot + 1); } else { // no subkey (dots are okay in value field) key = prequal; } // Clean up our data... lowercase(trim_string(key)); lowercase(trim_string(subkey)); // some fields want capitals... none care about external spaces trim_string(field); // Keep unlowercased field around const string orig_field = field; if (key != "name" && key != "crawl_dir" && key != "macro_dir" && key != "species" && key != "background" && key != "job" && key != "race" && key != "class" && key != "ban_pickup" && key != "autopickup_exceptions" && key != "explore_stop_pickup_ignore" && key != "stop_travel" && key != "sound" && key != "force_more_message" && key != "drop_filter" && key != "lua_file" && key != "terp_file" && key != "note_items" && key != "autoinscribe" && key != "note_monsters" && key != "note_messages" && key.find("cset") != 0 && key != "dungeon" && key != "feature" && key != "fire_items_start" && key != "mon_glyph" && key != "item_glyph" && key != "opt" && key != "option" && key != "menu_colour" && key != "menu_color" && key != "message_colour" && key != "message_color" && key != "levels" && key != "level" && key != "entries" && key != "include" && key != "bindkey" && key != "spell_slot" && key.find("font") == string::npos) { lowercase(field); } if (key == "include") include(field, true, runscript); else if (key == "opt" || key == "option") split_parse(field, ",", &game_options::set_option_fragment); else if (key == "autopickup") { // clear out autopickup autopickups.reset(); ucs_t c; for (const char* tp = field.c_str(); int s = utf8towc(&c, tp); tp += s) { object_class_type type = item_class_by_sym(c); if (type < NUM_OBJECT_CLASSES) autopickups.set(type); else report_error("Bad object type '%*s' for autopickup.\n", s, tp); } } #if !defined(DGAMELAUNCH) || defined(DGL_REMEMBER_NAME) else if (key == "name") { // field is already cleaned up from trim_string() game.name = field; } #endif else if (key == "char_set") { if (field == "ascii") char_set = CSET_ASCII; else if (field == "ibm") char_set = CSET_IBM; else if (field == "dec") char_set = CSET_DEC; else if (field == "utf" || field == "unicode") char_set = CSET_OLD_UNICODE; else if (field == "default") char_set = CSET_DEFAULT; else fprintf(stderr, "Bad character set: %s\n", field.c_str()); } else if (key == "language") { if (!set_lang(field.c_str())) report_error("No translations for language: %s\n", field.c_str()); } else if (key == "default_autopickup") { if (_read_bool(field, true)) autopickup_on = 1; else autopickup_on = 0; } else BOOL_OPTION(autopickup_starting_ammo); else if (key == "default_manual_training") { if (_read_bool(field, true)) default_manual_training = true; else default_manual_training = false; } #ifndef DGAMELAUNCH else BOOL_OPTION(restart_after_game); #endif else BOOL_OPTION(auto_switch); else BOOL_OPTION(suppress_startup_errors); else if (key == "easy_confirm") { // decide when to allow both 'Y'/'N' and 'y'/'n' on yesno() prompts if (field == "none") easy_confirm = CONFIRM_NONE_EASY; else if (field == "safe") easy_confirm = CONFIRM_SAFE_EASY; else if (field == "all") easy_confirm = CONFIRM_ALL_EASY; } else if (key == "allow_self_target") { if (field == "yes") allow_self_target = CONFIRM_NONE; else if (field == "no") allow_self_target = CONFIRM_CANCEL; else if (field == "prompt") allow_self_target = CONFIRM_PROMPT; } else BOOL_OPTION(easy_quit_item_prompts); else BOOL_OPTION_NAMED("easy_quit_item_lists", easy_quit_item_prompts); else BOOL_OPTION(travel_open_doors); else BOOL_OPTION(easy_unequip); else BOOL_OPTION(equip_unequip); else BOOL_OPTION(jewellery_prompt); else BOOL_OPTION_NAMED("easy_armour", easy_unequip); else BOOL_OPTION_NAMED("easy_armor", easy_unequip); else if (key == "confirm_butcher") { if (field == "always") confirm_butcher = CONFIRM_ALWAYS; else if (field == "never") confirm_butcher = CONFIRM_NEVER; else if (field == "auto") confirm_butcher = CONFIRM_AUTO; } else BOOL_OPTION(chunks_autopickup); else BOOL_OPTION(prompt_for_swap); else BOOL_OPTION(easy_eat_chunks); else BOOL_OPTION(auto_eat_chunks); else if (key == "auto_drop_chunks") { if (field == "never") auto_drop_chunks = ADC_NEVER; else if (field == "rotten") auto_drop_chunks = ADC_ROTTEN; else if (field == "yes" || field == "true") auto_drop_chunks = ADC_YES; else report_error("Invalid auto_drop_chunks: \"%s\"", field.c_str()); } else if (key == "lua_file" && runscript) { #ifdef CLUA_BINDINGS clua.execfile(field.c_str(), false, false); if (!clua.error.empty()) mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str()); #endif } else if (key == "terp_file" && runscript) terp_files.push_back(field); else if (key == "colour" || key == "color") { const int orig_col = str_to_colour(subkey); const int result_col = str_to_colour(field); if (orig_col != -1 && result_col != -1) colour[orig_col] = result_col; else { fprintf(stderr, "Bad colour -- %s=%d or %s=%d\n", subkey.c_str(), orig_col, field.c_str(), result_col); } } else if (key == "channel") { const int chnl = str_to_channel(subkey); const msg_colour_type col = _str_to_channel_colour(field); if (chnl != -1 && col != MSGCOL_NONE) channels[chnl] = col; else if (chnl == -1) fprintf(stderr, "Bad channel -- %s\n", subkey.c_str()); else if (col == MSGCOL_NONE) fprintf(stderr, "Bad colour -- %s\n", field.c_str()); } else COLOUR_OPTION(background_colour); else COLOUR_OPTION(detected_item_colour); else COLOUR_OPTION(detected_monster_colour); else COLOUR_OPTION(remembered_monster_colour); else if (key.find(interrupt_prefix) == 0) { set_activity_interrupt(key.substr(interrupt_prefix.length()), field, plus_equal || caret_equal, minus_equal); } else if (key.find("cset") == 0) { string cset = key.substr(4); if (!cset.empty() && cset[0] == '_') cset = cset.substr(1); char_set_type cs = NUM_CSET; if (cset == "ibm") cs = CSET_IBM; else if (cset == "dec") cs = CSET_DEC; add_cset_override(cs, field); } else if (key == "feature" || key == "dungeon") split_parse(field, ";", &game_options::add_feature_override); else if (key == "mon_glyph") split_parse(field, ",", &game_options::add_mon_glyph_override); else if (key == "item_glyph") split_parse(field, ",", &game_options::add_item_glyph_override); else CURSES_OPTION(friend_brand); else CURSES_OPTION(neutral_brand); else CURSES_OPTION(stab_brand); else CURSES_OPTION(may_stab_brand); else CURSES_OPTION(feature_item_brand); else CURSES_OPTION(trap_item_brand); // This is useful for terms where dark grey does // not have standout modes (since it's black on black). // This option will use light-grey instead in these cases. else BOOL_OPTION(no_dark_brand); // no_dark_brand applies here as well. else CURSES_OPTION(heap_brand); else COLOUR_OPTION(status_caption_colour); else if (key == "arena_teams") game.arena_teams = field; // [ds] Allow changing map only if the map hasn't been set on the // command-line. else if (key == "map" && crawl_state.sprint_map.empty()) game.map = field; // [ds] For dgamelaunch setups, the player should *not* be able to // set game type in their rc; the only way to set game type for // DGL builds should be the command-line options. else if (key == "type") { #if defined(DGAMELAUNCH) game.type = Options.game.type; #else game.type = _str_to_gametype(field); #endif } else if (key == "species" || key == "race") game.species = _str_to_species(field); else if (key == "background" || key == "job" || key == "class") game.job = _str_to_job(field); else if (key == "weapon") { // Choose this weapon for backgrounds that get choice. game.weapon = str_to_weapon(field); } BOOL_OPTION_NAMED("fully_random", game.fully_random); else if (key == "fire_items_start") { if (isaalpha(field[0])) fire_items_start = letter_to_index(field[0]); else { fprintf(stderr, "Bad fire item start index: %s\n", field.c_str()); } } else if (key == "assign_item_slot") { if (field == "forward") assign_item_slot = SS_FORWARD; else if (field == "backward") assign_item_slot = SS_BACKWARD; } else if (key == "show_god_gift") { if (field == "yes") show_god_gift = MB_TRUE; else if (field == "unid" || field == "unident" || field == "unidentified") show_god_gift = MB_MAYBE; else if (field == "no") show_god_gift = MB_FALSE; else report_error("Unknown show_god_gift value: %s\n", field.c_str()); } else if (key == "fire_order") set_fire_order(field, plus_equal, caret_equal); #if !defined(DGAMELAUNCH) || defined(DGL_REMEMBER_NAME) else BOOL_OPTION(remember_name); #endif #ifndef DGAMELAUNCH else if (key == "save_dir") save_dir = field; else if (key == "morgue_dir") morgue_dir = field; #endif else BOOL_OPTION(show_newturn_mark); else BOOL_OPTION(show_game_turns); else INT_OPTION(hp_warning, 0, 100); else INT_OPTION_NAMED("mp_warning", magic_point_warning, 0, 100); else LIST_OPTION(note_monsters); else LIST_OPTION(note_messages); else INT_OPTION(note_hp_percent, 0, 100); #ifndef DGAMELAUNCH // If DATA_DIR_PATH is set, don't set crawl_dir from .crawlrc. #ifndef DATA_DIR_PATH else if (key == "crawl_dir") { // We shouldn't bother to allocate this a second time // if the user puts two crawl_dir lines in the init file. SysEnv.crawl_dir = field; } else if (key == "macro_dir") macro_dir = field; #endif #endif #ifdef DGL_SIMPLE_MESSAGING else BOOL_OPTION(messaging); #endif else BOOL_OPTION(mouse_input); // These need to be odd, hence allow +1. else INT_OPTION(view_max_width, VIEW_MIN_WIDTH, GXM + 1); else INT_OPTION(view_max_height, VIEW_MIN_HEIGHT, GYM + 1); else INT_OPTION(mlist_min_height, 0, INT_MAX); else INT_OPTION(msg_min_height, MSG_MIN_HEIGHT, INT_MAX); else INT_OPTION(msg_max_height, MSG_MIN_HEIGHT, INT_MAX); else BOOL_OPTION(mlist_allow_alternate_layout); else BOOL_OPTION(messages_at_top); #ifndef USE_TILE else BOOL_OPTION(mlist_targeting); else BOOL_OPTION_NAMED("mlist_targetting", mlist_targeting); #endif else BOOL_OPTION(msg_condense_repeats); else BOOL_OPTION(msg_condense_short); else BOOL_OPTION(view_lock_x); else BOOL_OPTION(view_lock_y); else if (key == "view_lock") { const bool lock = _read_bool(field, true); view_lock_x = view_lock_y = lock; } else BOOL_OPTION(center_on_scroll); else BOOL_OPTION(symmetric_scroll); else if (key == "scroll_margin_x") { scroll_margin_x = atoi(field.c_str()); if (scroll_margin_x < 0) scroll_margin_x = 0; } else if (key == "scroll_margin_y") { scroll_margin_y = atoi(field.c_str()); if (scroll_margin_y < 0) scroll_margin_y = 0; } else if (key == "scroll_margin") { int scrollmarg = atoi(field.c_str()); if (scrollmarg < 0) scrollmarg = 0; scroll_margin_x = scroll_margin_y = scrollmarg; } else if (key == "user_note_prefix") { // field is already cleaned up from trim_string() user_note_prefix = orig_field; } else if (key == "skill_focus") { if (field == "toggle") skill_focus = SKM_FOCUS_TOGGLE; else if (_read_bool(field, true)) skill_focus = SKM_FOCUS_ON; else skill_focus = SKM_FOCUS_OFF; } else BOOL_OPTION(note_all_skill_levels); else BOOL_OPTION(note_skill_max); else BOOL_OPTION(note_xom_effects); else BOOL_OPTION(note_chat_messages); else BOOL_OPTION(note_dgl_messages); else BOOL_OPTION(clear_messages); else BOOL_OPTION(show_more); else BOOL_OPTION(small_more); else if (key == "flush") { if (subkey == "failure") { flush_input[FLUSH_ON_FAILURE] = _read_bool(field, flush_input[FLUSH_ON_FAILURE]); } else if (subkey == "command") { flush_input[FLUSH_BEFORE_COMMAND] = _read_bool(field, flush_input[FLUSH_BEFORE_COMMAND]); } else if (subkey == "message") { flush_input[FLUSH_ON_MESSAGE] = _read_bool(field, flush_input[FLUSH_ON_MESSAGE]); } else if (subkey == "lua") { flush_input[FLUSH_LUA] = _read_bool(field, flush_input[FLUSH_LUA]); } } else if (key == "wiz_mode") { // wiz_mode is recognised as a legal key in all compiles -- bwr #ifdef WIZARD #ifndef DGAMELAUNCH if (field == "never") wiz_mode = WIZ_NEVER; else if (field == "no") wiz_mode = WIZ_NO; else if (field == "yes") wiz_mode = WIZ_YES; else report_error("Unknown wiz_mode option: %s\n", field.c_str()); #endif #endif } else if (key == "ban_pickup") { if (plain) { // Only remove negative, not positive, exceptions. force_autopickup.erase(remove_if(force_autopickup.begin(), force_autopickup.end(), _is_autopickup_ban), force_autopickup.end()); } vector > new_entries; vector args = split_string(",", field); for (int i = 0, size = args.size(); i < size; ++i) { const string &s = args[i]; if (s.empty()) continue; const pair f_a(s, false); if (minus_equal) remove_matching(force_autopickup, f_a); else new_entries.push_back(f_a); } _merge_lists(force_autopickup, new_entries, caret_equal); } else if (key == "autopickup_exceptions") { if (plain) force_autopickup.clear(); vector > new_entries; vector args = split_string(",", field); for (int i = 0, size = args.size(); i < size; ++i) { const string &s = args[i]; if (s.empty()) continue; pair f_a; if (s[0] == '>') f_a = make_pair(s.substr(1), false); else if (s[0] == '<') f_a = make_pair(s.substr(1), true); else f_a = make_pair(s, false); if (minus_equal) remove_matching(force_autopickup, f_a); else new_entries.push_back(f_a); } _merge_lists(force_autopickup, new_entries, caret_equal); } else LIST_OPTION(note_items); #ifndef _MSC_VER // break if-else chain on broken Microsoft compilers with stupid nesting limits else #endif if (key == "autoinscribe") { if (plain) autoinscriptions.clear(); const size_t first = field.find_first_of(':'); const size_t last = field.find_last_of(':'); if (first == string::npos || first != last) { return report_error("Autoinscribe string must have exactly " "one colon: %s\n", field.c_str()); } if (first == 0) { report_error("Autoinscribe pattern is empty: %s\n", field.c_str()); return; } if (last == field.length() - 1) { report_error("Autoinscribe result is empty: %s\n", field.c_str()); return; } vector thesplit = split_string(":", field); if (thesplit.size() != 2) { report_error("Error parsing autoinscribe string: %s\n", field.c_str()); return; } pair entry(thesplit[0], thesplit[1]); if (minus_equal) remove_matching(autoinscriptions, entry); else if (caret_equal) autoinscriptions.insert(autoinscriptions.begin(), entry); else autoinscriptions.push_back(entry); } else BOOL_OPTION(autoinscribe_cursed); #ifndef DGAMELAUNCH else if (key == "map_file_name") map_file_name = field; #endif else if (key == "hp_colour" || key == "hp_color") { if (plain) hp_colour.clear(); vector thesplit = split_string(",", field); for (unsigned i = 0; i < thesplit.size(); ++i) { vector insplit = split_string(":", thesplit[i]); int hp_percent = 100; if (insplit.empty() || insplit.size() > 2 || insplit.size() == 1 && i != 0) { report_error("Bad hp_colour string: %s\n", field.c_str()); break; } if (insplit.size() == 2) hp_percent = atoi(insplit[0].c_str()); const string colstr = insplit[(insplit.size() == 1) ? 0 : 1]; const int scolour = str_to_colour(colstr); if (scolour > 0) { pair entry(hp_percent, scolour); // We do not treat prepend differently since we will be sorting. if (minus_equal) remove_matching(hp_colour, entry); else hp_colour.push_back(entry); } else { report_error("Bad hp_colour: %s", colstr.c_str()); break; } } stable_sort(hp_colour.begin(), hp_colour.end(), _first_greater); } else if (key == "mp_color" || key == "mp_colour") { if (plain) mp_colour.clear(); vector thesplit = split_string(",", field); for (unsigned i = 0; i < thesplit.size(); ++i) { vector insplit = split_string(":", thesplit[i]); int mp_percent = 100; if (insplit.empty() || insplit.size() > 2 || insplit.size() == 1 && i != 0) { report_error("Bad mp_colour string: %s\n", field.c_str()); break; } if (insplit.size() == 2) mp_percent = atoi(insplit[0].c_str()); const string colstr = insplit[(insplit.size() == 1) ? 0 : 1]; const int scolour = str_to_colour(colstr); if (scolour > 0) { pair entry(mp_percent, scolour); // We do not treat prepend differently since we will be sorting. if (minus_equal) remove_matching(mp_colour, entry); else mp_colour.push_back(entry); } else { report_error("Bad mp_colour: %s", colstr.c_str()); break; } } stable_sort(mp_colour.begin(), mp_colour.end(), _first_greater); } else if (key == "stat_colour" || key == "stat_color") { if (plain) stat_colour.clear(); vector thesplit = split_string(",", field); for (unsigned i = 0; i < thesplit.size(); ++i) { vector insplit = split_string(":", thesplit[i]); if (insplit.empty() || insplit.size() > 2 || insplit.size() == 1 && i != 0) { report_error("Bad stat_colour string: %s\n", field.c_str()); break; } int stat_limit = 1; if (insplit.size() == 2) stat_limit = atoi(insplit[0].c_str()); const string colstr = insplit[(insplit.size() == 1) ? 0 : 1]; const int scolour = str_to_colour(colstr); if (scolour > 0) { pair entry(stat_limit, scolour); // We do not treat prepend differently since we will be sorting. if (minus_equal) remove_matching(stat_colour, entry); else stat_colour.push_back(entry); } else { report_error("Bad stat_colour: %s", colstr.c_str()); break; } } stable_sort(stat_colour.begin(), stat_colour.end(), _first_less); } else if (key == "enemy_hp_colour" || key == "enemy_hp_color") { if (plain) enemy_hp_colour.clear(); str_to_enemy_hp_colour(field, caret_equal); } else if (key == "monster_list_colour" || key == "monster_list_color") { if (plain) clear_monster_list_colours(); vector thesplit = split_string(",", field); for (unsigned i = 0; i < thesplit.size(); ++i) { vector insplit = split_string(":", thesplit[i]); if (insplit.empty() || insplit.size() > 2 || insplit.size() == 1 && !minus_equal || insplit.size() == 2 && minus_equal) { report_error("Bad monster_list_colour string: %s\n", field.c_str()); break; } const int scolour = minus_equal ? -1 : str_to_colour(insplit[1]); // No elemental colours! if (scolour >= 16 || scolour < 0 && !minus_equal) { report_error("Bad monster_list_colour: %s", insplit[1].c_str()); break; } if (!set_monster_list_colour(insplit[0], scolour)) { report_error("Bad monster_list_colour key: %s\n", insplit[0].c_str()); break; } } } else if (key == "note_skill_levels") { if (plain) note_skill_levels.reset(); vector thesplit = split_string(",", field); for (unsigned i = 0; i < thesplit.size(); ++i) { int num = atoi(thesplit[i].c_str()); if (num > 0 && num <= 27) note_skill_levels.set(num, !minus_equal); else { report_error("Bad skill level to note -- %s\n", thesplit[i].c_str()); continue; } } } else if (key == "spell_slot") { if (plain) auto_spell_letters.clear(); vector thesplit = split_string(":", field); if (thesplit.size() != 2) { return report_error("Error parsing spell lettering string: %s\n", field.c_str()); } pair entry(lowercase_string(thesplit[0]), thesplit[1]); if (minus_equal) remove_matching(auto_spell_letters, entry); else if (caret_equal) auto_spell_letters.insert(auto_spell_letters.begin(), entry); else auto_spell_letters.push_back(entry); } else BOOL_OPTION(pickup_thrown); #ifdef WIZARD else if (key == "fsim_mode") fsim_mode = field; else BOOL_OPTION(fsim_csv); else LIST_OPTION(fsim_scale); else LIST_OPTION(fsim_kit); else if (key == "fsim_rounds") { fsim_rounds = atol(field.c_str()); if (fsim_rounds < 1000) fsim_rounds = 1000; if (fsim_rounds > 500000L) fsim_rounds = 500000L; } else if (key == "fsim_mons") fsim_mons = field; #endif // WIZARD else if (key == "sort_menus") { vector frags = split_string(";", field); for (int i = 0, size = frags.size(); i < size; ++i) { if (frags[i].empty()) continue; set_menu_sort(frags[i]); } } else if (key == "travel_delay") { // Read travel delay in milliseconds. travel_delay = atoi(field.c_str()); if (travel_delay < -1) travel_delay = -1; if (travel_delay > 2000) travel_delay = 2000; } else if (key == "explore_delay") { // Read explore delay in milliseconds. explore_delay = atoi(field.c_str()); if (explore_delay < -1) explore_delay = -1; if (explore_delay > 2000) explore_delay = 2000; } else if (key == "rest_delay") { // Read explore delay in milliseconds. rest_delay = atoi(field.c_str()); if (rest_delay < -1) rest_delay = -1; if (rest_delay > 2000) rest_delay = 2000; } else BOOL_OPTION(show_travel_trail); else if (key == "level_map_cursor_step") { level_map_cursor_step = atoi(field.c_str()); if (level_map_cursor_step < 1) level_map_cursor_step = 1; if (level_map_cursor_step > 50) level_map_cursor_step = 50; } else BOOL_OPTION(use_fake_cursor); else BOOL_OPTION(use_fake_player_cursor); else BOOL_OPTION(show_player_species); else if (key == "force_more_message") { if (plain) force_more_message.clear(); vector new_entries; vector fragments = split_string(",", field); for (int i = 0, count = fragments.size(); i < count; ++i) { if (fragments[i].empty()) continue; message_filter mf(fragments[i]); string::size_type pos = fragments[i].find(":"); if (pos && pos != string::npos) { string prefix = fragments[i].substr(0, pos); int channel = str_to_channel(prefix); if (channel != -1 || prefix == "any") { string s = fragments[i].substr(pos + 1); mf = message_filter(channel, trim_string(s)); } } if (minus_equal) remove_matching(force_more_message, mf); else new_entries.push_back(mf); } _merge_lists(force_more_message, new_entries, caret_equal); } else LIST_OPTION(drop_filter); else if (key == "travel_avoid_terrain") { // TODO: allow resetting (need reset_forbidden_terrain()) vector seg = split_string(",", field); for (int i = 0, count = seg.size(); i < count; ++i) prevent_travel_to(seg[i]); } else if (key == "tc_reachable") tc_reachable = str_to_colour(field, tc_reachable); else if (key == "tc_excluded") tc_excluded = str_to_colour(field, tc_excluded); else if (key == "tc_exclude_circle") { tc_exclude_circle = str_to_colour(field, tc_exclude_circle); } else if (key == "tc_dangerous") tc_dangerous = str_to_colour(field, tc_dangerous); else if (key == "tc_disconnected") tc_disconnected = str_to_colour(field, tc_disconnected); else LIST_OPTION(auto_exclude); else BOOL_OPTION(easy_exit_menu); else BOOL_OPTION(dos_use_background_intensity); else if (key == "item_stack_summary_minimum") item_stack_summary_minimum = atoi(field.c_str()); else if (key == "explore_stop") { if (plain) explore_stop = ES_NONE; const int new_conditions = read_explore_stop_conditions(field); if (minus_equal) explore_stop &= ~new_conditions; else explore_stop |= new_conditions; } else if (key == "explore_stop_prompt") { if (plain) explore_stop_prompt = ES_NONE; const int new_conditions = read_explore_stop_conditions(field); if (minus_equal) explore_stop_prompt &= ~new_conditions; else explore_stop_prompt |= new_conditions; } else LIST_OPTION(explore_stop_pickup_ignore); else if (key == "explore_item_greed") { explore_item_greed = atoi(field.c_str()); if (explore_item_greed > 1000) explore_item_greed = 1000; else if (explore_item_greed < -1000) explore_item_greed = -1000; } else BOOL_OPTION(explore_greedy); else if (key == "explore_wall_bias") { explore_wall_bias = atoi(field.c_str()); if (explore_wall_bias > 1000) explore_wall_bias = 1000; else if (explore_wall_bias < 0) explore_wall_bias = 0; } else BOOL_OPTION(explore_improved); else BOOL_OPTION(travel_key_stop); else if (key == "auto_sacrifice") { if (field == "prompt_ignore") auto_sacrifice = AS_PROMPT_IGNORE; else if (field == "prompt" || field == "ask") auto_sacrifice = AS_PROMPT; else if (field == "before_explore") auto_sacrifice = AS_BEFORE_EXPLORE; else auto_sacrifice = _read_bool(field, false) ? AS_YES : AS_NO; } else if (key == "sound") { if (plain) sound_mappings.clear(); vector new_entries; vector seg = split_string(",", field); for (int i = 0, count = seg.size(); i < count; ++i) { const string &sub = seg[i]; string::size_type cpos = sub.find(":", 0); if (cpos != string::npos) { sound_mapping entry; entry.pattern = sub.substr(0, cpos); entry.soundfile = sub.substr(cpos + 1); if (minus_equal) remove_matching(sound_mappings, entry); else new_entries.push_back(entry); } } _merge_lists(sound_mappings, new_entries, caret_equal); } #ifndef TARGET_COMPILER_VC // MSVC has a limit on how many if/else if can be chained together. else #endif if (key == "menu_colour" || key == "menu_color") { if (plain) menu_colour_mappings.clear(); vector new_entries; vector seg = split_string(",", field); for (int i = 0, count = seg.size(); i < count; ++i) { // Format is "tag:colour:pattern" or "colour:pattern" (default tag). // FIXME: arrange so that you can use ':' inside a pattern vector subseg = split_string(":", seg[i], false); string tagname, patname, colname; if (subseg.size() < 2) continue; if (subseg.size() >= 3) { tagname = subseg[0]; colname = subseg[1]; patname = subseg[2]; } else { colname = subseg[0]; patname = subseg[1]; } colour_mapping mapping; mapping.tag = tagname; mapping.pattern = patname; mapping.colour = str_to_colour(colname); if (mapping.colour == -1) continue; else if (minus_equal) remove_matching(menu_colour_mappings, mapping); else new_entries.push_back(mapping); } _merge_lists(menu_colour_mappings, new_entries, caret_equal); } else if (key == "message_colour" || key == "message_color") { // TODO: support -= here. if (plain) message_colour_mappings.clear(); add_message_colour_mappings(field, caret_equal, minus_equal); } else if (key == "dump_order") { if (plain) dump_order.clear(); new_dump_fields(field, !minus_equal, caret_equal); } else BOOL_OPTION(dump_on_save); else if (key == "dump_kill_places") { dump_kill_places = (field == "none" ? KDO_NO_PLACES : field == "all" ? KDO_ALL_PLACES : KDO_ONE_PLACE); } else if (key == "kill_map") { // TODO: treat this as a map option (e.g. kill_map.you = friendly) if (plain && field.empty()) { kill_map[KC_YOU] = KC_YOU; kill_map[KC_FRIENDLY] = KC_FRIENDLY; kill_map[KC_OTHER] = KC_OTHER; } vector seg = split_string(",", field); for (int i = 0, count = seg.size(); i < count; ++i) { const string &s = seg[i]; string::size_type cpos = s.find(":", 0); if (cpos != string::npos) { string from = s.substr(0, cpos); string to = s.substr(cpos + 1); do_kill_map(from, to); } } } else BOOL_OPTION(rest_wait_both); else if (key == "dump_message_count") { // Capping is implicit dump_message_count = atoi(field.c_str()); } else if (key == "dump_item_origins") { if (plain) dump_item_origins = IODS_PRICE; vector choices = split_string(",", field); for (int i = 0, count = choices.size(); i < count; ++i) { const string &ch = choices[i]; if (ch == "artefacts" || ch == "artifacts" || ch == "artefact" || ch == "artifact") { dump_item_origins |= IODS_ARTEFACTS; } else if (ch == "ego_arm" || ch == "ego armour" || ch == "ego_armour" || ch == "ego armor" || ch == "ego_armor") { dump_item_origins |= IODS_EGO_ARMOUR; } else if (ch == "ego_weap" || ch == "ego weapon" || ch == "ego_weapon" || ch == "ego weapons" || ch == "ego_weapons") { dump_item_origins |= IODS_EGO_WEAPON; } else if (ch == "jewellery" || ch == "jewelry") dump_item_origins |= IODS_JEWELLERY; else if (ch == "runes") dump_item_origins |= IODS_RUNES; else if (ch == "rods") dump_item_origins |= IODS_RODS; else if (ch == "staves") dump_item_origins |= IODS_STAVES; else if (ch == "books") dump_item_origins |= IODS_BOOKS; else if (ch == "all" || ch == "everything") dump_item_origins = IODS_EVERYTHING; } } else if (key == "dump_item_origin_price") { dump_item_origin_price = atoi(field.c_str()); if (dump_item_origin_price < -1) dump_item_origin_price = -1; } else BOOL_OPTION(dump_book_spells); else if (key == "darken_beyond_range") darken_beyond_range = _read_bool(field, darken_beyond_range); else INT_OPTION(pickup_menu_limit, INT_MIN, INT_MAX); else if (key == "additional_macro_file") { // TODO: this option could probably be improved. For now, keep the // "= means append" behaviour, and don't allow clearing the list; // if we rename to "additional_macro_files" then it could work like // other list options. const string resolved = resolve_include(orig_field, "macro "); if (!resolved.empty()) additional_macro_files.push_back(resolved); } #ifdef USE_TILE else if (key == "tile_show_items") tile_show_items = field; else BOOL_OPTION(tile_skip_title); else BOOL_OPTION(tile_menu_icons); else if (key == "tile_player_col") tile_player_col = str_to_tile_colour(field); else if (key == "tile_monster_col") tile_monster_col = str_to_tile_colour(field); else if (key == "tile_neutral_col") tile_neutral_col = str_to_tile_colour(field); else if (key == "tile_friendly_col") tile_friendly_col = str_to_tile_colour(field); else if (key == "tile_plant_col") tile_plant_col = str_to_tile_colour(field); else if (key == "tile_item_col") tile_item_col = str_to_tile_colour(field); else if (key == "tile_unseen_col") tile_unseen_col = str_to_tile_colour(field); else if (key == "tile_floor_col") tile_floor_col = str_to_tile_colour(field); else if (key == "tile_wall_col") tile_wall_col = str_to_tile_colour(field); else if (key == "tile_mapped_floor_col") tile_mapped_floor_col = str_to_tile_colour(field); else if (key == "tile_mapped_wall_col") tile_mapped_wall_col = str_to_tile_colour(field); else if (key == "tile_door_col") tile_door_col = str_to_tile_colour(field); else if (key == "tile_downstairs_col") tile_downstairs_col = str_to_tile_colour(field); else if (key == "tile_upstairs_col") tile_upstairs_col = str_to_tile_colour(field); else if (key == "tile_branchstairs_col") tile_branchstairs_col = str_to_tile_colour(field); else if (key == "tile_portal_col") tile_portal_col = str_to_tile_colour(field); else if (key == "tile_feature_col") tile_feature_col = str_to_tile_colour(field); else if (key == "tile_trap_col") tile_trap_col = str_to_tile_colour(field); else if (key == "tile_water_col") tile_water_col = str_to_tile_colour(field); else if (key == "tile_deep_water_col") tile_deep_water_col = str_to_tile_colour(field); else if (key == "tile_lava_col") tile_lava_col = str_to_tile_colour(field); else if (key == "tile_excluded_col") tile_excluded_col = str_to_tile_colour(field); else if (key == "tile_excl_centre_col" || key == "tile_excl_center_col") tile_excl_centre_col = str_to_tile_colour(field); else if (key == "tile_window_col") tile_window_col = str_to_tile_colour(field); #ifdef USE_TILE_LOCAL else if (key == "tile_font_crt_file") tile_font_crt_file = field; else if (key == "tile_font_msg_file") tile_font_msg_file = field; else if (key == "tile_font_stat_file") tile_font_stat_file = field; else if (key == "tile_font_tip_file") tile_font_tip_file = field; else if (key == "tile_font_lbl_file") tile_font_lbl_file = field; #endif #ifdef USE_TILE_WEB else if (key == "tile_font_crt_family") tile_font_crt_family = field; else if (key == "tile_font_msg_family") tile_font_msg_family = field; else if (key == "tile_font_stat_family") tile_font_stat_family = field; else if (key == "tile_font_lbl_family") tile_font_lbl_family = field; #endif else INT_OPTION(tile_font_crt_size, 0, INT_MAX); else INT_OPTION(tile_font_msg_size, 0, INT_MAX); else INT_OPTION(tile_font_stat_size, 0, INT_MAX); else INT_OPTION(tile_font_tip_size, 0, INT_MAX); else INT_OPTION(tile_font_lbl_size, 0, INT_MAX); #ifdef USE_TILE_LOCAL #ifdef USE_FT else BOOL_OPTION(tile_font_ft_light); #endif else INT_OPTION(tile_key_repeat_delay, 0, INT_MAX); else if (key == "tile_full_screen") tile_full_screen = (screen_mode)_read_bool(field, tile_full_screen); else INT_OPTION(tile_window_width, INT_MIN, INT_MAX); else INT_OPTION(tile_window_height, INT_MIN, INT_MAX); #endif // USE_TILE_LOCAL #ifdef TOUCH_UI // else BOOL_OPTION(tile_use_small_layout); else if (key == "tile_use_small_layout") { if (field == "true") tile_use_small_layout = MB_TRUE; else if (field == "false") tile_use_small_layout = MB_FALSE; else tile_use_small_layout = MB_MAYBE; } #endif else INT_OPTION(tile_cell_pixels, 1, INT_MAX); else BOOL_OPTION(tile_filter_scaling); else INT_OPTION(tile_map_pixels, 0, INT_MAX); else BOOL_OPTION(tile_force_overlay); else INT_OPTION(tile_tooltip_ms, 0, INT_MAX); else INT_OPTION(tile_update_rate, 50, INT_MAX); else INT_OPTION(tile_runrest_rate, 0, INT_MAX); else BOOL_OPTION(tile_show_minihealthbar); else BOOL_OPTION(tile_show_minimagicbar); else BOOL_OPTION(tile_show_demon_tier); else BOOL_OPTION(tile_water_anim); else BOOL_OPTION(tile_misc_anim); else BOOL_OPTION(tile_show_player_species); else LIST_OPTION(tile_layout_priority); else if (key == "tile_tag_pref") tile_tag_pref = _str_to_tag_pref(field.c_str()); #ifdef USE_TILE_WEB else BOOL_OPTION(tile_realtime_anim); else if (key == "tile_display_mode") { if (field == "tiles" || field == "glyphs" || field == "hybrid") tile_display_mode = field; } else BOOL_OPTION(tile_level_map_hide_messages); else BOOL_OPTION(tile_level_map_hide_sidebar); #endif #endif // USE_TILE else if (key == "bindkey") _bindkey(field); else if (key == "constant") { if (!variables.count(field)) report_error("No variable named '%s' to make constant", field.c_str()); else if (constants.count(field)) report_error("'%s' is already a constant", field.c_str()); else constants.insert(field); } else INT_OPTION(view_delay, 0, INT_MAX); else BOOL_OPTION(arena_dump_msgs); else BOOL_OPTION(arena_dump_msgs_all); else BOOL_OPTION(arena_list_eq); // Catch-all else, copies option into map else if (runscript) { #ifdef CLUA_BINDINGS int setmode = 0; if (plus_equal) setmode = 1; if (minus_equal) setmode = -1; if (caret_equal) setmode = 2; if (!clua.callbooleanfn(false, "c_process_lua_option", "ssd", key.c_str(), orig_field.c_str(), setmode)) #endif { #ifdef CLUA_BINDINGS if (!clua.error.empty()) mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str()); #endif named_options[key] = orig_field; } } } bool game_options::set_lang(const char *lc) { if (!lc) return false; if (lc[0] && lc[1] && (lc[2] == '_' || lc[2] == '-')) return set_lang(string(lc, 2).c_str()); const string l = lowercase_string(lc); // Windows returns it capitalized. if (l == "en" || l == "english") lang = LANG_EN, lang_name = 0; // disable the db else if (l == "cs" || l == "czech" || l == "český" || l == "cesky") lang = LANG_CS, lang_name = "cs"; else if (l == "da" || l == "danish" || l == "dansk") lang = LANG_DA, lang_name = "da"; else if (l == "de" || l == "german" || l == "deutsch") lang = LANG_DE, lang_name = "de"; else if (l == "el" || l == "greek" || l == "ελληνικά" || l == "ελληνικα") lang = LANG_EL, lang_name = "el"; else if (l == "es" || l == "spanish" || l == "español" || l == "espanol") lang = LANG_ES, lang_name = "es"; else if (l == "fi" || l == "finnish" || l == "suomi") lang = LANG_FI, lang_name = "fi"; else if (l == "fr" || l == "french" || l == "français" || l == "francais") lang = LANG_FR, lang_name = "fr"; else if (l == "hu" || l == "hungarian" || l == "magyar") lang = LANG_HU, lang_name = "hu"; else if (l == "it" || l == "italian" || l == "italiano") lang = LANG_IT, lang_name = "it"; else if (l == "ja" || l == "japanese" || l == "日本人") lang = LANG_JA, lang_name = "ja"; else if (l == "ko" || l == "korean" || l == "한국의") lang = LANG_KO, lang_name = "ko"; else if (l == "lt" || l == "lithuanian" || l == "lietuvos") lang = LANG_LT, lang_name = "lt"; else if (l == "lv" || l == "latvian" || l == "lettish" || l == "latvijas" || l == "latviešu" || l == "latvieshu" || l == "latviesu") { lang = LANG_LV, lang_name = "lv"; } else if (l == "nl" || l == "dutch" || l == "nederlands") lang = LANG_NL, lang_name = "nl"; else if (l == "pl" || l == "polish" || l == "polski") lang = LANG_PL, lang_name = "pl"; else if (l == "pt" || l == "portuguese" || l == "português" || l == "portugues") lang = LANG_PT, lang_name = "pt"; else if (l == "ru" || l == "russian" || l == "русский" || l == "русскии") lang = LANG_RU, lang_name = "ru"; else if (l == "sv" || l == "swedish" || l == "svenska") lang = LANG_SV, lang_name = "sv"; else if (l == "zh" || l == "chinese" || l == "中国的" || l == "中國的") lang = LANG_ZH, lang_name = "zh"; // Fake languages do not reset lang_name, allowing a translated // database in an actual language. This is probably pointless for // most fake langs, though. else if (l == "dwarven" || l == "dwarf") lang = LANG_DWARVEN; else if (l == "jäger" || l == "jägerkin" || l == "jager" || l == "jagerkin" || l == "jaeger" || l == "jaegerkin") { lang = LANG_JAGERKIN; } // Due to a conflict with actual "de", this uses slang names for the // option. Let's try to keep to less rude ones, though. else if (l == "kraut" || l == "jerry" || l == "fritz") lang = LANG_KRAUT; else if (l == "futhark" || l == "runes" || l == "runic") lang = LANG_FUTHARK; /* else if (l == "cyr" || l == "cyrillic" || l == "commie" || l == "кириллица") lang = LANG_CYRILLIC; */ else if (l == "wide" || l == "doublewidth" || l == "fullwidth") lang = LANG_WIDE; else if (l == "grunt" || l == "sgrunt" || l == "!!!") lang = LANG_GRUNT; else return false; return true; } // Checks an include file name for safety and resolves it to a readable path. // If safety check fails, throws a string with the reason for failure. // If file cannot be resolved, returns the empty string (this does not throw!) // If file can be resolved, returns the resolved path. string game_options::resolve_include(string parent_file, string included_file, const vector *rcdirs) throw (string) { // Before we start, make sure we convert forward slashes to the platform's // favoured file separator. parent_file = canonicalise_file_separator(parent_file); included_file = canonicalise_file_separator(included_file); // How we resolve include paths: // 1. If it's an absolute path, use it directly. // 2. Try the name relative to the parent filename, if supplied. // 3. Try the name relative to any of the provided rcdirs. // 4. Try locating the name as a regular data file (also checks for the // file name relative to the current directory). // 5. Fail, and return empty string. assert_read_safe_path(included_file); // There's only so much you can do with an absolute path. // Note: absolute paths can only get here if we're not on a // multiuser system. On multiuser systems assert_read_safe_path() // will throw an exception if it sees absolute paths. if (is_absolute_path(included_file)) return file_exists(included_file)? included_file : ""; if (!parent_file.empty()) { const string candidate = get_path_relative_to(parent_file, included_file); if (file_exists(candidate)) return candidate; } if (rcdirs) { const vector &dirs(*rcdirs); for (int i = 0, size = dirs.size(); i < size; ++i) { const string candidate(catpath(dirs[i], included_file)); if (file_exists(candidate)) return candidate; } } return datafile_path(included_file, false, true); } string game_options::resolve_include(const string &file, const char *type) { try { const string resolved = resolve_include(filename, file, &SysEnv.rcdirs); if (resolved.empty()) report_error("Cannot find %sfile \"%s\".", type, file.c_str()); return resolved; } catch (const string &err) { report_error("Cannot include %sfile: %s", type, err.c_str()); return ""; } } bool game_options::was_included(const string &file) const { return included.count(file); } void game_options::include(const string &rawfilename, bool resolve, bool runscript) { const string include_file = resolve ? resolve_include(rawfilename) : rawfilename; if (was_included(include_file)) return; included.insert(include_file); // Change filename to the included filename while we're reading it. unwind_var optfile(filename, include_file); unwind_var basefile(basefilename, rawfilename); // Save current line number unwind_var currlinenum(line_num, 0); // Also unwind any aliases defined in included files. unwind_var unwalias(aliases); FileLineInput fl(include_file.c_str()); if (!fl.error()) read_options(fl, runscript, false); } void game_options::report_error(const char* format, ...) { va_list args; va_start(args, format); string error = vmake_stringf(format, args); va_end(args); // If called before game starts, log a startup error, // otherwise spam the warning channel. if (crawl_state.need_save) { mprf(MSGCH_ERROR, "Warning: %s (%s:%d)", error.c_str(), basefilename.c_str(), line_num); } else { crawl_state.add_startup_error(make_stringf("%s (%s:%d)", error.c_str(), basefilename.c_str(), line_num)); } } static string check_string(const char *s) { return s? s : ""; } void get_system_environment() { // The player's name SysEnv.crawl_name = check_string(getenv("CRAWL_NAME")); // The directory which contians init.txt, macro.txt, morgue.txt // This should end with the appropriate path delimiter. SysEnv.crawl_dir = check_string(getenv("CRAWL_DIR")); #ifdef SAVE_DIR_PATH if (SysEnv.crawl_dir == "") SysEnv.crawl_dir = SAVE_DIR_PATH; #endif #ifdef DGL_SIMPLE_MESSAGING // Enable DGL_SIMPLE_MESSAGING only if SIMPLEMAIL and MAIL are set. const char *simplemail = getenv("SIMPLEMAIL"); if (simplemail && strcmp(simplemail, "0")) { const char *mail = getenv("MAIL"); SysEnv.messagefile = mail? mail : ""; } #endif // The full path to the init file -- this overrides CRAWL_DIR. SysEnv.crawl_rc = check_string(getenv("CRAWL_RC")); #ifdef UNIX // The user's home directory (used to look for ~/.crawlrc file) SysEnv.home = check_string(getenv("HOME")); #endif } static void set_crawl_base_dir(const char *arg) { if (!arg) return; SysEnv.crawl_base = get_parent_directory(arg); } // parse args, filling in Options and game environment as we go. // returns true if no unknown or malformed arguments were found. // Keep this in sync with the option names. enum commandline_option_type { CLO_SCORES, CLO_NAME, CLO_RACE, CLO_CLASS, CLO_PLAIN, CLO_DIR, CLO_RC, CLO_RCDIR, CLO_TSCORES, CLO_VSCORES, CLO_SCOREFILE, CLO_MORGUE, CLO_MACRO, CLO_MAPSTAT, CLO_OBJSTAT, CLO_ITERATIONS, CLO_ARENA, CLO_DUMP_MAPS, CLO_TEST, CLO_SCRIPT, CLO_BUILDDB, CLO_HELP, CLO_VERSION, CLO_SEED, CLO_SAVE_VERSION, CLO_SPRINT, CLO_EXTRA_OPT_FIRST, CLO_EXTRA_OPT_LAST, CLO_SPRINT_MAP, CLO_EDIT_SAVE, CLO_PRINT_CHARSET, CLO_ZOTDEF, CLO_TUTORIAL, CLO_WIZARD, CLO_NO_SAVE, CLO_GDB, CLO_NO_GDB, CLO_NOGDB, #ifdef USE_TILE_WEB CLO_WEBTILES_SOCKET, CLO_AWAIT_CONNECTION, CLO_PRINT_WEBTILES_OPTIONS, #endif CLO_NOPS }; static const char *cmd_ops[] = { "scores", "name", "species", "background", "plain", "dir", "rc", "rcdir", "tscores", "vscores", "scorefile", "morgue", "macro", "mapstat", "objstat", "iters", "arena", "dump-maps", "test", "script", "builddb", "help", "version", "seed", "save-version", "sprint", "extra-opt-first", "extra-opt-last", "sprint-map", "edit-save", "print-charset", "zotdef", "tutorial", "wizard", "no-save", "gdb", "no-gdb", "nogdb", #ifdef USE_TILE_WEB "webtiles-socket", "await-connection", "print-webtiles-options", #endif }; static const int num_cmd_ops = CLO_NOPS; static bool arg_seen[num_cmd_ops]; static string _find_executable_path() { // A lot of OSes give ways to find the location of the running app's // binary executable. This is useful, because argv[0] can be relative // when we really need an absolute path in order to locate the game's // resources. #if defined (TARGET_OS_WINDOWS) wchar_t tempPath[MAX_PATH]; if (GetModuleFileNameW(NULL, tempPath, MAX_PATH)) return utf16_to_8(tempPath); else return ""; #elif defined (TARGET_OS_LINUX) char tempPath[2048]; const ssize_t rsize = readlink("/proc/self/exe", tempPath, sizeof(tempPath) - 1); if (rsize > 0) { tempPath[rsize] = 0; return mb_to_utf8(tempPath); } return ""; #elif defined (TARGET_OS_MACOSX) return mb_to_utf8(NXArgv[0]); #else // We don't know how to find the executable's path on this OS. return ""; #endif } static void _print_version() { printf("Crawl version %s%s", Version::Long, "\n"); printf("Save file version %d.%d%s", TAG_MAJOR_VERSION, TAG_MINOR_VERSION, "\n"); printf("%s", compilation_info); } static void _print_save_version(char *name) { try { string filename = name; // Check for the exact filename first, then go by char name. if (!file_exists(filename)) filename = get_savedir_filename(filename); package save(filename.c_str(), false); reader chrf(&save, "chr"); int major, minor; if (!get_save_version(chrf, major, minor)) fail("Save file is invalid."); else printf("Save file version for %s is %d.%d\n", name, major, minor); } catch (ext_fail_exception &fe) { fprintf(stderr, "Error: %s\n", fe.msg.c_str()); } } enum es_command_type { ES_LS, ES_RM, ES_GET, ES_PUT, ES_REPACK, ES_INFO, NUM_ES }; static struct es_command { es_command_type cmd; const char* name; bool rw; int min_args, max_args; } es_commands[] = { { ES_LS, "ls", false, 0, 0, }, { ES_GET, "get", false, 1, 2, }, { ES_PUT, "put", true, 1, 2, }, { ES_RM, "rm", true, 1, 1, }, { ES_REPACK, "repack", false, 0, 0, }, { ES_INFO, "info", false, 0, 0, }, }; #define FAIL(...) do { fprintf(stderr, __VA_ARGS__); return; } while (0) static void _edit_save(int argc, char **argv) { if (argc <= 1 || !strcmp(argv[1], "help")) { printf("Usage: crawl --edit-save , where may be:\n" " ls list the chunks\n" " get [] extract a chunk into \n" " put [] import a chunk from \n" " defaults to \"chunk\"; use \"-\" for stdout/stdin\n" " rm delete a chunk\n" " repack defrag and reclaim unused space\n" ); return; } const char *name = argv[0]; const char *cmdn = argv[1]; es_command_type cmd = NUM_ES; bool rw; for (unsigned int nc = 0; nc < ARRAYSZ(es_commands); nc++) if (!strcmp(es_commands[nc].name, cmdn)) { if (argc < es_commands[nc].min_args + 2) FAIL("Too few arguments for %s.\n", cmdn); else if (argc > es_commands[nc].max_args + 2) FAIL("Too many arguments for %s.\n", cmdn); cmd = es_commands[nc].cmd; rw = es_commands[nc].rw; break; } if (cmd == NUM_ES) FAIL("Unknown command: %s.\n", cmdn); try { string filename = name; // Check for the exact filename first, then go by char name. if (!file_exists(filename)) filename = get_savedir_filename(filename); package save(filename.c_str(), rw); if (cmd == ES_LS) { vector list = save.list_chunks(); sort(list.begin(), list.end(), numcmpstr); for (size_t i = 0; i < list.size(); i++) printf("%s\n", list[i].c_str()); } else if (cmd == ES_GET) { const char *chunk = argv[2]; if (!*chunk || strlen(chunk) > MAX_CHUNK_NAME_LENGTH) FAIL("Invalid chunk name \"%s\".\n", chunk); if (!save.has_chunk(chunk)) FAIL("No such chunk in the save file.\n"); chunk_reader inc(&save, chunk); const char *file = (argc == 4) ? argv[3] : "chunk"; FILE *f; if (strcmp(file, "-")) f = fopen_u(file, "wb"); else f = stdout; if (!f) sysfail("Can't open \"%s\" for writing", file); char buf[16384]; while (size_t s = inc.read(buf, sizeof(buf))) if (fwrite(buf, 1, s, f) != s) sysfail("Error writing \"%s\"", file); if (f != stdout) if (fclose(f)) sysfail("Write error on close of \"%s\"", file); } else if (cmd == ES_PUT) { const char *chunk = argv[2]; if (!*chunk || strlen(chunk) > MAX_CHUNK_NAME_LENGTH) FAIL("Invalid chunk name \"%s\".\n", chunk); const char *file = (argc == 4) ? argv[3] : "chunk"; FILE *f; if (strcmp(file, "-")) f = fopen_u(file, "rb"); else f = stdin; if (!f) sysfail("Can't read \"%s\"", file); chunk_writer outc(&save, chunk); char buf[16384]; while (size_t s = fread(buf, 1, sizeof(buf), f)) outc.write(buf, s); if (ferror(f)) sysfail("Error reading \"%s\"", file); if (f != stdin) fclose(f); } else if (cmd == ES_RM) { const char *chunk = argv[2]; if (!*chunk || strlen(chunk) > MAX_CHUNK_NAME_LENGTH) FAIL("Invalid chunk name \"%s\".\n", chunk); if (!save.has_chunk(chunk)) FAIL("No such chunk in the save file.\n"); save.delete_chunk(chunk); } else if (cmd == ES_REPACK) { package save2((filename + ".tmp").c_str(), true, true); vector list = save.list_chunks(); for (size_t i = 0; i < list.size(); i++) { char buf[16384]; chunk_reader in(&save, list[i]); chunk_writer out(&save2, list[i]); while (plen_t s = in.read(buf, sizeof(buf))) out.write(buf, s); } save2.commit(); save.unlink(); rename_u((filename + ".tmp").c_str(), filename.c_str()); } else if (cmd == ES_INFO) { vector list = save.list_chunks(); sort(list.begin(), list.end(), numcmpstr); plen_t nchunks = list.size(); plen_t frag = save.get_chunk_fragmentation(""); plen_t flen = save.get_size(); plen_t slack = save.get_slack(); printf("Chunks: (size compressed/uncompressed, fragments, name)\n"); for (size_t i = 0; i < list.size(); i++) { int cfrag = save.get_chunk_fragmentation(list[i]); frag += cfrag; int cclen = save.get_chunk_compressed_length(list[i]); char buf[16384]; chunk_reader in(&save, list[i]); plen_t clen = 0; while (plen_t s = in.read(buf, sizeof(buf))) clen += s; printf("%7u/%7u %3u %s\n", cclen, clen, cfrag, list[i].c_str()); } // the directory is not a chunk visible from the outside printf("Fragmentation: %u/%u (%4.2f)\n", frag, nchunks + 1, ((float)frag) / (nchunks + 1)); printf("Unused space: %u/%u (%u%%)\n", slack, flen, 100 - (100 * (flen - slack) / flen)); // there's also wasted space due to fragmentation, but since // it's linear, there's no need to print it } } catch (ext_fail_exception &fe) { fprintf(stderr, "Error: %s\n", fe.msg.c_str()); } } #undef FAIL #ifdef USE_TILE_WEB static void _write_colour_list(const vector > variable, const string &name) { tiles.json_open_array(name); for (unsigned int i = 0; i < variable.size(); i++) { tiles.json_open_object(); tiles.json_write_int("value", variable[i].first); tiles.json_write_string("colour", colour_to_str(variable[i].second)); tiles.json_close_object(); } tiles.json_close_array(); } static void _write_vcolour(const string &name, VColour colour) { tiles.json_open_object(name); tiles.json_write_int("r", colour.r); tiles.json_write_int("g", colour.g); tiles.json_write_int("b", colour.b); if (colour.a != 255) tiles.json_write_int("a", colour.a); tiles.json_close_object(); } static void _write_minimap_colours() { _write_vcolour("tile_player_col", Options.tile_player_col); _write_vcolour("tile_monster_col", Options.tile_monster_col); _write_vcolour("tile_neutral_col", Options.tile_neutral_col); _write_vcolour("tile_peaceful_col", Options.tile_peaceful_col); _write_vcolour("tile_friendly_col", Options.tile_friendly_col); _write_vcolour("tile_plant_col", Options.tile_plant_col); _write_vcolour("tile_item_col", Options.tile_item_col); _write_vcolour("tile_unseen_col", Options.tile_unseen_col); _write_vcolour("tile_floor_col", Options.tile_floor_col); _write_vcolour("tile_wall_col", Options.tile_wall_col); _write_vcolour("tile_mapped_floor_col", Options.tile_mapped_floor_col); _write_vcolour("tile_mapped_wall_col", Options.tile_mapped_wall_col); _write_vcolour("tile_door_col", Options.tile_door_col); _write_vcolour("tile_downstairs_col", Options.tile_downstairs_col); _write_vcolour("tile_upstairs_col", Options.tile_upstairs_col); _write_vcolour("tile_branchstairs_col", Options.tile_branchstairs_col); _write_vcolour("tile_portal_col", Options.tile_portal_col); _write_vcolour("tile_feature_col", Options.tile_feature_col); _write_vcolour("tile_trap_col", Options.tile_trap_col); _write_vcolour("tile_water_col", Options.tile_water_col); _write_vcolour("tile_deep_water_col", Options.tile_deep_water_col); _write_vcolour("tile_lava_col", Options.tile_lava_col); _write_vcolour("tile_excluded_col", Options.tile_excluded_col); _write_vcolour("tile_excl_centre_col", Options.tile_excl_centre_col); _write_vcolour("tile_window_col", Options.tile_window_col); } void game_options::write_webtiles_options(const string& name) { tiles.json_open_object(name); _write_colour_list(Options.hp_colour, "hp_colour"); _write_colour_list(Options.mp_colour, "mp_colour"); _write_colour_list(Options.stat_colour, "stat_colour"); tiles.json_write_bool("tile_show_minihealthbar", Options.tile_show_minihealthbar); tiles.json_write_bool("tile_show_minimagicbar", Options.tile_show_minimagicbar); tiles.json_write_bool("tile_show_demon_tier", Options.tile_show_demon_tier); tiles.json_write_int("tile_map_pixels", Options.tile_map_pixels); tiles.json_write_string("tile_display_mode", Options.tile_display_mode); tiles.json_write_int("tile_cell_pixels", Options.tile_cell_pixels); tiles.json_write_bool("tile_filter_scaling", Options.tile_filter_scaling); tiles.json_write_bool("tile_water_anim", Options.tile_water_anim); tiles.json_write_bool("tile_misc_anim", Options.tile_misc_anim); tiles.json_write_bool("tile_realtime_anim", Options.tile_realtime_anim); tiles.json_write_bool("tile_level_map_hide_messages", Options.tile_level_map_hide_messages); tiles.json_write_bool("tile_level_map_hide_sidebar", Options.tile_level_map_hide_sidebar); tiles.json_write_string("tile_font_crt_family", Options.tile_font_crt_family); tiles.json_write_string("tile_font_stat_family", Options.tile_font_stat_family); tiles.json_write_string("tile_font_msg_family", Options.tile_font_msg_family); tiles.json_write_string("tile_font_lbl_family", Options.tile_font_lbl_family); tiles.json_write_int("tile_font_crt_size", Options.tile_font_crt_size); tiles.json_write_int("tile_font_stat_size", Options.tile_font_stat_size); tiles.json_write_int("tile_font_msg_size", Options.tile_font_msg_size); tiles.json_write_int("tile_font_lbl_size", Options.tile_font_lbl_size); tiles.json_write_bool("show_game_turns", Options.show_game_turns); _write_minimap_colours(); tiles.json_close_object(); } static void _print_webtiles_options() { Options.write_webtiles_options(""); printf("%s\n", tiles.get_message().c_str()); } #endif static bool _check_extra_opt(char* _opt) { string opt(_opt); trim_string(opt); if (opt[0] == ':' || opt[0] == '<' || opt[0] == '{' || starts_with(opt, "L<") || starts_with(opt, "Lua{")) { fprintf(stderr, "An extra option can't use Lua (%s)\n", _opt); return false; } if (opt[0] == '#') { fprintf(stderr, "An extra option can't be a comment (%s)\n", _opt); return false; } if (opt.find_first_of('=') == string::npos) { fprintf(stderr, "An extra opt must contain a '=' (%s)\n", _opt); return false; } vector parts = split_string(opt, "="); if (opt.find_first_of('=') == 0 || parts[0].length() == 0) { fprintf(stderr, "An extra opt must have an option name (%s)\n", _opt); return false; } return true; } bool parse_args(int argc, char **argv, bool rc_only) { COMPILE_CHECK(ARRAYSZ(cmd_ops) == CLO_NOPS); if (crawl_state.command_line_arguments.empty()) { crawl_state.command_line_arguments.insert( crawl_state.command_line_arguments.end(), argv, argv + argc); } string exe_path = _find_executable_path(); if (!exe_path.empty()) set_crawl_base_dir(exe_path.c_str()); else set_crawl_base_dir(argv[0]); SysEnv.crawl_exe = get_base_filename(argv[0]); SysEnv.rcdirs.clear(); SysEnv.map_gen_iters = 0; if (argc < 2) // no args! return true; char *arg, *next_arg; int current = 1; bool nextUsed = false; int ecount; // initialise for (int i = 0; i < num_cmd_ops; i++) arg_seen[i] = false; if (SysEnv.cmd_args.empty()) { for (int i = 1; i < argc; ++i) SysEnv.cmd_args.push_back(argv[i]); } while (current < argc) { // get argument arg = argv[current]; // next argument (if there is one) if (current+1 < argc) next_arg = argv[current+1]; else next_arg = NULL; nextUsed = false; // arg MUST begin with '-' char c = arg[0]; if (c != '-') { fprintf(stderr, "Option '%s' is invalid; options must be prefixed " "with -\n\n", arg); return false; } // Look for match (we accept both -option and --option). if (arg[1] == '-') arg = &arg[2]; else arg = &arg[1]; int o; for (o = 0; o < num_cmd_ops; o++) if (strcasecmp(cmd_ops[o], arg) == 0) break; // Print the list of commandline options for "--help". if (o == CLO_HELP) return false; if (o == num_cmd_ops) { fprintf(stderr, "Unknown option: %s\n\n", argv[current]); return false; } // Disallow options specified more than once. if (arg_seen[o] == true) return false; // Set arg to 'seen'. arg_seen[o] = true; // Partially parse next argument. bool next_is_param = false; if (next_arg != NULL && (next_arg[0] != '-' || strlen(next_arg) == 1)) { next_is_param = true; } // Take action according to the cmd chosen. switch (o) { case CLO_SCORES: case CLO_TSCORES: case CLO_VSCORES: if (!next_is_param) ecount = -1; // default else // optional number given { ecount = atoi(next_arg); if (ecount < 1) ecount = 1; if (ecount > SCORE_FILE_ENTRIES) ecount = SCORE_FILE_ENTRIES; nextUsed = true; } if (!rc_only) { Options.sc_entries = ecount; if (o == CLO_TSCORES) Options.sc_format = SCORE_TERSE; else if (o == CLO_VSCORES) Options.sc_format = SCORE_VERBOSE; else if (o == CLO_SCORES) Options.sc_format = SCORE_REGULAR; } break; case CLO_MAPSTAT: case CLO_OBJSTAT: #ifdef DEBUG_DIAGNOSTICS if (o == CLO_MAPSTAT) crawl_state.map_stat_gen = true; else crawl_state.obj_stat_gen = true; if (!SysEnv.map_gen_iters) SysEnv.map_gen_iters = 100; if (next_is_param) { SysEnv.map_gen_range.reset(new depth_ranges); *SysEnv.map_gen_range = depth_ranges::parse_depth_ranges(next_arg); nextUsed = true; } break; #else fprintf(stderr, "mapstat and objstat are available only in " "DEBUG_DIAGNOSTICS builds.\n"); end(1); #endif case CLO_ITERATIONS: #ifdef DEBUG_DIAGNOSTICS if (!next_is_param || !isadigit(*next_arg)) { fprintf(stderr, "Integer argument required for -%s\n", arg); end(1); } else { SysEnv.map_gen_iters = atoi(next_arg); if (SysEnv.map_gen_iters < 1) SysEnv.map_gen_iters = 1; else if (SysEnv.map_gen_iters > 10000) SysEnv.map_gen_iters = 10000; nextUsed = true; } #else fprintf(stderr, "mapstat and objstat are available only in " "DEBUG_DIAGNOSTICS builds.\n"); end(1); #endif break; case CLO_ARENA: if (!rc_only) { Options.game.type = GAME_TYPE_ARENA; Options.restart_after_game = false; } if (next_is_param) { if (!rc_only) Options.game.arena_teams = next_arg; nextUsed = true; } break; case CLO_DUMP_MAPS: crawl_state.dump_maps = true; break; case CLO_TEST: crawl_state.test = true; if (next_is_param) { if (!(crawl_state.test_list = !strcmp(next_arg, "list"))) crawl_state.tests_selected = split_string(",", next_arg); nextUsed = true; } break; case CLO_SCRIPT: crawl_state.test = true; crawl_state.script = true; if (current < argc - 1) { crawl_state.tests_selected = split_string(",", next_arg); for (int extra = current + 2; extra < argc; ++extra) crawl_state.script_args.push_back(argv[extra]); current = argc; } else end(1, false, "-script must specify comma-separated script names"); break; case CLO_BUILDDB: if (next_is_param) return false; crawl_state.build_db = true; break; case CLO_GDB: crawl_state.no_gdb = 0; break; case CLO_NO_GDB: case CLO_NOGDB: crawl_state.no_gdb = "GDB disabled via the command line."; break; case CLO_MACRO: if (!next_is_param) return false; SysEnv.macro_dir = next_arg; nextUsed = true; break; case CLO_MORGUE: if (!next_is_param) return false; if (!rc_only) SysEnv.morgue_dir = next_arg; nextUsed = true; break; case CLO_SCOREFILE: if (!next_is_param) return false; if (!rc_only) SysEnv.scorefile = next_arg; nextUsed = true; break; case CLO_NAME: if (!next_is_param) return false; if (!rc_only) Options.game.name = next_arg; nextUsed = true; break; case CLO_RACE: case CLO_CLASS: if (!next_is_param) return false; if (!rc_only) { if (o == 2) Options.game.species = _str_to_species(string(next_arg)); if (o == 3) Options.game.job = _str_to_job(string(next_arg)); } nextUsed = true; break; case CLO_PLAIN: if (next_is_param) return false; if (!rc_only) { Options.char_set = CSET_ASCII; init_char_table(Options.char_set); } break; case CLO_RCDIR: // Always parse. if (!next_is_param) return false; SysEnv.add_rcdir(next_arg); nextUsed = true; break; case CLO_DIR: // Always parse. if (!next_is_param) return false; SysEnv.crawl_dir = next_arg; nextUsed = true; break; case CLO_RC: // Always parse. if (!next_is_param) return false; SysEnv.crawl_rc = next_arg; nextUsed = true; break; case CLO_HELP: // Shouldn't happen. return false; case CLO_VERSION: _print_version(); end(0); case CLO_SAVE_VERSION: // Always parse. if (!next_is_param) return false; _print_save_version(next_arg); end(0); case CLO_EDIT_SAVE: // Always parse. _edit_save(argc - current - 1, argv + current + 1); end(0); case CLO_SEED: if (!next_is_param) return false; if (!sscanf(next_arg, "%x", &Options.seed)) return false; nextUsed = true; break; case CLO_SPRINT: if (!rc_only) Options.game.type = GAME_TYPE_SPRINT; break; case CLO_SPRINT_MAP: if (!next_is_param) return false; nextUsed = true; crawl_state.sprint_map = next_arg; Options.game.map = next_arg; break; case CLO_ZOTDEF: if (!rc_only) Options.game.type = GAME_TYPE_ZOTDEF; break; case CLO_TUTORIAL: if (!rc_only) Options.game.type = GAME_TYPE_TUTORIAL; break; case CLO_WIZARD: #ifdef WIZARD if (!rc_only) Options.wiz_mode = WIZ_NO; #endif break; case CLO_NO_SAVE: if (!rc_only) Options.no_save = true; break; #ifdef USE_TILE_WEB case CLO_WEBTILES_SOCKET: nextUsed = true; tiles.m_sock_name = next_arg; break; case CLO_AWAIT_CONNECTION: tiles.m_await_connection = true; break; case CLO_PRINT_WEBTILES_OPTIONS: if (!rc_only) { _print_webtiles_options(); end(0); } break; #endif case CLO_PRINT_CHARSET: if (rc_only) break; #ifdef DGAMELAUNCH // Tell DGL we don't use ancient charsets anymore. The glyph set // doesn't matter here, just the encoding. printf("UNICODE\n"); #else printf("This option is for DGL use only.\n"); #endif end(0); break; case CLO_EXTRA_OPT_FIRST: if (!next_is_param) return false; // Don't print the help message if the opt was wrong if (!_check_extra_opt(next_arg)) return true; SysEnv.extra_opts_first.push_back(next_arg); nextUsed = true; // Can be used multiple times. arg_seen[o] = false; break; case CLO_EXTRA_OPT_LAST: if (!next_is_param) return false; // Don't print the help message if the opt was wrong if (!_check_extra_opt(next_arg)) return true; SysEnv.extra_opts_last.push_back(next_arg); nextUsed = true; // Can be used multiple times. arg_seen[o] = false; break; } // Update position. current++; if (nextUsed) current++; } return true; } ////////////////////////////////////////////////////////////////////////// // game_options int game_options::o_int(const char *name, int def) const { int val = def; opt_map::const_iterator i = named_options.find(name); if (i != named_options.end()) val = atoi(i->second.c_str()); return val; } bool game_options::o_bool(const char *name, bool def) const { bool val = def; opt_map::const_iterator i = named_options.find(name); if (i != named_options.end()) val = _read_bool(i->second, val); return val; } string game_options::o_str(const char *name, const char *def) const { string val; opt_map::const_iterator i = named_options.find(name); if (i != named_options.end()) val = i->second; else if (def) val = def; return val; } int game_options::o_colour(const char *name, int def) const { string val = o_str(name); trim_string(val); lowercase(val); int col = str_to_colour(val); return col == -1? def : col; } /////////////////////////////////////////////////////////////////////// // system_environment void system_environment::add_rcdir(const string &dir) { string cdir = canonicalise_file_separator(dir); if (dir_exists(cdir)) rcdirs.push_back(cdir); else end(1, false, "Cannot find -rcdir \"%s\"", cdir.c_str()); } /////////////////////////////////////////////////////////////////////// // menu_sort_condition menu_sort_condition::menu_sort_condition(menu_type _mt, int _sort) : mtype(_mt), sort(_sort), cmp() { } menu_sort_condition::menu_sort_condition(const string &s) : mtype(MT_ANY), sort(-1), cmp() { string cp = s; set_menu_type(cp); set_sort(cp); set_comparators(cp); } bool menu_sort_condition::matches(menu_type mt) const { return mtype == MT_ANY || mtype == mt; } void menu_sort_condition::set_menu_type(string &s) { static struct { const string mname; menu_type mtype; } menu_type_map[] = { { "any:", MT_ANY }, { "inv:", MT_INVLIST }, { "drop:", MT_DROP }, { "pickup:", MT_PICKUP }, { "know:", MT_KNOW } }; for (unsigned mi = 0; mi < ARRAYSZ(menu_type_map); ++mi) { const string &name = menu_type_map[mi].mname; if (s.find(name) == 0) { s = s.substr(name.length()); mtype = menu_type_map[mi].mtype; break; } } } void menu_sort_condition::set_sort(string &s) { // Strip off the optional sort clauses and get the primary sort condition. string::size_type trail_pos = s.find(':'); if (s.find("auto:") == 0) trail_pos = s.find(':', trail_pos + 1); string sort_cond = trail_pos == string::npos? s : s.substr(0, trail_pos); trim_string(sort_cond); sort = _read_bool_or_number(sort_cond, sort, "auto:"); if (trail_pos != string::npos) s = s.substr(trail_pos + 1); else s.clear(); } void menu_sort_condition::set_comparators(string &s) { init_item_sort_comparators( cmp, s.empty()? "equipped, basename, qualname, curse, qty" : s); }