wesnoth/src/game.cpp
Ali El Gariani 30c52fc7c5 Safer window-icon handling.
Kill set_wm_icon function, since SDL_WM_SetIcon can be called safely
only at one place. Clearly hardcode the image's url instead of
pretending that we use game_config. Rename the image to
"game-icon.png" (since you are forced to use it).
2010-08-08 18:33:48 +00:00

2471 lines
75 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 - 2010 by David White <dave@whitevine.net>
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 <cerrno>
#include <clocale>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/filter/gzip.hpp>
// 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<config>& 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<game_display> 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<int>(argv_[arg_], 50);
fps = std::min<int>(fps, 1000);
fps = std::max<int>(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<std::string> res = utils::split(val, 'x');
if(res.size() == 2) {
const int xres = lexical_cast_default<int>(res.front());
const int yres = lexical_cast_default<int>(res.back());
if(xres > 0 && yres > 0) {
const std::pair<int,int> resolution(xres,yres);
preferences::set_resolution(resolution);
}
}
}
} else if(val == "--bpp") {
if(arg_+1 != argc_) {
++arg_;
force_bpp_ = lexical_cast_default<int>(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[[<difficulty>] <id_campaign> [<id_scenario>]]
// --campaign[[<difficulty>] <id_campaign> [<id_scenario>]]
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<int>(val[val.size()-1]);
++arg_;
jump_to_campaign_.campaign_id_ = std::string(argv_[arg_]);
std::cerr<<"selected campaign id: ["<<jump_to_campaign_.campaign_id_
<<"] | difficulty: ["<<jump_to_campaign_.difficulty_<<"]\n";
}
if (arg_ + 2 < argc_ && argv_[arg_+1][0] != '-')
{
++arg_;
jump_to_campaign_.scenario_id_ = std::string(argv_[arg_]);
std::cerr<<"selected scenario id: ["<<jump_to_campaign_.scenario_id_<<"]\n";
}
} else if(val == "--server" || val == "-s"){
jump_to_multiplayer_ = true;
//Do we have any server specified ?
if(argc_ > 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<int,int> 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<int,std::string> side_types, side_controllers, side_algorithms, side_ai_configs;
std::map<int,string_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<std::string> 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<std::string> 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<config*> 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<int,std::string>::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<std::string> 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<game_config::scoped_preproc_define> define_ptr;
std::deque<define_ptr> extra_defines;
for(std::vector<std::string>::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<config> &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<config> 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: ["<<jump_to_campaign_.campaign_id_<<"]\n";
return false;
}
}
const config &campaign = campaigns[campaign_num];
state_.classification().campaign = campaign["id"].str();
state_.classification().abbrev = campaign["abbrev"].str();
// we didn't specify in the command line the scenario to be started
if (jump_to_campaign_.scenario_id_.empty())
state_.classification().scenario = campaign["first_scenario"].str();
else
{
config scenario;
foreach(const config &sc, campaign.child_range("scenario"))
{
if (sc["id"] == jump_to_campaign_.scenario_id_)
scenario = sc;
}
if (scenario == NULL)
{
std::cerr<<"No such scenario id to jump to: ["<<jump_to_campaign_.scenario_id_<<"]\n";
return false;
}
state_.classification().scenario = jump_to_campaign_.scenario_id_;
}
state_.classification().end_text = campaign["end_text"].str();
state_.classification().end_text_duration = campaign["end_text_duration"];
const std::string difficulty_descriptions = campaign["difficulty_descriptions"];
std::vector<std::string> difficulty_options = utils::split(difficulty_descriptions, ';');
const std::vector<std::string> 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<int>(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<std::string> 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();
// clone and put the gfx rules aside so that we can prepend the add-on
// rules to them.
config core_terrain_rules;
// FIXME: there should be a canned algorithm for cloning child_list objects,
// along with the memory their elements point to... little implementation detail.
foreach (config const &p_cfg, game_config_.child_range("terrain_graphics")) {
core_terrain_rules.add_child("terrain_graphics", p_cfg);
}
game_config_.clear_children("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<std::string> user_dirs;
// Scan for standalone files
std::vector<std::string> user_files;
// The addons that we'll actually load
std::vector<std::string> 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<std::string>::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<std::string>::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<std::string>::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<std::string>::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_.append(core_terrain_rules);
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<game_config::scoped_preproc_define> define_ptr;
std::deque<define_ptr> extra_defines;
for(std::vector<std::string>::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<boost::iostreams::input> 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]
<< " [<options>] [<data-directory>]\n"
<< "Available options:\n"
<< " --bpp <number> sets BitsPerPixel value. Example: --bpp 32\n"
<< " -c, --campaign[[<difficulty>] <id_c> [<id_s>]]\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 <name> sets the path of the user config directory to\n"
<< " $HOME/<name> or My Documents\\My Games\\<name> 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 <directory> 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=<level1>,<level2>,...\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=<domain1>,<domain2>,...\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 [<file>] starts the in-game map editor directly. If <file>\n"
<< " is specified, equivalent to -e --load <file>.\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 <infile>.gz decompresses a file (<infile>.gz) in gzip format\n"
<< " and stores it without the .gz suffix.\n"
<< " <infile>.gz will be removed.\n"
<< " --gzip <infile> compresses a file (<infile>) in gzip format,\n"
<< " stores it as <infile>.gz and removes <infile>.\n"
<< " -h, --help prints this message and exits.\n"
<< " -l, --load <file> loads the save <file> from the standard save\n"
<< " game directory.\n"
#ifndef DISABLE_EDITOR
<< " When launching the map editor via -e, the map\n"
<< " <file> 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-<level>=<domain1>,<domain2>,...\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<number>=value selects a configuration file to load for this side.\n"
<< " --algorithm<number>=value selects a non-standard algorithm to be used by\n"
<< " the AI controller for this side.\n"
<< " --controller<number>=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<number>=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<number>=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[=<define1>,<define2>,...] <file/folder> <target directory>\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 <source file>\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 [<target file>]\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 <number> 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 <map> <output> Saves a screenshot of <map> to <output> without\n"
<< " initializing a screen. Editor must be compiled\n"
<< " in for this to work.\n"
<< " -s, --server [<host>] 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 << " <map file> <output file>\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 << " <input file>\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 << " <input file>\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<unsigned int>(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<gui::TITLE_RESULT>(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