/* $Id$ */ /* Copyright (C) 2003 - 2010 by David White Part of the Battle for Wesnoth Project http://www.wesnoth.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 or at your option any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING file for more details. */ #include "global.hpp" #include "SDL.h" #include "SDL_mixer.h" #ifndef DISABLE_EDITOR #include "SDL_getenv.h" #endif #include "about.hpp" #include "ai/configuration.hpp" #include "config.hpp" #include "config_cache.hpp" #include "construct_dialog.hpp" #include "cursor.hpp" #include "dialogs.hpp" #include "foreach.hpp" #include "game_display.hpp" #include "builder.hpp" #include "filesystem.hpp" #include "font.hpp" #include "formula.hpp" #include "game_config.hpp" #include "game_errors.hpp" #include "gamestatus.hpp" #include "gettext.hpp" #include "gui/dialogs/addon_connect.hpp" #include "gui/dialogs/campaign_selection.hpp" #include "gui/dialogs/language_selection.hpp" #include "gui/dialogs/message.hpp" #include "gui/dialogs/mp_method_selection.hpp" #include "gui/dialogs/title_screen.hpp" #include "gui/dialogs/transient_message.hpp" #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS #include "gui/widgets/debug.hpp" #endif #include "gui/auxiliary/event/handler.hpp" #include "gui/widgets/settings.hpp" #include "gui/widgets/window.hpp" #include "help.hpp" #include "hotkeys.hpp" #include "intro.hpp" #include "language.hpp" #include "loadscreen.hpp" #include "log.hpp" #include "map_exception.hpp" #include "widgets/menu.hpp" #include "marked-up_text.hpp" #include "multiplayer.hpp" #include "network.hpp" #include "playcampaign.hpp" #include "preferences_display.hpp" #include "addon/manager.hpp" #include "replay.hpp" #include "savegame.hpp" #include "scripting/lua.hpp" #include "sound.hpp" #include "statistics.hpp" #include "thread.hpp" #include "titlescreen.hpp" #include "upload_log.hpp" #include "wml_exception.hpp" #include "wml_separators.hpp" #include "serialization/binary_or_text.hpp" #include "serialization/parser.hpp" #include "serialization/preprocessor.hpp" #include "serialization/string_utils.hpp" #include "sha1.hpp" #include "version.hpp" //#ifdef _WIN32 //#include "locale.h" //#endif #ifndef DISABLE_EDITOR #include "editor/editor_main.hpp" #endif #include "wesconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // Minimum stack cookie to prevent stack overflow on AmigaOS4 #ifdef __amigaos4__ const char __attribute__((used)) stackcookie[] = "\0$STACK: 16000000"; #endif static lg::log_domain log_config("config"); #define ERR_CONFIG LOG_STREAM(err, log_config) #define WRN_CONFIG LOG_STREAM(warn, log_config) #define LOG_CONFIG LOG_STREAM(info, log_config) #define LOG_GENERAL LOG_STREAM(info, lg::general) #define WRN_GENERAL LOG_STREAM(warn, lg::general) #define DBG_GENERAL LOG_STREAM(debug, lg::general) static lg::log_domain log_network("network"); #define ERR_NET LOG_STREAM(err, log_network) static lg::log_domain log_preprocessor("preprocessor"); #define LOG_PREPROC LOG_STREAM(info,log_preprocessor) static bool less_campaigns_rank(const config &a, const config &b) { return a["rank"].to_int(1000) < b["rank"].to_int(1000); } namespace { struct jump_to_campaign_info { public: jump_to_campaign_info(bool jump,int difficulty, std::string campaign_id,std::string scenario_id) : jump_(jump) , difficulty_(difficulty) , campaign_id_(campaign_id) , scenario_id_(scenario_id) { } bool jump_; int difficulty_; std::string campaign_id_,scenario_id_; }; class game_controller { public: game_controller(int argc, char** argv); ~game_controller(); game_display& disp(); bool init_video(); bool init_config(const bool force=false); bool init_language(); bool play_test(); bool play_multiplayer_mode(); bool play_screenshot_mode(); void reload_changed_game_config(); bool is_loading() const; void clear_loaded_game() { game::load_game_exception::game.clear(); } bool load_game(); void set_tutorial(); std::string jump_to_campaign_id() const; bool new_campaign(); bool goto_campaign(); bool goto_multiplayer(); #ifndef DISABLE_EDITOR bool goto_editor(); #endif bool play_multiplayer(); bool change_language(); void show_preferences(); void show_upload_begging(); enum RELOAD_GAME_DATA { RELOAD_DATA, NO_RELOAD_DATA }; void launch_game(RELOAD_GAME_DATA reload=RELOAD_DATA); void play_replay(); #ifndef DISABLE_EDITOR editor::EXIT_STATUS start_editor(const std::string& filename = ""); #endif void start_wesnothd(); const config& game_config() const { return game_config_; } private: game_controller(const game_controller&); void operator=(const game_controller&); void load_game_cfg(const bool force=false); void set_unit_data(); void mark_completed_campaigns(std::vector& campaigns); const int argc_; int arg_; const char* const * const argv_; //this should get destroyed *after* the video, since we want //to clean up threads after the display disappears. const threading::manager thread_manager; CVideo video_; const font::manager font_manager_; const preferences::manager prefs_manager_; const image::manager image_manager_; const events::event_context main_event_context_; const hotkey::manager hotkey_manager_; const upload_log::manager upload_log_manager_; sound::music_thinker music_thinker_; resize_monitor resize_monitor_; binary_paths_manager paths_manager_; std::string test_scenario_; bool test_mode_, multiplayer_mode_, no_gui_, screenshot_mode_; std::string screenshot_map_, screenshot_filename_; int force_bpp_; config game_config_; preproc_map old_defines_map_; util::scoped_ptr disp_; game_state state_; std::string multiplayer_server_; bool jump_to_multiplayer_; jump_to_campaign_info jump_to_campaign_; #ifndef DISABLE_EDITOR bool jump_to_editor_; #endif game_config::config_cache& cache_; }; game_controller::game_controller(int argc, char** argv) : argc_(argc), arg_(1), argv_(argv), thread_manager(), video_(), font_manager_(), prefs_manager_(), image_manager_(), main_event_context_(), hotkey_manager_(), upload_log_manager_(), music_thinker_(), resize_monitor_(), paths_manager_(), test_scenario_("test"), test_mode_(false), multiplayer_mode_(false), no_gui_(false), screenshot_mode_(false), screenshot_map_(), screenshot_filename_(), force_bpp_(-1), game_config_(), old_defines_map_(), disp_(NULL), state_(), multiplayer_server_(), jump_to_multiplayer_(false), jump_to_campaign_(false,-1,"","") #ifndef DISABLE_EDITOR ,jump_to_editor_(false) #endif ,cache_(game_config::config_cache::instance()) { bool no_music = false; bool no_sound = false; // The path can be hardcoded and it might be a relative path. if(!game_config::path.empty() && #ifdef _WIN32 // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string game_config::path.c_str()[1] != ':' #else game_config::path[0] != '/' #endif ) { game_config::path = get_cwd() + '/' + game_config::path; font_manager_.update_font_path(); } #ifndef DISABLE_EDITOR const std::string app_basename = file_name(argv[0]); jump_to_editor_ = app_basename.find("editor") != std::string::npos; #endif for(arg_ = 1; arg_ != argc_; ++arg_) { const std::string val(argv_[arg_]); if(val.empty()) { continue; } if(val == "--fps") { preferences::set_show_fps(true); } else if(val == "--nocache") { cache_.set_use_cache(false); } else if(val == "--max-fps") { if(arg_+1 != argc_) { ++arg_; int fps = lexical_cast_default(argv_[arg_], 50); fps = std::min(fps, 1000); fps = std::max(fps, 1); fps = 1000 / fps; // increase the delay to avoid going above the maximum if(1000 % fps != 0) { ++fps; } preferences::set_draw_delay(fps); } } else if(val == "--validcache") { cache_.set_force_valid_cache(true); } else if(val == "--resolution" || val == "-r") { if(arg_+1 != argc_) { ++arg_; const std::string val(argv_[arg_]); const std::vector res = utils::split(val, 'x'); if(res.size() == 2) { const int xres = lexical_cast_default(res.front()); const int yres = lexical_cast_default(res.back()); if(xres > 0 && yres > 0) { const std::pair resolution(xres,yres); preferences::set_resolution(resolution); } } } } else if(val == "--bpp") { if(arg_+1 != argc_) { ++arg_; force_bpp_ = lexical_cast_default(argv_[arg_],-1); } } else if(val == "--load" || val == "-l") { if(arg_+1 != argc_) { ++arg_; game::load_game_exception::game = argv_[arg_]; } } else if(val == "--with-replay") { game::load_game_exception::show_replay = true; } else if(val == "--nogui") { no_gui_ = true; no_sound = true; preferences::disable_preferences_save(); } #ifndef DISABLE_EDITOR else if(val == "--screenshot") { if(arg_+2 != argc_) { ++arg_; screenshot_map_ = argv_[arg_]; ++arg_; screenshot_filename_ = argv_[arg_]; no_sound = true; screenshot_mode_ = true; preferences::disable_preferences_save(); force_bpp_ = 32; } } #endif else if(val == "--smallgui") { game_config::small_gui = true; } else if(val == "--config-dir") { if (argc_ <= ++arg_) break; } else if(val == "--windowed" || val == "-w") { preferences::set_fullscreen(false); } else if(val == "--fullscreen" || val == "-f") { preferences::set_fullscreen(true); } else if(val.find("--campaign") == 0 || val.find("-c") == 0) { // campaign starting template: // -c[[] []] // --campaign[[] []] jump_to_campaign_.jump_ = true; // we don't know if the next argument is from --campaign // or for setting the data directory, so we assume is the latter if (arg_ + 2 < argc_ && argv_[arg_+1][0] != '-') { // we parse difficulty only here, since a campaign is supplied from command line if (isdigit(val[val.size()-1])) jump_to_campaign_.difficulty_ = lexical_cast(val[val.size()-1]); ++arg_; jump_to_campaign_.campaign_id_ = std::string(argv_[arg_]); std::cerr<<"selected campaign id: ["< arg_+1){ multiplayer_server_ = argv_[arg_+1]; ++arg_; //Pick the first server in config }else{ if(game_config::server_list.size() > 0) multiplayer_server_ = preferences::network_host(); else multiplayer_server_ = ""; } } else if(val == "--multiplayer" || val == "-m") { multiplayer_mode_ = true; break; //parse the rest of the arguments when we set up the game } else if(val == "--test" || val == "-t") { test_mode_ = true; // If we have -t foo it's ambiguous whether it foo is the parameter // for Wesnoth or the start directory so we assume it's the starting // directory. if(arg_ + 2 < argc_ && argv_[arg_ + 1][0] != '-') { ++arg_; test_scenario_ = argv_[arg_]; } } else if(val == "--debug" || val == "-d") { game_config::debug = true; game_config::mp_debug = true; #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS } else if (val.substr(0, 18) == "--debug-dot-level=") { gui2::tdebug_layout_graph::set_level(val.substr(18)); } else if (val.substr(0, 19) == "--debug-dot-domain=") { gui2::tdebug_layout_graph::set_domain(val.substr(19)); #endif } else if(val == "--no-delay") { game_config::no_delay = true; } else if (val.substr(0, 6) == "--log-") { } else if (val == "--rng-seed") { ++arg_; } else if(val == "--nosound") { no_sound = true; } else if(val == "--nomusic") { no_music = true; } else if(val == "--new-storyscreens") { // This is a hidden option to help testing // the work-in-progress new storyscreen code. // Don't document. set_new_storyscreen(true); } else if(val == "--new-widgets") { // This is a hidden option to enable the new widget toolkit. gui2::new_widgets = true; #ifndef DISABLE_EDITOR } else if(val == "-e" || val == "--editor") { jump_to_editor_ = true; if(arg_+1 != argc_) { if (argv_[arg_ + 1][0] != '-') { ++arg_; game::load_game_exception::game = argv_[arg_]; } } #endif } else if(val[0] == '-') { std::cerr << "unknown option: " << val << std::endl; throw config::error("unknown option"); } else { std::cerr << "Overriding data directory with " << val << std::endl; #ifdef _WIN32 // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string if(val.c_str()[1] == ':') { #else if(val[0] == '/') { #endif game_config::path = val; } else { game_config::path = get_cwd() + '/' + val; } if(!is_directory(game_config::path)) { std::cerr << "Could not find directory '" << game_config::path << "'\n"; throw config::error("directory not found"); } font_manager_.update_font_path(); } } std::cerr << '\n'; std::cerr << "Data directory: " << game_config::path << "\nUser configuration directory: " << get_user_config_dir() << "\nUser data directory: " << get_user_data_dir() << "\nCache directory: " << get_cache_dir() << '\n'; // disable sound in nosound mode, or when sound engine failed to initialize if (no_sound || ((preferences::sound_on() || preferences::music_on() || preferences::turn_bell() || preferences::UI_sound_on()) && !sound::init_sound())) { preferences::set_sound(false); preferences::set_music(false); preferences::set_turn_bell(false); preferences::set_UI_sound(false); } else if (no_music) { // else disable the music in nomusic mode preferences::set_music(false); } } game_display& game_controller::disp() { if(disp_.get() == NULL) { if(get_video_surface() == NULL) { throw CVideo::error(); } disp_.assign(game_display::create_dummy_display(video_)); } return *disp_.get(); } bool game_controller::init_video() { if(no_gui_) { if( !(multiplayer_mode_ || screenshot_mode_) ) { std::cerr << "--nogui flag is only valid with --multiplayer flag or --screenshot flag\n"; return false; } video_.make_fake(); game_config::no_delay = true; return true; } #if !(defined(__APPLE__)) surface icon(image::get_image("game-icon.png", image::UNSCALED)); if(icon != NULL) { ///must be called after SDL_Init() and before setting video mode ::SDL_WM_SetIcon(icon,NULL); } #endif std::pair resolution; int bpp = 0; int video_flags = 0; bool found_matching = preferences::detect_video_settings(video_, resolution, bpp, video_flags); if(force_bpp_ > 0) { bpp = force_bpp_; } if(!found_matching) { std::cerr << "Video mode " << resolution.first << 'x' << resolution.second << 'x' << bpp << " is not supported.\n"; if ((video_flags & FULL_SCREEN)) { std::cerr << "Try running the program with the --windowed option " << "using a " << bpp << "bpp setting for your display adapter.\n"; } else { std::cerr << "Try running the program with the --fullscreen option.\n"; } return false; } std::cerr << "setting mode to " << resolution.first << "x" << resolution.second << "x" << bpp << "\n"; const int res = video_.setMode(resolution.first,resolution.second,bpp,video_flags); video_.setBpp(bpp); if(res == 0) { std::cerr << "required video mode, " << resolution.first << "x" << resolution.second << "x" << bpp << " is not supported\n"; return false; } return true; } bool game_controller::init_config(const bool force) { cache_.clear_defines(); // make sure that multiplayer mode is set if command line parameter is selected if (multiplayer_mode_) cache_.add_define("MULTIPLAYER"); if (test_mode_) cache_.add_define("TEST"); if (jump_to_editor_) cache_.add_define("EDITOR"); if (!multiplayer_mode_ && !test_mode_ && !jump_to_editor_) cache_.add_define("TITLE_SCREEN"); load_game_cfg(force); const config &cfg = game_config().child("game_config"); game_config::load_config(cfg ? &cfg : NULL); hotkey::deactivate_all_scopes(); hotkey::set_scope_active(hotkey::SCOPE_GENERAL); hotkey::set_scope_active(hotkey::SCOPE_GAME); hotkey::load_hotkeys(game_config()); paths_manager_.set_paths(game_config()); ::init_textdomains(game_config()); about::set_about(game_config()); ai::configuration::init(game_config()); return true; } bool game_controller::init_language() { if(!::load_language_list()) return false; if (!::set_language(get_locale())) return false; if(!no_gui_) { std::string wm_title_string = _("The Battle for Wesnoth"); wm_title_string += " - " + game_config::revision; SDL_WM_SetCaption(wm_title_string.c_str(), NULL); } hotkey::load_descriptions(); return true; } bool game_controller::play_test() { static bool first_time = true; if(test_mode_ == false) { return true; } if(!first_time) return false; first_time = false; state_.classification().campaign_type = "test"; state_.classification().scenario = test_scenario_; state_.classification().campaign_define = "TEST"; cache_.add_define("TEST"); load_game_cfg(); paths_manager_.set_paths(game_config()); try { upload_log nolog(false); play_game(disp(),state_,game_config(),nolog); } catch (game::load_game_exception &) { test_mode_ = false; return true; } return false; } bool game_controller::play_screenshot_mode() { if(!screenshot_mode_) { return true; } #ifndef DISABLE_EDITOR cache_.clear_defines(); cache_.add_define("EDITOR"); load_game_cfg(); const binary_paths_manager bin_paths_manager(game_config()); ::init_textdomains(game_config()); editor::start(game_config(), video_, screenshot_map_, true, screenshot_filename_); return false; #else return false; #endif } bool game_controller::play_multiplayer_mode() { state_ = game_state(); if(!multiplayer_mode_) { return true; } std::string era = "era_default"; std::string scenario = "multiplayer_The_Freelands"; std::map side_types, side_controllers, side_algorithms, side_ai_configs; std::map side_parameters; std::string turns = "50"; std::string label = ""; size_t sides_counted = 0; for(++arg_; arg_ < argc_; ++arg_) { const std::string val(argv_[arg_]); if(val.empty()) { continue; } std::vector name_value = utils::split(val, '='); if(name_value.size() > 2) { std::cerr << "invalid argument '" << val << "'\n"; return false; } else if(name_value.size() == 2) { const std::string name = name_value.front(); const std::string value = name_value.back(); const std::string name_head = name.substr(0,name.size()-1); const char name_tail = name[name.size()-1]; const bool last_digit = isdigit(name_tail) ? true:false; const size_t side = name_tail - '0'; if(last_digit && side > sides_counted) { std::cerr << "counted sides: " << side << "\n"; sides_counted = side; } if(name == "--scenario") { scenario = value; } else if(name == "--turns") { turns = value; } else if(name == "--era") { era = value; } else if(name == "--label") { label = value; } else if(last_digit && name_head == "--controller") { side_controllers[side] = value; } else if(last_digit && name_head == "--ai_config") { side_ai_configs[side] = value; } else if(last_digit && name_head == "--algorithm") { side_algorithms[side] = value; } else if(last_digit && name_head == "--side") { side_types[side] = value; } else if(last_digit && name_head == "--parm") { const std::vector name_value = utils::split(value, ':'); if(name_value.size() != 2) { std::cerr << "argument to '" << name << "' must be in the format name:value\n"; return false; } side_parameters[side][name_value.front()] = name_value.back(); } else { std::cerr << "unrecognized option: '" << name << "'\n"; return false; } } else { if (val == "--exit-at-end") { game_config::exit_at_end = true; } } } const config &lvl = game_config().find_child("multiplayer", "id", scenario); if (!lvl) { std::cerr << "Could not find scenario '" << scenario << "'\n"; return false; } state_.classification().campaign_type = "multiplayer"; state_.classification().scenario = ""; state_.snapshot = config(); config level = lvl; std::vector story; const config &era_cfg = game_config().find_child("era","id",era); if (!era_cfg) { std::cerr << "Could not find era '" << era << "'\n"; return false; } level["turns"] = turns; const config &side = era_cfg.child("multiplayer_side"); if (!side) { std::cerr << "Could not find multiplayer side\n"; return false; } while (level.child_count("side") < sides_counted) { std::cerr << "now adding side...\n"; level.add_child("side"); } int side_num = 1; foreach (config &s, level.child_range("side")) { std::map::const_iterator type = side_types.find(side_num), controller = side_controllers.find(side_num), algorithm = side_algorithms.find(side_num), ai_config = side_ai_configs.find(side_num); const config* side = type == side_types.end() ? &era_cfg.find_child("multiplayer_side", "random_faction", "yes") : &era_cfg.find_child("multiplayer_side", "id", type->second); if (!*side) { std::string side_name = (type == side_types.end() ? "default" : type->second); std::cerr << "Could not find side '" << side_name << "' for side " << side_num << "\n"; return false; } if ((*side)["random_faction"].to_bool()) { std::vector faction_choices, faction_excepts; faction_choices = utils::split((*side)["choices"]); if(faction_choices.size() == 1 && faction_choices.front() == "") { faction_choices.clear(); } faction_excepts = utils::split((*side)["except"]);; if(faction_excepts.size() == 1 && faction_excepts.front() == "") { faction_excepts.clear(); } unsigned j = 0; foreach (const config &faction, era_cfg.child_range("multiplayer_side")) { if (faction["random_faction"].to_bool()) continue; const std::string &faction_id = faction["id"]; if (!faction_choices.empty() && std::find(faction_choices.begin(), faction_choices.end(), faction_id) == faction_choices.end()) continue; if (!faction_excepts.empty() && std::find(faction_excepts.begin(), faction_excepts.end(), faction_id) != faction_excepts.end()) continue; if (rand() % ++j == 0) side = &faction; } if ((*side)["random_faction"].to_bool()) { std::string side_name = (type == side_types.end() ? "default" : type->second); std::cerr << "Could not find any non-random faction for side " << side_num << "\n"; return false; } std::cerr << " Faction " << (*side)["name"] << " selected for side " << side_num << ".\n"; } s["side"] = side_num; s["canrecruit"] = true; s.append(*side); if(controller != side_controllers.end()) { s["controller"] = controller->second; } if(algorithm != side_algorithms.end()) { s["ai_algorithm"] = algorithm->second; } if(ai_config != side_ai_configs.end()) { s["ai_config"] = ai_config->second; } config& ai_params = s.add_child("ai"); //now add in any arbitrary parameters given to the side for(string_map::const_iterator j = side_parameters[side_num].begin(); j != side_parameters[side_num].end(); ++j) { s[j->first] = j->second; ai_params[j->first] = j->second; } ++side_num; } level.add_child("era", era_cfg); try { //check if all sides are AI and we are using new uploader -> log these games bool all_ai = true; foreach (config &s, level.child_range("side")) { if( s["controller"] != "ai" ) { all_ai = false; } } upload_log log( all_ai ); recorder.add_log_data("ai_log","ai_label",label); state_.snapshot = level; play_game(disp(), state_, game_config(), log); } catch (game::load_game_exception &) { //the user's trying to load a game, so go into the normal title screen loop and load one return true; } catch(twml_exception& e) { e.show(disp()); return false; } catch(std::exception& e) { std::cerr << "caught exception: " << e.what() << "\n"; } catch(...) { std::cerr << "caught unknown error playing level...\n"; } return false; } bool game_controller::is_loading() const { return !game::load_game_exception::game.empty(); } bool game_controller::load_game() { savegame::loadgame load(disp(), game_config(), state_); try { load.load_game(game::load_game_exception::game, game::load_game_exception::show_replay, game::load_game_exception::cancel_orders); cache_.clear_defines(); game_config::scoped_preproc_define dificulty_def(state_.classification().difficulty); game_config::scoped_preproc_define campaign_define_def(state_.classification().campaign_define, !state_.classification().campaign_define.empty()); game_config::scoped_preproc_define campaign_type_def("MULTIPLAYER", state_.classification().campaign_define.empty() && (state_.classification().campaign_type == "multiplayer")); typedef boost::shared_ptr define_ptr; std::deque extra_defines; for(std::vector::const_iterator i = state_.classification().campaign_xtra_defines.begin(); i != state_.classification().campaign_xtra_defines.end(); ++i) { define_ptr newdefine(new game_config::scoped_preproc_define(*i)); extra_defines.push_back(newdefine); } try { load_game_cfg(); } catch(config::error&) { cache_.clear_defines(); load_game_cfg(); return false; } paths_manager_.set_paths(game_config()); load.set_gamestate(); } catch(load_game_cancelled_exception&) { clear_loaded_game(); return false; } catch(config::error& e) { if(e.message.empty()) { gui2::show_error_message(disp().video(), _("The file you have tried to load is corrupt")); } else { gui2::show_error_message(disp().video(), _("The file you have tried to load is corrupt: '") + e.message + '\''); } return false; } catch(twml_exception& e) { e.show(disp()); return false; } catch(io_exception& e) { if(e.message.empty()) { gui2::show_error_message(disp().video(), _("File I/O Error while reading the game")); } else { gui2::show_error_message(disp().video(), _("File I/O Error while reading the game: '") + e.message + '\''); } return false; } catch(game::error& e) { if(e.message.empty()) { gui2::show_error_message(disp().video(), _("The file you have tried to load is corrupt")); } else { gui2::show_error_message(disp().video(), _("The file you have tried to load is corrupt: '") + e.message + '\''); } return false; } recorder = replay(state_.replay_data); recorder.start_replay(); recorder.set_skip(false); LOG_CONFIG << "has snapshot: " << (state_.snapshot.child("side") ? "yes" : "no") << "\n"; if (!state_.snapshot.child("side")) { // No snapshot; this is a start-of-scenario if (load.show_replay()) { // There won't be any turns to replay, but the // user gets to watch the intro sequence again ... LOG_CONFIG << "replaying (start of scenario)\n"; } else { LOG_CONFIG << "skipping...\n"; recorder.set_skip(false); } } else { // We have a snapshot. But does the user want to see a replay? if(load.show_replay()) { statistics::clear_current_scenario(); LOG_CONFIG << "replaying (snapshot)\n"; } else { LOG_CONFIG << "setting replay to end...\n"; recorder.set_to_end(); if(!recorder.at_end()) { WRN_CONFIG << "recorder is not at the end!!!\n"; } } } if(state_.classification().campaign_type == "multiplayer") { foreach (config &side, state_.snapshot.child_range("side")) { if (side["controller"] == "network") side["controller"] = "human"; if (side["controller"] == "network_ai") side["controller"] = "human_ai"; } } if (load.cancel_orders()) { foreach (config &side, state_.snapshot.child_range("side")) { if (side["controller"] != "human") continue; foreach (config &unit, side.child_range("unit")) { unit["goto_x"] = -999; unit["goto_y"] = -999; } } } return true; } void game_controller::set_tutorial() { state_ = game_state(); state_.classification().campaign_type = "tutorial"; state_.classification().scenario = "tutorial"; state_.classification().campaign_define = "TUTORIAL"; cache_.clear_defines(); cache_.add_define("TUTORIAL"); } void game_controller::mark_completed_campaigns(std::vector &campaigns) { foreach (config &campaign, campaigns) { campaign["completed"] = preferences::is_campaign_completed(campaign["id"]); } } bool game_controller::new_campaign() { state_ = game_state(); state_.classification().campaign_type = "scenario"; const config::const_child_itors &ci = game_config().child_range("campaign"); std::vector campaigns(ci.first, ci.second); mark_completed_campaigns(campaigns); std::sort(campaigns.begin(),campaigns.end(),less_campaigns_rank); if(campaigns.begin() == campaigns.end()) { gui2::show_error_message(disp().video(), _("No campaigns are available.\n")); return false; } int campaign_num = -1; // No campaign selected from command line if (jump_to_campaign_.campaign_id_.empty() == true) { gui2::tcampaign_selection dlg(campaigns); try { dlg.show(disp().video()); } catch(twml_exception& e) { e.show(disp()); return false; } if(dlg.get_retval() != gui2::twindow::OK) { return false; } campaign_num = dlg.get_choice(); } else { // don't reset the campaign_id_ so we can know // if we should quit the game or return to the main menu // checking for valid campaign name for(size_t i = 0; i < campaigns.size(); ++i) { if (campaigns[i]["id"] == jump_to_campaign_.campaign_id_) { campaign_num = i; break; } } // didn't found any campaign with that id if (campaign_num == -1) { std::cerr<<"No such campaign id to jump to: ["< difficulty_options = utils::split(difficulty_descriptions, ';'); const std::vector difficulties = utils::split(campaign["difficulties"]); if(difficulties.empty() == false) { int difficulty = 0; if (jump_to_campaign_.difficulty_ == -1){ if(difficulty_options.size() != difficulties.size()) { difficulty_options.resize(difficulties.size()); std::copy(difficulties.begin(),difficulties.end(),difficulty_options.begin()); } gui::dialog dlg(disp(), _("Difficulty"), _("Select difficulty level:"), gui::OK_CANCEL); dlg.set_menu(difficulty_options); if(dlg.show() == -1) { if (jump_to_campaign_.campaign_id_.empty() == false) { jump_to_campaign_.campaign_id_ = ""; } // canceled difficulty dialog, relaunch the campaign selection dialog return new_campaign(); } difficulty = dlg.result(); } else { if (jump_to_campaign_.difficulty_ > static_cast(difficulties.size())) { std::cerr << "incorrect difficulty number: [" << jump_to_campaign_.difficulty_ << "]. maximum is [" << difficulties.size() << "].\n"; return false; } else { difficulty = jump_to_campaign_.difficulty_ - 1; } } state_.classification().difficulty = difficulties[difficulty]; cache_.clear_defines(); cache_.add_define(difficulties[difficulty]); } else { //clear even when there is no difficulty cache_.clear_defines(); } state_.classification().campaign_define = campaign["define"].str(); state_.classification().campaign_xtra_defines = utils::split(campaign["extra_defines"]); return true; } } std::string game_controller::jump_to_campaign_id() const { return jump_to_campaign_.campaign_id_; } bool game_controller::goto_campaign() { if(jump_to_campaign_.jump_){ if(new_campaign()) { jump_to_campaign_.jump_ = false; launch_game(game_controller::RELOAD_DATA); }else{ jump_to_campaign_.jump_ = false; return false; } } return true; } bool game_controller::goto_multiplayer() { if(jump_to_multiplayer_){ jump_to_multiplayer_ = false; if(play_multiplayer()){ ; }else{ return false; } } return true; } #ifndef DISABLE_EDITOR bool game_controller::goto_editor() { if(jump_to_editor_){ jump_to_editor_ = false; if (start_editor(normalize_path(game::load_game_exception::game)) == editor::EXIT_QUIT_TO_DESKTOP) { return false; } clear_loaded_game(); } return true; } #endif namespace { void game_controller::reload_changed_game_config() { // rebuild addon version info cache refresh_addon_version_info_cache(); //force a reload of configuration information cache_.recheck_filetree_checksum(); old_defines_map_.clear(); clear_binary_paths_cache(); init_config(true); } void game_controller::start_wesnothd() { const std::string wesnothd_program = preferences::get_mp_server_program_name().empty() ? get_program_invocation("wesnothd") : preferences::get_mp_server_program_name(); std::string config = get_user_config_dir() + "/lan_server.cfg"; if (!file_exists(config)) { // copy file if it isn't created yet write_file(config, read_file(get_wml_location("lan_server.cfg"))); } #ifndef _WIN32 std::string command = "\"" + wesnothd_program +"\" -c \"" + config + "\" -d -t 2 -T 5"; #else // start wesnoth as background job std::string command = "cmd /C start \"wesnoth server\" /B \"" + wesnothd_program + "\" -c \"" + config + "\" -t 2 -T 5"; #endif LOG_GENERAL << "Starting wesnothd: "<< command << "\n"; if (std::system(command.c_str()) == 0) { // Give server a moment to start up SDL_Delay(50); return; } preferences::set_mp_server_program_name(""); // Couldn't start server so throw error WRN_GENERAL << "Failed to run server start script\n"; throw game::mp_server_error("Starting MP server failed!"); } bool game_controller::play_multiplayer() { int res; state_ = game_state(); state_.classification().campaign_type = "multiplayer"; state_.classification().campaign_define = "MULTIPLAYER"; //Print Gui only if the user hasn't specified any server if( multiplayer_server_.empty() ){ int start_server; do { start_server = 0; gui2::tmp_method_selection dlg; dlg.show(disp().video()); if(dlg.get_retval() == gui2::twindow::OK) { res = dlg.get_choice(); } else { return false; } if (res == 2 && preferences::mp_server_warning_disabled() < 2) { gui::dialog d(disp(), _("Do you really want to start the server?"), _("The server will run in a background process until all users have disconnected.") , gui::OK_CANCEL); bool checked = preferences::mp_server_warning_disabled() != 1; d.add_option(_("Don't show again"), checked, gui::dialog::BUTTON_CHECKBOX_LEFT); start_server = d.show(); if (start_server == 0) preferences::set_mp_server_warning_disabled(d.option_checked()?2:1); } } while (start_server); if (res < 0) { return false; } }else{ res = 4; } try { if (res == 2) { try { start_wesnothd(); } catch(game::mp_server_error&) { std::string path = preferences::show_wesnothd_server_search(disp()); if (!path.empty()) { preferences::set_mp_server_program_name(path); start_wesnothd(); } else { throw game::mp_server_error("No path given for mp server program."); } } } /* do */ { cache_.clear_defines(); game_config::scoped_preproc_define multiplayer(state_.classification().campaign_define); load_game_cfg(); events::discard(INPUT_MASK); // prevent the "keylogger" effect cursor::set(cursor::NORMAL); // update binary paths paths_manager_.set_paths(game_config()); clear_binary_paths_cache(); } if(res == 3) { std::vector chat; config game_data; const mp::controller cntr = mp::CNTR_LOCAL; mp::start_local_game(disp(), game_config(), cntr); } else if((res >= 0 && res <= 2) || res == 4) { std::string host; if(res == 0) { host = preferences::server_list().front().address; }else if(res == 2) { host = "localhost"; }else if(res == 4){ host = multiplayer_server_; multiplayer_server_ = ""; } mp::start_client(disp(), game_config(), host); } } catch(game::mp_server_error& e) { gui2::show_error_message(disp().video(), _("Error while starting server: ") + e.message); } catch(game::load_game_failed& e) { gui2::show_error_message(disp().video(), _("The game could not be loaded: ") + e.message); } catch(game::game_error& e) { gui2::show_error_message(disp().video(), _("Error while playing the game: ") + e.message); } catch(network::error& e) { if(e.message != "") { ERR_NET << "caught network::error: " << e.message << "\n"; gui2::show_transient_message(disp().video() , "" , gettext(e.message.c_str())); } else { ERR_NET << "caught network::error\n"; } } catch(config::error& e) { if(e.message != "") { ERR_CONFIG << "caught config::error: " << e.message << "\n"; gui2::show_transient_message(disp().video(), "", e.message); } else { ERR_CONFIG << "caught config::error\n"; } } catch(incorrect_map_format_error& e) { gui2::show_error_message(disp().video(), std::string(_("The game map could not be loaded: ")) + e.message); } catch (game::load_game_exception &) { //this will make it so next time through the title screen loop, this game is loaded } catch(twml_exception& e) { e.show(disp()); } return false; } bool game_controller::change_language() { gui2::tlanguage_selection dlg; dlg.show(disp().video()); if (dlg.get_retval() != gui2::twindow::OK) return false; if (!no_gui_) { std::string wm_title_string = _("The Battle for Wesnoth"); wm_title_string += " - " + game_config::revision; SDL_WM_SetCaption(wm_title_string.c_str(), NULL); } return true; } void game_controller::show_preferences() { const preferences::display_manager disp_manager(&disp()); preferences::show_preferences_dialog(disp(),game_config()); disp().redraw_everything(); } void game_controller::show_upload_begging() { upload_log_dialog::show_beg_dialog(disp()); disp().redraw_everything(); } void game_controller::set_unit_data() { if (config &units = game_config_.child("units")) { unit_types.set_config(units); } } void game_controller::load_game_cfg(const bool force) { // make sure that 'debug mode' symbol is set if command line parameter is selected // also if we're in multiplayer and actual debug mode is disabled if (game_config::debug || game_config::mp_debug) { cache_.add_define("DEBUG_MODE"); } gui::set_background_dirty(); if (!game_config_.empty() && !force && old_defines_map_ == cache_.get_preproc_map()) return; // game_config already holds requested config in memory old_defines_map_ = cache_.get_preproc_map(); loadscreen::global_loadscreen_manager loadscreen_manager(disp().video()); cursor::setter cur(cursor::WAIT); // The loadscreen will erase the titlescreen // NOTE: even without loadscreen, needed after MP lobby try { /** * Read all game configs * First we should load data/ * Then handle terrains so that they are last loaded from data/ * 2nd everything in userdata **/ //reset the parse counter before reading the game files data_tree_checksum(); loadscreen::global_loadscreen->parser_counter = 0; // start transaction so macros are shared game_config::config_cache_transaction main_transaction; cache_.get_config(game_config::path +"/data", game_config_); main_transaction.lock(); /* Put the gfx rules aside so that we can prepend the add-on rules to them. */ config core_terrain_rules; core_terrain_rules.splice_children(game_config_, "terrain_graphics"); // load usermade add-ons const std::string user_campaign_dir = get_addon_campaigns_dir(); std::vector< std::string > error_addons; // Scan addon directories std::vector user_dirs; // Scan for standalone files std::vector user_files; // The addons that we'll actually load std::vector addons_to_load; get_files_in_dir(user_campaign_dir,&user_files,&user_dirs,ENTIRE_FILE_PATH); std::string user_error_log; // Append the $user_campaign_dir/*.cfg files to addons_to_load. for(std::vector::const_iterator uc = user_files.begin(); uc != user_files.end(); ++uc) { const std::string file = *uc; if(file.substr(file.size() - 4, file.size()) == ".cfg") addons_to_load.push_back(file); } // Append the $user_campaign_dir/*/_main.cfg files to addons_to_load. for(std::vector::const_iterator uc = user_dirs.begin(); uc != user_dirs.end(); ++uc){ const std::string main_cfg = *uc + "/_main.cfg"; if (file_exists(main_cfg)) addons_to_load.push_back(main_cfg); } // Load the addons for(std::vector::const_iterator uc = addons_to_load.begin(); uc != addons_to_load.end(); ++uc) { const std::string toplevel = *uc; try { config umc_cfg; cache_.get_config(toplevel, umc_cfg); game_config_.append(umc_cfg); } catch(config::error& err) { ERR_CONFIG << "error reading usermade add-on '" << *uc << "'\n"; error_addons.push_back(*uc); user_error_log += err.message + "\n"; } catch(preproc_config::error& err) { ERR_CONFIG << "error reading usermade add-on '" << *uc << "'\n"; error_addons.push_back(*uc); user_error_log += err.message + "\n"; } catch(io_exception&) { ERR_CONFIG << "error reading usermade add-on '" << *uc << "'\n"; error_addons.push_back(*uc); } if(error_addons.empty() == false) { std::stringstream msg; msg << _n("The following add-on had errors and could not be loaded:", "The following add-ons had errors and could not be loaded:", error_addons.size()); for(std::vector::const_iterator i = error_addons.begin(); i != error_addons.end(); ++i) { msg << "\n" << *i; } msg << '\n' << _("ERROR DETAILS:") << '\n' << user_error_log; gui2::show_error_message(disp().video(),msg.str()); } } // Extract the Lua scripts at toplevel. extract_preload_scripts(game_config_); game_config_.clear_children("lua"); game_config_.merge_children("units"); game_config_.splice_children(core_terrain_rules, "terrain_graphics"); config& hashes = game_config_.add_child("multiplayer_hashes"); foreach (const config &ch, game_config_.child_range("multiplayer")) { hashes[ch["id"]] = ch.hash(); } set_unit_data(); terrain_builder::set_terrain_rules_cfg(game_config()); } catch(game::error& e) { ERR_CONFIG << "Error loading game configuration files\n"; gui2::show_error_message(disp().video(), _("Error loading game configuration files: '") + e.message + _("' (The game will now exit)")); throw; } } void game_controller::launch_game(RELOAD_GAME_DATA reload) { loadscreen::global_loadscreen_manager loadscreen_manager(disp().video()); loadscreen::global_loadscreen->set_progress(0, _("Loading data files")); if(reload == RELOAD_DATA) { game_config::scoped_preproc_define campaign_define(state_.classification().campaign_define, state_.classification().campaign_define.empty() == false); typedef boost::shared_ptr define_ptr; std::deque extra_defines; for(std::vector::const_iterator i = state_.classification().campaign_xtra_defines.begin(); i != state_.classification().campaign_xtra_defines.end(); ++i) { define_ptr newdefine(new game_config::scoped_preproc_define(*i)); extra_defines.push_back(newdefine); } try { load_game_cfg(); } catch(config::error&) { cache_.clear_defines(); load_game_cfg(); return; } } loadscreen::global_loadscreen->set_progress(60); const binary_paths_manager bin_paths_manager(game_config()); try { // Only record log for single-player games & tutorial. upload_log log(state_.classification().campaign_type.empty() || state_.classification().campaign_type == "scenario" || state_.classification().campaign_type == "tutorial"); const LEVEL_RESULT result = play_game(disp(),state_,game_config(), log); // don't show The End for multiplayer scenario // change this if MP campaigns are implemented if(result == VICTORY && (state_.classification().campaign_type.empty() || state_.classification().campaign_type != "multiplayer")) { preferences::add_completed_campaign(state_.classification().campaign); the_end(disp(), state_.classification().end_text, state_.classification().end_text_duration); about::show_about(disp(),state_.classification().campaign); } clear_loaded_game(); } catch (game::load_game_exception &) { //this will make it so next time through the title screen loop, this game is loaded } catch(twml_exception& e) { e.show(disp()); } } } //end anon namespace void game_controller::play_replay() { const binary_paths_manager bin_paths_manager(game_config()); try { ::play_replay(disp(),state_,game_config(),video_); clear_loaded_game(); } catch (game::load_game_exception &) { //this will make it so next time through the title screen loop, this game is loaded } catch(twml_exception& e) { e.show(disp()); } } #ifndef DISABLE_EDITOR editor::EXIT_STATUS game_controller::start_editor(const std::string& filename) { cache_.clear_defines(); cache_.add_define("EDITOR"); load_game_cfg(); const binary_paths_manager bin_paths_manager(game_config()); ::init_textdomains(game_config()); return editor::start(game_config(), video_, filename); } #endif game_controller::~game_controller() { delete gui::empty_menu; sound::close_sound(); } // this is needed to allow identical functionality with clean refactoring // play_game only returns on an error, all returns within play_game can // be replaced with this static void safe_exit(int res) { LOG_GENERAL << "exiting with code " << res << "\n"; #ifdef OS2 /* required to correctly shutdown SDL on OS/2 */ SDL_Quit(); #endif exit(res); } // maybe this should go in a util file somewhere? static void gzip_codec(const std::string & input_file, const std::string & output_file, bool encode) { try { std::ofstream ofile(output_file.c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::binary); std::ifstream ifile(input_file.c_str(), std::ios_base::in | std::ios_base::binary); boost::iostreams::filtering_streambuf in; if(encode) in.push(boost::iostreams::gzip_compressor()); else in.push(boost::iostreams::gzip_decompressor()); in.push(ifile); boost::iostreams::copy(in, ofile); ifile.close(); safe_exit(remove(input_file.c_str())); } catch(io_exception& e) { std::cerr << "IO error: " << e.what() << "\n"; } } static void gzip_encode(const std::string & input_file, const std::string & output_file) { gzip_codec(input_file, output_file, true); } static void gzip_decode(const std::string & input_file, const std::string & output_file) { gzip_codec(input_file, output_file, false); } struct preprocess_options { public: preprocess_options(): output_macros_path_("false"), input_macros_() { } std::string output_macros_path_; preproc_map input_macros_; }; /** Process commandline-arguments */ static int process_command_args(int argc, char** argv) { const std::string program = argv[0]; game_config::wesnoth_program_dir = directory_name(program); preprocess_options preproc; //parse arguments that shouldn't require a display device int arg; for(arg = 1; arg != argc; ++arg) { const std::string val(argv[arg]); if(val.empty()) { continue; } if(val == "--help" || val == "-h") { // When adding items don't forget to update doc/man/wesnoth.6 // Options are sorted alphabetically by --long-option. // Please keep the output to 80 chars per line. std::cout << "usage: " << argv[0] << " [] []\n" << "Available options:\n" << " --bpp sets BitsPerPixel value. Example: --bpp 32\n" << " -c, --campaign[[] []]\n" << " goes directly to the campaign.\n" << " - difficulty : the difficulty of the specified\n" << " campaign (1 to max - Default is 1)\n" << " - id_c: the id of the campaign. A selection \n" << " menu will appear if none specified\n" << " - id_s: the id of the scenario from the\n" << " specified campaign\n" << " --config-dir sets the path of the user config directory to\n" << " $HOME/ or My Documents\\My Games\\ for windows.\n" << " You can specify also an absolute path outside the\n" << " $HOME or My Documents\\My Games directory." << " --config-path prints the path of the user config directory and\n" << " exits.\n" << " --data-dir overrides the data directory with the one specified.\n" << " -d, --debug enables additional command mode options in-game.\n" #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS << " --debug-dot-level=,,...\n" << " sets the level of the debug dot files.\n" << " These files are used for debugging the widgets\n" << " especially the for the layout engine. When enabled\n" << " the engine will produce dot files which can be\n" << " converted to images with the dot tool.\n" << " Available levels:\n" << " - size : generate the size info of the widget.\n" << " - state : generate the state info of the widget.\n" << " --debug-dot-domain=,,...\n" << " sets the domain of the debug dot files.\n" << " see --debug-dot-level for more info.\n" << " Available domains:\n" << " show : generate the data when the dialog is\n" << " about to be shown.\n" << " layout : generate the data during the layout\n" << " phase (might result in multiple files. \n" << " The data can also be generated when the F12 is\n" << " pressed in a dialog.\n" #endif #ifndef DISABLE_EDITOR << " -e, --editor [] starts the in-game map editor directly. If \n" << " is specified, equivalent to -e --load .\n" #endif << " --fps displays the number of frames per second the\n" << " game is currently running at, in a corner of\n" << " the screen.\n" << " -f, --fullscreen runs the game in full screen mode.\n" << " --gunzip .gz decompresses a file (.gz) in gzip format\n" << " and stores it without the .gz suffix.\n" << " .gz will be removed.\n" << " --gzip compresses a file () in gzip format,\n" << " stores it as .gz and removes .\n" << " -h, --help prints this message and exits.\n" << " -l, --load loads the save from the standard save\n" << " game directory.\n" #ifndef DISABLE_EDITOR << " When launching the map editor via -e, the map\n" << " is loaded, relative to the current\n" << " directory. If it is a directory, the editor\n" << " will start with a load map dialog opened there.\n" #endif << " --log-=,,...\n" << " sets the severity level of the log domains.\n" << " 'all' can be used to match any log domain.\n" << " Available levels: error, warning, info, debug.\n" << " By default the 'error' level is used.\n" << " --logdomains [filter] lists defined log domains containing filter and\n" << " exits.\n" << " --max-fps the maximum fps the game tries to run at. Values\n" << " should be between 1 and 1000, the default is 50.\n" << " -m, --multiplayer starts a multiplayer game. There are additional\n" << " options that can be used as explained below:\n" << " --ai_config=value selects a configuration file to load for this side.\n" << " --algorithm=value selects a non-standard algorithm to be used by\n" << " the AI controller for this side.\n" << " --controller=value selects the controller for this side.\n" << " --era=value selects the era to be played in by its id.\n" << " --exit-at-end exit Wesnoth at the end of the scenario.\n" << " --nogui runs the game without the GUI. Must appear before\n" << " --multiplayer to have the desired effect.\n" << " --parm=name:value sets additional parameters for this side.\n" << " --scenario=value selects a multiplayer scenario. The default\n" << " scenario is \"multiplayer_The_Freelands\".\n" << " --side=value selects a faction of the current era for this\n" << " side by id.\n" << " --turns=value sets the number of turns. The default is \"50\".\n" << " --no-delay runs the game without any delays.\n" << " --nocache disables caching of game data.\n" << " --nomusic runs the game without music.\n" << " --nosound runs the game without sounds and music.\n" << " --path prints the path to the data directory and exits.\n" << " --preprocess, -p[=,,...] \n" << " preprocesses a specified file/folder. The preprocessed\n" << " file(s) will be written in the specified target\n" << " directory: a plain cfg file and a processed cfg file.\n" << " define1,define2,... - the extra defines will\n" << " be added before processing the files. If you add\n" << " them you must add the '=' character before.\n" << " If 'SKIP_CORE' is in the define list the\n" << " data/core won't be preprocessed.\n" << " --preprocess-input-macros \n" << " used only by the '--preprocess' command.\n" << " Specifies a file that contains [preproc_define]s\n" << " to be included before preprocessing.\n" << " --preprocess-output-macros []\n" << " used only by the '--preprocess' command.\n" << " Will output all preprocessed macros in the target file.\n" << " If the file is not specified the output will be\n" << " file '_MACROS_.cfg' in the target directory of\n" << " preprocess's command. This switch should be typed\n" << " before the --preprocess command.\n" << " -r, --resolution XxY sets the screen resolution. Example: -r 800x600\n" << " --rng-seed seeds the random number generator with number\n" << " Example: --rng-seed 0\n" << " --smallgui allows to use screen resolutions down to 800x480\n" << " and resizes a few interface elements.\n" << " --screenshot Saves a screenshot of to without\n" << " initializing a screen. Editor must be compiled\n" << " in for this to work.\n" << " -s, --server [] connects to the host if specified\n" << " or to the first host in your preferences.\n" << " -t, --test runs the game in a small test scenario.\n" << " --validcache assumes that the cache is valid. (dangerous)\n" << " -v, --version prints the game's version number and exits.\n" << " -w, --windowed runs the game in windowed mode.\n" << " --with-replay replays the file loaded with the --load option.\n" << " --new-widgets there is a new WIP widget toolkit this switch\n" << " enables the new toolkit (VERY EXPERIMENTAL don't\n" << " file bug reports since most are known).\n" << " Parts of the library are deemed stable and will\n" << " work without this switch.\n" ; return 0; } else if(val == "--version" || val == "-v") { std::cout << "Battle for Wesnoth" << " " << game_config::version << "\n"; return 0; } else if (val == "--config-path") { std::cout << get_user_data_dir() << '\n'; return 0; } else if(val == "--path") { std::cout << game_config::path << "\n"; return 0; } #ifndef DISABLE_EDITOR else if (val == "--screenshot" ) { if(!(argc > arg + 2)) { std::cerr << "format of " << val << " command: " << val << " \n"; return 2; } static char opt[] = "SDL_VIDEODRIVER=dummy"; SDL_putenv(opt); } #endif else if(val == "--config-dir") { if (argc <= ++arg) break; set_preferences_dir(argv[arg]); } else if(val == "--data-dir") { if(arg +1 != argc) { ++arg; const std::string datadir(argv[arg]); std::cerr << "Overriding data directory with " << datadir << std::endl; #ifdef _WIN32 // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string if(datadir.c_str()[1] == ':') { #else if(datadir[0] == '/') { #endif game_config::path = datadir; } else { game_config::path = get_cwd() + '/' + datadir; } if(!is_directory(game_config::path)) { std::cerr << "Could not find directory '" << game_config::path << "'\n"; throw config::error("directory not found"); } // don't update font as we already updating it in game ctor } else std::cerr << "please specify a data directory\n"; } else if (val.substr(0, 6) == "--log-") { size_t p = val.find('='); if (p == std::string::npos) { std::cerr << "unknown option: " << val << '\n'; return 2; } std::string s = val.substr(6, p - 6); int severity; if (s == "error") severity = 0; else if (s == "warning") severity = 1; else if (s == "info") severity = 2; else if (s == "debug") severity = 3; else { std::cerr << "unknown debug level: " << s << '\n'; return 2; } while (p != std::string::npos) { size_t q = val.find(',', p + 1); s = val.substr(p + 1, q == std::string::npos ? q : q - (p + 1)); if (!lg::set_log_domain_severity(s, severity)) { std::cerr << "unknown debug domain: " << s << '\n'; return 2; } p = q; } } else if(val == "--gzip") { if(argc != arg + 2) { std::cerr << "format of " << val << " command: " << val << " \n"; return 2; } const std::string input_file(argv[arg + 1]); const std::string output_file(input_file + ".gz"); gzip_encode(input_file, output_file); } else if(val == "--gunzip") { if(argc != arg + 2) { std::cerr << "format of " << val << " command: " << val << " \n"; return 2; } const std::string input_file(argv[arg + 1]); if(! is_gzip_file(input_file)) { std::cerr << "file '" << input_file << "'isn't a .gz file\n"; return 2; } const std::string output_file( input_file, 0, input_file.length() - 3); gzip_decode(input_file, output_file); } else if(val == "--logdomains") { std::string filter; if(arg + 1 != argc) { ++arg; filter = argv[arg]; } std::cout << lg::list_logdomains(filter); return 0; } else if(val == "--rng-seed") { if (argc <= ++arg) { std::cerr << "format of \" " << val << " " << argv[arg] << " \" is bad\n"; return 2; } srand(lexical_cast_default(argv[arg])); } else if (val == "--preprocess-input-macros") { if (arg + 1 < argc) { ++arg; std::string file = argv[arg]; if (file_exists(file) == false) { std::cerr << "please specify an existing file. File "<< file <<" doesn't exist.\n"; return 1; } std::cerr << SDL_GetTicks() << " Reading cached defines from: " << file << "\n"; config cfg; std::string error_log; scoped_istream stream = istream_file(file); read(cfg, *stream); int read = 0; // use static preproc_define::read_pair(config) to make a object foreach (const config::any_child &value, cfg.all_children_range()) { const preproc_map::value_type def = preproc_define::read_pair(value.cfg); preproc.input_macros_[def.first] = def.second; ++read; } std::cerr << SDL_GetTicks() << " Read " << read << " defines.\n"; } else { std::cerr << "please specify input macros file.\n"; return 2; } } else if (val == "--preprocess-output-macros") { preproc.output_macros_path_ = "true"; if (arg + 1 < argc && argv[arg+1][0] != '-') { ++arg; preproc.output_macros_path_ = argv[arg]; } } else if (val.find("--preprocess") == 0 || val.find("-p") == 0){ if (arg + 2 < argc){ ++arg; const std::string resourceToProcess(argv[arg]); ++arg; const std::string targetDir(argv[arg]); Uint32 startTime = SDL_GetTicks(); // if the users add the SKIP_CORE define we won't preprocess data/core bool skipCore = false; // the 'core_defines_map' is the one got from data/core macros preproc_map defines_map(preproc.input_macros_); std::string error_log; // add the specified defines size_t pos=std::string::npos; if (val.find("--preprocess=") == 0) pos = val.find("="); else if (val.find("-p=") == 0) pos = val.find("="); // we have some defines specified if (pos != std::string::npos) { std::string tmp_val = val.substr(pos+1); while (pos != std::string::npos) { size_t tmpPos = val.find(',', pos+1); tmp_val = val.substr(pos + 1, tmpPos == std::string::npos ? tmpPos : tmpPos - (pos+1)); pos = tmpPos; if (tmp_val.empty()){ std::cerr << "empty define supplied\n"; continue; } LOG_PREPROC<<"adding define: "<< tmp_val<<'\n'; defines_map.insert(std::make_pair(tmp_val, preproc_define(tmp_val))); if (tmp_val == "SKIP_CORE") { std::cerr << "'SKIP_CORE' defined.\n"; skipCore = true; } } std::cerr << "added " << defines_map.size() << " defines.\n"; } // preprocess core macros first if we don't skip the core if (skipCore == false) { std::cerr << "preprocessing common macros from 'data/core' ...\n"; preprocess_resource(game_config::path + "/data/core",&defines_map); std::cerr << "acquired " << (defines_map.size() - preproc.input_macros_.size()) << " 'data/core' defines.\n"; } else std::cerr << "skipped 'data/core'\n"; // preprocess resource std::cerr << "preprocessing specified resource: " << resourceToProcess << " ...\n"; preprocess_resource(resourceToProcess, &defines_map, true,true, targetDir); std::cerr << "acquired " << (defines_map.size() - preproc.input_macros_.size()) << " total defines.\n"; if (preproc.output_macros_path_ != "false") { std::string outputPath = targetDir + "/_MACROS_.cfg"; if (preproc.output_macros_path_ != "true") outputPath = preproc.output_macros_path_; std::cerr << "writing '" << outputPath << "' with " << defines_map.size() << " defines.\n"; scoped_ostream out = ostream_file(outputPath); if (!out->fail()) { config_writer writer(*out,false); for(preproc_map::iterator itor = defines_map.begin(); itor != defines_map.end(); ++itor) { (*itor).second.write(writer, (*itor).first); } } else std::cerr << "couldn't open the file.\n"; } std::cerr << "preprocessing finished. Took "<< SDL_GetTicks() - startTime << " ticks.\n"; return 0; } else{ std::cerr << "Please specify a source file/folder and a target folder\n"; return 2; } } } // Not the most intuitive solution, but I wanted to leave current semantics for now return -1; } /** * I would prefer to setup locale first so that early error * messages can get localized, but we need the game_controller * initialized to have get_intl_dir() to work. Note: setlocale() * does not take GUI language setting into account. */ static void init_locale() { #ifdef _WIN32 std::setlocale(LC_ALL, "English"); #else std::setlocale(LC_ALL, "C"); std::setlocale(LC_MESSAGES, ""); #endif const std::string& intl_dir = get_intl_dir(); bindtextdomain (PACKAGE, intl_dir.c_str()); bind_textdomain_codeset (PACKAGE, "UTF-8"); bindtextdomain (PACKAGE "-lib", intl_dir.c_str()); bind_textdomain_codeset (PACKAGE "-lib", "UTF-8"); textdomain (PACKAGE); } /** * Setups the game environment and enters * the titlescreen or game loops. */ static int do_gameloop(int argc, char** argv) { srand(time(NULL)); int finished = process_command_args(argc, argv); if(finished != -1) { return finished; } //ensure recorder has an actually random seed instead of what it got during //static initialization (befire any srand() call) recorder.set_seed(rand()); game_controller game(argc,argv); const int start_ticks = SDL_GetTicks(); init_locale(); bool res; // do initialize fonts before reading the game config, to have game // config error messages displayed. fonts will be re-initialized later // when the language is read from the game config. res = font::load_font_config(); if(res == false) { std::cerr << "could not initialize fonts\n"; return 1; } res = game.init_language(); if(res == false) { std::cerr << "could not initialize the language\n"; return 1; } res = game.init_video(); if(res == false) { std::cerr << "could not initialize display\n"; return 1; } const cursor::manager cursor_manager; cursor::set(cursor::WAIT); gui2::init(); const gui2::event::tmanager gui_event_manager; loadscreen::global_loadscreen_manager loadscreen_manager(game.disp().video()); loadscreen::global_loadscreen->increment_progress(5, _("Loading game configuration.")); res = game.init_config(); if(res == false) { std::cerr << "could not initialize game config\n"; return 1; } loadscreen::global_loadscreen->increment_progress(10, _("Re-initialize fonts for the current language.")); res = font::load_font_config(); if(res == false) { std::cerr << "could not re-initialize fonts for the current language\n"; return 1; } loadscreen::global_loadscreen->increment_progress(0, _("Searching for installed add-ons.")); refresh_addon_version_info_cache(); #if defined(_X11) && !defined(__APPLE__) SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); #endif config tips_of_day; loadscreen::global_loadscreen->set_progress(100, _("Loading title screen.")); LOG_CONFIG << "time elapsed: "<< (SDL_GetTicks() - start_ticks) << " ms\n"; for (;;) { game::exception::sticky = NULL; // reset the TC, since a game can modify it, and it may be used // by images in add-ons or campaigns dialogs image::set_team_colors(); statistics::fresh_stats(); if (!game.is_loading()) { const config &cfg = game.game_config().child("titlescreen_music"); if (cfg) { sound::play_music_repeatedly(game_config::title_music); foreach (const config &i, cfg.child_range("music")) { sound::play_music_config(i); } sound::commit_music_changes(); } else { sound::empty_playlist(); sound::stop_music(); } } if(game.play_test() == false) { return 0; } if(game.play_multiplayer_mode() == false) { return 0; } if(game.play_screenshot_mode() == false) { return 0; } recorder.clear(); //Start directly a campaign if(game.goto_campaign() == false){ if (game.jump_to_campaign_id().empty()) continue; //Go to main menu else return 1; //we got an error starting the campaign from command line } //Start directly a multiplayer //Eventually with a specified server if(game.goto_multiplayer() == false){ continue; //Go to main menu } #ifndef DISABLE_EDITOR if (game.goto_editor() == false) { return 0; } #endif loadscreen_manager.reset(); gui::TITLE_RESULT res = game.is_loading() ? gui::LOAD_GAME : gui::NOTHING; if(gui2::new_widgets) { const preferences::display_manager disp_manager(&game.disp()); const hotkey::basic_handler key_handler(&game.disp()); const font::floating_label_context label_manager; cursor::set(cursor::NORMAL); gui2::ttitle_screen dlg; dlg.show(game.disp().video()); res = static_cast(dlg.get_retval()); } else { while(res == gui::NOTHING) { res = gui::show_title(game.disp(),tips_of_day); if (res == gui::REDRAW_BACKGROUND) { gui::set_background_dirty(); res = gui::NOTHING; } } } game_controller::RELOAD_GAME_DATA should_reload = game_controller::RELOAD_DATA; if(res == gui::QUIT_GAME) { LOG_GENERAL << "quitting game...\n"; return 0; } else if(res == gui::LOAD_GAME) { if(game.load_game() == false) { game.clear_loaded_game(); res = gui::NOTHING; continue; } should_reload = game_controller::NO_RELOAD_DATA; } else if(res == gui::TUTORIAL) { game.set_tutorial(); } else if(res == gui::NEW_CAMPAIGN) { if(game.new_campaign() == false) { continue; } } else if(res == gui::MULTIPLAYER) { if (!game_config::mp_debug) { game_config::debug = false; } if(game.play_multiplayer() == false) { continue; } } else if(res == gui::CHANGE_LANGUAGE) { if (game.change_language()) { tips_of_day.clear(); t_string::reset_translations(); image::flush_cache(); gui::set_background_dirty(); } continue; } else if(res == gui::EDIT_PREFERENCES) { game.show_preferences(); if (game.disp().video().modeChanged()) { gui::set_background_dirty(); } continue; } else if(res == gui::SHOW_ABOUT) { about::show_about(game.disp()); continue; } else if(res == gui::SHOW_HELP) { help::help_manager help_manager(&game.game_config(), NULL); help::show_help(game.disp()); continue; } else if(res == gui::GET_ADDONS) { try { manage_addons(game.disp()); } catch(config_changed_exception const&) { game.reload_changed_game_config(); } continue; } else if(res == gui::BEG_FOR_UPLOAD) { game.show_upload_begging(); continue; } else if(res == gui::RELOAD_GAME_DATA) { loadscreen::global_loadscreen_manager loadscreen(game.disp().video()); game.reload_changed_game_config(); image::flush_cache(); continue; #ifndef DISABLE_EDITOR } else if(res == gui::START_MAP_EDITOR) { ///@todo editor can ask the game to quit completely if (game.start_editor() == editor::EXIT_QUIT_TO_DESKTOP) { return 0; } else { gui::set_background_dirty(); } continue; #endif } if (recorder.at_end()){ game.launch_game(should_reload); } else{ game.play_replay(); } } return 0; } #ifndef DISABLE_POOL_ALLOC extern "C" { void init_custom_malloc(); } #endif int main(int argc, char** argv) { #ifndef DISABLE_POOL_ALLOC init_custom_malloc(); #endif if(SDL_Init(SDL_INIT_TIMER) < 0) { fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); return(1); } try { std::cerr << "Battle for Wesnoth v" << game_config::revision << '\n'; const time_t t = time(NULL); std::cerr << "Started on " << ctime(&t) << "\n"; const std::string exe_dir = get_exe_dir(); if(!exe_dir.empty() && file_exists(exe_dir + "/data/_main.cfg")) { std::cerr << "Automatically found a possible data directory at " << exe_dir << '\n'; game_config::path = exe_dir; } const int res = do_gameloop(argc,argv); safe_exit(res); } catch(CVideo::error&) { std::cerr << "Could not initialize video. Exiting.\n"; return 1; } catch(font::manager::error&) { std::cerr << "Could not initialize fonts. Exiting.\n"; return 1; } catch(config::error& e) { std::cerr << e.message << "\n"; return 1; } catch(gui::button::error&) { std::cerr << "Could not create button: Image could not be found\n"; } catch(CVideo::quit&) { //just means the game should quit } catch(end_level_exception&) { std::cerr << "caught end_level_exception (quitting)\n"; } catch(twml_exception& e) { std::cerr << "WML exception:\nUser message: " << e.message << "\nDev message: " << e.dev_message << '\n'; return 1; } catch(game_logic::formula_error& e) { std::cerr << e.what() << "\n\nGame will be aborted.\n"; } catch(game::error &) { // A message has already been displayed. return 1; } catch(std::bad_alloc&) { std::cerr << "Ran out of memory. Aborted.\n"; return ENOMEM; } return 0; } // end main