53 #define DBG_MP LOG_STREAM(debug, log_mp)
54 #define LOG_MP LOG_STREAM(info, log_mp)
55 #define WRN_MP LOG_STREAM(warn, log_mp)
56 #define ERR_MP LOG_STREAM(err, log_mp)
63 class mp_manager* manager =
nullptr;
74 mp_manager(
const std::optional<std::string> host);
81 if(network_worker.joinable()) {
83 network_worker.join();
93 void run_lobby_loop();
97 bool post_scenario_wait(
bool observe);
101 struct session_metadata
103 session_metadata() =
default;
105 session_metadata(
const config& cfg)
106 : is_moderator(cfg[
"is_moderator"].to_bool(
false))
107 , profile_url_prefix(cfg[
"profile_url_prefix"].str())
112 bool is_moderator =
false;
115 std::string profile_url_prefix;
119 std::unique_ptr<wesnothd_connection> open_connection(std::string host);
122 bool enter_lobby_mode();
125 void enter_create_mode();
128 void enter_staging_mode();
131 void enter_wait_mode(
int game_id,
bool observe);
134 std::thread network_worker;
137 std::atomic_bool stop;
140 std::unique_ptr<wesnothd_connection> connection;
143 session_metadata session_info;
150 std::list<mp::network_registrar::handler> process_handlers;
153 const session_metadata& get_session_info()
const
158 auto add_network_handler(decltype(process_handlers)::value_type func)
160 return [
this, iter = process_handlers.insert(process_handlers.end(), func)]() { process_handlers.erase(iter); };
164 mp_manager::mp_manager(
const std::optional<std::string> host)
167 , connection(
nullptr)
173 state.classification().type = campaign_type::type::multiplayer;
177 connection = open_connection(*host);
182 if(connection ==
nullptr) {
191 connection->wait_and_receive_data(
data);
193 if(
const auto error =
data.optional_child(
"error")) {
197 else if(
data.has_child(
"gamelist")) {
198 this->lobby_info.process_gamelist(
data);
202 else if(
const auto gamelist_diff =
data.optional_child(
"gamelist_diff")) {
203 this->lobby_info.process_gamelist_diff(*gamelist_diff);
208 for(
const auto& handler : process_handlers) {
222 std::unique_ptr<wesnothd_connection> mp_manager::open_connection(std::string host)
224 DBG_MP <<
"opening connection";
231 std::set<std::pair<std::string, std::string>> shown_hosts;
232 auto addr = shown_hosts.end();
236 }
catch(
const std::runtime_error&) {
237 throw wesnothd_error(
_(
"Invalid address specified for multiplayer server"));
244 auto conn = std::make_unique<wesnothd_connection>(addr->first, addr->second);
247 conn->wait_for_handshake();
256 conn->wait_and_receive_data(
data);
258 if(
const auto reject =
data.optional_child(
"reject"); reject ||
data.has_attribute(
"version")) {
262 version = (*reject)[
"accepted_versions"].str();
265 version =
data[
"version"].str();
269 i18n_symbols[
"required_version"] = version;
272 const std::string errorstring =
VGETTEXT(
"The server accepts versions '$required_version', but you are using version '$your_version'", i18n_symbols);
277 if(
const auto redirect =
data.optional_child(
"redirect")) {
278 auto redirect_host = (*redirect)[
"host"].str();
279 auto redirect_port = (*redirect)[
"port"].str(
"15000");
282 std::tie(std::ignore, recorded_host) = shown_hosts.emplace(redirect_host, redirect_port);
292 conn = std::make_unique<wesnothd_connection>(redirect_host, redirect_port);
295 conn->wait_for_handshake();
301 if(
data.has_child(
"version")) {
306 conn->send_data(res);
309 if(
const auto error =
data.optional_child(
"error")) {
314 if(!
data.has_child(
"mustlogin")) {
326 conn->send_data(response);
327 conn->wait_and_receive_data(
data);
331 if(
const auto warning =
data.optional_child(
"warning")) {
332 std::string warning_msg;
335 warning_msg =
VGETTEXT(
"The nickname ‘$nick’ is inactive. "
336 "You cannot claim ownership of this nickname until you "
337 "activate your account via email or ask an "
338 "administrator to do it for you.", {{
"nick",
login}});
340 warning_msg = (*warning)[
"message"].str();
343 warning_msg +=
"\n\n";
344 warning_msg +=
_(
"Do you want to continue?");
353 auto error =
data.optional_child(
"error");
361 const bool fall_through = (*error)[
"force_confirmation"].to_bool()
381 conn->send_data(response);
382 conn->wait_and_receive_data(
data);
386 error =
data.optional_child(
"error");
397 std::string error_message;
399 i18n_symbols[
"nick"] =
login;
401 const auto extra_data = error->optional_child(
"data");
406 const std::string ec = (*error)[
"error_code"];
409 error_message =
_(
"The remote server requested a password while using an insecure connection.");
411 error_message =
_(
"You must login first.");
413 error_message =
VGETTEXT(
"The nickname ‘$nick’ is already taken.", i18n_symbols);
415 error_message =
VGETTEXT(
"The nickname ‘$nick’ contains invalid "
416 "characters. Only alpha-numeric characters (one at minimum), underscores and "
417 "hyphens are allowed.", i18n_symbols);
419 error_message =
VGETTEXT(
"The nickname ‘$nick’ is too long. Nicks must be 20 characters or less.", i18n_symbols);
421 error_message =
VGETTEXT(
"The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols);
423 error_message =
VGETTEXT(
"The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
424 +
_(
" This server disallows unregistered nicknames.");
427 error_message =
VGETTEXT(
"The nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
429 error_message =
VGETTEXT(
"The nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
433 error_message =
VGETTEXT(
"Your IP address is banned on this server’s forums for $duration|.", i18n_symbols);
435 error_message =
_(
"Your IP address is banned on this server’s forums.");
439 error_message =
VGETTEXT(
"The email address for the nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
441 error_message =
VGETTEXT(
"The email address for the nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
444 error_message =
VGETTEXT(
"The nickname ‘$nick’ is registered on this server.", i18n_symbols);
446 error_message =
VGETTEXT(
"The nickname ‘$nick’ is registered on this server.", i18n_symbols)
447 +
"\n\n" +
_(
"WARNING: There is already a client using this nickname, "
448 "logging in will cause that client to be kicked!");
450 error_message =
_(
"The password you provided was incorrect.");
452 error_message =
_(
"You have made too many login attempts.");
454 error_message =
_(
"Password hashing failed.");
456 error_message = (*error)[
"message"].str();
481 if(
const auto join_lobby =
data.optional_child(
"join_lobby")) {
483 session_info = { join_lobby.value() };
493 void mp_manager::run_lobby_loop()
502 while(!enter_lobby_mode()) {
507 lobby_info.refresh_installed_addons_cache();
509 connection->send_data(
config(
"refresh_lobby"));
513 bool mp_manager::enter_lobby_mode()
515 DBG_MP <<
"entering lobby mode";
523 for(
const config&
i : cfg->child_range(
"music")) {
534 int dlg_joined_game_id = 0;
564 connection->send_data(
config(
"refresh_lobby"));
571 void mp_manager::enter_create_mode()
573 DBG_MP <<
"entering create mode";
575 if(gui2::dialogs::mp_create_game::execute(state, connection ==
nullptr)) {
576 enter_staging_mode();
577 }
else if(connection) {
578 connection->send_data(
config(
"refresh_lobby"));
582 void mp_manager::enter_staging_mode()
584 DBG_MP <<
"entering connect mode";
586 std::unique_ptr<mp_game_metadata> metadata;
590 metadata = std::make_unique<mp_game_metadata>(*connection);
592 metadata->is_host =
true;
598 dlg_ok = gui2::dialogs::mp_staging::execute(connect_engine, connection.get());
608 connection->send_data(
config(
"leave_game"));
612 void mp_manager::enter_wait_mode(
int game_id,
bool observe)
614 DBG_MP <<
"entering wait mode";
622 if(
const mp::game_info* gi = lobby_info.get_game_by_id(game_id)) {
636 connection->send_data(
config(
"leave_game"));
649 connection->send_data(
config(
"leave_game"));
654 return gui2::dialogs::mp_staging::execute(engine, connection.get());
657 bool mp_manager::post_scenario_wait(
bool observe)
662 connection->send_data(
config(
"leave_game"));
679 DBG_MP <<
"starting client";
680 mp_manager(host).run_lobby_loop();
685 DBG_MP <<
"starting local game";
689 mp_manager(std::nullopt).enter_create_mode();
694 DBG_MP <<
"starting local MP game from commandline";
703 DBG_MP <<
"entering create mode";
713 parameters.
name =
"multiplayer_The_Freelands";
723 DBG_MP <<
"ignoring map settings";
751 if(
auto cfg_multiplayer =
game_config.find_child(
"multiplayer",
"id", parameters.
name)) {
754 PLAIN_LOG <<
"Could not find [multiplayer] '" << parameters.
name <<
"'";
774 DBG_MP <<
"entering connect mode";
789 for(
unsigned int i = 0;
i < repeat;
i++){
798 return manager && manager->post_scenario_staging(engine);
803 return manager && manager->post_scenario_wait(observe);
808 return manager && manager->get_session_info().is_moderator;
814 const std::string& prefix = manager->get_session_info().profile_url_prefix;
816 if(!prefix.empty()) {
817 return prefix + std::to_string(user_id);
826 if(manager && manager->connection) {
827 manager->connection->send_data(
data);
847 return manager ? &manager->
lobby_info :
nullptr;
std::optional< unsigned int > multiplayer_repeat
Repeats specified by –multiplayer-repeat option.
std::optional< std::string > multiplayer_label
Non-empty if –label was given on the command line.
std::optional< std::string > multiplayer_scenario
Non-empty if –scenario was given on the command line.
std::optional< std::string > multiplayer_era
Non-empty if –era was given on the command line.
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line.
A config object defines a single node in a WML file, with access to child nodes.
config & add_child(config_key_type key)
std::string scenario_define
If there is a define the scenario uses to customize data.
std::string era_define
If there is a define the era uses to customize data.
static game_config_manager * get()
void load_game_config_for_game(const game_classification &classification, const std::string &scenario_id)
void load_game_config_for_create(bool is_mp, bool is_test=false)
void reload_changed_game_config()
const game_config_view & game_config() const
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
static void display(std::function< void()> f)
@ yes_no_buttons
Shows a yes and no button.
@ ok_cancel_buttons
Shows an ok and cancel button.
bool show(const unsigned auto_close_time=0)
Shows the window.
int get_retval() const
Returns the cached window exit code.
This shows the dialog to log in to the MP server.
This class represents the collective information the client has about the players and games on the se...
std::function< void()> remove_handler
std::function< void(const config &)> handler
network_registrar(handler func)
void start_game_commandline(const commandline_options &cmdline_opts, const game_config_view &game_config)
void add_log_data(const std::string &key, const std::string &var)
game_classification & classification()
void expand_mp_options()
adds values of [option]s into [carryover_sides_start][variables] so that they are applied in the next...
void set_carryover_sides_start(config carryover_sides_start)
std::string get_scenario_id() const
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
config & get_starting_point()
void expand_mp_events()
adds [event]s from [era] and [modification] into this scenario does NOT expand [option]s because vari...
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
std::string str() const
Serializes the version number into string form.
static std::string _(const char *str)
std::string label
What to show in the filter's drop-down list.
Standard logging facilities (interface).
General settings and defaults for scenarios.
static lg::log_domain log_mp("mp/main")
Define the errors the server may send during the login procedure.
#define MP_INCORRECT_PASSWORD_ERROR
#define MP_NAME_AUTH_BAN_USER_ERROR
#define MP_NAME_RESERVED_ERROR
#define MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME
#define MP_NAME_AUTH_BAN_EMAIL_ERROR
#define MP_TOO_MANY_ATTEMPTS_ERROR
#define MP_HASHING_PASSWORD_FAILED
#define MP_NAME_TOO_LONG_ERROR
#define MP_NAME_AUTH_BAN_IP_ERROR
#define MP_PASSWORD_REQUEST
#define MP_NAME_INACTIVE_WARNING
#define MP_NAME_UNREGISTERED_ERROR
#define MP_INVALID_CHARS_IN_NAME_ERROR
#define MP_NAME_TAKEN_ERROR
void call_in_main_thread(const std::function< void(void)> &f)
Game configuration data as global variables.
const version_info wesnoth_version(VERSION)
std::string dist_channel_id()
Return the distribution channel identifier, or "Default" if missing.
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
@ OK
Dialog was closed with the OK button.
@ CANCEL
Dialog was closed with the CANCEL button.
Main entry points of multiplayer mode.
lobby_info * get_lobby_info()
Returns the lobby_info object for the given session.
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
void start_local_game()
Starts a multiplayer game in single-user mode.
bool goto_mp_staging(ng::connect_engine &engine)
Opens the MP Staging screen and sets the game state according to the changes made.
void start_local_game_commandline(const commandline_options &cmdline_opts)
Starts a multiplayer game in single-user mode using command line settings.
std::string get_profile_link(int user_id)
Gets the forum profile link for the given user.
void start_client(const std::string &host)
Pubic entry points for the MP workflow.
bool logged_in_as_moderator()
Gets whether the currently logged-in user is a moderator.
bool goto_mp_wait(bool observe)
Opens the MP Join Game screen and sets the game state according to the changes made.
void set_message_private(bool value)
std::string password(const std::string &server, const std::string &login)
int get_village_support(const std::string &value)
Gets the village unit level support.
int get_turns(const std::string &value)
Gets the number of turns.
int get_xp_modifier(const std::string &value)
Gets the xp modifier.
int get_village_gold(const std::string &value, const game_classification *classification)
Gets the village gold.
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
void commit_music_changes()
std::string format_timespan(std::time_t time, bool detailed)
Formats a timespan into human-readable text for player authentication functions.
std::map< std::string, t_string > string_map
std::pair< std::string, std::string > parse_network_address(const std::string &address, const std::string &default_port)
Parse a host:port style network address, supporting [] notation for ipv6 addresses.
This class represents the info a client has about a game on the server.
An error occurred during when trying to communicate with the wesnothd server.
Error used when the client is rejected by the MP server.