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