MP Lobby: port 'missing addon requirements' interface from GUI1 lobby
Still could use some polish, perhaps.
This commit is contained in:
parent
b24fdbcbe1
commit
432fac588e
7 changed files with 177 additions and 9 deletions
|
@ -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
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue