Merge pull request #284 from cbeck88/autodownload_addons
automatically download needed addons in mp lobby
This commit is contained in:
commit
844dd5d799
11 changed files with 862 additions and 359 deletions
|
@ -116,9 +116,45 @@ std::string describe_addon_status(const addon_tracking_info& info)
|
|||
}
|
||||
}
|
||||
|
||||
/** Warns the user about unresolved dependencies and installs them if they choose to do so. */
|
||||
bool do_resolve_addon_dependencies(display& disp, addons_client& client, const addons_list& addons, const addon_info& addon, bool& wml_changed)
|
||||
// Asks the client to download and install an addon, reporting errors in a gui dialog. Returns true if new content was installed, false otherwise.
|
||||
static bool try_fetch_addon(display & disp, addons_client & client, const addon_info & addon)
|
||||
{
|
||||
config archive;
|
||||
|
||||
if(!(
|
||||
client.download_addon(archive, addon.id, addon.title, !is_addon_installed(addon.id)) &&
|
||||
client.install_addon(archive, addon)
|
||||
)) {
|
||||
const std::string& server_error = client.get_last_server_error();
|
||||
if(!server_error.empty()) {
|
||||
gui2::show_error_message(disp.video(),
|
||||
std::string(_("The server responded with an error:")) + "\n" + server_error);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
enum OUTCOME { SUCCESS, FAILURE, ABORT };
|
||||
|
||||
// A structure which summarizes the outcome of one or more add-on install operations.
|
||||
struct ADDON_OP_RESULT {
|
||||
OUTCOME outcome_;
|
||||
bool wml_changed_;
|
||||
};
|
||||
|
||||
/** Warns the user about unresolved dependencies and installs them if they choose to do so.
|
||||
* Returns: outcome: ABORT in case the user chose to abort because of an issue
|
||||
* SUCCESS otherwise
|
||||
* wml_change: indicates if new wml content was installed
|
||||
*/
|
||||
ADDON_OP_RESULT do_resolve_addon_dependencies(display& disp, addons_client& client, const addons_list& addons, const addon_info& addon)
|
||||
{
|
||||
ADDON_OP_RESULT result;
|
||||
result.outcome_ = SUCCESS;
|
||||
result.wml_changed_ = false;
|
||||
|
||||
boost::scoped_ptr<cursor::setter> cursor_setter(new cursor::setter(cursor::WAIT));
|
||||
|
||||
// TODO: We don't currently check for the need to upgrade. I'll probably
|
||||
|
@ -155,13 +191,14 @@ bool do_resolve_addon_dependencies(display& disp, addons_client& client, const a
|
|||
}
|
||||
|
||||
if(gui2::show_message(disp.video(), _("Broken Dependencies"), broken_deps_report, gui2::tmessage::yes_no_buttons) != gui2::twindow::OK) {
|
||||
return false; // canceled by user
|
||||
result.outcome_ = ABORT;
|
||||
return result; // canceled by user
|
||||
}
|
||||
}
|
||||
|
||||
if(missing_deps.empty()) {
|
||||
// No dependencies to install, carry on.
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -216,7 +253,7 @@ bool do_resolve_addon_dependencies(display& disp, addons_client& client, const a
|
|||
cursor_setter.reset();
|
||||
|
||||
if(dlg.show() < 0) {
|
||||
return true;
|
||||
return result; // the user has chosen to continue without installing anything.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,21 +266,10 @@ bool do_resolve_addon_dependencies(display& disp, addons_client& client, const a
|
|||
BOOST_FOREACH(const std::string& dep, missing_deps) {
|
||||
const addon_info& addon = addon_at(dep, addons);
|
||||
|
||||
config archive;
|
||||
|
||||
if(!(
|
||||
client.download_addon(archive, addon.id, addon.title, !is_addon_installed(addon.id)) &&
|
||||
client.install_addon(archive, addon)
|
||||
)) {
|
||||
const std::string& server_error = client.get_last_server_error();
|
||||
if(!server_error.empty()) {
|
||||
gui2::show_error_message(disp.video(),
|
||||
std::string(_("The server responded with an error:")) + "\n" + server_error);
|
||||
}
|
||||
|
||||
if(!try_fetch_addon(disp, client, addon)) {
|
||||
failed_titles.push_back(addon.title);
|
||||
} else {
|
||||
wml_changed = true;
|
||||
result.wml_changed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,10 +279,11 @@ bool do_resolve_addon_dependencies(display& disp, addons_client& client, const a
|
|||
"The following dependencies could not be installed. Do you still wish to continue?",
|
||||
failed_titles.size()) + std::string("\n\n") + utils::bullet_list(failed_titles);
|
||||
|
||||
return gui2::show_message(disp.video(), _("Dependencies Installation Failed"), failed_deps_report, gui2::tmessage::yes_no_buttons) == gui2::twindow::OK;
|
||||
result.outcome_ = gui2::show_message(disp.video(), _("Dependencies Installation Failed"), failed_deps_report, gui2::tmessage::yes_no_buttons) == gui2::twindow::OK ? SUCCESS : ABORT; // If the user cancels, return ABORT. Otherwise, return SUCCESS, since the user chose to ignore the failure.
|
||||
return result;
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Checks whether the given add-on has local .pbl or VCS information and asks before overwriting it. */
|
||||
|
@ -293,6 +320,38 @@ bool do_check_before_overwriting_addon(CVideo& video, const addon_info& addon)
|
|||
return gui2::show_message(video, _("Confirm"), text, gui2::tmessage::yes_no_buttons) == gui2::twindow::OK;
|
||||
}
|
||||
|
||||
/** Do a 'smart' fetch of an add-on, checking to avoid overwrites for devs and resolving dependencies, using gui interaction to handle issues that arise
|
||||
* Returns: outcome: ABORT in case the user chose to abort because of an issue
|
||||
* FAILURE in case we resolved checks and dependencies, but fetching this particular add-on failed
|
||||
* SUCCESS otherwise
|
||||
* wml_changed: indicates if new wml content was installed at any point
|
||||
*/
|
||||
static ADDON_OP_RESULT try_fetch_addon_with_checks(display & disp, addons_client& client, const addons_list& addons, const addon_info& addon)
|
||||
{
|
||||
if(!(do_check_before_overwriting_addon(disp.video(), addon))) {
|
||||
// Just do nothing and leave.
|
||||
ADDON_OP_RESULT result;
|
||||
result.outcome_ = ABORT;
|
||||
result.wml_changed_ = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Resolve any dependencies
|
||||
ADDON_OP_RESULT res = do_resolve_addon_dependencies(disp, client, addons, addon);
|
||||
if (res.outcome_ != SUCCESS) { // this function only returns SUCCESS and ABORT as outcomes
|
||||
return res; // user aborted
|
||||
}
|
||||
|
||||
if(!try_fetch_addon(disp, client, addon)) {
|
||||
res.outcome_ = FAILURE;
|
||||
return res; //wml_changed should have whatever value was obtained in resolving dependencies
|
||||
} else {
|
||||
res.wml_changed_ = true;
|
||||
return res; //we successfully installed something, so now the wml was definitely changed
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs all backend and UI actions for taking down the specified add-on. */
|
||||
void do_remote_addon_delete(CVideo& video, addons_client& client, const std::string& addon_id)
|
||||
{
|
||||
|
@ -866,24 +925,13 @@ void show_addons_manager_dialog(display& disp, addons_client& client, addons_lis
|
|||
BOOST_FOREACH(const std::string& id, ids_to_install) {
|
||||
const addon_info& addon = addon_at(id, addons);
|
||||
|
||||
if(!(do_check_before_overwriting_addon(disp.video(), addon) && do_resolve_addon_dependencies(disp, client, addons, addon, wml_changed))) {
|
||||
// Just do nothing and leave.
|
||||
return;
|
||||
}
|
||||
|
||||
config archive;
|
||||
|
||||
if(!(
|
||||
client.download_addon(archive, addon.id, addon.title, !is_addon_installed(addon.id)) &&
|
||||
client.install_addon(archive, addon)
|
||||
)) {
|
||||
failed_titles.push_back(addon.title);
|
||||
const std::string& server_error = client.get_last_server_error();
|
||||
if(!server_error.empty()) {
|
||||
gui2::show_error_message(disp.video(),
|
||||
std::string(_("The server responded with an error:")) + "\n" + server_error);
|
||||
}
|
||||
} else {
|
||||
ADDON_OP_RESULT res = try_fetch_addon_with_checks(disp, client, addons, addon);
|
||||
wml_changed |= res.wml_changed_; // take note if any wml_changes occurred
|
||||
if (res.outcome_ == ABORT) {
|
||||
return; // the user aborted because of some issue encountered
|
||||
} else if (res.outcome_ == FAILURE) {
|
||||
failed_titles.push_back(addon.title); // we resolved dependencies, but fetching this particular addon failed.
|
||||
} else { // res.outcome == SUCCESS
|
||||
wml_changed = true;
|
||||
}
|
||||
}
|
||||
|
@ -1159,3 +1207,62 @@ bool manage_addons(display& disp)
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ad_hoc_addon_fetch_session(display & disp, const std::vector<std::string> & addon_ids)
|
||||
{
|
||||
std::string remote_address = preferences::campaign_server();
|
||||
|
||||
// These exception handlers copied from addon_manager_ui fcn above.
|
||||
try {
|
||||
|
||||
addons_client client(disp, remote_address);
|
||||
client.connect();
|
||||
|
||||
addons_list addons;
|
||||
|
||||
if(!get_addons_list(client, addons)) {
|
||||
gui2::show_error_message(disp.video(), _("An error occurred while downloading the add-ons list from the server."));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool return_value = true;
|
||||
BOOST_FOREACH(const std::string & addon_id, addon_ids) {
|
||||
const addon_info& addon = addon_at(addon_id, addons);
|
||||
|
||||
ADDON_OP_RESULT res = try_fetch_addon_with_checks(disp, client, addons, addon);
|
||||
return_value = return_value && (res.outcome_ == SUCCESS);
|
||||
}
|
||||
|
||||
return return_value;
|
||||
|
||||
} catch(const config::error& e) {
|
||||
ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"" << std::endl;
|
||||
gui2::show_error_message(disp.video(), _("Network communication error."));
|
||||
} catch(const network::error& e) {
|
||||
ERR_NET << "network::error thrown during transaction with add-on server; \""<< e.message << "\"" << std::endl;
|
||||
gui2::show_error_message(disp.video(), _("Remote host disconnected."));
|
||||
} catch(const network_asio::error& e) {
|
||||
ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"" << std::endl;
|
||||
gui2::show_error_message(disp.video(), _("Remote host disconnected."));
|
||||
} catch(const filesystem::io_exception& e) {
|
||||
ERR_FS << "io_exception thrown while installing an addon; \"" << e.what() << "\"" << std::endl;
|
||||
gui2::show_error_message(disp.video(), _("A problem occurred when trying to create the files necessary to install this add-on."));
|
||||
} catch(const invalid_pbl_exception& e) {
|
||||
ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message << std::endl;
|
||||
|
||||
utils::string_map symbols;
|
||||
symbols["path"] = e.path;
|
||||
symbols["msg"] = e.message;
|
||||
|
||||
gui2::show_error_message(disp.video(),
|
||||
vgettext("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
|
||||
} catch(twml_exception& e) {
|
||||
e.show(disp);
|
||||
} catch(const addons_client::user_exit&) {
|
||||
LOG_AC << "initial connection canceled by user\n";
|
||||
} catch(const addons_client::invalid_server_address&) {
|
||||
gui2::show_error_message(disp.video(), _("The add-ons server address specified is not valid."));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#define ADDON_MANAGER_UI_HPP_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class display;
|
||||
|
||||
|
@ -30,4 +31,15 @@ class display;
|
|||
*/
|
||||
bool manage_addons(display& disp);
|
||||
|
||||
/**
|
||||
* Conducts an ad-hoc add-ons server connection to download an add-on with a particular id and all
|
||||
* it's dependencies. Launches gui dialogs when issues arise.
|
||||
*
|
||||
* @param disp Display object on which to render UI elements.
|
||||
* @param id The id of the target add-on.
|
||||
*
|
||||
* @return @a true when we successfully installed the target (possibly the user chose to ignore failures)
|
||||
*/
|
||||
bool ad_hoc_addon_fetch_session(display & disp, const std::vector<std::string> & addon_ids);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "terrain_builder.hpp"
|
||||
#include "terrain_type_data.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
|
@ -316,6 +317,12 @@ void game_config_manager::load_game_config(FORCE_RELOAD_CONFIG force_reload,
|
|||
paths_manager_.set_paths(game_config());
|
||||
}
|
||||
|
||||
struct addon_source {
|
||||
std::string main_cfg;
|
||||
std::string addon_id;
|
||||
version_info version;
|
||||
};
|
||||
|
||||
void game_config_manager::load_addons_cfg()
|
||||
{
|
||||
const std::string user_campaign_dir = filesystem::get_addons_dir();
|
||||
|
@ -323,7 +330,7 @@ void game_config_manager::load_addons_cfg()
|
|||
std::vector<std::string> error_addons;
|
||||
std::vector<std::string> user_dirs;
|
||||
std::vector<std::string> user_files;
|
||||
std::vector<std::string> addons_to_load;
|
||||
std::vector<addon_source> addons_to_load;
|
||||
|
||||
filesystem::get_files_in_dir(user_campaign_dir, &user_files, &user_dirs,
|
||||
filesystem::ENTIRE_FILE_PATH);
|
||||
|
@ -335,86 +342,93 @@ void game_config_manager::load_addons_cfg()
|
|||
const std::string file = uc;
|
||||
const int size_minus_extension = file.size() - 4;
|
||||
if(file.substr(size_minus_extension, file.size()) == ".cfg") {
|
||||
bool ok = true;
|
||||
// Allowing it if the dir doesn't exist,
|
||||
// for the single-file add-on.
|
||||
if(filesystem::file_exists(file.substr(0, size_minus_extension))) {
|
||||
// Unfortunately, we create the dir plus
|
||||
// _info.cfg ourselves on download.
|
||||
std::vector<std::string> dirs, files;
|
||||
filesystem::get_files_in_dir(file.substr(0, size_minus_extension),
|
||||
&files, &dirs);
|
||||
if(dirs.size() > 0) {
|
||||
ok = false;
|
||||
}
|
||||
if(files.size() > 1) {
|
||||
ok = false;
|
||||
}
|
||||
if(files.size() == 1 && files[0] != "_info.cfg") {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if(!ok) {
|
||||
const int userdata_loc = file.find("data/add-ons") + 5;
|
||||
ERR_CONFIG << "error reading usermade add-on '"
|
||||
<< file << "'\n";
|
||||
error_addons.push_back(file);
|
||||
error_log.push_back("The format '~" + file.substr(userdata_loc)
|
||||
+ "' is only for single-file add-ons, use '~"
|
||||
+ "' (for single-file add-ons) is not supported anymore, use '~"
|
||||
+ file.substr(userdata_loc,
|
||||
size_minus_extension - userdata_loc)
|
||||
+ "/_main.cfg' instead.");
|
||||
}
|
||||
else {
|
||||
addons_to_load.push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rerun the directory scan using filename only, to get the addon_ids more easily.
|
||||
user_files.clear();
|
||||
user_dirs.clear();
|
||||
filesystem::get_files_in_dir(user_campaign_dir, &user_files, &user_dirs,
|
||||
filesystem::FILE_NAME_ONLY);
|
||||
|
||||
// Append the $user_campaign_dir/*/_main.cfg files to addons_to_load.
|
||||
BOOST_FOREACH(const std::string& uc, user_dirs) {
|
||||
const std::string addon_id = uc;
|
||||
const std::string addon_dir = user_campaign_dir + "/" + uc;
|
||||
|
||||
const std::string info_cfg = uc + "/_info.cfg";
|
||||
if (filesystem::file_exists(info_cfg)) {
|
||||
const std::string main_cfg = addon_dir + "/_main.cfg";
|
||||
const std::string info_cfg = addon_dir + "/_info.cfg";
|
||||
|
||||
config info;
|
||||
cache_.get_config(info_cfg, info);
|
||||
const config info_tag = info.child_or_empty("info");
|
||||
std::string core = info_tag["core"];
|
||||
if (core.empty()) core = "default";
|
||||
if ( !info_tag.empty() && // Don't skip addons which have no [info], they are most likely manually installed.
|
||||
info_tag["type"] != "core" && // Don't skip cores, we want them selectable at all times.
|
||||
core != preferences::core_id() // Don't skip addons matching our current core.
|
||||
)
|
||||
continue; // Skip add-ons not matching our current core.
|
||||
}
|
||||
addon_source addon;
|
||||
addon.main_cfg = main_cfg;
|
||||
addon.addon_id = addon_id;
|
||||
|
||||
const std::string main_cfg = uc + "/_main.cfg";
|
||||
if(filesystem::file_exists(main_cfg)) {
|
||||
addons_to_load.push_back(main_cfg);
|
||||
if (filesystem::file_exists(main_cfg)) {
|
||||
if (filesystem::file_exists(info_cfg)) {
|
||||
config info;
|
||||
cache_.get_config(info_cfg, info);
|
||||
const config info_tag = info.child_or_empty("info");
|
||||
std::string core = info_tag["core"];
|
||||
if (core.empty()) core = "default";
|
||||
if ( !info_tag.empty() && // Don't skip addons which have no [info], they are most likely manually installed.
|
||||
info_tag["type"] != "core" && // Don't skip cores, we want them selectable at all times.
|
||||
core != preferences::core_id() // Don't skip addons matching our current core.
|
||||
) {
|
||||
continue; // Skip add-ons not matching our current core.
|
||||
}
|
||||
}
|
||||
|
||||
// Ask the addon manager to find version info for us (from info, pbl file)
|
||||
addon.version = get_addon_version_info(addon_id);
|
||||
addons_to_load.push_back(addon);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the addons.
|
||||
BOOST_FOREACH(const std::string& uc, addons_to_load) {
|
||||
const std::string toplevel = uc;
|
||||
BOOST_FOREACH(const addon_source & addon, addons_to_load) {
|
||||
try {
|
||||
// Load this addon from the cache, to a config
|
||||
config umc_cfg;
|
||||
cache_.get_config(toplevel, umc_cfg);
|
||||
cache_.get_config(addon.main_cfg, umc_cfg);
|
||||
|
||||
// Annotate "era" and "modification" tags with addon_id info
|
||||
const char * tags_with_addon_id [] = { "era", "modification", NULL };
|
||||
|
||||
for (const char ** type = tags_with_addon_id; *type; type++)
|
||||
{
|
||||
BOOST_FOREACH(config & cfg, umc_cfg.child_range(*type)) {
|
||||
cfg["addon_id"] = addon.addon_id;
|
||||
if (addon.version.good()) {
|
||||
// If the addon string was not "sane" then reject it, we can't compare non-sane version strings.
|
||||
// This may also happen if no version info could be found.
|
||||
cfg["addon_version"] = addon.version.str(); // Note that this may reformat the string in a canonical form.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game_config_.append(umc_cfg);
|
||||
} catch(config::error& err) {
|
||||
ERR_CONFIG << "error reading usermade add-on '" << uc << "'" << std::endl;
|
||||
ERR_CONFIG << "error reading usermade add-on '" << addon.main_cfg << "'" << std::endl;
|
||||
ERR_CONFIG << err.message << '\n';
|
||||
error_addons.push_back(uc);
|
||||
error_addons.push_back(addon.main_cfg);
|
||||
error_log.push_back(err.message);
|
||||
} catch(preproc_config::error& err) {
|
||||
ERR_CONFIG << "error reading usermade add-on '" << uc << "'" << std::endl;
|
||||
ERR_CONFIG << "error reading usermade add-on '" << addon.main_cfg << "'" << std::endl;
|
||||
ERR_CONFIG << err.message << '\n';
|
||||
error_addons.push_back(uc);
|
||||
error_addons.push_back(addon.main_cfg);
|
||||
error_log.push_back(err.message);
|
||||
} catch(filesystem::io_exception&) {
|
||||
ERR_CONFIG << "error reading usermade add-on '" << uc << "'" << std::endl;
|
||||
error_addons.push_back(uc);
|
||||
ERR_CONFIG << "error reading usermade add-on '" << addon.main_cfg << "'" << std::endl;
|
||||
error_addons.push_back(addon.main_cfg);
|
||||
}
|
||||
}
|
||||
if(error_addons.empty() == false) {
|
||||
|
|
28
src/game_initialization/lobby_reload_request_exception.hpp
Normal file
28
src/game_initialization/lobby_reload_request_exception.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright (C) 2015 by Chris Beck <render787@gmail.com>
|
||||
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 as published by
|
||||
the Free Software Foundation; either version 2 of the License, 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.
|
||||
*/
|
||||
#ifndef LOBBY_RELOAD_REQUEST_EXCEPTION_HPP_INCLUDED
|
||||
#define LOBBY_RELOAD_REQUEST_EXCEPTION_HPP_INCLUDED
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
namespace mp {
|
||||
|
||||
struct lobby_reload_request_exception : game::error {
|
||||
lobby_reload_request_exception() : game::error("Lobby requested to reload the game config and launch again") {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -13,6 +13,7 @@
|
|||
*/
|
||||
#include "multiplayer.hpp"
|
||||
|
||||
#include "addon/manager.hpp" // for get_installed_addons
|
||||
#include "dialogs.hpp"
|
||||
#include "formula_string_utils.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
|
@ -27,6 +28,7 @@
|
|||
#include "gui/widgets/settings.hpp"
|
||||
#include "gui/widgets/window.hpp"
|
||||
#include "hash.hpp"
|
||||
#include "lobby_reload_request_exception.hpp"
|
||||
#include "log.hpp"
|
||||
#include "generators/map_create.hpp"
|
||||
#include "mp_game_utils.hpp"
|
||||
|
@ -646,7 +648,7 @@ static void do_preferences_dialog(game_display& disp, const config& game_config)
|
|||
}
|
||||
|
||||
static void enter_lobby_mode(game_display& disp, const config& game_config,
|
||||
saved_game& state)
|
||||
saved_game& state, const std::vector<std::string> & installed_addons)
|
||||
{
|
||||
DBG_MP << "entering lobby mode" << std::endl;
|
||||
|
||||
|
@ -695,7 +697,7 @@ static void enter_lobby_mode(game_display& disp, const config& game_config,
|
|||
res = mp::ui::QUIT;
|
||||
}
|
||||
} else {
|
||||
mp::lobby ui(disp, game_config, gamechat, gamelist);
|
||||
mp::lobby ui(disp, game_config, gamechat, gamelist, installed_addons);
|
||||
run_lobby_loop(disp, ui);
|
||||
res = ui.get_result();
|
||||
}
|
||||
|
@ -904,6 +906,12 @@ void start_local_game_commandline(game_display& disp, const config& game_config,
|
|||
void start_client(game_display& disp, const config& game_config,
|
||||
saved_game& state, const std::string& host)
|
||||
{
|
||||
const config * game_config_ptr = &game_config;
|
||||
std::vector<std::string> installed_addons = ::installed_addons();
|
||||
// This function does not refer to an addon database, it calls filesystem functions.
|
||||
// For the sanity of the mp lobby, this list should be fixed for the entire lobby session,
|
||||
// even if the user changes the contents of the addon directory in the meantime.
|
||||
|
||||
DBG_MP << "starting client" << std::endl;
|
||||
const network::manager net_manager(1,1);
|
||||
|
||||
|
@ -913,12 +921,29 @@ void start_client(game_display& disp, const config& game_config,
|
|||
|
||||
switch(type) {
|
||||
case WESNOTHD_SERVER:
|
||||
enter_lobby_mode(disp, game_config, state);
|
||||
bool re_enter;
|
||||
do {
|
||||
re_enter = false;
|
||||
try {
|
||||
enter_lobby_mode(disp, *game_config_ptr, state, installed_addons);
|
||||
} catch (lobby_reload_request_exception & ex) {
|
||||
re_enter = true;
|
||||
game_config_manager * gcm = game_config_manager::get();
|
||||
gcm->reload_changed_game_config();
|
||||
gcm->load_game_config_for_game(state.classification()); // NOTE: Using reload_changed_game_config only doesn't seem to work here
|
||||
game_config_ptr = &gcm->game_config();
|
||||
|
||||
installed_addons = ::installed_addons(); // Refersh the installed add-on list for this session.
|
||||
|
||||
gamelist.clear(); //needed to make sure we update which games we have content for
|
||||
network::send_data(config("refresh_lobby"), 0);
|
||||
}
|
||||
} while (re_enter);
|
||||
break;
|
||||
case SIMPLE_SERVER:
|
||||
playmp_controller::set_replay_last_turn(0);
|
||||
preferences::set_message_private(false);
|
||||
enter_wait_mode(disp, game_config, state, false);
|
||||
enter_wait_mode(disp, *game_config_ptr, state, false);
|
||||
break;
|
||||
case ABORT_SERVER:
|
||||
break;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "global.hpp"
|
||||
|
||||
#include "addon/manager_ui.hpp"
|
||||
#include "construct_dialog.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
|
@ -28,6 +29,9 @@
|
|||
#include "multiplayer_lobby.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "gui/auxiliary/old_markup.hpp"
|
||||
#include "gui/dialogs/message.hpp" // for gui2::show_message
|
||||
#include "gui/widgets/window.hpp" // for gui2::twindow::OK
|
||||
#include "lobby_reload_request_exception.hpp"
|
||||
#include "log.hpp"
|
||||
#include "playmp_controller.hpp"
|
||||
#include "sound.hpp"
|
||||
|
@ -43,6 +47,12 @@
|
|||
static lg::log_domain log_config("config");
|
||||
#define ERR_CF LOG_STREAM(err, log_config)
|
||||
|
||||
static lg::log_domain log_lobby("mp/lobby");
|
||||
#define ERR_MP LOG_STREAM(err, log_lobby)
|
||||
#define WRN_MP LOG_STREAM(warn, log_lobby)
|
||||
#define LOG_MP LOG_STREAM(info, log_lobby)
|
||||
#define DBG_MP LOG_STREAM(debug, log_lobby)
|
||||
|
||||
namespace {
|
||||
std::vector<std::string> empty_string_vector;
|
||||
}
|
||||
|
@ -172,6 +182,16 @@ void gamebrowser::draw_row(const size_t index, const SDL_Rect& item_rect, ROW_TY
|
|||
if(!game.have_all_mods && font_color != font::BAD_COLOR) {
|
||||
font_color = font::DISABLED_COLOR;
|
||||
}
|
||||
if(game.addons_outcome != SATISFIED) {
|
||||
font_color = font::DISABLED_COLOR;
|
||||
if (game.addons_outcome == NEED_DOWNLOAD) {
|
||||
no_era_string += _(" (Need to download addons)");
|
||||
} else {
|
||||
no_era_string += _(" (Outdated addons)");
|
||||
}
|
||||
} /*else {
|
||||
no_era_string += _(" (You have this addon)");
|
||||
}*/
|
||||
|
||||
const surface status_text(font::get_rendered_text(game.status,
|
||||
font::SIZE_NORMAL, font_color, TTF_STYLE_BOLD));
|
||||
|
@ -435,6 +455,104 @@ void gamebrowser::handle_event(const SDL_Event& event)
|
|||
}
|
||||
}
|
||||
|
||||
// Determine if this is a campaign or scenario and add info string to this game item.
|
||||
void gamebrowser::populate_game_item_campaign_or_scenario_info(gamebrowser::game_item & item, const config & game, const config & game_config, bool & verified)
|
||||
{
|
||||
if (game["mp_campaign"].empty()) {
|
||||
if (!game["mp_scenario"].empty()) {
|
||||
// Check if it's a multiplayer scenario.
|
||||
const config* level_cfg = &game_config.find_child("multiplayer",
|
||||
"id", game["mp_scenario"]);
|
||||
if (!*level_cfg) {
|
||||
// Check if it's a user map.
|
||||
level_cfg = &game_config.find_child("generic_multiplayer",
|
||||
"id", game["mp_scenario"]);
|
||||
}
|
||||
if (*level_cfg) {
|
||||
item.map_info = _("Scenario:");
|
||||
item.map_info += " ";
|
||||
item.map_info += (*level_cfg)["name"].str();
|
||||
// Reloaded games do not match the original scenario hash,
|
||||
// so it makes no sense to test them,
|
||||
// they always would appear as remote scenarios.
|
||||
if (map_hashes_ && !item.reloaded) {
|
||||
std::string hash = game["hash"];
|
||||
bool hash_found = false;
|
||||
BOOST_FOREACH(const config::attribute& i,
|
||||
map_hashes_.attribute_range()) {
|
||||
|
||||
if (i.first == game["mp_scenario"] &&
|
||||
i.second == hash) {
|
||||
|
||||
hash_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hash_found) {
|
||||
item.map_info += " — ";
|
||||
item.map_info += _("Remote scenario");
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
utils::string_map symbols;
|
||||
symbols["scenario_id"] = game["mp_scenario"];
|
||||
item.map_info =
|
||||
vgettext("Unknown scenario: $scenario_id", symbols);
|
||||
verified = false;
|
||||
}
|
||||
} else {
|
||||
item.map_info = _("Unknown scenario");
|
||||
verified = false;
|
||||
}
|
||||
} else { // Is a campaign
|
||||
const config* level_cfg = &game_config.find_child("campaign", "id",
|
||||
game["mp_campaign"]);
|
||||
if (*level_cfg) {
|
||||
item.map_info = _("Campaign:");
|
||||
item.map_info += " ";
|
||||
item.map_info += (*level_cfg)["name"].str();
|
||||
item.map_info += " — ";
|
||||
item.map_info += game["mp_scenario_name"].str();
|
||||
|
||||
// Difficulty.
|
||||
const std::vector<std::string> difficulties =
|
||||
utils::split((*level_cfg)["difficulties"]);
|
||||
const std::string difficulty_descriptions =
|
||||
(*level_cfg)["difficulty_descriptions"];
|
||||
std::vector<std::string> difficulty_options =
|
||||
utils::split(difficulty_descriptions, ';');
|
||||
int index = 0;
|
||||
//TODO: use difficulties instead of difficulty_descriptions if
|
||||
//difficulty_descriptions is not available
|
||||
assert(difficulties.size() == difficulty_options.size());
|
||||
BOOST_FOREACH(const std::string& difficulty, difficulties) {
|
||||
if (difficulty == game["difficulty_define"]) {
|
||||
gui2::tlegacy_menu_item menu_item(difficulty_options[index]);
|
||||
item.map_info += " — ";
|
||||
item.map_info += menu_item.label();
|
||||
item.map_info += " ";
|
||||
item.map_info += menu_item.description();
|
||||
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
} else {
|
||||
utils::string_map symbols;
|
||||
symbols["campaign_id"] = game["mp_campaign"];
|
||||
item.map_info =
|
||||
vgettext("Unknown campaign: $campaign_id", symbols);
|
||||
verified = false;
|
||||
|
||||
if (game["require_scenario"].to_bool(false)) {
|
||||
item.have_scenario = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct minimap_cache_item {
|
||||
|
||||
minimap_cache_item() :
|
||||
|
@ -449,14 +567,9 @@ struct minimap_cache_item {
|
|||
std::string map_info_size;
|
||||
};
|
||||
|
||||
void gamebrowser::set_game_items(const config& cfg, const config& game_config)
|
||||
// Handle the minimap data. Some caching is taking place to avoid repeatedly rendering the minimaps.
|
||||
void gamebrowser::populate_game_item_map_info(gamebrowser::game_item & item, const config & game, const config & game_config, bool & verified)
|
||||
{
|
||||
const bool scrolled_to_max = (has_scrollbar() && get_position() == get_max_position());
|
||||
const bool selection_visible = (selected_ >= visible_range_.first && selected_ <= visible_range_.second);
|
||||
const std::string selected_game = (selected_ < games_.size()) ? games_[selected_].id : "";
|
||||
|
||||
item_height_ = 100;
|
||||
|
||||
// Don't throw the rendered minimaps away
|
||||
std::vector<minimap_cache_item> minimap_cache;
|
||||
for(std::vector<game_item>::iterator oldgame = games_.begin(); oldgame != games_.end(); ++oldgame) {
|
||||
|
@ -467,252 +580,272 @@ void gamebrowser::set_game_items(const config& cfg, const config& game_config)
|
|||
minimap_cache.push_back(item);
|
||||
}
|
||||
|
||||
item.map_data = game["map_data"].str();
|
||||
if(item.map_data.empty()) {
|
||||
item.map_data = filesystem::read_map(game["map"]);
|
||||
}
|
||||
if(! item.map_data.empty()) {
|
||||
try {
|
||||
std::vector<minimap_cache_item>::iterator i;
|
||||
bool found = false;
|
||||
for(i = minimap_cache.begin(); i != minimap_cache.end() && !found; ++i) {
|
||||
if (i->map_data == item.map_data) {
|
||||
found = true;
|
||||
item.map_info_size = i->map_info_size;
|
||||
item.mini_map = i->mini_map;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// Parsing the map and generating the minimap are both cpu expensive
|
||||
gamemap map(boost::make_shared<terrain_type_data>(game_config), item.map_data);
|
||||
item.mini_map = image::getMinimap(minimap_size_, minimap_size_, map, 0);
|
||||
item.map_info_size = str_cast(map.w()) + utils::unicode_multiplication_sign
|
||||
+ str_cast(map.h());
|
||||
}
|
||||
} catch (incorrect_map_format_error &e) {
|
||||
ERR_CF << "illegal map: " << e.message << '\n';
|
||||
verified = false;
|
||||
} catch(twml_exception& e) {
|
||||
ERR_CF << "map could not be loaded: " << e.dev_message << '\n';
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// local_item is either an [era] or [modification] tag, something with addon_version and addon_id.
|
||||
// (These are currently added at add-on loading time in the game_config_manager.)
|
||||
// It is checked whether the local item's add-on version is required for this game, and if the
|
||||
// versions are compatible. If it's not, a record is made in the req_list passed as argument.
|
||||
static mp::ADDON_REQ check_addon_version_compatibility (const config & local_item, const config & game, std::vector<required_addon> & req_list)
|
||||
{
|
||||
if (local_item.has_attribute("addon_id") && local_item.has_attribute("addon_version")) {
|
||||
if (const config & game_req = game.find_child("addon", "id", local_item["addon_id"])) {
|
||||
// Record object which we will potentially store for this check
|
||||
required_addon r;
|
||||
r.addon_id = local_item["addon_id"].str();
|
||||
|
||||
const version_info local_ver(local_item["addon_version"].str());
|
||||
version_info local_min_ver(local_item.has_attribute("addon_min_version") ? local_item["addon_min_version"] : local_item["addon_version"]);
|
||||
// If UMC didn't specify last compatible version, assume no backwards compatibility.
|
||||
if (local_min_ver > local_ver) {
|
||||
// Some sanity checking regarding min version. If the min ver doens't make sense, ignore it.
|
||||
local_min_ver = local_ver;
|
||||
}
|
||||
|
||||
const version_info remote_ver(game_req["version"].str());
|
||||
version_info remote_min_ver(game_req.has_attribute("min_version") ? game_req["min_version"] : game_req["version"]);
|
||||
if (remote_min_ver > remote_ver) {
|
||||
remote_min_ver = remote_ver;
|
||||
}
|
||||
|
||||
// Check if the host is too out of date to play.
|
||||
if (local_min_ver > remote_ver) {
|
||||
r.outcome = CANNOT_SATISFY;
|
||||
|
||||
utils::string_map symbols;
|
||||
symbols["addon"] = r.addon_id; // TODO: Figure out how to ask the add-on manager for the user-friendly name of this add-on.
|
||||
symbols["host_ver"] = remote_ver.str();
|
||||
symbols["local_ver"] = local_ver.str();
|
||||
r.message = vgettext("Host's version of $addon is too old: host's version $host_ver < your version $local_ver.", symbols);
|
||||
req_list.push_back(r);
|
||||
return r.outcome;
|
||||
}
|
||||
|
||||
// Check if our version is too out of date to play.
|
||||
if (remote_min_ver > local_ver) {
|
||||
r.outcome = NEED_DOWNLOAD;
|
||||
|
||||
utils::string_map symbols;
|
||||
symbols["addon"] = r.addon_id; // TODO: Figure out how to ask the add-on manager for the user-friendly name of this add-on.
|
||||
symbols["host_ver"] = remote_ver.str();
|
||||
symbols["local_ver"] = local_ver.str();
|
||||
r.message = vgettext("Your version of $addon is out of date: host's version $host_ver > your version $local_ver.", symbols);
|
||||
req_list.push_back(r);
|
||||
return r.outcome;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SATISFIED;
|
||||
}
|
||||
|
||||
// Check the era of the game, whether it is available, and compatible with installed content, and add info strings to this game_item.
|
||||
void gamebrowser::populate_game_item_era_info(gamebrowser::game_item & item, const config & game, const config & game_config, bool & verified) {
|
||||
if (!game["mp_era"].empty())
|
||||
{
|
||||
const config &era_cfg = game_config.find_child("era", "id", game["mp_era"]);
|
||||
utils::string_map symbols;
|
||||
symbols["era_id"] = game["mp_era"];
|
||||
if (era_cfg) {
|
||||
item.era_and_mod_info = _("Era:");
|
||||
item.era_and_mod_info += " ";
|
||||
item.era_and_mod_info += era_cfg["name"].str();
|
||||
|
||||
mp::ADDON_REQ result = check_addon_version_compatibility(era_cfg, game, item.addons);
|
||||
item.addons_outcome = std::max(item.addons_outcome, result); //elevate to most severe error level encountered so far
|
||||
} else {
|
||||
if (!game["require_era"].to_bool(true)) {
|
||||
item.have_era = true;
|
||||
} else {
|
||||
item.have_era = false;
|
||||
}
|
||||
item.era_and_mod_info = vgettext("Unknown era: $era_id", symbols);
|
||||
verified = false;
|
||||
}
|
||||
} else {
|
||||
item.era_and_mod_info = _("Unknown era");
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the mods applied to the game, whether they are available / required, and compatible with installed content, and add info strings to this game_item.
|
||||
void gamebrowser::populate_game_item_mod_info(gamebrowser::game_item & item, const config & game, const config & game_config, bool & verified) {
|
||||
(void) verified;
|
||||
if (!game.child_or_empty("modification").empty()) {
|
||||
item.have_all_mods = true;
|
||||
item.era_and_mod_info += " — ";
|
||||
item.era_and_mod_info += _("Modifications:");
|
||||
item.era_and_mod_info += " ";
|
||||
|
||||
BOOST_FOREACH (const config& m, game.child_range("modification")) {
|
||||
const config& mod_cfg = game_config.find_child("modification", "id", m["id"]);
|
||||
if (mod_cfg) {
|
||||
item.era_and_mod_info += mod_cfg["name"].str();
|
||||
item.era_and_mod_info += ", ";
|
||||
|
||||
mp::ADDON_REQ result = check_addon_version_compatibility(mod_cfg, game, item.addons);
|
||||
item.addons_outcome = std::max(item.addons_outcome, result); //elevate to most severe error level encountered so far
|
||||
} else {
|
||||
item.era_and_mod_info += m["id"].str();
|
||||
if (m["require_modification"].to_bool(false)) {
|
||||
item.have_all_mods = false;
|
||||
item.era_and_mod_info += _(" (missing)");
|
||||
}
|
||||
item.era_and_mod_info += ", ";
|
||||
}
|
||||
}
|
||||
item.era_and_mod_info.erase(item.era_and_mod_info.size()-2, 2);
|
||||
} else {
|
||||
item.have_all_mods = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Do this before populating eras and mods, to get reports of missing add-ons in the most sensible order.
|
||||
void gamebrowser::populate_game_item_addons_installed(gamebrowser::game_item & item, const config & game, const std::vector<std::string> & installed_addons)
|
||||
{
|
||||
BOOST_FOREACH(const config & addon, game.child_range("addon")) {
|
||||
if (addon.has_attribute("id")) {
|
||||
if (std::find(installed_addons.begin(), installed_addons.end(), addon["id"].str()) == installed_addons.end()) {
|
||||
required_addon r;
|
||||
r.addon_id = addon["id"].str();
|
||||
r.outcome = NEED_DOWNLOAD;
|
||||
|
||||
utils::string_map symbols;
|
||||
symbols["id"] = addon["id"].str();
|
||||
r.message = vgettext("Missing addon: $id", symbols);
|
||||
item.addons.push_back(r);
|
||||
if (item.addons_outcome == SATISFIED) {
|
||||
item.addons_outcome = NEED_DOWNLOAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build an appropriate game_item corresponding to this game (config) which we retrieved from [gamelist] from the server.
|
||||
void gamebrowser::populate_game_item(gamebrowser::game_item & item, const config & game, const config & game_config, const std::vector<std::string> & installed_addons)
|
||||
{
|
||||
bool verified = true;
|
||||
item.password_required = game["password"].to_bool();
|
||||
item.reloaded = game["savegame"].to_bool();
|
||||
item.have_era = true;
|
||||
item.have_scenario = true;
|
||||
|
||||
populate_game_item_addons_installed(item, game, installed_addons);
|
||||
populate_game_item_campaign_or_scenario_info(item, game, game_config, verified);
|
||||
populate_game_item_map_info(item, game, game_config, verified);
|
||||
populate_game_item_era_info(item, game, game_config, verified);
|
||||
populate_game_item_mod_info(item, game, game_config, verified);
|
||||
|
||||
if (item.reloaded) {
|
||||
item.map_info += " — ";
|
||||
item.map_info += _("Reloaded game");
|
||||
verified = false;
|
||||
}
|
||||
item.id = game["id"].str();
|
||||
item.name = game["name"].str();
|
||||
std::string turn = game["turn"];
|
||||
std::string slots = game["slots"];
|
||||
item.vacant_slots = lexical_cast_default<size_t>(slots, 0);
|
||||
item.current_turn = 0;
|
||||
if (!turn.empty()) {
|
||||
item.started = true;
|
||||
int index = turn.find_first_of('/');
|
||||
if (index > -1){
|
||||
const std::string current_turn = turn.substr(0, index);
|
||||
item.current_turn = lexical_cast<unsigned int>(current_turn);
|
||||
}
|
||||
item.status = _("Turn ") + turn;
|
||||
} else {
|
||||
item.started = false;
|
||||
if (item.vacant_slots > 0) {
|
||||
item.status = std::string(_n("Vacant Slot:", "Vacant Slots:",
|
||||
item.vacant_slots)) + " " + slots;
|
||||
if (item.password_required) {
|
||||
item.status += std::string(" (") + std::string(_("Password Required")) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.use_map_settings = game["mp_use_map_settings"].to_bool();
|
||||
item.gold = game["mp_village_gold"].str();
|
||||
if (game["mp_fog"].to_bool()) {
|
||||
item.vision = _("Fog");
|
||||
item.fog = true;
|
||||
if (game["mp_shroud"].to_bool()) {
|
||||
item.vision += "/";
|
||||
item.vision += _("Shroud");
|
||||
item.shroud = true;
|
||||
} else {
|
||||
item.shroud = false;
|
||||
}
|
||||
} else if (game["mp_shroud"].to_bool()) {
|
||||
item.vision = _("Shroud");
|
||||
item.fog = false;
|
||||
item.shroud = true;
|
||||
} else {
|
||||
item.vision = _("none");
|
||||
item.fog = false;
|
||||
item.shroud = false;
|
||||
}
|
||||
if (game["mp_countdown"].to_bool()) {
|
||||
item.time_limit = game["mp_countdown_init_time"].str() + " / +"
|
||||
+ game["mp_countdown_turn_bonus"].str() + " "
|
||||
+ game["mp_countdown_action_bonus"].str();
|
||||
} else {
|
||||
item.time_limit = "";
|
||||
}
|
||||
item.xp = game["experience_modifier"].str() + "%";
|
||||
item.observers = game["observer"].to_bool(true);
|
||||
item.shuffle_sides = game["shuffle_sides"].to_bool(true);
|
||||
item.verified = verified;
|
||||
}
|
||||
|
||||
void gamebrowser::set_game_items(const config& cfg, const config& game_config, const std::vector<std::string> & installed_addons)
|
||||
{
|
||||
//DBG_MP << "** gamelist **\n" << cfg.debug() << "****\n";
|
||||
|
||||
const bool scrolled_to_max = (has_scrollbar() && get_position() == get_max_position());
|
||||
const bool selection_visible = (selected_ >= visible_range_.first && selected_ <= visible_range_.second);
|
||||
const std::string selected_game = (selected_ < games_.size()) ? games_[selected_].id : "";
|
||||
|
||||
item_height_ = 100;
|
||||
|
||||
games_.clear();
|
||||
|
||||
BOOST_FOREACH(const config &game, cfg.child("gamelist").child_range("game"))
|
||||
{
|
||||
bool verified = true;
|
||||
games_.push_back(game_item());
|
||||
games_.back().password_required = game["password"].to_bool();
|
||||
games_.back().reloaded = game["savegame"].to_bool();
|
||||
games_.back().have_era = true;
|
||||
games_.back().have_scenario = true;
|
||||
if (game["mp_campaign"].empty()) {
|
||||
if (!game["mp_scenario"].empty()) {
|
||||
// Check if it's a multiplayer scenario.
|
||||
const config* level_cfg = &game_config.find_child("multiplayer",
|
||||
"id", game["mp_scenario"]);
|
||||
if (!*level_cfg) {
|
||||
// Check if it's a user map.
|
||||
level_cfg = &game_config.find_child("generic_multiplayer",
|
||||
"id", game["mp_scenario"]);
|
||||
}
|
||||
if (*level_cfg) {
|
||||
games_.back().map_info = _("Scenario:");
|
||||
games_.back().map_info += " ";
|
||||
games_.back().map_info += (*level_cfg)["name"].str();
|
||||
// Reloaded games do not match the original scenario hash,
|
||||
// so it makes no sense to test them,
|
||||
// they always would appear as remote scenarios.
|
||||
if (map_hashes_ && !games_.back().reloaded) {
|
||||
std::string hash = game["hash"];
|
||||
bool hash_found = false;
|
||||
BOOST_FOREACH(const config::attribute& i,
|
||||
map_hashes_.attribute_range()) {
|
||||
|
||||
if (i.first == game["mp_scenario"] &&
|
||||
i.second == hash) {
|
||||
|
||||
hash_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hash_found) {
|
||||
games_.back().map_info += " — ";
|
||||
games_.back().map_info += _("Remote scenario");
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
utils::string_map symbols;
|
||||
symbols["scenario_id"] = game["mp_scenario"];
|
||||
games_.back().map_info =
|
||||
vgettext("Unknown scenario: $scenario_id", symbols);
|
||||
verified = false;
|
||||
}
|
||||
} else {
|
||||
games_.back().map_info = _("Unknown scenario");
|
||||
verified = false;
|
||||
}
|
||||
} else { // Is a campaign
|
||||
const config* level_cfg = &game_config.find_child("campaign", "id",
|
||||
game["mp_campaign"]);
|
||||
if (*level_cfg) {
|
||||
games_.back().map_info = _("Campaign:");
|
||||
games_.back().map_info += " ";
|
||||
games_.back().map_info += (*level_cfg)["name"].str();
|
||||
games_.back().map_info += " — ";
|
||||
games_.back().map_info += game["mp_scenario_name"].str();
|
||||
|
||||
// Difficulty.
|
||||
const std::vector<std::string> difficulties =
|
||||
utils::split((*level_cfg)["difficulties"]);
|
||||
const std::string difficulty_descriptions =
|
||||
(*level_cfg)["difficulty_descriptions"];
|
||||
std::vector<std::string> difficulty_options =
|
||||
utils::split(difficulty_descriptions, ';');
|
||||
int index = 0;
|
||||
//TODO: use difficulties instead of difficulty_descriptions if
|
||||
//difficulty_descriptions is not available
|
||||
assert(difficulties.size() == difficulty_options.size());
|
||||
BOOST_FOREACH(const std::string& difficulty, difficulties) {
|
||||
if (difficulty == game["difficulty_define"]) {
|
||||
gui2::tlegacy_menu_item menu_item(difficulty_options[index]);
|
||||
games_.back().map_info += " — ";
|
||||
games_.back().map_info += menu_item.label();
|
||||
games_.back().map_info += " ";
|
||||
games_.back().map_info += menu_item.description();
|
||||
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
} else {
|
||||
utils::string_map symbols;
|
||||
symbols["campaign_id"] = game["mp_campaign"];
|
||||
games_.back().map_info =
|
||||
vgettext("Unknown campaign: $campaign_id", symbols);
|
||||
verified = false;
|
||||
|
||||
if (game["require_scenario"].to_bool(false)) {
|
||||
games_.back().have_scenario = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
games_.back().map_data = game["map_data"].str();
|
||||
if(games_.back().map_data.empty()) {
|
||||
games_.back().map_data = filesystem::read_map(game["map"]);
|
||||
}
|
||||
if(! games_.back().map_data.empty()) {
|
||||
try {
|
||||
std::vector<minimap_cache_item>::iterator i;
|
||||
bool found = false;
|
||||
for(i = minimap_cache.begin(); i != minimap_cache.end() && !found; ++i) {
|
||||
if (i->map_data == games_.back().map_data) {
|
||||
found = true;
|
||||
games_.back().map_info_size = i->map_info_size;
|
||||
games_.back().mini_map = i->mini_map;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// Parsing the map and generating the minimap are both cpu expensive
|
||||
gamemap map(boost::make_shared<terrain_type_data>(game_config), games_.back().map_data);
|
||||
games_.back().mini_map = image::getMinimap(minimap_size_, minimap_size_, map, 0);
|
||||
games_.back().map_info_size = str_cast(map.w()) + utils::unicode_multiplication_sign
|
||||
+ str_cast(map.h());
|
||||
}
|
||||
} catch (incorrect_map_format_error &e) {
|
||||
ERR_CF << "illegal map: " << e.message << '\n';
|
||||
verified = false;
|
||||
} catch(twml_exception& e) {
|
||||
ERR_CF << "map could not be loaded: " << e.dev_message << '\n';
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!game["mp_era"].empty())
|
||||
{
|
||||
const config &era_cfg = game_config.find_child("era", "id", game["mp_era"]);
|
||||
utils::string_map symbols;
|
||||
symbols["era_id"] = game["mp_era"];
|
||||
if (era_cfg) {
|
||||
games_.back().era_and_mod_info = _("Era:");
|
||||
games_.back().era_and_mod_info += " ";
|
||||
games_.back().era_and_mod_info += era_cfg["name"].str();
|
||||
} else {
|
||||
if (!game["require_era"].to_bool(true)) {
|
||||
games_.back().have_era = true;
|
||||
} else {
|
||||
games_.back().have_era = false;
|
||||
}
|
||||
games_.back().era_and_mod_info = vgettext("Unknown era: $era_id", symbols);
|
||||
verified = false;
|
||||
}
|
||||
} else {
|
||||
games_.back().era_and_mod_info = _("Unknown era");
|
||||
verified = false;
|
||||
}
|
||||
|
||||
if (!game.child_or_empty("modification").empty()) {
|
||||
games_.back().have_all_mods = true;
|
||||
games_.back().era_and_mod_info += " — ";
|
||||
games_.back().era_and_mod_info += _("Modifications:");
|
||||
games_.back().era_and_mod_info += " ";
|
||||
|
||||
BOOST_FOREACH (const config& m, game.child_range("modification")) {
|
||||
const config& mod_cfg = game_config.find_child("modification", "id", m["id"]);
|
||||
if (mod_cfg) {
|
||||
games_.back().era_and_mod_info += mod_cfg["name"].str();
|
||||
games_.back().era_and_mod_info += ", ";
|
||||
} else {
|
||||
games_.back().era_and_mod_info += m["id"].str();
|
||||
if (m["require_modification"].to_bool(false)) {
|
||||
games_.back().have_all_mods = false;
|
||||
games_.back().era_and_mod_info += _(" (missing)");
|
||||
}
|
||||
games_.back().era_and_mod_info += ", ";
|
||||
}
|
||||
}
|
||||
games_.back().era_and_mod_info.erase(games_.back().era_and_mod_info.size()-2, 2);
|
||||
} else {
|
||||
games_.back().have_all_mods = true;
|
||||
}
|
||||
|
||||
|
||||
if (games_.back().reloaded) {
|
||||
games_.back().map_info += " — ";
|
||||
games_.back().map_info += _("Reloaded game");
|
||||
verified = false;
|
||||
}
|
||||
games_.back().id = game["id"].str();
|
||||
games_.back().name = game["name"].str();
|
||||
std::string turn = game["turn"];
|
||||
std::string slots = game["slots"];
|
||||
games_.back().vacant_slots = lexical_cast_default<size_t>(slots, 0);
|
||||
games_.back().current_turn = 0;
|
||||
if (!turn.empty()) {
|
||||
games_.back().started = true;
|
||||
int index = turn.find_first_of('/');
|
||||
if (index > -1){
|
||||
const std::string current_turn = turn.substr(0, index);
|
||||
games_.back().current_turn = lexical_cast<unsigned int>(current_turn);
|
||||
}
|
||||
games_.back().status = _("Turn ") + turn;
|
||||
} else {
|
||||
games_.back().started = false;
|
||||
if (games_.back().vacant_slots > 0) {
|
||||
games_.back().status = std::string(_n("Vacant Slot:", "Vacant Slots:",
|
||||
games_.back().vacant_slots)) + " " + slots;
|
||||
if (games_.back().password_required) {
|
||||
games_.back().status += std::string(" (") + std::string(_("Password Required")) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
games_.back().use_map_settings = game["mp_use_map_settings"].to_bool();
|
||||
games_.back().gold = game["mp_village_gold"].str();
|
||||
if (game["mp_fog"].to_bool()) {
|
||||
games_.back().vision = _("Fog");
|
||||
games_.back().fog = true;
|
||||
if (game["mp_shroud"].to_bool()) {
|
||||
games_.back().vision += "/";
|
||||
games_.back().vision += _("Shroud");
|
||||
games_.back().shroud = true;
|
||||
} else {
|
||||
games_.back().shroud = false;
|
||||
}
|
||||
} else if (game["mp_shroud"].to_bool()) {
|
||||
games_.back().vision = _("Shroud");
|
||||
games_.back().fog = false;
|
||||
games_.back().shroud = true;
|
||||
} else {
|
||||
games_.back().vision = _("none");
|
||||
games_.back().fog = false;
|
||||
games_.back().shroud = false;
|
||||
}
|
||||
if (game["mp_countdown"].to_bool()) {
|
||||
games_.back().time_limit = game["mp_countdown_init_time"].str() + " / +"
|
||||
+ game["mp_countdown_turn_bonus"].str() + " "
|
||||
+ game["mp_countdown_action_bonus"].str();
|
||||
} else {
|
||||
games_.back().time_limit = "";
|
||||
}
|
||||
games_.back().xp = game["experience_modifier"].str() + "%";
|
||||
games_.back().observers = game["observer"].to_bool(true);
|
||||
games_.back().shuffle_sides = game["shuffle_sides"].to_bool(true);
|
||||
games_.back().verified = verified;
|
||||
|
||||
populate_game_item(games_.back(), game, game_config, installed_addons);
|
||||
// Hack...
|
||||
if(preferences::fi_invert() ? game_matches_filter(games_.back(), cfg) : !game_matches_filter(games_.back(), cfg)) games_.pop_back();
|
||||
}
|
||||
|
@ -857,7 +990,7 @@ bool lobby::lobby_sorter::less(int column, const gui::menu::item& row1, const gu
|
|||
return basic_sorter::less(column,row1,row2);
|
||||
}
|
||||
|
||||
lobby::lobby(game_display& disp, const config& cfg, chat& c, config& gamelist) :
|
||||
lobby::lobby(game_display& disp, const config& cfg, chat& c, config& gamelist, const std::vector<std::string> & installed_addons) :
|
||||
mp::ui(disp, _("Game Lobby"), cfg, c, gamelist),
|
||||
|
||||
game_vacant_slots_(),
|
||||
|
@ -878,7 +1011,8 @@ lobby::lobby(game_display& disp, const config& cfg, chat& c, config& gamelist) :
|
|||
last_selected_game_(-1), sorter_(gamelist),
|
||||
games_menu_(disp.video(),cfg.child("multiplayer_hashes")),
|
||||
minimaps_(),
|
||||
search_string_(preferences::fi_text())
|
||||
search_string_(preferences::fi_text()),
|
||||
installed_addons_(installed_addons)
|
||||
{
|
||||
std::vector<std::string> replay_options_strings_;
|
||||
replay_options_strings_.push_back(_("Normal Replays"));
|
||||
|
@ -1004,7 +1138,7 @@ void lobby::gamelist_updated(bool silent)
|
|||
// No gamelist yet. Do not update anything.
|
||||
return;
|
||||
}
|
||||
games_menu_.set_game_items(gamelist(), game_config());
|
||||
games_menu_.set_game_items(gamelist(), game_config(), installed_addons_);
|
||||
join_game_.enable(games_menu_.selection_is_joinable());
|
||||
observe_game_.enable(games_menu_.selection_is_observable());
|
||||
}
|
||||
|
@ -1020,14 +1154,72 @@ void lobby::process_event()
|
|||
process_event_impl(data);
|
||||
}
|
||||
|
||||
static void handle_addon_requirements_gui(display & disp, const std::vector<required_addon> & reqs, mp::ADDON_REQ addon_outcome)
|
||||
{
|
||||
if (addon_outcome == CANNOT_SATISFY) {
|
||||
std::string e_title = _("Incompatible user-made content.");
|
||||
std::string err_msg = _("This game cannot be joined because the host has out-of-date add-ons which are incompatible with your version. You might suggest to them that they update their add-ons.");
|
||||
|
||||
err_msg +="\n\n";
|
||||
err_msg += _("Details:");
|
||||
err_msg += "\n";
|
||||
|
||||
BOOST_FOREACH(const required_addon & a, reqs) {
|
||||
if (a.outcome == CANNOT_SATISFY) {
|
||||
err_msg += a.message;
|
||||
err_msg += "\n";
|
||||
}
|
||||
}
|
||||
gui2::show_message(disp.video(), e_title, err_msg, gui2::tmessage::auto_close);
|
||||
} else if (addon_outcome == NEED_DOWNLOAD) {
|
||||
std::string e_title = _("Missing user-made content.");
|
||||
std::string err_msg = _("This game requires one or more user-made addons to be installed or updated in order to join. Do you want to try to install them?");
|
||||
|
||||
err_msg +="\n\n";
|
||||
err_msg += _("Details:");
|
||||
err_msg += "\n";
|
||||
|
||||
std::vector<std::string> needs_download;
|
||||
BOOST_FOREACH(const required_addon & a, reqs) {
|
||||
if (a.outcome == NEED_DOWNLOAD) {
|
||||
err_msg += a.message;
|
||||
err_msg += "\n";
|
||||
|
||||
needs_download.push_back(a.addon_id);
|
||||
} else if (a.outcome == CANNOT_SATISFY) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
assert(needs_download.size() > 0);
|
||||
|
||||
if (gui2::show_message(disp.video(), e_title, err_msg, gui2::tmessage::yes_no_buttons) == gui2::twindow::OK) {
|
||||
ad_hoc_addon_fetch_session(disp, needs_download);
|
||||
throw lobby_reload_request_exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The return value should be true if the gui result was not chnaged
|
||||
void lobby::process_event_impl(const process_event_data & data)
|
||||
{
|
||||
join_game_.enable(games_menu_.selection_is_joinable());
|
||||
observe_game_.enable(games_menu_.selection_is_observable());
|
||||
|
||||
// check whehter to try to download addons
|
||||
if ((games_menu_.selected() || data.observe || data.join) && games_menu_.selection_needs_addons())
|
||||
{
|
||||
if (const std::vector<required_addon> * reqs = games_menu_.selection_addon_requirements()) {
|
||||
handle_addon_requirements_gui(disp(), *reqs, games_menu_.selection_addon_outcome());
|
||||
} else {
|
||||
gui2::show_error_message(video(), _("Something is wrong with the addon version check database supporting the multiplayer lobby, please report this at bugs.wesnoth.org."));
|
||||
}
|
||||
games_menu_.reset_selection();
|
||||
return ;
|
||||
}
|
||||
|
||||
const bool observe = (data.observe || (games_menu_.selected() && !games_menu_.selection_is_joinable())) && games_menu_.selection_is_observable();
|
||||
const bool join = (data.join || games_menu_.selected()) && games_menu_.selection_is_joinable();
|
||||
|
||||
games_menu_.reset_selection();
|
||||
preferences::set_skip_mp_replay(replay_options_.selected() == 1);
|
||||
preferences::set_blindfold_replay(replay_options_.selected() == 2);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "multiplayer_ui.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
#include "image.hpp"
|
||||
#include "serialization/string_utils.hpp"
|
||||
|
||||
class config;
|
||||
class video;
|
||||
|
@ -32,6 +33,15 @@ class game_display;
|
|||
* games.
|
||||
*/
|
||||
namespace mp {
|
||||
|
||||
enum ADDON_REQ { SATISFIED, NEED_DOWNLOAD, CANNOT_SATISFY };
|
||||
|
||||
struct required_addon {
|
||||
std::string addon_id;
|
||||
ADDON_REQ outcome;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
class gamebrowser : public gui::menu {
|
||||
public:
|
||||
struct game_item {
|
||||
|
@ -62,7 +72,9 @@ public:
|
|||
password_required(false),
|
||||
have_scenario(false),
|
||||
have_era(false),
|
||||
have_all_mods(false)
|
||||
have_all_mods(false),
|
||||
addons(),
|
||||
addons_outcome(SATISFIED)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -92,31 +104,48 @@ public:
|
|||
bool have_scenario;
|
||||
bool have_era;
|
||||
bool have_all_mods;
|
||||
std::vector<required_addon> addons;
|
||||
ADDON_REQ addons_outcome;
|
||||
};
|
||||
gamebrowser(CVideo& video, const config &map_hashes);
|
||||
void scroll(unsigned int pos);
|
||||
void handle_event(const SDL_Event& event);
|
||||
void set_inner_location(const SDL_Rect& rect);
|
||||
void set_item_height(unsigned int height);
|
||||
void set_game_items(const config& cfg, const config& game_config);
|
||||
|
||||
void populate_game_item(game_item &, const config &, const config &, const std::vector<std::string> &);
|
||||
void populate_game_item_campaign_or_scenario_info(game_item &, const config &, const config &, bool &);
|
||||
void populate_game_item_map_info(game_item &, const config &, const config &, bool &);
|
||||
void populate_game_item_era_info(game_item &, const config &, const config &, bool &);
|
||||
void populate_game_item_mod_info(game_item &, const config &, const config &, bool &);
|
||||
void populate_game_item_addons_installed(game_item &, const config &, const std::vector<std::string> &);
|
||||
|
||||
void set_game_items(const config& cfg, const config& game_config, const std::vector<std::string> & installed_addons);
|
||||
void draw();
|
||||
void draw_contents();
|
||||
void draw_row(const size_t row_index, const SDL_Rect& rect, ROW_TYPE type);
|
||||
SDL_Rect get_item_rect(size_t index) const;
|
||||
bool empty() const { return games_.empty(); }
|
||||
bool selection_is_joinable() const
|
||||
{ return empty() ? false : (games_[selected_].vacant_slots > 0 &&
|
||||
!games_[selected_].started &&
|
||||
games_[selected_].have_scenario &&
|
||||
games_[selected_].have_era &&
|
||||
games_[selected_].have_all_mods); }
|
||||
// Moderators may observe any game.
|
||||
{ return selection_in_joinable_state() && (!selection_needs_content() || selection_needs_addons()); }
|
||||
bool selection_is_observable() const
|
||||
{ return empty() ? false : (games_[selected_].observers &&
|
||||
games_[selected_].have_scenario &&
|
||||
games_[selected_].have_era &&
|
||||
games_[selected_].have_all_mods) ||
|
||||
preferences::is_authenticated(); }
|
||||
{ return selection_in_observable_state() && (!selection_needs_content() || selection_needs_addons()); }
|
||||
bool selection_needs_content() const
|
||||
{ return empty() ? false : !games_[selected_].have_era ||
|
||||
!games_[selected_].have_all_mods ||
|
||||
!games_[selected_].have_scenario; }
|
||||
bool selection_needs_addons() const
|
||||
{ return empty() ? false : (games_[selected_].addons_outcome != SATISFIED); }
|
||||
bool selection_in_joinable_state() const
|
||||
{ return empty() ? false : (games_[selected_].vacant_slots > 0 && !games_[selected_].started); }
|
||||
// Moderators may observe any game.
|
||||
bool selection_in_observable_state() const
|
||||
{ return empty() ? false : (games_[selected_].observers || preferences::is_authenticated()); }
|
||||
|
||||
ADDON_REQ selection_addon_outcome() const
|
||||
{ return empty() ? SATISFIED : games_[selected_].addons_outcome; }
|
||||
const std::vector<required_addon> * selection_addon_requirements() const
|
||||
{ return empty() ? NULL : &games_[selected_].addons; }
|
||||
bool selected() const { return double_clicked_ && !empty(); }
|
||||
void reset_selection() { double_clicked_ = false; }
|
||||
int selection() const { return selected_; }
|
||||
|
@ -156,7 +185,7 @@ private:
|
|||
class lobby : public ui
|
||||
{
|
||||
public:
|
||||
lobby(game_display& d, const config& cfg, chat& c, config& gamelist);
|
||||
lobby(game_display& d, const config& cfg, chat& c, config& gamelist, const std::vector<std::string> & installed_addons);
|
||||
|
||||
virtual void process_event();
|
||||
|
||||
|
@ -206,6 +235,8 @@ private:
|
|||
|
||||
std::string search_string_;
|
||||
|
||||
const std::vector<std::string> & installed_addons_;
|
||||
|
||||
struct process_event_data {
|
||||
bool join;
|
||||
bool observe;
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include "mp_game_settings.hpp"
|
||||
#include "formula_string_utils.hpp"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
mp_game_settings::mp_game_settings() :
|
||||
savegame_config(),
|
||||
name(),
|
||||
|
@ -52,7 +54,8 @@ mp_game_settings::mp_game_settings() :
|
|||
shuffle_sides(false),
|
||||
saved_game(false),
|
||||
random_faction_mode(DEFAULT),
|
||||
options()
|
||||
options(),
|
||||
addons()
|
||||
{}
|
||||
|
||||
mp_game_settings::mp_game_settings(const config& cfg)
|
||||
|
@ -87,7 +90,11 @@ mp_game_settings::mp_game_settings(const config& cfg)
|
|||
, saved_game(cfg["savegame"].to_bool())
|
||||
, random_faction_mode(string_to_RANDOM_FACTION_MODE_default(cfg["random_faction_mode"].str(), DEFAULT))
|
||||
, options(cfg.child_or_empty("options"))
|
||||
, addons()
|
||||
{
|
||||
BOOST_FOREACH(const config & a, cfg.child_range("addon")) {
|
||||
addons.push_back(addon_version_info(a));
|
||||
}
|
||||
}
|
||||
|
||||
config mp_game_settings::to_config() const
|
||||
|
@ -124,5 +131,32 @@ config mp_game_settings::to_config() const
|
|||
cfg["savegame"] = saved_game;
|
||||
cfg.add_child("options", options);
|
||||
|
||||
BOOST_FOREACH(const addon_version_info & a, addons) {
|
||||
a.write(cfg.add_child("addon"));
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
mp_game_settings::addon_version_info::addon_version_info(const config & cfg)
|
||||
: id(cfg["id"])
|
||||
, version()
|
||||
, min_version()
|
||||
{
|
||||
if (cfg.has_attribute("version") && !cfg["version"].empty()) {
|
||||
version = cfg["version"].str();
|
||||
}
|
||||
if (cfg.has_attribute("min_version") && !cfg["min_version"].empty()) {
|
||||
min_version = cfg["min_version"].str();
|
||||
}
|
||||
}
|
||||
|
||||
void mp_game_settings::addon_version_info::write(config & cfg) const {
|
||||
cfg["id"] = id;
|
||||
if (version) {
|
||||
cfg["version"] = *version;
|
||||
}
|
||||
if (min_version) {
|
||||
cfg["min_version"] = *min_version;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
#include "gettext.hpp"
|
||||
#include "make_enum.hpp"
|
||||
#include "savegame_config.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
struct mp_game_settings : public savegame::savegame_config
|
||||
{
|
||||
|
@ -72,6 +75,17 @@ struct mp_game_settings : public savegame::savegame_config
|
|||
RANDOM_FACTION_MODE random_faction_mode;
|
||||
|
||||
config options;
|
||||
|
||||
struct addon_version_info {
|
||||
std::string id;
|
||||
boost::optional<version_info> version;
|
||||
boost::optional<version_info> min_version;
|
||||
|
||||
explicit addon_version_info(const config &);
|
||||
void write(config &) const;
|
||||
};
|
||||
|
||||
std::vector<addon_version_info> addons;
|
||||
};
|
||||
|
||||
MAKE_ENUM_STREAM_OPS2(mp_game_settings, RANDOM_FACTION_MODE)
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
#include "saved_game.hpp"
|
||||
#include "carryover.hpp"
|
||||
#include "config_assign.hpp"
|
||||
#include "cursor.hpp"
|
||||
#include "log.hpp"
|
||||
#include "game_config_manager.hpp"
|
||||
|
@ -221,6 +222,8 @@ private:
|
|||
std::string type_;
|
||||
};
|
||||
|
||||
// Gets the ids of the mp_era and modifications which were set to be active, then fetches these configs from the game_config and copies their [event] and [lua] to the starting_pos_.
|
||||
// At this time, also collect the addon_id attributes which appeared in them and put this list in the addon_ids attribute of the mp_settings.
|
||||
void saved_game::expand_mp_events()
|
||||
{
|
||||
expand_scenario();
|
||||
|
@ -239,10 +242,45 @@ void saved_game::expand_mp_events()
|
|||
if(const config& cfg = game_config_manager::get()->
|
||||
game_config().find_child(mod.type, "id", mod.id))
|
||||
{
|
||||
// Note the addon_id if this mod is required to play the game in mp
|
||||
std::string require_attr = "require_" + mod.type;
|
||||
if (cfg.has_attribute("addon_id") && !cfg["addon_id"].empty() && cfg[require_attr].to_bool(true)) {
|
||||
config addon_data = config_of("id",cfg["addon_id"])("version", cfg["addon_version"])("min_version", cfg["addon_min_version"]);
|
||||
mp_game_settings::addon_version_info new_data(addon_data);
|
||||
|
||||
bool found = false;
|
||||
BOOST_FOREACH(mp_game_settings::addon_version_info & addon, mp_settings_.addons) {
|
||||
if (addon.id == cfg["addon_id"].str()) {
|
||||
if (found) {
|
||||
ERR_NG << "An mp_settings addons list contains repeated entries, this indicates a bug which may affect add-on versioning. Please report at bugs.wesnoth.org. Relevant add-on: '" << cfg["addon_id"].str() <<"'\n";
|
||||
}
|
||||
found = true;
|
||||
if (new_data.version) {
|
||||
if (!addon.version || (*addon.version != *new_data.version)) {
|
||||
WRN_NG << "Addon version data mismatch -- not all local WML has same version of '" << cfg["addon_id"].str() << "' addon.\n";
|
||||
}
|
||||
}
|
||||
if (addon.version && !new_data.version) {
|
||||
WRN_NG << "Addon version data mismatch -- not all local WML has same version of '" << cfg["addon_id"].str() << "' addon.\n";
|
||||
}
|
||||
if (new_data.min_version) {
|
||||
if (!addon.min_version || (*new_data.min_version > *addon.min_version)) {
|
||||
addon.min_version = *new_data.min_version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
mp_settings_.addons.push_back(new_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy events
|
||||
BOOST_FOREACH(const config& modevent, cfg.child_range("event"))
|
||||
{
|
||||
this->starting_pos_.add_child("event", modevent);
|
||||
}
|
||||
// Copy lua
|
||||
BOOST_FOREACH(const config& modlua, cfg.child_range("lua"))
|
||||
{
|
||||
this->starting_pos_.add_child("lua", modlua);
|
||||
|
|
|
@ -455,6 +455,14 @@ BOOST_AUTO_TEST_CASE(test_gui2)
|
|||
std::remove(list.begin(), list.end(), "lua_interpreter")
|
||||
, list.end());
|
||||
|
||||
//Window 'addon_description' registered but not tested.
|
||||
//Window 'addon_filter_options' registered but not tested.
|
||||
//Window 'addon_uninstall_list' registered but not tested.
|
||||
//Window 'network_transmission' registered but not tested.
|
||||
list.erase(std::remove(list.begin(), list.end(), "addon_description"), list.end());
|
||||
list.erase(std::remove(list.begin(), list.end(), "addon_filter_options"), list.end());
|
||||
list.erase(std::remove(list.begin(), list.end(), "addon_uninstall_list"), list.end());
|
||||
list.erase(std::remove(list.begin(), list.end(), "network_transmission"), list.end());
|
||||
|
||||
// Test size() instead of empty() to get the number of offenders
|
||||
BOOST_CHECK_EQUAL(list.size(), 0);
|
||||
|
|
Loading…
Add table
Reference in a new issue