The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
multiplayer.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2007 - 2025
3 by David White <dave@whitevine.net>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
17
18#include "build_info.hpp"
20#include "connect_engine.hpp"
21#include "events.hpp"
26#include "gettext.hpp"
34#include "log.hpp"
35#include "map_settings.hpp"
38#include "replay.hpp"
39#include "resources.hpp"
40#include "saved_game.hpp"
41#include "sound.hpp"
44
45#include <functional>
47#include <thread>
48
49static lg::log_domain log_mp("mp/main");
50#define DBG_MP LOG_STREAM(debug, log_mp)
51#define LOG_MP LOG_STREAM(info, log_mp)
52#define WRN_MP LOG_STREAM(warn, log_mp)
53#define ERR_MP LOG_STREAM(err, log_mp)
54
55namespace mp
56{
57namespace
58{
59/** Pointer to the current mp_manager instance. */
60class mp_manager* manager = nullptr;
61
62/** The main controller of the MP workflow. */
63class mp_manager
64{
65public:
66 // Declare this as a friend to allow direct access to enter_create_mode
67 friend void mp::start_local_game();
68 friend void mp::send_to_server(const config&);
70
71 mp_manager(const utils::optional<std::string> host);
72
73 ~mp_manager()
74 {
75 assert(manager);
76 manager = nullptr;
77
78 if(network_worker.joinable()) {
79 stop = true;
80 network_worker.join();
81 }
82 }
83
84 /**
85 * Enters the mp loop. It consists of four screens:
86 *
87 * Host POV: LOBBY <---> CREATE GAME ---> STAGING -----> GAME BEGINS
88 * Player POV: LOBBY <--------------------> JOIN GAME ---> GAME BEGINS
89 */
90 void run_lobby_loop();
91
92 bool post_scenario_staging(ng::connect_engine& engine);
93
94 bool post_scenario_wait(bool observe);
95
96private:
97 /** Represents the contents of the [join_lobby] response. */
98 struct session_metadata
99 {
100 session_metadata() = default;
101
102 session_metadata(const config& cfg)
103 : is_moderator(cfg["is_moderator"].to_bool(false))
104 , profile_url_prefix(cfg["profile_url_prefix"].str())
105 , queues()
106 {
107 if(cfg.has_child("queues")) {
108 for(const config& queue : cfg.mandatory_child("queues").child_range("queue")) {
109 queue_info info;
110 info.id = queue["id"].to_int();
111 info.display_name = queue["display_name"].str();
112 info.players_required = queue["players_required"].to_int();
113 info.current_players = utils::split_set(queue["current_players"].str());
114 info.required_addons = utils::split_set(queue["addons"].str());
115 queues.emplace_back(info);
116 }
117 }
118 }
119
120 /** Whether you are logged in as a server moderator. */
121 bool is_moderator = false;
122
123 /** The external URL prefix for player profiles (empty if the server doesn't have an attached database). */
124 std::string profile_url_prefix;
125
126 /** The list of server-side queues */
127 std::vector<queue_info> queues;
128 };
129
130 /** Opens a new server connection and prompts the client for login credentials, if necessary. */
131 std::unique_ptr<wesnothd_connection> open_connection(const std::string& host);
132
133 /** Opens the MP lobby. */
134 bool enter_lobby_mode();
135
136 /**
137 * Opens the MP Create screen for hosts to configure a new game.
138 */
139 void enter_create_mode(utils::optional<config> preset = utils::nullopt, int queue_id = 0);
140
141 /** Opens the MP Staging screen for hosts to wait for players. */
142 void enter_staging_mode(queue_type::type queue_type, int queue_id = 0);
143
144 /** Opens the MP Join Game screen for non-host players and observers. */
145 void enter_wait_mode(int game_id, bool observe);
146
147 /** Worker thread to handle receiving and processing network data. */
148 std::thread network_worker;
149
150 /** Flag to signal the worker thread terminate. */
151 std::atomic_bool stop;
152
153 /** The connection to the server. */
154 std::unique_ptr<wesnothd_connection> connection;
155
156 /** The current session's info sent by the server on login. */
157 session_metadata session_info;
158
159 /** This single instance is reused for all games played during the current connection to the server. */
160 saved_game state;
161
162 mp::lobby_info lobby_info;
163
164 std::list<mp::network_registrar::handler> process_handlers;
165
166public:
167 const session_metadata& get_session_info() const
168 {
169 return session_info;
170 }
171 session_metadata& get_session_info()
172 {
173 return session_info;
174 }
175
176 auto add_network_handler(const decltype(process_handlers)::value_type& func)
177 {
178 return [this, iter = process_handlers.insert(process_handlers.end(), func)]() { process_handlers.erase(iter); };
179 }
180};
181
182mp_manager::mp_manager(const utils::optional<std::string> host)
183 : network_worker()
184 , stop(false)
185 , connection(nullptr)
186 , session_info()
187 , state()
188 , lobby_info()
189 , process_handlers()
190{
191 state.classification().type = campaign_type::type::multiplayer;
192
193 if(host) {
195 connection = open_connection(*host);
196
197 // If for whatever reason our connection is null at this point (dismissing the password prompt, for
198 // instance), treat it as a normal condition and exit. Any actual error conditions throw exceptions
199 // which can be handled higher up the stack.
200 if(connection == nullptr) {
201 return;
202 }
203
205
206 config data;
207
208 while(!stop) {
209 connection->wait_and_receive_data(data);
210
211 if(const auto error = data.optional_child("error")) {
212 throw wesnothd_error((*error)["message"]);
213 }
214
215 else if(data.has_child("gamelist")) {
216 this->lobby_info.process_gamelist(data);
217 break;
218 }
219
220 else if(const auto gamelist_diff = data.optional_child("gamelist_diff")) {
221 this->lobby_info.process_gamelist_diff(*gamelist_diff);
222 }
223
224 else {
225 // No special actions to take. Pass the data on to the network handlers.
226 for(const auto& handler : process_handlers) {
227 handler(data);
228 }
229 }
230 }
231 });
232 }
233
234 // Avoid setting this until the connection has been fully established. open_connection may throw,
235 // in which case we don't want to point to an object instance that has not properly connected.
236 assert(!manager);
237 manager = this;
238}
239
240std::unique_ptr<wesnothd_connection> mp_manager::open_connection(const std::string& host)
241{
242 DBG_MP << "opening connection";
243
244 if(host.empty()) {
245 return nullptr;
246 }
247
248 // shown_hosts is used to prevent the client being locked in a redirect loop.
249 std::set<std::pair<std::string, std::string>> shown_hosts;
250 auto addr = shown_hosts.end();
251
252 try {
253 std::tie(addr, std::ignore) = shown_hosts.insert(parse_network_address(host, "15000"));
254 } catch(const std::runtime_error&) {
255 throw wesnothd_error(_("Invalid address specified for multiplayer server"));
256 }
257
258 // Start stage
260
261 // Initializes the connection to the server.
262 auto conn = std::make_unique<wesnothd_connection>(addr->first, addr->second);
263
264 // First, spin until we get a handshake from the server.
265 conn->wait_for_handshake();
266
268
269 config data;
270
271 // Then, log in and wait for the lobby/game join prompt.
272 while(true) {
273 data.clear();
274 conn->wait_and_receive_data(data);
275
276 if(const auto reject = data.optional_child("reject"); reject || data.has_attribute("version")) {
277 std::string version;
278
279 if(reject) {
280 version = (*reject)["accepted_versions"].str();
281 } else {
282 // Backwards-compatibility "version" attribute
283 version = data["version"].str();
284 }
285
286 utils::string_map i18n_symbols;
287 i18n_symbols["required_version"] = version;
288 i18n_symbols["your_version"] = game_config::wesnoth_version.str();
289
290 const std::string errorstring = VGETTEXT("The server accepts versions ‘$required_version’, but you are using version ‘$your_version’", i18n_symbols);
291 throw wesnothd_error(errorstring);
292 }
293
294 // Check for "redirect" messages
295 if(const auto redirect = data.optional_child("redirect")) {
296 auto redirect_host = (*redirect)["host"].str();
297 auto redirect_port = (*redirect)["port"].str("15000");
298
299 bool recorded_host;
300 std::tie(std::ignore, recorded_host) = shown_hosts.emplace(redirect_host, redirect_port);
301
302 if(!recorded_host) {
303 throw wesnothd_error(_("Server-side redirect loop"));
304 }
305
307
308 // Open a new connection with the new host and port.
309 conn.reset();
310 conn = std::make_unique<wesnothd_connection>(redirect_host, redirect_port);
311
312 // Wait for new handshake.
313 conn->wait_for_handshake();
314
316 continue;
317 }
318
319 if(data.has_child("version")) {
320 std::string version = game_config::wesnoth_version;
322 conn->send_data(config{"version", config{"version", version, "client_source", channel}});
323 }
324
325 if(const auto error = data.optional_child("error")) {
326 throw wesnothd_rejected_client_error((*error)["message"].str());
327 }
328
329 // Continue if we did not get a direction to login
330 if(!data.has_child("mustlogin")) {
331 continue;
332 }
333
334 // Enter login loop
335 while(true) {
336 std::string login = prefs::get().login();
337
338 conn->send_data(config{"login", config{"username", login}});
339 conn->wait_and_receive_data(data);
340
342
343 if(const auto warning = data.optional_child("warning")) {
344 std::string warning_msg;
345
346 if((*warning)["warning_code"] == MP_NAME_INACTIVE_WARNING) {
347 warning_msg = VGETTEXT("The nickname ‘$nick’ is inactive. "
348 "You cannot claim ownership of this nickname until you "
349 "activate your account via email or ask an "
350 "administrator to do it for you.", {{"nick", login}});
351 } else {
352 warning_msg = (*warning)["message"].str();
353 }
354
355 warning_msg += "\n\n";
356 warning_msg += _("Do you want to continue?");
357
359 return nullptr;
360 } else {
361 continue;
362 }
363 }
364
365 auto error = data.optional_child("error");
366
367 // ... and get us out of here if the server did not complain
368 if(!error) break;
369
370 do {
371 std::string password = prefs::get().password(host, login);
372
373 const bool fall_through = (*error)["force_confirmation"].to_bool()
375 : false;
376
377 // If:
378 // * the server asked for a password
379 // * the password isn't empty
380 // * the user didn't press Cancel
381 // * the connection is secure or the client was started with the option to use insecure connections
382 // send the password to the server
383 // otherwise go directly to the username/password dialog
384 if(!(*error)["password_request"].empty() && !password.empty() && !fall_through && (conn->using_tls() || game_config::allow_insecure)) {
385 // the possible cases here are that either:
386 // 1) TLS encryption is enabled, thus sending the plaintext password is still secure
387 // 2) TLS encryption is not enabled, in which case the server should not be requesting a password in the first place
388 // 3) This is being used for local testing/development, so using an insecure connection is enabled manually
389
390 // Once again send our request...
391 conn->send_data(config{"login", config{"username", login, "password", password}});
392 conn->wait_and_receive_data(data);
393
395
396 error = data.optional_child("error");
397
398 // ... and get us out of here if the server is happy now
399 if(!error) break;
400 }
401
402 // Providing a password either was not attempted because we did not
403 // have any or failed:
404 // Now show a dialog that displays the error and allows to
405 // enter a new user name and/or password
406
407 std::string error_message;
408 utils::string_map i18n_symbols;
409 i18n_symbols["nick"] = login;
410
411 const auto extra_data = error->optional_child("data");
412 if(extra_data) {
413 using namespace std::chrono_literals;
414 i18n_symbols["duration"] = utils::format_timespan(chrono::parse_duration((*extra_data)["duration"], 0s));
415 }
416
417 const std::string ec = (*error)["error_code"];
418
419 if(!(*error)["password_request"].empty() && !conn->using_tls() && !game_config::allow_insecure) {
420 error_message = _("The remote server requested a password while using an insecure connection.");
421 } else if(ec == MP_MUST_LOGIN) {
422 error_message = _("You must login first.");
423 } else if(ec == MP_NAME_TAKEN_ERROR) {
424 error_message = VGETTEXT("The nickname ‘$nick’ is already taken.", i18n_symbols);
425 } else if(ec == MP_INVALID_CHARS_IN_NAME_ERROR) {
426 error_message = VGETTEXT("The nickname ‘$nick’ contains invalid "
427 "characters. Only alpha-numeric characters (one at minimum), underscores and "
428 "hyphens are allowed.", i18n_symbols);
429 } else if(ec == MP_NAME_TOO_LONG_ERROR) {
430 error_message = VGETTEXT("The nickname ‘$nick’ is too long. Nicks must be 20 characters or less.", i18n_symbols);
431 } else if(ec == MP_NAME_RESERVED_ERROR) {
432 error_message = VGETTEXT("The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols);
433 } else if(ec == MP_NAME_UNREGISTERED_ERROR) {
434 error_message = VGETTEXT("The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
435 + _(" This server disallows unregistered nicknames.");
436 } else if(ec == MP_SERVER_IP_BAN_ERROR) {
437 if(extra_data) {
438 error_message = VGETTEXT("Your IP address is banned on this server for $duration|.", i18n_symbols);
439 } else {
440 error_message = _("Your IP address is banned on this server.");
441 }
442 } else if(ec == MP_NAME_AUTH_BAN_USER_ERROR) {
443 if(extra_data) {
444 error_message = VGETTEXT("The nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
445 } else {
446 error_message = VGETTEXT("The nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
447 }
448 } else if(ec == MP_NAME_AUTH_BAN_IP_ERROR) {
449 if(extra_data) {
450 error_message = VGETTEXT("Your IP address is banned on this server’s forums for $duration|.", i18n_symbols);
451 } else {
452 error_message = _("Your IP address is banned on this server’s forums.");
453 }
454 } else if(ec == MP_NAME_AUTH_BAN_EMAIL_ERROR) {
455 if(extra_data) {
456 error_message = VGETTEXT("The email address for the nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
457 } else {
458 error_message = VGETTEXT("The email address for the nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
459 }
460 } else if(ec == MP_PASSWORD_REQUEST) {
461 error_message = VGETTEXT("The nickname ‘$nick’ is registered on this server.", i18n_symbols);
463 error_message = VGETTEXT("The nickname ‘$nick’ is registered on this server.", i18n_symbols)
464 + "\n\n" + _("WARNING: There is already a client using this nickname, "
465 "logging in will cause that client to be kicked!");
466 } else if(ec == MP_INCORRECT_PASSWORD_ERROR) {
467 error_message = _("The password you provided was incorrect.");
468 } else if(ec == MP_TOO_MANY_ATTEMPTS_ERROR) {
469 error_message = _("You have made too many login attempts.");
470 } else if(ec == MP_HASHING_PASSWORD_FAILED) {
471 error_message = _("Password hashing failed.");
472 } else {
473 error_message = (*error)["message"].str();
474 }
475
476 gui2::dialogs::mp_login dlg(host, error_message, !((*error)["password_request"].empty()));
477
478 // Need to show the dialog from the main thread or it won't appear.
479 events::call_in_main_thread([&dlg]() { dlg.show(); });
480
481 switch(dlg.get_retval()) {
482 // Log in with password
483 case gui2::retval::OK:
484 break;
485 // Cancel
486 default:
487 return nullptr;
488 }
489
490 // If we have got a new username we have to start all over again
491 } while(login == prefs::get().login());
492
493 // Somewhat hacky...
494 // If we broke out of the do-while loop above error is still going to be nullopt
495 if(!error) break;
496 } // end login loop
497
498 if(const auto join_lobby = data.optional_child("join_lobby")) {
499 // Note any session data sent with the response. This should be the only place session_info is set.
500 session_info = { join_lobby.value() };
501
502 // All done!
503 break;
504 }
505 }
506
507 return conn;
508}
509
510void mp_manager::run_lobby_loop()
511{
512 // This should only work if we have a connection. If we're in a local mode,
513 // enter_create_mode should be accessed directly.
514 if(!connection) {
515 return;
516 }
517
518 // A return of false means a config reload was requested, so do that and then loop.
519 while(!enter_lobby_mode()) {
522 gcm->load_game_config_for_create(true); // NOTE: Using reload_changed_game_config only doesn't seem to work here
523
524 lobby_info.refresh_installed_addons_cache();
525
526 connection->send_data(config{"refresh_lobby"});
527 }
528}
529
530bool mp_manager::enter_lobby_mode()
531{
532 DBG_MP << "entering lobby mode";
533
534 // Connection should never be null in the lobby.
535 assert(connection);
536
537 // We use a loop here to allow returning to the lobby if you, say, cancel game creation.
538 while(true) {
539 if(auto cfg = game_config_manager::get()->game_config().optional_child("lobby_music")) {
540 for(const config& i : cfg->child_range("music")) {
542 }
543
545 } else {
548 }
549
550 int dlg_retval = 0;
551 int dlg_joined_game_id = 0;
552 std::string preset_scenario = "";
553 config server_preset;
554 int queue_id = 0;
555 {
556 gui2::dialogs::mp_lobby dlg(lobby_info, *connection, dlg_joined_game_id);
557 dlg.show();
558 dlg_retval = dlg.get_retval();
559 server_preset = dlg.queue_game_server_preset();
560 queue_id = dlg.queue_id();
561 }
562
563 try {
564 switch(dlg_retval) {
566 enter_create_mode(utils::make_optional(server_preset), queue_id);
567 break;
569 enter_create_mode();
570 break;
572 [[fallthrough]];
574 enter_wait_mode(dlg_joined_game_id, dlg_retval == gui2::dialogs::mp_lobby::OBSERVE);
575 break;
577 // Let this function's caller reload the config and re-call.
578 return false;
579 default:
580 // Needed to handle the Quit signal and exit the loop
581 return true;
582 }
583 } catch(const config::error& error) {
584 if(!error.message.empty()) {
586 }
587
589 connection->send_data(config{"leave_server_queue", config{"queue_id", queue_id}});
590 }
591
592 // Update lobby content
593 connection->send_data(config{"refresh_lobby"});
594 }
595 }
596
597 return true;
598}
599
600void mp_manager::enter_create_mode(utils::optional<config> preset, int queue_id)
601{
602 DBG_MP << "entering create mode";
603
604 // if this is using pre-determined settings and the settings came from the server, use those
605 // else look for them locally
606 if(preset) {
608 enter_staging_mode(queue_id >= 0 ? queue_type::type::server_preset : queue_type::type::normal, queue_id);
609 } else if(gui2::dialogs::mp_create_game::execute(state, connection == nullptr)) {
610 enter_staging_mode(queue_type::type::normal);
611 } else if(connection) {
612 connection->send_data(config{"refresh_lobby"});
613 }
614}
615
616void mp_manager::enter_staging_mode(queue_type::type queue_type, int queue_id)
617{
618 DBG_MP << "entering connect mode";
619
620 std::unique_ptr<mp_game_metadata> metadata;
621
622 // If we have a connection, set the appropriate info. No connection means we're in local game mode.
623 if(connection) {
624 metadata = std::make_unique<mp_game_metadata>(*connection);
625 metadata->connected_players.insert(prefs::get().login());
626 metadata->is_host = true;
627 metadata->queue_type = queue_type::get_string(queue_type);
628 metadata->queue_id = queue_id;
629 }
630
631 bool dlg_ok = false;
632 {
633 ng::connect_engine connect_engine(state, true, metadata.get());
634 dlg_ok = gui2::dialogs::mp_staging::execute(connect_engine, connection.get());
635 } // end connect_engine
636
637 if(dlg_ok) {
638 campaign_controller controller(state);
639 controller.set_mp_info(metadata.get());
640 controller.play_game();
641 }
642
643 if(connection) {
644 connection->send_data(config{"leave_game"});
645 }
646}
647
648void mp_manager::enter_wait_mode(int game_id, bool observe)
649{
650 DBG_MP << "entering wait mode";
651
652 // The connection should never be null here, since one should never reach this screen in local game mode.
653 assert(connection);
654
655 mp_game_metadata metadata(*connection);
656 metadata.is_host = false;
657
658 if(const mp::game_info* gi = lobby_info.get_game_by_id(game_id)) {
659 metadata.current_turn = gi->current_turn;
660 }
661
662 if(prefs::get().skip_mp_replay() || prefs::get().blindfold_replay()) {
663 metadata.skip_replay = true;
664 metadata.skip_replay_blindfolded = prefs::get().blindfold_replay();
665 }
666
667 bool dlg_ok = false;
668 {
669 gui2::dialogs::mp_join_game dlg(state, *connection, true, observe);
670
671 if(!dlg.fetch_game_config()) {
672 connection->send_data(config{"leave_game"});
673 return;
674 }
675
676 dlg_ok = dlg.show();
677 }
678
679 if(dlg_ok) {
680 campaign_controller controller(state);
681 controller.set_mp_info(&metadata);
682 controller.play_game();
683 }
684
685 connection->send_data(config{"leave_game"});
686}
687
688bool mp_manager::post_scenario_staging(ng::connect_engine& engine)
689{
690 return gui2::dialogs::mp_staging::execute(engine, connection.get());
691}
692
693bool mp_manager::post_scenario_wait(bool observe)
694{
695 gui2::dialogs::mp_join_game dlg(state, *connection, false, observe);
696
697 if(!dlg.fetch_game_config()) {
698 connection->send_data(config{"leave_game"});
699 return false;
700 }
701
702 if(dlg.started()) {
703 return true;
704 }
705
706 return dlg.show();
707}
708
709} // end anon namespace
710
711/** Pubic entry points for the MP workflow */
712
713void start_client(const std::string& host)
714{
715 DBG_MP << "starting client";
716 mp_manager(host).run_lobby_loop();
717}
718
720{
721 DBG_MP << "starting local game";
722
724
725 mp_manager(utils::nullopt).enter_create_mode();
726}
727
729{
730 DBG_MP << "starting local MP game from commandline";
731
733
734 // The setup is done equivalently to lobby MP games using as much of existing
735 // code as possible. This means that some things are set up that are not
736 // needed in commandline mode, but they are required by the functions called.
738
739 DBG_MP << "entering create mode";
740
741 // Set the default parameters
742 saved_game state;
743 state.classification().type = campaign_type::type::multiplayer;
744
745 mp_game_settings& parameters = state.mp_settings();
746
747 // Hardcoded default values
748 state.classification().era_id = "era_default";
749 parameters.name = "multiplayer_The_Freelands";
750
751 // Default values for which at getter function exists
752 parameters.num_turns = settings::get_turns("");
755 parameters.xp_modifier = settings::get_xp_modifier("");
756
757 // Do not use map settings if --ignore-map-settings commandline option is set
758 if(cmdline_opts.multiplayer_ignore_map_settings) {
759 DBG_MP << "ignoring map settings";
760 parameters.use_map_settings = false;
761 } else {
762 parameters.use_map_settings = true;
763 }
764
765 // None of the other parameters need to be set, as their creation values above are good enough for CL mode.
766 // In particular, we do not want to use the preferences values.
767
768 state.classification().type = campaign_type::type::multiplayer;
769
770 // [era] define.
771 if(cmdline_opts.multiplayer_era) {
772 state.classification().era_id = *cmdline_opts.multiplayer_era;
773 }
774
775 if(auto cfg_era = game_config.find_child("era", "id", state.classification().era_id)) {
776 state.classification().era_define = cfg_era["define"].str();
777 } else {
778 PLAIN_LOG << "Could not find era '" << state.classification().era_id << "'";
779 return;
780 }
781
782 // [multiplayer] define.
783 if(cmdline_opts.multiplayer_scenario) {
784 parameters.name = *cmdline_opts.multiplayer_scenario;
785 }
786
787 if(auto cfg_multiplayer = game_config.find_child("multiplayer", "id", parameters.name)) {
788 state.classification().scenario_define = cfg_multiplayer["define"].str();
789 } else {
790 PLAIN_LOG << "Could not find [multiplayer] '" << parameters.name << "'";
791 return;
792 }
793
795 config {"next_scenario", parameters.name}
796 );
797
799
801 state.expand_mp_events();
802 state.expand_mp_options();
803
804 // Should number of turns be determined from scenario data?
805 if(parameters.use_map_settings && state.get_starting_point().has_attribute("turns")) {
806 DBG_MP << "setting turns from scenario data: " << state.get_starting_point()["turns"];
807 parameters.num_turns = state.get_starting_point()["turns"].to_int();
808 }
809
810 DBG_MP << "entering connect mode";
811
812 {
813 ng::connect_engine connect_engine(state, true, nullptr);
814
815 // Update the parameters to reflect game start conditions
816 connect_engine.start_game_commandline(cmdline_opts, game_config);
817 }
818
819 if(resources::recorder && cmdline_opts.multiplayer_label) {
820 std::string label = *cmdline_opts.multiplayer_label;
821 resources::recorder->add_log_data("ai_log","ai_label",label);
822 }
823
824 unsigned int repeat = (cmdline_opts.multiplayer_repeat) ? *cmdline_opts.multiplayer_repeat : 1;
825 for(unsigned int i = 0; i < repeat; i++){
826 saved_game state_copy(state);
827 campaign_controller controller(state_copy);
828 controller.play_game();
829 }
830}
831
833{
834 return manager && manager->post_scenario_staging(engine);
835}
836
837bool goto_mp_wait(bool observe)
838{
839 return manager && manager->post_scenario_wait(observe);
840}
841
843{
844 return manager && manager->get_session_info().is_moderator;
845}
846
847std::string get_profile_link(int user_id)
848{
849 if(manager) {
850 const std::string& prefix = manager->get_session_info().profile_url_prefix;
851
852 if(!prefix.empty()) {
853 return prefix + std::to_string(user_id);
854 }
855 }
856
857 return "";
858}
859
860std::vector<queue_info>& get_server_queues()
861{
862 static std::vector<queue_info> queues;
863 if(manager) {
864 return manager->get_session_info().queues;
865 }
866 return queues;
867}
868
870{
871 if(manager && manager->connection) {
872 manager->connection->send_data(data);
873 }
874}
875
877{
878 if(manager /*&& manager->connection*/) {
879 remove_handler = manager->add_network_handler(func);
880 }
881}
882
889
891{
892 return manager ? &manager->lobby_info : nullptr;
893}
894
895} // end namespace mp
utils::optional< std::string > multiplayer_scenario
Non-empty if –scenario was given on the command line.
utils::optional< unsigned int > multiplayer_repeat
Repeats specified by –multiplayer-repeat option.
utils::optional< std::string > multiplayer_label
Non-empty if –label was given on the command line.
utils::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.
Definition config.hpp:157
child_itors child_range(std::string_view key)
Definition config.cpp:268
bool has_attribute(std::string_view key) const
Definition config.cpp:157
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition config.cpp:312
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition config.cpp:362
void clear()
Definition config.cpp:802
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.
campaign_type::type type
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)
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(const std::function< void()> &f)
@ yes_no_buttons
Shows a yes and no button.
Definition message.hpp:81
@ ok_cancel_buttons
Shows an ok and cancel button.
Definition message.hpp:77
bool show(const unsigned auto_close_time=0)
Shows the window.
static void quick_mp_setup(saved_game &state, const config presets)
presets needs to be a copy! Otherwise you'll get segfaults when clicking the Join button since it res...
int queue_id() const
Definition lobby.hpp:70
const config queue_game_server_preset() const
Definition lobby.hpp:69
@ RELOAD_CONFIG
player clicked the Create button
Definition lobby.hpp:65
int get_retval()
Definition window.hpp:394
This class represents the collective information the client has about the players and games on the se...
network_registrar(const handler &func)
std::function< void()> remove_handler
std::function< void(const config &)> handler
void start_game_commandline(const commandline_options &cmdline_opts, const game_config_view &game_config)
static prefs & get()
void set_message_private(bool value)
std::string login()
std::string password(const std::string &server, const std::string &login)
void add_log_data(const std::string &key, const std::string &var)
Definition replay.cpp:298
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
game_classification & classification()
config & get_starting_point()
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
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.
#define DBG_MP
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition gettext.hpp:100
std::string label
What to show in the filter's drop-down list.
Definition manager.cpp:201
Standard logging facilities (interface).
#define PLAIN_LOG
Definition log.hpp:296
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_MUST_LOGIN
#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_SERVER_IP_BAN_ERROR
#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
auto parse_duration(const config_attribute_value &val, const Duration &def=Duration{0})
Definition chrono.hpp:82
void call_in_main_thread(const std::function< void(void)> &f)
Definition events.cpp:773
Game configuration data as global variables.
const version_info wesnoth_version(VERSION)
bool allow_insecure
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.
Definition message.cpp:201
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.
Definition message.cpp:148
@ OK
Dialog was closed with the OK button.
Definition retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition retval.hpp:38
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.
std::vector< queue_info > & get_server_queues()
Gets the list of server-side queues received on login.
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.
replay * recorder
Definition resources.cpp:28
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 empty_playlist()
Definition sound.cpp:608
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
Definition sound.cpp:687
void stop_music()
Definition sound.cpp:536
void commit_music_changes()
Definition sound.cpp:795
static std::string format_timespan(const std::chrono::duration< Rep, Period > &span, bool detailed=false)
Formats a timespan into human-readable text for player authentication functions.
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
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.
std::string_view data
Definition picture.cpp:188
Replay control code.
std::string message
This class represents the info a client has about a game on the server.
The base template for associating string values with enum values.
Definition enum_base.hpp:33
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition enum_base.hpp:46
An error occurred during when trying to communicate with the wesnothd server.
Error used when the client is rejected by the MP server.
static map_location::direction s
channel
Definition utils.hpp:103
#define i