MP Lobby: port 'missing addon requirements' interface from GUI1 lobby

Still could use some polish, perhaps.
This commit is contained in:
Charles Dang 2016-08-31 10:28:09 +11:00
parent b24fdbcbe1
commit 432fac588e
7 changed files with 177 additions and 9 deletions

View file

@ -652,7 +652,7 @@ static void enter_lobby_mode(CVideo& video, const config& game_config,
sound::empty_playlist();
sound::stop_music();
}
lobby_info li(game_config, *wesnothd_connection);
lobby_info li(game_config, installed_addons, *wesnothd_connection);
// Force a black background
const Uint32 color = SDL_MapRGBA(video.getSurface()->format

View file

@ -24,6 +24,7 @@
#include "map/exception.hpp"
#include "terrain/type_data.hpp"
#include "wml_exception.hpp"
#include "version.hpp"
#include <iterator>
@ -159,7 +160,66 @@ std::string make_short_name(const std::string& long_name)
} // end anonymous namespace
game_info::game_info(const config& game, const config& game_config)
// 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 game_info::ADDON_REQ check_addon_version_compatibility(const config& local_item, const config& game, std::vector<game_info::required_addon>& req_list)
{
if(!local_item.has_attribute("addon_id") || !local_item.has_attribute("addon_version")) {
return game_info::SATISFIED;
}
if(const config& game_req = game.find_child("addon", "id", local_item["addon_id"])) {
// Record object which we will potentially store for this check
game_info::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 = game_info::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 = game_info::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 game_info::SATISFIED;
}
game_info::game_info(const config& game, const config& game_config, const std::vector<std::string>& installed_addons)
: mini_map()
, id(game["id"])
, map_data(game["map_data"])
@ -194,6 +254,24 @@ game_info::game_info(const config& game, const config& game_config)
, has_ignored(false)
, display_status(NEW)
{
for(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);
addons.push_back(r);
if(addons_outcome == SATISFIED) {
addons_outcome = NEED_DOWNLOAD;
}
}
}
}
std::string turn = game["turn"];
if(!game["mp_era"].empty()) {
const config& era_cfg = game_config.find_child("era", "id", game["mp_era"]);
@ -205,6 +283,9 @@ game_info::game_info(const config& game, const config& game_config)
if(era_short.empty()) {
era_short = make_short_name(era);
}
ADDON_REQ result = check_addon_version_compatibility(era_cfg, game, addons);
addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
} else {
have_era = !game["require_era"].to_bool(true);
era = vgettext("Unknown era: $era_id", symbols);
@ -226,6 +307,8 @@ game_info::game_info(const config& game, const config& game_config)
have_all_mods = false;
break;
}
ADDON_REQ result = check_addon_version_compatibility(mod, game, addons);
addons_outcome = std::max(addons_outcome, result); //elevate to most severe error level encountered so far
}
}
}
@ -285,6 +368,11 @@ game_info::game_info(const config& game, const config& game_config)
}
}
}
if((*level_cfg)["require_scenario"].to_bool(false)) {
ADDON_REQ result = check_addon_version_compatibility((*level_cfg), game, addons);
addons_outcome = std::max(addons_outcome, result); //elevate to most severe error level encountered so far
}
} else {
utils::string_map symbols;
symbols["scenario_id"] = game["mp_scenario"];

View file

@ -135,7 +135,7 @@ struct user_info
*/
struct game_info
{
game_info(const config& c, const config& game_config);
game_info(const config& c, const config& game_config, const std::vector<std::string>& installed_addons);
bool can_join() const;
bool can_observe() const;
@ -184,6 +184,17 @@ struct game_info
};
game_display_status display_status;
enum ADDON_REQ { SATISFIED, NEED_DOWNLOAD, CANNOT_SATISFY };
struct required_addon {
std::string addon_id;
ADDON_REQ outcome;
std::string message;
};
std::vector<required_addon> addons;
ADDON_REQ addons_outcome;
const char* display_status_string() const;
bool match_string_filter(const std::string& filter) const;

View file

@ -35,8 +35,9 @@ static lg::log_domain log_lobby("lobby");
#define SCOPE_LB log_scope2(log_lobby, __func__)
lobby_info::lobby_info(const config& game_config, twesnothd_connection& wesnothd_connection)
lobby_info::lobby_info(const config& game_config, const std::vector<std::string>& installed_addons, twesnothd_connection& wesnothd_connection)
: game_config_(game_config)
, installed_addons_(installed_addons)
, gamelist_()
, gamelist_initialized_(false)
, rooms_()
@ -102,7 +103,7 @@ void lobby_info::process_gamelist(const config& data)
games_by_id_.clear();
for(const auto & c : gamelist_.child("gamelist").child_range("game")) {
game_info* game = new game_info(c, game_config_);
game_info* game = new game_info(c, game_config_, installed_addons_);
games_by_id_[game->id] = game;
}
@ -146,12 +147,12 @@ bool lobby_info::process_gamelist_diff(const config& data)
const std::string& diff_result = c[config::diff_track_attribute];
if(diff_result == "new" || diff_result == "modified") {
if(current_i == games_by_id_.end()) {
games_by_id_.insert({game_id, new game_info(c, game_config_)});
games_by_id_.insert({game_id, new game_info(c, game_config_, installed_addons_)});
continue;
}
// Had a game with that id, so update it and mark it as such
*(current_i->second) = game_info(c, game_config_);
*(current_i->second) = game_info(c, game_config_, installed_addons_);
current_i->second->display_status = game_info::UPDATED;
} else if(diff_result == "deleted") {
if(current_i == games_by_id_.end()) {

View file

@ -25,7 +25,7 @@ class twesnothd_connection;
class lobby_info
{
public:
explicit lobby_info(const config& game_config, twesnothd_connection&);
explicit lobby_info(const config& game_config, const std::vector<std::string>& installed_addons, twesnothd_connection&);
~lobby_info();
@ -101,6 +101,7 @@ private:
void process_userlist();
const config& game_config_;
const std::vector<std::string>& installed_addons_;
config gamelist_;
bool gamelist_initialized_;
std::vector<room_info> rooms_;

View file

@ -17,6 +17,7 @@
#include "gui/auxiliary/field.hpp"
#include "gui/dialogs/lobby/player_info.hpp"
#include "gui/dialogs/message.hpp"
#include "gui/dialogs/multiplayer/mp_join_game_password_prompt.hpp"
#include "gui/dialogs/helper.hpp"
@ -40,9 +41,11 @@
#include "gui/widgets/toggle_panel.hpp"
#include "gui/widgets/tree_view_node.hpp"
#include "addon/manager_ui.hpp"
#include "formatter.hpp"
#include "formula/string_utils.hpp"
#include "game_preferences.hpp"
#include "game_initialization/lobby_reload_request_exception.hpp"
#include "gettext.hpp"
#include "lobby_preferences.hpp"
#include "log.hpp"
@ -1505,6 +1508,56 @@ void tlobby_main::join_or_observe(int idx)
}
}
static bool handle_addon_requirements_gui(CVideo& v, const std::vector<game_info::required_addon>& reqs, game_info::ADDON_REQ addon_outcome)
{
if(addon_outcome == game_info::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 they update their add-ons.");
err_msg +="\n\n";
err_msg += _("Details:");
err_msg += "\n";
for(const game_info::required_addon & a : reqs) {
if (a.outcome == game_info::CANNOT_SATISFY) {
err_msg += "" + a.message + "\n";
}
}
gui2::show_message(v, e_title, err_msg, gui2::tmessage::auto_close);
return false;
} else if(addon_outcome == game_info::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.\nDo you want to try to install them?");
err_msg +="\n\n";
err_msg += _("Details:");
err_msg += "\n";
std::vector<std::string> needs_download;
for(const game_info::required_addon & a : reqs) {
if(a.outcome == game_info::NEED_DOWNLOAD) {
err_msg += "" + a.message + "\n";
needs_download.push_back(a.addon_id);
} else if(a.outcome == game_info::CANNOT_SATISFY) {
assert(false);
}
}
assert(needs_download.size() > 0);
if(gui2::show_message(v, e_title, err_msg, gui2::tmessage::yes_no_buttons) == gui2::twindow::OK) {
ad_hoc_addon_fetch_session(v, needs_download);
// Evil exception throwing. Boooo.
throw mp::lobby_reload_request_exception();
return true;
}
}
return false;
}
bool tlobby_main::do_game_join(int idx, bool observe)
{
if(idx < 0 || idx > static_cast<int>(lobby_info_.games().size())) {
@ -1527,6 +1580,19 @@ bool tlobby_main::do_game_join(int idx, bool observe)
return false;
}
}
// check whehter to try to download addons
if(game.addons_outcome != game_info::SATISFIED) {
if(game.addons.empty()) {
gui2::show_error_message(window_->video(), _("Something is wrong with the addon version check database supporting the multiplayer lobby. Please report this at http://bugs.wesnoth.org."));
return false;
}
if(!handle_addon_requirements_gui(window_->video(), game.addons, game.addons_outcome)) {
return false;
}
}
config response;
config& join = response.add_child("join");
join["id"] = std::to_string(game.id);

View file

@ -782,8 +782,9 @@ struct twrapper<gui2::tlobby_main>
config game_config;
twesnothd_connection wesnothd_connection;
twesnothd_connection_init wesnothd_connection_init;
std::vector<std::string> installed_addons;
lobby_info li;
twrapper() : wesnothd_connection("", ""), wesnothd_connection_init(wesnothd_connection), li(game_config, wesnothd_connection)
twrapper() : wesnothd_connection("", ""), wesnothd_connection_init(wesnothd_connection), li(game_config, installed_addons, wesnothd_connection)
{
}
gui2::tlobby_main* create()