1 /*
2 Copyright (C) 2013 - 2018 by Andrius Silinskas <silinskas.andrius@gmail.com>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14 #include "game_initialization/connect_engine.hpp"
15
16 #include "ai/configuration.hpp"
17 #include "formula/string_utils.hpp"
18 #include "game_initialization/mp_game_utils.hpp"
19 #include "game_initialization/playcampaign.hpp"
20 #include "preferences/credentials.hpp"
21 #include "preferences/game.hpp"
22 #include "gettext.hpp"
23 #include "log.hpp"
24 #include "map/map.hpp"
25 #include "mt_rng.hpp"
26 #include "team.hpp"
27 #include "tod_manager.hpp"
28 #include "wesnothd_connection.hpp"
29
30 #include <cstdlib>
31 #include <ctime>
32
33 static lg::log_domain log_config("config");
34 #define LOG_CF LOG_STREAM(info, log_config)
35 #define ERR_CF LOG_STREAM(err, log_config)
36
37 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
38 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
39 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
40 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
41 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
42
43 static lg::log_domain log_network("network");
44 #define LOG_NW LOG_STREAM(info, log_network)
45
46 static const std::string controller_names[] {
47 "human",
48 "human",
49 "ai",
50 "null",
51 "reserved"
52 };
53
54 namespace ng {
55
connect_engine(saved_game & state,const bool first_scenario,mp_campaign_info * campaign_info)56 connect_engine::connect_engine(saved_game& state, const bool first_scenario, mp_campaign_info* campaign_info)
57 : level_()
58 , state_(state)
59 , params_(state.mp_settings())
60 , default_controller_(campaign_info ? CNTR_NETWORK : CNTR_LOCAL)
61 , campaign_info_(campaign_info)
62 , first_scenario_(first_scenario)
63 , force_lock_settings_()
64 , side_engines_()
65 , era_factions_()
66 , team_data_()
67 {
68 // Initial level config from the mp_game_settings.
69 level_ = mp::initial_level_config(state_);
70 if(level_.empty()) {
71 return;
72 }
73
74 const bool is_mp = state_.classification().is_normal_mp_game();
75 force_lock_settings_ = (!state.mp_settings().saved_game) && scenario()["force_lock_settings"].to_bool(!is_mp);
76
77 // Original level sides.
78 config::child_itors sides = current_config()->child_range("side");
79
80 // AI algorithms.
81 ai::configuration::add_era_ai_from_config(level_.child("era"));
82 ai::configuration::add_mod_ai_from_config(level_.child_range("modification"));
83
84 // Set the team name lists and modify the original level sides if necessary.
85 std::vector<std::string> original_team_names;
86 std::string team_prefix(_("Team") + " ");
87
88 int side_count = 1;
89 for(config& side : sides) {
90 const std::string side_str = std::to_string(side_count);
91
92 config::attribute_value& team_name = side["team_name"];
93 config::attribute_value& user_team_name = side["user_team_name"];
94
95 // Revert to default values if appropriate.
96 if(team_name.empty()) {
97 team_name = side_str;
98 }
99
100 if(params_.use_map_settings && user_team_name.empty()) {
101 user_team_name = team_name;
102 }
103
104 bool add_team = true;
105 if(params_.use_map_settings) {
106 // Only add a team if it is not found.
107 if(std::any_of(team_data_.begin(), team_data_.end(), [&team_name](const team_data_pod& data){
108 return data.team_name == team_name.str();
109 })) {
110 add_team = false;
111 }
112 } else {
113 // Always add a new team for every side, but leave the specified team assigned to a side if there is one.
114 auto name_itor = std::find(original_team_names.begin(), original_team_names.end(), team_name.str());
115
116 // Note that the prefix "Team " is untranslatable, as team_name is not meant to be translated. This is needed
117 // so that the attribute is not interpretted as an int when reading from config, which causes bugs later.
118 if(name_itor == original_team_names.end()) {
119 original_team_names.push_back(team_name);
120
121 team_name = "Team " + std::to_string(original_team_names.size());
122 } else {
123 team_name = "Team " + std::to_string(std::distance(original_team_names.begin(), name_itor) + 1);
124 }
125
126 user_team_name = team_prefix + side_str;
127 }
128
129 // Write the serialized translatable team name back to the config. Without this,
130 // the string can appear all messed up after leaving and rejoining a game (see
131 // issue #2040. This affected the mp_join_game dialog). I don't know why that issue
132 // didn't appear the first time you join a game, but whatever.
133 //
134 // The difference between that dialog and mp_staging is that the latter has access
135 // to connect_engine object, meaning it has access to serialized versions of the
136 // user_team_name string stored in the team_data_ vector. mp_join_game handled the
137 // raw side config instead. Again, I don't know why issues only cropped up on a
138 // subsequent join and not the first, but it doesn't really matter.
139 //
140 // This ensures both dialogs have access to the serialized form of the utn string.
141 // As for why this needs to be done in the first place, apparently the simple_wml
142 // format the server (wesnothd) uses doesn't preserve translatable strings (see
143 // issue #342).
144 //
145 // --vultraz, 2018-02-06
146 user_team_name = user_team_name.t_str().to_serialized();
147
148 if(add_team) {
149 team_data_pod data;
150 data.team_name = params_.use_map_settings ? team_name : "Team " + side_str;
151 data.user_team_name = user_team_name.str();
152 data.is_player_team = side["allow_player"].to_bool(true);
153
154 team_data_.push_back(data);
155 }
156
157 ++side_count;
158 }
159
160 // Selected era's factions.
161 for(const config& era : level_.child("era").child_range("multiplayer_side")) {
162 era_factions_.push_back(&era);
163 }
164
165 // Sort alphabetically, but with the random faction options always first.
166 // Since some eras have multiple random options we can't just assume there is
167 // only one random faction on top of the list.
168 std::sort(era_factions_.begin(), era_factions_.end(), [](const config* c1, const config* c2) {
169 const config& lhs = *c1;
170 const config& rhs = *c2;
171
172 // Random factions always first.
173 if(lhs["random_faction"].to_bool() && !rhs["random_faction"].to_bool()) {
174 return true;
175 }
176
177 if(!lhs["random_faction"].to_bool() && rhs["random_faction"].to_bool()) {
178 return false;
179 }
180
181 return translation::compare(lhs["name"].str(), rhs["name"].str()) < 0;
182 });
183
184 game_config::add_color_info(scenario());
185
186 // Create side engines.
187 int index = 0;
188 for(const config& s : sides) {
189 side_engines_.emplace_back(new side_engine(s, *this, index));
190
191 index++;
192 }
193
194 if(first_scenario_) {
195 // Add host to the connected users list.
196 import_user(preferences::login(), false);
197 } else {
198 // Add host but don't assign a side to him.
199 import_user(preferences::login(), true);
200
201 // Load reserved players information into the sides.
202 load_previous_sides_users();
203 }
204
205 // Only updates the sides in the level.
206 update_level();
207
208 // If we are connected, send data to the connected host.
209 send_level_data();
210 }
211
212
current_config()213 config* connect_engine::current_config() {
214 if(config& s = scenario()) {
215 return &s;
216 }
217
218 return nullptr;
219 }
220
import_user(const std::string & name,const bool observer,int side_taken)221 void connect_engine::import_user(const std::string& name, const bool observer, int side_taken)
222 {
223 config user_data;
224 user_data["name"] = name;
225 import_user(user_data, observer, side_taken);
226 }
227
import_user(const config & data,const bool observer,int side_taken)228 void connect_engine::import_user(const config& data, const bool observer, int side_taken)
229 {
230 const std::string& username = data["name"];
231 assert(!username.empty());
232 if(campaign_info_) {
233 connected_users_rw().insert(username);
234 }
235
236 update_side_controller_options();
237
238 if(observer) {
239 return;
240 }
241
242 bool side_assigned = false;
243 if(side_taken >= 0) {
244 side_engines_[side_taken]->place_user(data, true);
245 side_assigned = true;
246 }
247
248 // Check if user has a side(s) reserved for him.
249 for(side_engine_ptr side : side_engines_) {
250 if(side->reserved_for() == username && side->player_id().empty() && side->controller() != CNTR_COMPUTER) {
251 side->place_user(data);
252
253 side_assigned = true;
254 }
255 }
256
257 // If no sides were assigned for a user,
258 // take a first available side.
259 if(side_taken < 0 && !side_assigned) {
260 for(side_engine_ptr side : side_engines_) {
261 if(side->available_for_user(username) ||
262 side->controller() == CNTR_LOCAL) {
263 side->place_user(data);
264
265 side_assigned = true;
266 break;
267 }
268 }
269 }
270
271 // Check if user has taken any sides, which should get control
272 // over any other sides.
273 for(side_engine_ptr user_side : side_engines_) {
274 if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
275 for(side_engine_ptr side : side_engines_){
276 if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
277 side->place_user(data);
278 }
279 }
280 }
281 }
282 }
283
sides_available() const284 bool connect_engine::sides_available() const
285 {
286 for(side_engine_ptr side : side_engines_) {
287 if(side->available_for_user()) {
288 return true;
289 }
290 }
291
292 return false;
293 }
294
update_level()295 void connect_engine::update_level()
296 {
297 DBG_MP << "updating level" << std::endl;
298
299 scenario().clear_children("side");
300
301 for(side_engine_ptr side : side_engines_) {
302 scenario().add_child("side", side->new_config());
303 }
304 }
305
update_and_send_diff(bool)306 void connect_engine::update_and_send_diff(bool /*update_time_of_day*/)
307 {
308 config old_level = level_;
309 update_level();
310
311 config diff = level_.get_diff(old_level);
312 if(!diff.empty()) {
313 config scenario_diff;
314 scenario_diff.add_child("scenario_diff", std::move(diff));
315 send_to_server(scenario_diff);
316 }
317 }
318
can_start_game() const319 bool connect_engine::can_start_game() const
320 {
321 if(side_engines_.empty()) {
322 return true;
323 }
324
325 // First check if all sides are ready to start the game.
326 for(side_engine_ptr side : side_engines_) {
327 if(!side->ready_for_start()) {
328 const int side_num = side->index() + 1;
329 DBG_MP << "not all sides are ready, side " <<
330 side_num << " not ready\n";
331
332 return false;
333 }
334 }
335
336 DBG_MP << "all sides are ready" << std::endl;
337
338 /*
339 * If at least one human player is slotted with a player/ai we're allowed
340 * to start. Before used a more advanced test but it seems people are
341 * creative in what is used in multiplayer [1] so use a simpler test now.
342 * [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
343 */
344 for(side_engine_ptr side : side_engines_) {
345 if(side->controller() != CNTR_EMPTY && side->allow_player()) {
346 return true;
347 }
348 }
349
350 return false;
351 }
352
send_to_server(const config & cfg) const353 void connect_engine::send_to_server(const config& cfg) const
354 {
355 if(campaign_info_) {
356 campaign_info_->connection.send_data(cfg);
357 }
358 }
359
receive_from_server(config & dst) const360 bool connect_engine::receive_from_server(config& dst) const
361 {
362 if(campaign_info_) {
363 return campaign_info_->connection.receive_data(dst);
364 }
365 else {
366 return false;
367 }
368 }
369
get_children_to_swap()370 std::vector<std::string> side_engine::get_children_to_swap()
371 {
372 std::vector<std::string> children;
373
374 children.push_back("village");
375 children.push_back("unit");
376 children.push_back("ai");
377
378 return children;
379 }
380
get_side_children()381 std::multimap<std::string, config> side_engine::get_side_children()
382 {
383 std::multimap<std::string, config> children;
384
385 for(const std::string& children_to_swap : get_children_to_swap()) {
386 for(const config& child : cfg_.child_range(children_to_swap)) {
387 children.emplace(children_to_swap, child);
388 }
389 }
390
391 return children;
392 }
393
set_side_children(std::multimap<std::string,config> children)394 void side_engine::set_side_children(std::multimap<std::string, config> children)
395 {
396 for(const std::string& children_to_remove : get_children_to_swap()) {
397 cfg_.clear_children(children_to_remove);
398 }
399
400 for(std::pair<std::string, config> child_map : children) {
401 cfg_.add_child(child_map.first, child_map.second);
402 }
403 }
404
start_game()405 void connect_engine::start_game()
406 {
407 DBG_MP << "starting a new game" << std::endl;
408
409 // Resolves the "random faction", "random gender" and "random message"
410 // Must be done before shuffle sides, or some cases will cause errors
411 randomness::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
412 for(side_engine_ptr side : side_engines_) {
413 std::vector<std::string> avoid_faction_ids;
414
415 // If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
416 if(params_.random_faction_mode != mp_game_settings::RANDOM_FACTION_MODE::DEFAULT) {
417 for(side_engine_ptr side2 : side_engines_) {
418 if(!side2->flg().is_random_faction()) {
419 switch(params_.random_faction_mode.v) {
420 case mp_game_settings::RANDOM_FACTION_MODE::NO_MIRROR:
421 avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
422 break;
423 case mp_game_settings::RANDOM_FACTION_MODE::NO_ALLY_MIRROR:
424 if(side2->team() == side->team()) {// TODO: When the connect engines are fixed to allow multiple teams, this should be changed to "if side1 and side2 are allied, i.e. their list of teams has nonempty intersection"
425 avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
426 }
427 break;
428 default:
429 break; // assert(false);
430 }
431 }
432 }
433 }
434 side->resolve_random(rng, avoid_faction_ids);
435 }
436
437 // Shuffle sides (check settings and if it is a re-loaded game).
438 // Must be done after resolve_random() or shuffle sides, or they won't work.
439 if(state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.child("snapshot") && level_.child("snapshot").child("side"))) {
440
441 // Only playable sides should be shuffled.
442 std::vector<int> playable_sides;
443 for(side_engine_ptr side : side_engines_) {
444 if(side->allow_player() && side->allow_shuffle()) {
445 playable_sides.push_back(side->index());
446 }
447 }
448
449 // Fisher-Yates shuffle.
450 for(int i = playable_sides.size(); i > 1; i--) {
451 int j_side = playable_sides[rng.get_next_random() % i];
452 int i_side = playable_sides[i - 1];
453
454 if(i_side == j_side) continue; //nothing to swap
455
456 // First we swap everything about a side with another
457 side_engine_ptr tmp_side = side_engines_[j_side];
458 side_engines_[j_side] = side_engines_[i_side];
459 side_engines_[i_side] = tmp_side;
460
461 // Some 'child' variables such as village ownership and
462 // initial side units need to be swapped over as well
463 std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
464 side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
465 side_engines_[i_side]->set_side_children(tmp_side_children);
466
467 // Then we revert the swap for fields that are unique to
468 // player control and the team they selected
469 int tmp_index = side_engines_[j_side]->index();
470 side_engines_[j_side]->set_index(side_engines_[i_side]->index());
471 side_engines_[i_side]->set_index(tmp_index);
472
473 int tmp_team = side_engines_[j_side]->team();
474 side_engines_[j_side]->set_team(side_engines_[i_side]->team());
475 side_engines_[i_side]->set_team(tmp_team);
476 }
477 }
478
479 // Make other clients not show the results of resolve_random().
480 config lock("stop_updates");
481 send_to_server(lock);
482
483 update_and_send_diff(true);
484
485 save_reserved_sides_information();
486
487 // Build the gamestate object after updating the level.
488 mp::level_to_gamestate(level_, state_);
489
490 send_to_server(config("start_game"));
491 }
492
start_game_commandline(const commandline_options & cmdline_opts,const config & game_config)493 void connect_engine::start_game_commandline(const commandline_options& cmdline_opts, const config& game_config)
494 {
495 DBG_MP << "starting a new game in commandline mode" << std::endl;
496
497 typedef std::tuple<unsigned int, std::string> mp_option;
498
499 randomness::mt_rng rng;
500
501 unsigned num = 0;
502 for(side_engine_ptr side : side_engines_) {
503 num++;
504
505 // Set the faction, if commandline option is given.
506 if(cmdline_opts.multiplayer_side) {
507 for(const mp_option& option : *cmdline_opts.multiplayer_side) {
508
509 if(std::get<0>(option) == num) {
510 if(std::find_if(era_factions_.begin(), era_factions_.end(), [&option](const config* faction) { return (*faction)["id"] == std::get<1>(option); }) != era_factions_.end()) {
511 DBG_MP << "\tsetting side " << std::get<0>(option) << "\tfaction: " << std::get<1>(option) << std::endl;
512
513 side->set_faction_commandline(std::get<1>(option));
514 }
515 else {
516 ERR_MP << "failed to set side " << std::get<0>(option) << " to faction " << std::get<1>(option) << std::endl;
517 }
518 }
519 }
520 }
521
522 // Set the controller, if commandline option is given.
523 if(cmdline_opts.multiplayer_controller) {
524 for(const mp_option& option : *cmdline_opts.multiplayer_controller) {
525
526 if(std::get<0>(option) == num) {
527 DBG_MP << "\tsetting side " << std::get<0>(option) <<
528 "\tfaction: " << std::get<1>(option) << std::endl;
529
530 side->set_controller_commandline(std::get<1>(option));
531 }
532 }
533 }
534
535 // Set AI algorithm to default for all sides,
536 // then override if commandline option was given.
537 std::string ai_algorithm = game_config.child("ais")["default_ai_algorithm"].str();
538 side->set_ai_algorithm(ai_algorithm);
539 if(cmdline_opts.multiplayer_algorithm) {
540 for(const mp_option& option : *cmdline_opts.multiplayer_algorithm) {
541
542 if(std::get<0>(option) == num) {
543 DBG_MP << "\tsetting side " << std::get<0>(option) <<
544 "\tfaction: " << std::get<1>(option) << std::endl;
545
546 side->set_ai_algorithm(std::get<1>(option));
547 }
548 }
549 }
550
551 // Finally, resolve "random faction",
552 // "random gender" and "random message", if any remains unresolved.
553 side->resolve_random(rng);
554 } // end top-level loop
555
556 update_and_send_diff(true);
557
558 // Update sides with commandline parameters.
559 if(cmdline_opts.multiplayer_turns) {
560 DBG_MP << "\tsetting turns: " << *cmdline_opts.multiplayer_turns <<
561 std::endl;
562 scenario()["turns"] = *cmdline_opts.multiplayer_turns;
563 }
564
565 for(config &side : scenario().child_range("side")) {
566 if(cmdline_opts.multiplayer_ai_config) {
567 for(const mp_option& option : *cmdline_opts.multiplayer_ai_config) {
568
569 if(std::get<0>(option) == side["side"].to_unsigned()) {
570 DBG_MP << "\tsetting side " << side["side"] <<
571 "\tai_config: " << std::get<1>(option) << std::endl;
572
573 side["ai_config"] = std::get<1>(option);
574 }
575 }
576 }
577
578 // Having hard-coded values here is undesirable,
579 // but that's how it is done in the MP lobby
580 // part of the code also.
581 // Should be replaced by settings/constants in both places
582 if(cmdline_opts.multiplayer_ignore_map_settings) {
583 side["gold"] = 100;
584 side["income"] = 1;
585 }
586
587 typedef std::tuple<unsigned int, std::string, std::string> mp_parameter;
588
589 if(cmdline_opts.multiplayer_parm) {
590 for(const mp_parameter& parameter : *cmdline_opts.multiplayer_parm) {
591
592 if(std::get<0>(parameter) == side["side"].to_unsigned()) {
593 DBG_MP << "\tsetting side " << side["side"] << " " <<
594 std::get<1>(parameter) << ": " << std::get<2>(parameter) << std::endl;
595
596 side[std::get<1>(parameter)] = std::get<2>(parameter);
597 }
598 }
599 }
600 }
601
602 save_reserved_sides_information();
603
604 // Build the gamestate object after updating the level
605 mp::level_to_gamestate(level_, state_);
606 send_to_server(config("start_game"));
607 }
608
leave_game()609 void connect_engine::leave_game()
610 {
611 DBG_MP << "leaving the game" << std::endl;
612
613 send_to_server(config("leave_game"));
614 }
615
process_network_data(const config & data)616 std::pair<bool, bool> connect_engine::process_network_data(const config& data)
617 {
618 std::pair<bool, bool> result(std::make_pair(false, true));
619
620 if(data.child("leave_game")) {
621 result.first = true;
622 return result;
623 }
624
625 // A side has been dropped.
626 if(const config& side_drop = data.child("side_drop")) {
627 unsigned side_index = side_drop["side_num"].to_int() - 1;
628
629 if(side_index < side_engines_.size()) {
630 side_engine_ptr side_to_drop = side_engines_[side_index];
631
632 // Remove user, whose side was dropped.
633 connected_users_rw().erase(side_to_drop->player_id());
634 update_side_controller_options();
635
636 side_to_drop->reset();
637
638 update_and_send_diff();
639
640 return result;
641 }
642 }
643
644 // A player is connecting to the game.
645 if(!data["side"].empty()) {
646 unsigned side_taken = data["side"].to_int() - 1;
647
648 // Checks if the connecting user has a valid and unique name.
649 const std::string name = data["name"];
650 if(name.empty()) {
651 config response;
652 response["failed"] = true;
653 send_to_server(response);
654
655 ERR_CF << "ERROR: No username provided with the side." << std::endl;
656
657 return result;
658 }
659
660 if(connected_users().find(name) != connected_users().end()) {
661 // TODO: Seems like a needless limitation
662 // to only allow one side per player.
663 if(find_user_side_index_by_id(name) != -1) {
664 config response;
665 response["failed"] = true;
666 response["message"] = "The nickname '" + name +
667 "' is already in use.";
668 send_to_server(response);
669
670 return result;
671 } else {
672 connected_users_rw().erase(name);
673 update_side_controller_options();
674 config observer_quit;
675 observer_quit.add_child("observer_quit")["name"] = name;
676 send_to_server(observer_quit);
677 }
678 }
679
680 // Assigns this user to a side.
681 if(side_taken < side_engines_.size()) {
682 if(!side_engines_[side_taken]->available_for_user(name)) {
683 // This side is already taken.
684 // Try to reassing the player to a different position.
685 side_taken = 0;
686 for(side_engine_ptr s : side_engines_) {
687 if(s->available_for_user()) {
688 break;
689 }
690
691 side_taken++;
692 }
693
694 if(side_taken >= side_engines_.size()) {
695 config response;
696 response["failed"] = true;
697 send_to_server(response);
698
699 config res;
700 config& kick = res.add_child("kick");
701 kick["username"] = data["name"];
702 send_to_server(res);
703
704 update_and_send_diff();
705
706 ERR_CF << "ERROR: Couldn't assign a side to '" <<
707 name << "'\n";
708
709 return result;
710 }
711 }
712
713 LOG_CF << "client has taken a valid position\n";
714
715 import_user(data, false, side_taken);
716 update_and_send_diff();
717
718 // Wait for them to choose faction if allowed.
719 side_engines_[side_taken]->set_waiting_to_choose_status(side_engines_[side_taken]->allow_changes());
720 LOG_MP << "waiting to choose status = " << side_engines_[side_taken]->allow_changes() << std::endl;
721 result.second = false;
722
723 LOG_NW << "sent player data\n";
724 } else {
725 ERR_CF << "tried to take illegal side: " << side_taken << std::endl;
726
727 config response;
728 response["failed"] = true;
729 send_to_server(response);
730 }
731 }
732
733 if(const config& change_faction = data.child("change_faction")) {
734 int side_taken = find_user_side_index_by_id(change_faction["name"]);
735 if(side_taken != -1 || !first_scenario_) {
736 import_user(change_faction, false, side_taken);
737 update_and_send_diff();
738 }
739 }
740
741 if(const config& observer = data.child("observer")) {
742 import_user(observer, true);
743 update_and_send_diff();
744 }
745
746 if(const config& observer = data.child("observer_quit")) {
747 const std::string& observer_name = observer["name"];
748
749 if(connected_users().find(observer_name) != connected_users().end()) {
750 connected_users_rw().erase(observer_name);
751 update_side_controller_options();
752
753 // If the observer was assigned a side, we need to send an update to other
754 // players so they no longer see the observer assigned to that side.
755 if(find_user_side_index_by_id(observer_name) != -1) {
756 update_and_send_diff();
757 }
758 }
759 }
760
761 return result;
762 }
763
find_user_side_index_by_id(const std::string & id) const764 int connect_engine::find_user_side_index_by_id(const std::string& id) const
765 {
766 size_t i = 0;
767 for(side_engine_ptr side : side_engines_) {
768 if(side->player_id() == id) {
769 break;
770 }
771
772 i++;
773 }
774
775 if(i >= side_engines_.size()) {
776 return -1;
777 }
778
779 return i;
780 }
781
send_level_data() const782 void connect_engine::send_level_data() const
783 {
784 // Send initial information.
785 if(first_scenario_) {
786 send_to_server(config {
787 "create_game", config {
788 "name", params_.name,
789 "password", params_.password,
790 },
791 });
792 send_to_server(level_);
793 } else {
794 send_to_server(config {"update_game", config()});
795 config next_level;
796 next_level.add_child("store_next_scenario", level_);
797 send_to_server(next_level);
798 }
799 }
800
save_reserved_sides_information()801 void connect_engine::save_reserved_sides_information()
802 {
803 // Add information about reserved sides to the level config.
804 // N.B. This information is needed only for a host player.
805 std::map<std::string, std::string> side_users = utils::map_split(level_.child_or_empty("multiplayer")["side_users"]);
806 for(side_engine_ptr side : side_engines_) {
807 const std::string& save_id = side->save_id();
808 const std::string& player_id = side->player_id();
809 if(!save_id.empty() && !player_id.empty()) {
810 side_users[save_id] = player_id;
811 }
812 }
813
814 level_.child("multiplayer")["side_users"] = utils::join_map(side_users);
815 }
816
load_previous_sides_users()817 void connect_engine::load_previous_sides_users()
818 {
819 std::map<std::string, std::string> side_users = utils::map_split(level_.child("multiplayer")["side_users"]);
820 std::set<std::string> names;
821 for(side_engine_ptr side : side_engines_) {
822 const std::string& save_id = side->previous_save_id();
823 if(side_users.find(save_id) != side_users.end()) {
824 side->set_reserved_for(side_users[save_id]);
825
826 if(side->controller() != CNTR_COMPUTER) {
827 side->set_controller(CNTR_RESERVED);
828 names.insert(side_users[save_id]);
829 }
830
831 side->update_controller_options();
832 }
833 }
834
835 //Do this in an extra loop to make sure we import each user only once.
836 for(const std::string& name : names)
837 {
838 if(connected_users().find(name) != connected_users().end() || !campaign_info_) {
839 import_user(name, false);
840 }
841 }
842 }
843
update_side_controller_options()844 void connect_engine::update_side_controller_options()
845 {
846 for(side_engine_ptr side : side_engines_) {
847 side->update_controller_options();
848 }
849 }
850
connected_users() const851 const std::set<std::string>& connect_engine::connected_users() const
852 {
853 if(campaign_info_) {
854 return campaign_info_->connected_players;
855 }
856
857 static std::set<std::string> empty;
858 return empty;
859 }
860
connected_users_rw()861 std::set<std::string>& connect_engine::connected_users_rw()
862 {
863 assert(campaign_info_);
864 return campaign_info_->connected_players;
865 }
866
side_engine(const config & cfg,connect_engine & parent_engine,const int index)867 side_engine::side_engine(const config& cfg, connect_engine& parent_engine, const int index)
868 : cfg_(cfg)
869 , parent_(parent_engine)
870 , controller_(CNTR_NETWORK)
871 , current_controller_index_(0)
872 , controller_options_()
873 , allow_player_(cfg["allow_player"].to_bool(true))
874 , controller_lock_(cfg["controller_lock"].to_bool(parent_.force_lock_settings_) && parent_.params_.use_map_settings)
875 , index_(index)
876 , team_(0)
877 , color_(std::min(index, gamemap::MAX_PLAYERS - 1))
878 , gold_(cfg["gold"].to_int(100))
879 , income_(cfg["income"])
880 , reserved_for_(cfg["current_player"])
881 , player_id_()
882 , ai_algorithm_()
883 , chose_random_(cfg["chose_random"].to_bool(false))
884 , disallow_shuffle_(cfg["disallow_shuffle"].to_bool(false))
885 , flg_(parent_.era_factions_, cfg_, parent_.force_lock_settings_, parent_.params_.use_map_settings, parent_.params_.saved_game)
886 , allow_changes_(!parent_.params_.saved_game && !(flg_.choosable_factions().size() == 1 && flg_.choosable_leaders().size() == 1 && flg_.choosable_genders().size() == 1))
887 , waiting_to_choose_faction_(allow_changes_)
888 , color_options_(game_config::default_colors)
889 , color_id_(color_options_[color_])
890 {
891 // Save default attributes that could be overwirtten by the faction, so that correct faction lists would be
892 // initialized by flg_manager when the new side config is sent over network.
893 cfg_.add_child("default_faction", config {
894 "type", cfg_["type"],
895 "gender", cfg_["gender"],
896 "faction", cfg_["faction"],
897 "recruit", cfg_["recruit"],
898 });
899
900 if(cfg_["side"].to_int(index_ + 1) != index_ + 1) {
901 ERR_CF << "found invalid side=" << cfg_["side"].to_int(index_ + 1) << " in definition of side number " << index_ + 1 << std::endl;
902 }
903
904 cfg_["side"] = index_ + 1;
905
906 // Check if this side should give its control to some other side.
907 const size_t side_cntr_index = cfg_["controller"].to_int(-1) - 1;
908 if(side_cntr_index < parent_.side_engines().size()) {
909 // Remove this attribute to avoid locking side
910 // to non-existing controller type.
911 cfg_.remove_attribute("controller");
912
913 cfg_["previous_save_id"] = parent_.side_engines()[side_cntr_index]->previous_save_id();
914 ERR_MP << "controller=<number> is deperecated\n";
915 }
916
917 if(cfg_["controller"] != "human" && cfg_["controller"] != "ai" && cfg_["controller"] != "null") {
918 //an invalid controller type was specified. Remove it to prevent asertion failures later.
919 cfg_.remove_attribute("controller");
920 }
921
922 update_controller_options();
923
924 // Tweak the controllers.
925 if(parent_.state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::SCENARIO && cfg_["controller"].blank()) {
926 cfg_["controller"] = "ai";
927 }
928
929 if(cfg_["controller"] == "null") {
930 set_controller(CNTR_EMPTY);
931 } else if(cfg_["controller"] == "ai") {
932 set_controller(CNTR_COMPUTER);
933 } else if(parent_.default_controller_ == CNTR_NETWORK && !reserved_for_.empty()) {
934 // Reserve a side for "current_player", unless the side
935 // is played by an AI.
936 set_controller(CNTR_RESERVED);
937 } else if(allow_player_) {
938 set_controller(parent_.default_controller_);
939 } else {
940 // AI is the default.
941 set_controller(CNTR_COMPUTER);
942 }
943
944 // Initialize team and color.
945 unsigned team_name_index = 0;
946 for(const connect_engine::team_data_pod& data : parent_.team_data_) {
947 if(data.team_name == cfg["team_name"]) {
948 break;
949 }
950
951 ++team_name_index;
952 }
953
954 if(team_name_index >= parent_.team_data_.size()) {
955 assert(!parent_.team_data_.empty());
956 team_ = 0;
957 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!" << std::endl;
958 } else {
959 team_ = team_name_index;
960 }
961
962 // Check the value of the config's color= key.
963 const std::string given_color = team::get_side_color_id_from_config(cfg_);
964
965 if(!given_color.empty()) {
966 // If it's valid, save the color...
967 color_id_ = given_color;
968
969 // ... and find the appropriate index for it.
970 const auto iter = std::find(color_options_.begin(), color_options_.end(), color_id_);
971
972 if(iter != color_options_.end()) {
973 color_ = std::distance(color_options_.begin(), iter);
974 } else {
975 color_options_.push_back(color_id_);
976 color_ = color_options_.size() - 1;
977 }
978 }
979
980 // Initialize ai algorithm.
981 if(const config& ai = cfg.child("ai")) {
982 ai_algorithm_ = ai["ai_algorithm"].str();
983 }
984 }
985
user_description() const986 std::string side_engine::user_description() const
987 {
988 switch(controller_) {
989 case CNTR_LOCAL:
990 return N_("Anonymous player");
991 case CNTR_COMPUTER:
992 if(allow_player_) {
993 return ai::configuration::get_ai_config_for(ai_algorithm_)["description"];
994 } else {
995 return N_("Computer Player");
996 }
997 default:
998 return "";
999 }
1000 }
1001
new_config() const1002 config side_engine::new_config() const
1003 {
1004 config res = cfg_;
1005
1006 // In case of 'shuffle sides' the side index in cfg_ might be wrong which will confuse the team constructor later.
1007 res["side"] = index_ + 1;
1008
1009 // If the user is allowed to change type, faction, leader etc, then import their new values in the config.
1010 if(!parent_.params_.saved_game) {
1011 // Merge the faction data to res.
1012 config faction = flg_.current_faction();
1013 LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " faction=" << faction["id"] << " recruit=" << faction["recruit"] << "\n";
1014 res["faction_name"] = faction["name"];
1015 res["faction"] = faction["id"];
1016 faction.remove_attributes("id", "name", "image", "gender", "type", "description");
1017 res.append(faction);
1018 }
1019
1020 res["controller"] = controller_names[controller_];
1021
1022 // The hosts receives the serversided controller tweaks after the start event, but
1023 // for mp sync it's very important that the controller types are correct
1024 // during the start/prestart event (otherwise random unit creation during prestart fails).
1025 res["is_local"] = player_id_ == preferences::login() || controller_ == CNTR_COMPUTER || controller_ == CNTR_LOCAL;
1026
1027 // This function (new_config) is only meant to be called by the host's machine, which is why this check
1028 // works. It essentially certifies that whatever side has the player_id that matches the host's login
1029 // will be flagged. The reason we cannot check mp_campaign_info::is_host is because that flag is *always*
1030 // true on the host's machine, meaning this flag would be set to true for every side.
1031 res["is_host"] = player_id_ == preferences::login();
1032
1033 std::string desc = user_description();
1034 if(!desc.empty()) {
1035 res["user_description"] = t_string(desc, "wesnoth");
1036
1037 desc = VGETTEXT("$playername $side", {
1038 {"playername", _(desc.c_str())},
1039 {"side", res["side"].str()}
1040 });
1041 } else if(!player_id_.empty()) {
1042 desc = player_id_;
1043 }
1044
1045 if(res["name"].str().empty() && !desc.empty()) {
1046 //TODO: maybe we should add this in to the leaders config instead of the side config?
1047 res["name"] = desc;
1048 }
1049
1050 if(controller_ == CNTR_COMPUTER && allow_player_) {
1051 // If this is a saved game and "use_saved" (the default) was chosen for the
1052 // AI algorithm, we do nothing. Otherwise we add the chosen AI and if this
1053 // is a saved game, we also remove the old stages from the AI config.
1054 if(ai_algorithm_ != "use_saved") {
1055 if(parent_.params_.saved_game) {
1056 for (config &ai_config : res.child_range("ai")) {
1057 ai_config.clear_children("stage");
1058 }
1059 }
1060 res.add_child_at("ai", config {"ai_algorithm", ai_algorithm_}, 0);
1061 }
1062 }
1063
1064 // A side's "current_player" is the player which has currently taken that side or the one for which it is reserved.
1065 // The "player_id" is the id of the client who controls that side. It's always the host for Local and AI players and
1066 // always empty for free/reserved sides or null controlled sides. You can use !res["player_id"].empty() to check
1067 // whether a side is already taken.
1068 assert(!preferences::login().empty());
1069 if(controller_ == CNTR_LOCAL) {
1070 res["player_id"] = preferences::login();
1071 res["current_player"] = preferences::login();
1072 } else if(controller_ == CNTR_RESERVED) {
1073 res.remove_attribute("player_id");
1074 res["current_player"] = reserved_for_;
1075 } else if(controller_ == CNTR_COMPUTER) {
1076 // TODO: what is the content of player_id_ here ?
1077 res["current_player"] = desc;
1078 res["player_id"] = preferences::login();
1079 } else if(!player_id_.empty()) {
1080 res["player_id"] = player_id_;
1081 res["current_player"] = player_id_;
1082 }
1083
1084 res["allow_changes"] = allow_changes_;
1085 res["chose_random"] = chose_random_;
1086
1087 if(!parent_.params_.saved_game) {
1088 // Find a config where a default leader is and set a new type and gender values for it.
1089 config* leader = &res;
1090
1091 if(flg_.default_leader_cfg() != nullptr) {
1092 for(config& side_unit : res.child_range("unit")) {
1093 if(*flg_.default_leader_cfg() != side_unit) {
1094 continue;
1095 }
1096
1097 leader = &side_unit;
1098
1099 if(flg_.current_leader() != (*leader)["type"]) {
1100 // If a new leader type was selected from carryover, make sure that we reset the leader.
1101 std::string leader_id = (*leader)["id"];
1102 leader->clear();
1103
1104 if(!leader_id.empty()) {
1105 (*leader)["id"] = leader_id;
1106 }
1107 }
1108
1109 break;
1110 }
1111 }
1112
1113 // NOTE: the presence of a type= key overrides no_leader
1114 if(controller_ != CNTR_EMPTY) {
1115 (*leader)["type"] = flg_.current_leader();
1116 (*leader)["gender"] = flg_.current_gender();
1117 LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " type=" << (*leader)["type"] << " gender=" << (*leader)["gender"] << "\n";
1118 } else {
1119 // TODO: FIX THIS SHIT! We shouldn't have a special string to denote no-leader-ness...
1120 (*leader)["type"] = "null";
1121 (*leader)["gender"] = "null";
1122 }
1123
1124 res["team_name"] = parent_.team_data_[team_].team_name;
1125
1126 // TODO: Fix this mess!
1127 //
1128 // There is a fundamental disconnect, here. One the one hand we have the idea of
1129 // 'teams' (which don't actually exist). A 'team' has a name (internal key:
1130 // team_name) and a translatable display name (internal key: user_team_name). But
1131 // what we actually have are sides. Sides relate to each other by 'team' (internal
1132 // key: team_name) and each side has it's own name for the team (internal key:
1133 // user_team_name).
1134 //
1135 // The confusion is that the keys from the side have names which one might expect
1136 // always refer to the 'team' concept. THEY DO NOT! They are simply named in such
1137 // a way to confuse the unwary.
1138 //
1139 // There is no simple, clean way to clear up the confusion. So, I'm applying the
1140 // Principle of Least Surprise. The user can see the user_team_name, and it should
1141 // not change. So if the side already has a user_team_name, use it.
1142 //
1143 // In the rare and unlikely (like, probably never happens) case that the side does
1144 // not have a user_team_name, but an (nebulous and non-deterministic term here)
1145 // EARLIER side has the same team_name and that side gives a user_team_name, we'll
1146 // use it.
1147 //
1148 // The effect of this mess, and my lame fix for it, is probably only visible when
1149 // randomizing the sides on a team for multi-player games. But the effect when it's
1150 // not fixed is an obvious mistake on the player's screen when playing a campaign
1151 // in single-player mode.
1152 //
1153 // At some level, all this is probably wrong, but it is the least breakage from the
1154 // mess I found; so deal with it, or fix it.
1155 //
1156 // If, by now, you get the impression this is a kludged-together mess which cries
1157 // out for an honest design and a thoughtful implementation, you're correct! But
1158 // I'm tired, and I'm cranky from wasting a over day on this, and so I'm exercising
1159 // my prerogative as a grey-beard and leaving this for someone else to clean up.
1160 if(res["user_team_name"].empty() || !parent_.params_.use_map_settings) {
1161 res["user_team_name"] = parent_.team_data_[team_].user_team_name;
1162 }
1163
1164 res["allow_player"] = allow_player_;
1165 res["color"] = color_id_;
1166 res["gold"] = gold_;
1167 res["income"] = income_;
1168 }
1169
1170 if(parent_.params_.use_map_settings && !parent_.params_.saved_game) {
1171 if(cfg_.has_attribute("name")){
1172 res["name"] = cfg_["name"];
1173 }
1174 if(cfg_.has_attribute("user_description") && controller_ == CNTR_COMPUTER){
1175 res["user_description"] = cfg_["user_description"];
1176 }
1177 }
1178
1179 return res;
1180 }
1181
ready_for_start() const1182 bool side_engine::ready_for_start() const
1183 {
1184 if(!allow_player_) {
1185 // Sides without players are always ready.
1186 return true;
1187 }
1188
1189 if((controller_ == CNTR_COMPUTER) ||
1190 (controller_ == CNTR_EMPTY) ||
1191 (controller_ == CNTR_LOCAL)) {
1192
1193 return true;
1194 }
1195
1196 if(available_for_user()) {
1197 // If controller_ == CNTR_NETWORK and player_id_.empty().
1198 return false;
1199 }
1200
1201 if(controller_ == CNTR_NETWORK) {
1202 if(player_id_ == preferences::login() || !waiting_to_choose_faction_ || !allow_changes_) {
1203 // The host is ready. A network player, who got a chance
1204 // to choose faction if allowed, is also ready.
1205 return true;
1206 }
1207 }
1208
1209 return false;
1210 }
1211
available_for_user(const std::string & name) const1212 bool side_engine::available_for_user(const std::string& name) const
1213 {
1214 if(controller_ == CNTR_NETWORK && player_id_.empty()) {
1215 // Side is free and waiting for user.
1216 return true;
1217 }
1218
1219 if(controller_ == CNTR_RESERVED && name.empty()) {
1220 // Side is still available to someone.
1221 return true;
1222 }
1223
1224 if(controller_ == CNTR_RESERVED && reserved_for_ == name) {
1225 // Side is available only for the player with specific name.
1226 return true;
1227 }
1228
1229 return false;
1230 }
1231
resolve_random(randomness::mt_rng & rng,const std::vector<std::string> & avoid_faction_ids)1232 void side_engine::resolve_random(randomness::mt_rng & rng, const std::vector<std::string> & avoid_faction_ids)
1233 {
1234 if(parent_.params_.saved_game) {
1235 return;
1236 }
1237
1238 chose_random_ = flg_.is_random_faction();
1239
1240 flg_.resolve_random(rng, avoid_faction_ids);
1241
1242 LOG_MP << "side " << (index_ + 1) << ": faction=" <<
1243 (flg_.current_faction())["name"] << ", leader=" <<
1244 flg_.current_leader() << ", gender=" << flg_.current_gender() << "\n";
1245 }
1246
reset()1247 void side_engine::reset()
1248 {
1249 player_id_.clear();
1250 set_waiting_to_choose_status(false);
1251 set_controller(parent_.default_controller_);
1252
1253 if(!parent_.params_.saved_game) {
1254 flg_.set_current_faction(0);
1255 }
1256 }
1257
place_user(const std::string & name)1258 void side_engine::place_user(const std::string& name)
1259 {
1260 config data;
1261 data["name"] = name;
1262
1263 place_user(data);
1264 }
1265
place_user(const config & data,bool contains_selection)1266 void side_engine::place_user(const config& data, bool contains_selection)
1267 {
1268 player_id_ = data["name"].str();
1269 set_controller(parent_.default_controller_);
1270
1271 if(data["change_faction"].to_bool() && contains_selection) {
1272 // Network user's data carry information about chosen
1273 // faction, leader and genders.
1274 flg_.set_current_faction(data["faction"].str());
1275 flg_.set_current_leader(data["leader"].str());
1276 flg_.set_current_gender(data["gender"].str());
1277 }
1278
1279 waiting_to_choose_faction_ = false;
1280 }
1281
update_controller_options()1282 void side_engine::update_controller_options()
1283 {
1284 controller_options_.clear();
1285
1286 // Default options.
1287 if(parent_.campaign_info_) {
1288 add_controller_option(CNTR_NETWORK, _("Network Player"), "human");
1289 }
1290
1291 add_controller_option(CNTR_LOCAL, _("Local Player"), "human");
1292 add_controller_option(CNTR_COMPUTER, _("Computer Player"), "ai");
1293 add_controller_option(CNTR_EMPTY, _("Nobody"), "null");
1294
1295 if(!reserved_for_.empty()) {
1296 add_controller_option(CNTR_RESERVED, _("Reserved"), "human");
1297 }
1298
1299 // Connected users.
1300 for(const std::string& user : parent_.connected_users()) {
1301 add_controller_option(parent_.default_controller_, user, "human");
1302 }
1303
1304 update_current_controller_index();
1305 }
1306
update_current_controller_index()1307 void side_engine::update_current_controller_index()
1308 {
1309 int i = 0;
1310 for(const controller_option& option : controller_options_) {
1311 if(option.first == controller_) {
1312 current_controller_index_ = i;
1313
1314 if(player_id_.empty() || player_id_ == option.second) {
1315 // Stop searching if no user is assigned to a side
1316 // or the selected user is found.
1317 break;
1318 }
1319 }
1320
1321 i++;
1322 }
1323
1324 assert(current_controller_index_ < controller_options_.size());
1325 }
1326
controller_changed(const int selection)1327 bool side_engine::controller_changed(const int selection)
1328 {
1329 const ng::controller selected_cntr = controller_options_[selection].first;
1330
1331 // Check if user was selected. If so assign a side to him/her.
1332 // If not, make sure that no user is assigned to this side.
1333 if(selected_cntr == parent_.default_controller_ && selection != 0) {
1334 player_id_ = controller_options_[selection].second;
1335 set_waiting_to_choose_status(false);
1336 } else {
1337 player_id_.clear();
1338 }
1339
1340 set_controller(selected_cntr);
1341
1342 return true;
1343 }
1344
set_controller(ng::controller controller)1345 void side_engine::set_controller(ng::controller controller)
1346 {
1347 controller_ = controller;
1348
1349 update_current_controller_index();
1350 }
1351
set_faction_commandline(const std::string & faction_name)1352 void side_engine::set_faction_commandline(const std::string& faction_name)
1353 {
1354 flg_.set_current_faction(faction_name);
1355 }
1356
set_controller_commandline(const std::string & controller_name)1357 void side_engine::set_controller_commandline(const std::string& controller_name)
1358 {
1359 set_controller(CNTR_LOCAL);
1360
1361 if(controller_name == "ai") {
1362 set_controller(CNTR_COMPUTER);
1363 }
1364
1365 if(controller_name == "null") {
1366 set_controller(CNTR_EMPTY);
1367 }
1368
1369 player_id_.clear();
1370 }
1371
add_controller_option(ng::controller controller,const std::string & name,const std::string & controller_value)1372 void side_engine::add_controller_option(ng::controller controller,
1373 const std::string& name, const std::string& controller_value)
1374 {
1375 if(controller_lock_ && !cfg_["controller"].empty() &&
1376 cfg_["controller"] != controller_value) {
1377
1378 return;
1379 }
1380
1381 controller_options_.emplace_back(controller, name);
1382 }
1383
1384 } // end namespace ng
1385