35 #define LOG_CF LOG_STREAM(info, log_config)
36 #define ERR_CF LOG_STREAM(err, log_config)
39 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
40 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
41 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
42 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
45 #define LOG_NW LOG_STREAM(info, log_network)
49 const std::array controller_names {
50 side_controller::human,
51 side_controller::human,
53 side_controller::none,
54 side_controller::reserved
57 const std::set<std::string> children_to_swap {
69 , params_(state.mp_settings())
71 , mp_metadata_(metadata)
72 , first_scenario_(first_scenario)
73 , force_lock_settings_()
97 std::vector<std::string> original_team_names;
98 std::string team_prefix(
_(
"Team") +
" ");
101 for(
config& side : sides) {
102 const std::string side_str = std::to_string(side_count);
108 if(team_name.
empty()) {
109 team_name = side_str;
113 user_team_name = team_name;
116 bool add_team =
true;
120 return data.team_name == team_name.str();
126 auto name_itor = std::find(original_team_names.begin(), original_team_names.end(), team_name.
str());
130 if(name_itor == original_team_names.end()) {
131 original_team_names.push_back(team_name);
133 team_name =
"Team " + std::to_string(original_team_names.size());
135 team_name =
"Team " + std::to_string(std::distance(original_team_names.begin(), name_itor) + 1);
138 user_team_name = team_prefix + side_str;
163 data.user_team_name = user_team_name.
str();
164 data.is_player_team = side[
"allow_player"].to_bool(
true);
181 const config& lhs = *c1;
182 const config& rhs = *c2;
185 if(lhs[
"random_faction"].to_bool() && !rhs[
"random_faction"].to_bool()) {
189 if(!lhs[
"random_faction"].to_bool() && rhs[
"random_faction"].to_bool()) {
204 if(first_scenario_) {
212 load_previous_sides_users();
223 config* connect_engine::current_config() {
227 void connect_engine::import_user(
const std::string& name,
const bool observer,
int side_taken)
236 const std::string& username =
data[
"name"];
237 assert(!username.empty());
239 connected_users_rw().insert(username);
242 update_side_controller_options();
248 bool side_assigned =
false;
249 if(side_taken >= 0) {
250 side_engines_[side_taken]->place_user(
data,
true);
251 side_assigned =
true;
256 if(side->reserved_for() == username && side->player_id().empty() && side->controller() !=
CNTR_COMPUTER) {
257 side->place_user(
data);
259 side_assigned =
true;
265 if(side_taken < 0 && !side_assigned) {
267 if(side->available_for_user(username) ||
269 side->place_user(
data);
271 side_assigned =
true;
280 if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
282 if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
283 side->place_user(
data);
290 bool connect_engine::sides_available()
const
293 if(side->available_for_user()) {
301 void connect_engine::update_level()
303 DBG_MP <<
"updating level";
308 scenario().add_child(
"side", side->new_config());
312 void connect_engine::update_and_send_diff()
314 config old_level = level_;
320 scenario_diff.
add_child(
"scenario_diff", std::move(diff));
325 bool connect_engine::can_start_game()
const
327 if(side_engines_.empty()) {
333 if(!side->ready_for_start()) {
334 const int side_num = side->index() + 1;
335 DBG_MP <<
"not all sides are ready, side " <<
336 side_num <<
" not ready";
342 DBG_MP <<
"all sides are ready";
351 if(side->controller() !=
CNTR_EMPTY && side->allow_player()) {
359 std::multimap<std::string, config> side_engine::get_side_children()
361 std::multimap<std::string, config> children;
363 for(
const std::string& to_swap : children_to_swap) {
365 children.emplace(to_swap, child);
372 void side_engine::set_side_children(std::multimap<std::string, config> children)
374 for(
const std::string& children_to_remove : children_to_swap) {
375 cfg_.clear_children(children_to_remove);
378 for(std::pair<std::string, config> child_map : children) {
379 cfg_.add_child(child_map.first, child_map.second);
383 void connect_engine::start_game()
385 DBG_MP <<
"starting a new game";
391 std::vector<std::string> avoid_faction_ids;
394 if(params_.mode != random_faction_mode::type::independent) {
396 if(!side2->flg().is_random_faction()) {
397 switch(params_.mode) {
398 case random_faction_mode::type::no_mirror:
399 avoid_faction_ids.push_back(side2->flg().current_faction()[
"id"].str());
401 case random_faction_mode::type::no_ally_mirror:
402 if(side2->team() == side->team()) {
403 avoid_faction_ids.push_back(side2->flg().current_faction()[
"id"].str());
412 side->resolve_random(rng, avoid_faction_ids);
417 if(state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.has_child(
"snapshot") && level_.mandatory_child(
"snapshot").has_child(
"side"))) {
420 std::vector<int> playable_sides;
422 if(side->allow_player() && side->allow_shuffle()) {
423 playable_sides.push_back(side->index());
428 for(
int i = playable_sides.size();
i > 1;
i--) {
430 const int i_side = playable_sides[
i - 1];
432 if(i_side == j_side)
continue;
435 std::swap(side_engines_[j_side], side_engines_[i_side]);
438 std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
439 side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
440 side_engines_[i_side]->set_side_children(tmp_side_children);
443 std::swap(side_engines_[j_side]->index_, side_engines_[i_side]->index_);
444 std::swap(side_engines_[j_side]->team_, side_engines_[i_side]->team_);
449 config lock(
"stop_updates");
452 update_and_send_diff();
454 save_reserved_sides_information();
464 DBG_MP <<
"starting a new game in commandline mode";
475 if(side_num == num) {
476 if(std::find_if(era_factions_.begin(), era_factions_.end(),
477 [fid = faction_id](
const config* faction) { return (*faction)[
"id"] == fid; })
478 != era_factions_.end()
480 DBG_MP <<
"\tsetting side " << side_num <<
"\tfaction: " << faction_id;
481 side->set_faction_commandline(faction_id);
483 ERR_MP <<
"failed to set side " << side_num <<
" to faction " << faction_id;
492 if(side_num == num) {
493 DBG_MP <<
"\tsetting side " << side_num <<
"\tfaction: " << faction_id;
494 side->set_controller_commandline(faction_id);
501 std::string ai_algorithm =
game_config.mandatory_child(
"ais")[
"default_ai_algorithm"].str();
502 side->set_ai_algorithm(ai_algorithm);
506 if(side_num == num) {
507 DBG_MP <<
"\tsetting side " << side_num <<
"\tfaction: " << faction_id;
508 side->set_ai_algorithm(faction_id);
515 side->resolve_random(rng);
518 update_and_send_diff();
529 if(side_num == side[
"side"].to_unsigned()) {
530 DBG_MP <<
"\tsetting side " << side[
"side"] <<
"\tai_config: " << faction_id;
531 side[
"ai_config"] = faction_id;
546 for(
const auto& [side_num, pname, pvalue] : *cmdline_opts.
multiplayer_parm) {
547 if(side_num == side[
"side"].to_unsigned()) {
548 DBG_MP <<
"\tsetting side " << side[
"side"] <<
" " << pname <<
": " << pvalue;
549 side[pname] = pvalue;
555 save_reserved_sides_information();
562 void connect_engine::leave_game()
564 DBG_MP <<
"leaving the game";
569 std::pair<bool, bool> connect_engine::process_network_data(
const config&
data)
571 std::pair<bool, bool> result(
false,
true);
573 if(
data.has_child(
"leave_game")) {
579 if(
auto side_drop =
data.optional_child(
"side_drop")) {
580 unsigned side_index = side_drop[
"side_num"].to_int() - 1;
582 if(side_index < side_engines_.size()) {
586 connected_users_rw().erase(side_to_drop->player_id());
587 update_side_controller_options();
589 side_to_drop->reset();
591 update_and_send_diff();
598 if(!
data[
"side"].empty()) {
599 unsigned side_taken =
data[
"side"].to_int() - 1;
602 const std::string name =
data[
"name"];
605 response[
"failed"] =
true;
608 ERR_CF <<
"ERROR: No username provided with the side.";
613 if(connected_users().find(name) != connected_users().end()) {
616 if(find_user_side_index_by_id(name) != -1) {
618 response[
"failed"] =
true;
619 response[
"message"] =
"The nickname '" + name +
620 "' is already in use.";
625 connected_users_rw().erase(name);
626 update_side_controller_options();
628 observer_quit.
add_child(
"observer_quit")[
"name"] = name;
634 if(side_taken < side_engines_.size()) {
635 if(!side_engines_[side_taken]->available_for_user(name)) {
640 if(
s->available_for_user()) {
647 if(side_taken >= side_engines_.size()) {
649 response[
"failed"] =
true;
654 kick[
"username"] =
data[
"name"];
657 update_and_send_diff();
659 ERR_CF <<
"ERROR: Couldn't assign a side to '" <<
666 LOG_CF <<
"client has taken a valid position";
668 import_user(
data,
false, side_taken);
669 update_and_send_diff();
672 side_engines_[side_taken]->set_waiting_to_choose_status(side_engines_[side_taken]->allow_changes());
673 LOG_MP <<
"waiting to choose status = " << side_engines_[side_taken]->allow_changes();
674 result.second =
false;
676 LOG_NW <<
"sent player data";
678 ERR_CF <<
"tried to take illegal side: " << side_taken;
681 response[
"failed"] =
true;
686 if(
auto change_faction =
data.optional_child(
"change_faction")) {
687 int side_taken = find_user_side_index_by_id(change_faction[
"name"]);
688 if(side_taken != -1 || !first_scenario_) {
689 import_user(*change_faction,
false, side_taken);
690 update_and_send_diff();
696 update_and_send_diff();
699 if(
auto observer =
data.optional_child(
"observer_quit")) {
700 const std::string& observer_name =
observer[
"name"];
702 if(connected_users().find(observer_name) != connected_users().end()) {
703 connected_users_rw().erase(observer_name);
704 update_side_controller_options();
708 if(find_user_side_index_by_id(observer_name) != -1) {
709 update_and_send_diff();
717 int connect_engine::find_user_side_index_by_id(
const std::string&
id)
const
721 if(side->player_id() ==
id) {
728 if(
i >= side_engines_.size()) {
735 void connect_engine::send_level_data()
const
738 if(first_scenario_) {
741 "name", params_.name,
742 "password", params_.password,
744 "auto_hosted",
false,
750 next_level.
add_child(
"store_next_scenario", level_);
755 void connect_engine::save_reserved_sides_information()
759 std::map<std::string, std::string> side_users =
utils::map_split(level_.child_or_empty(
"multiplayer")[
"side_users"]);
761 const std::string& save_id = side->save_id();
762 const std::string& player_id = side->player_id();
763 if(!save_id.empty() && !player_id.empty()) {
764 side_users[save_id] = player_id;
768 level_.mandatory_child(
"multiplayer")[
"side_users"] =
utils::join_map(side_users);
771 void connect_engine::load_previous_sides_users()
773 std::map<std::string, std::string> side_users =
utils::map_split(level_.mandatory_child(
"multiplayer")[
"side_users"]);
774 std::set<std::string>
names;
776 const std::string& save_id = side->previous_save_id();
777 if(side_users.find(save_id) != side_users.end()) {
778 side->set_reserved_for(side_users[save_id]);
782 names.insert(side_users[save_id]);
785 side->update_controller_options();
790 for(
const std::string& name :
names)
792 if(connected_users().find(name) != connected_users().end() || !mp_metadata_) {
793 import_user(name,
false);
798 void connect_engine::update_side_controller_options()
801 side->update_controller_options();
805 const std::set<std::string>& connect_engine::connected_users()
const
808 return mp_metadata_->connected_players;
811 static std::set<std::string> empty;
815 std::set<std::string>& connect_engine::connected_users_rw()
817 assert(mp_metadata_);
818 return mp_metadata_->connected_players;
823 , parent_(parent_engine)
825 , current_controller_index_(0)
826 , controller_options_()
827 , allow_player_(cfg[
"allow_player"].to_bool(true))
828 , controller_lock_(cfg[
"controller_lock"].to_bool(parent_.force_lock_settings_) && parent_.params_.use_map_settings)
832 , gold_(cfg[
"gold"].to_int(100))
833 , income_(cfg[
"income"].to_int())
834 , reserved_for_(cfg[
"current_player"])
837 , chose_random_(cfg[
"chose_random"].to_bool(false))
838 , disallow_shuffle_(cfg[
"disallow_shuffle"].to_bool(false))
839 , flg_(parent_.era_factions_, cfg_, parent_.force_lock_settings_, parent_.params_.use_map_settings, parent_.params_.
saved_game ==
saved_game_mode::
type::midgame)
840 , allow_changes_(parent_.params_.
saved_game !=
saved_game_mode::
type::midgame && !(flg_.choosable_factions().
size() == 1 && flg_.choosable_leaders().
size() == 1 && flg_.choosable_genders().
size() == 1))
841 , waiting_to_choose_faction_(allow_changes_)
844 , color_id_(color_options_.
at(color_))
850 "type",
cfg_[
"type"],
851 "gender",
cfg_[
"gender"],
852 "faction",
cfg_[
"faction"],
853 "recruit",
cfg_[
"recruit"],
857 ERR_CF <<
"found invalid side=" <<
cfg_[
"side"].to_int(
index_ + 1) <<
" in definition of side number " <<
index_ + 1;
862 if(
cfg_[
"controller"] != side_controller::human &&
cfg_[
"controller"] != side_controller::ai &&
cfg_[
"controller"] != side_controller::none) {
871 cfg_[
"controller"] = side_controller::ai;
874 if(
cfg_[
"controller"] == side_controller::none) {
876 }
else if(
cfg_[
"controller"] == side_controller::ai) {
890 unsigned team_name_index = 0;
892 if(
data.team_name ==
cfg[
"team_name"]) {
902 WRN_MP <<
"In side_engine constructor: Could not find my team_name " <<
cfg[
"team_name"] <<
" among the mp connect engine's list of team names. I am being assigned to the first team. This may indicate a bug!";
904 team_ = team_name_index;
910 if(!given_color.empty()) {
935 return N_(
"Anonymous player");
940 return N_(
"Computer Player");
958 LOG_MP <<
"side_engine::new_config: side=" <<
index_ + 1 <<
" faction=" << faction[
"id"] <<
" recruit=" << faction[
"recruit"];
959 res[
"faction_name"] = faction[
"name"];
960 res[
"faction"] = faction[
"id"];
961 faction.
remove_attributes(
"id",
"name",
"image",
"gender",
"type",
"description");
980 res[
"user_description"] =
t_string(desc,
"wesnoth");
982 desc =
VGETTEXT(
"$playername $side", {
983 {
"playername",
_(desc.c_str())},
984 {
"side", res[
"side"].str()}
990 if(res[
"name"].str().
empty() && !desc.empty()) {
1022 res[
"current_player"] = desc;
1042 leader = &side_unit;
1046 std::string leader_id = (*leader)[
"id"];
1049 if(!leader_id.empty()) {
1050 (*leader)[
"id"] = leader_id;
1062 LOG_MP <<
"side_engine::new_config: side=" <<
index_ + 1 <<
" type=" << (*leader)[
"type"] <<
" gender=" << (*leader)[
"gender"];
1065 (*leader)[
"type"] =
"null";
1066 (*leader)[
"gender"] =
"null";
1072 res[
"team_name"] = new_team_name;
1078 res[
"gold"] =
gold_;
1085 res[
"name"] =
cfg_[
"name"];
1088 res[
"user_description"] =
cfg_[
"user_description"];
1174 data[
"name"] = name;
1184 if(
data[
"change_faction"].to_bool() && contains_selection) {
1286 const std::string& name,
const std::string& controller_value)
std::vector< std::string > names
static void add_mod_ai_from_config(config::const_child_itors configs)
static const config & get_ai_config_for(const std::string &id)
Return the config for a specified ai.
static void add_era_ai_from_config(const config &game_config)
utils::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_side
Non-empty if –side was given on the command line.
utils::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_ai_config
Non-empty if –ai-config was given on the command line.
utils::optional< std::string > multiplayer_turns
Non-empty if –turns was given on the command line.
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line.
utils::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_controller
Non-empty if –controller was given on the command line.
utils::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_algorithm
Non-empty if –algorithm was given on the command line.
utils::optional< std::vector< std::tuple< unsigned int, std::string, std::string > > > multiplayer_parm
Non-empty if –parm was given on the command line.
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
void append(const config &cfg)
Append data from another config object to this one.
void remove_attributes(T... keys)
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
boost::iterator_range< child_iterator > child_itors
config & add_child_at(config_key_type key, const config &val, std::size_t index)
void clear_children(T... keys)
bool has_attribute(config_key_type key) const
child_itors child_range(config_key_type key)
void remove_attribute(config_key_type key)
config get_diff(const config &c) const
A function to get the differences between this object, and 'c', as another config object.
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
config & add_child(config_key_type key)
bool is_normal_mp_game() const
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static game_config_view wrap(const config &cfg)
Encapsulates the map of the game.
std::vector< const config * > era_factions_
std::vector< team_data_pod > team_data_
const ng::controller default_controller_
config * current_config()
bool force_lock_settings_
const std::set< std::string > & connected_users() const
const mp_game_settings & params_
mp_game_metadata * mp_metadata_
connect_engine(saved_game &state, const bool first_scenario, mp_game_metadata *metadata)
const std::string & current_gender() const
void set_current_faction(const unsigned index)
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid)
const config * default_leader_cfg() const
void set_current_leader(const unsigned index)
void set_current_gender(const unsigned index)
const config & current_faction() const
const std::string & current_leader() const
const config & cfg() const
unsigned current_controller_index_
void update_controller_options()
const bool controller_lock_
const bool allow_changes_
void set_controller_commandline(const std::string &controller_name)
void set_faction_commandline(const std::string &faction_name)
void place_user(const std::string &name)
void add_controller_option(ng::controller controller, const std::string &name, const std::string &controller_value)
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid_faction_ids=std::vector< std::string >())
void update_current_controller_index()
std::vector< controller_option > controller_options_
std::string ai_algorithm_
std::string reserved_for_
std::vector< std::string > color_options_
void set_controller(ng::controller controller)
void set_waiting_to_choose_status(bool status)
bool controller_changed(const int selection)
std::string user_description() const
ng::controller controller_
bool ready_for_start() const
config new_config() const
bool waiting_to_choose_faction_
ng::controller controller() const
bool available_for_user(const std::string &name="") const
const std::string get_ignored_delim()
uint32_t get_next_random()
Get a new random number.
game_classification & classification()
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
std::string to_serialized() const
static std::string get_side_color_id_from_config(const config &cfg)
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Managing the AIs configuration - headers.
static lg::log_domain log_network("network")
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
static lg::log_domain log_config("config")
static std::string _(const char *str)
std::vector< const mp::user_info * > user_data
The associated user data for each node, index-to-index.
Standard logging facilities (interface).
A small explanation about what's going on here: Each action has access to two game_info objects First...
Game configuration data as global variables.
std::vector< std::string > default_colors
static void add_color_info(const game_config_view &v, bool build_defaults)
static std::string controller_name(const team &t)
config initial_level_config(saved_game &state)
void level_to_gamestate(const config &level, saved_game &state)
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
std::pair< ng::controller, std::string > controller_option
std::shared_ptr< side_engine > side_engine_ptr
static std::string at(const std::string &file, int line)
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
std::map< std::string, std::string > map_split(const std::string &val, char major, char minor, int flags, const std::string &default_value)
Splits a string based on two separators into a map.
std::string join_map(const T &v, const std::string &major=",", const std::string &minor=":")
saved_game_mode::type saved_game
The base template for associating string values with enum values.
static map_location::direction s