1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
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 
15 /**
16  *  @file
17  *  Team-management, allies, setup at start of scenario.
18  */
19 
20 #include "team.hpp"
21 
22 #include "ai/manager.hpp"
23 #include "color.hpp"
24 #include "game_data.hpp"
25 #include "game_events/pump.hpp"
26 #include "lexical_cast.hpp"
27 #include "map/map.hpp"
28 #include "play_controller.hpp"
29 #include "playsingle_controller.hpp"
30 #include "preferences/game.hpp"
31 #include "resources.hpp"
32 #include "serialization/string_utils.hpp"
33 #include "synced_context.hpp"
34 #include "units/types.hpp"
35 #include "whiteboard/side_actions.hpp"
36 
37 #include <boost/dynamic_bitset.hpp>
38 
39 static lg::log_domain log_engine("engine");
40 #define DBG_NG LOG_STREAM(debug, log_engine)
41 #define LOG_NG LOG_STREAM(info, log_engine)
42 #define WRN_NG LOG_STREAM(warn, log_engine)
43 #define ERR_NG LOG_STREAM(err, log_engine)
44 
45 static lg::log_domain log_engine_enemies("engine/enemies");
46 #define DBG_NGE LOG_STREAM(debug, log_engine_enemies)
47 #define LOG_NGE LOG_STREAM(info, log_engine_enemies)
48 #define WRN_NGE LOG_STREAM(warn, log_engine_enemies)
49 
50 // Static member initialization
51 const int team::default_team_gold_ = 100;
52 
53 // Update this list of attributes if you change what is used to define a side
54 // (excluding those attributes used to define the side's leader).
55 const std::set<std::string> team::attributes {
56 	"ai_config",
57 	"carryover_add",
58 	"carryover_percentage",
59 	"color",
60 	"controller",
61 	"current_player",
62 	"defeat_condition",
63 	"flag",
64 	"flag_icon",
65 	"fog",
66 	"fog_data",
67 	"gold",
68 	"hidden",
69 	"income",
70 	"no_leader",
71 	"objectives",
72 	"objectives_changed",
73 	"persistent",
74 	"lost",
75 	"recall_cost",
76 	"recruit",
77 	"save_id",
78 	"scroll_to_leader",
79 	"share_vision",
80 	"share_maps",
81 	"share_view",
82 	"shroud",
83 	"shroud_data",
84 	"start_gold",
85 	"suppress_end_turn_confirmation",
86 	"team_name",
87 	"user_team_name",
88 	"side_name",
89 	"village_gold",
90 	"village_support",
91 	"is_local",
92 	// Multiplayer attributes.
93 	"player_id",
94 	"is_host",
95 	"action_bonus_count",
96 	"allow_changes",
97 	"allow_player",
98 	"color_lock",
99 	"countdown_time",
100 	"disallow_observers",
101 	"faction",
102 	"faction_from_recruit",
103 	"faction_name",
104 	"faction_lock",
105 	"gold_lock",
106 	"income_lock",
107 	"leader",
108 	"leader_lock",
109 	"random_leader",
110 	"team_lock",
111 	"terrain_liked",
112 	"user_description",
113 	"controller_lock",
114 	"chose_random",
115 	"disallow_shuffle",
116 	"description"
117 };
118 
team_info()119 team::team_info::team_info()
120 	: gold(0)
121 	, start_gold(0)
122 	, income(0)
123 	, income_per_village(0)
124 	, support_per_village(1)
125 	, minimum_recruit_price(0)
126 	, recall_cost(0)
127 	, can_recruit()
128 	, team_name()
129 	, user_team_name()
130 	, side_name()
131 	, faction()
132 	, faction_name()
133 	, save_id()
134 	, current_player()
135 	, countdown_time()
136 	, action_bonus_count(0)
137 	, flag()
138 	, flag_icon()
139 	, id()
140 	, scroll_to_leader(true)
141 	, objectives()
142 	, objectives_changed(false)
143 	, controller()
144 	, is_local(true)
145 	, defeat_condition(team::DEFEAT_CONDITION::NO_LEADER)
146 	, proxy_controller(team::PROXY_CONTROLLER::PROXY_HUMAN)
147 	, share_vision(team::SHARE_VISION::ALL)
148 	, disallow_observers(false)
149 	, allow_player(false)
150 	, chose_random(false)
151 	, no_leader(true)
152 	, hidden(true)
153 	, no_turn_confirmation(false)
154 	, color()
155 	, side(1)
156 	, persistent(false)
157 	, lost(false)
158 	, carryover_percentage(game_config::gold_carryover_percentage)
159 	, carryover_add(false)
160 	, carryover_bonus(0)
161 	, carryover_gold(0)
162 {
163 }
164 
read(const config & cfg)165 void team::team_info::read(const config& cfg)
166 {
167 	gold = cfg["gold"];
168 	income = cfg["income"];
169 	team_name = cfg["team_name"].str();
170 	user_team_name = cfg["user_team_name"];
171 	side_name = cfg["side_name"];
172 	faction = cfg["faction"].str();
173 	faction_name = cfg["faction_name"];
174 	save_id = cfg["save_id"].str();
175 	current_player = cfg["current_player"].str();
176 	countdown_time = cfg["countdown_time"].str();
177 	action_bonus_count = cfg["action_bonus_count"];
178 	flag = cfg["flag"].str();
179 	flag_icon = cfg["flag_icon"].str();
180 	id = cfg["id"].str();
181 	scroll_to_leader = cfg["scroll_to_leader"].to_bool(true);
182 	objectives = cfg["objectives"];
183 	objectives_changed = cfg["objectives_changed"].to_bool();
184 	disallow_observers = cfg["disallow_observers"].to_bool();
185 	allow_player = cfg["allow_player"].to_bool(true);
186 	chose_random = cfg["chose_random"].to_bool(false);
187 	no_leader = cfg["no_leader"].to_bool();
188 	defeat_condition = cfg["defeat_condition"].to_enum<team::DEFEAT_CONDITION>(team::DEFEAT_CONDITION::NO_LEADER);
189 	lost = cfg["lost"].to_bool(false);
190 	hidden = cfg["hidden"].to_bool();
191 	no_turn_confirmation = cfg["suppress_end_turn_confirmation"].to_bool();
192 	side = cfg["side"].to_int(1);
193 	carryover_percentage = cfg["carryover_percentage"].to_int(game_config::gold_carryover_percentage);
194 	carryover_add = cfg["carryover_add"].to_bool(false);
195 	carryover_bonus = cfg["carryover_bonus"].to_double(1);
196 	carryover_gold = cfg["carryover_gold"].to_int(0);
197 	variables = cfg.child_or_empty("variables");
198 	is_local = cfg["is_local"].to_bool(true);
199 
200 	color = get_side_color_id_from_config(cfg);
201 
202 	// If starting new scenario override settings from [ai] tags
203 	if(!user_team_name.translatable())
204 		user_team_name = t_string::from_serialized(user_team_name);
205 
206 	if(ai::manager::has_manager()) {
207 		if(cfg.has_attribute("ai_config")) {
208 			ai::manager::get_singleton().add_ai_for_side_from_file(side, cfg["ai_config"], true);
209 		} else {
210 			ai::manager::get_singleton().add_ai_for_side_from_config(side, cfg, true);
211 		}
212 	}
213 
214 	std::vector<std::string> recruits = utils::split(cfg["recruit"]);
215 	can_recruit.insert(recruits.begin(), recruits.end());
216 
217 	// at the start of a scenario "start_gold" is not set, we need to take the
218 	// value from the gold setting (or fall back to the gold default)
219 	if(!cfg["start_gold"].empty()) {
220 		start_gold = cfg["start_gold"];
221 	} else if(!cfg["gold"].empty()) {
222 		start_gold = gold;
223 	} else {
224 		start_gold = default_team_gold_;
225 	}
226 
227 	if(team_name.empty()) {
228 		team_name = cfg["side"].str();
229 	}
230 
231 	if(save_id.empty()) {
232 		save_id = id;
233 	}
234 
235 	income_per_village = cfg["village_gold"].to_int(game_config::village_income);
236 	recall_cost = cfg["recall_cost"].to_int(game_config::recall_cost);
237 
238 	const std::string& village_support = cfg["village_support"];
239 	if(village_support.empty()) {
240 		support_per_village = game_config::village_support;
241 	} else {
242 		support_per_village = lexical_cast_default<int>(village_support, game_config::village_support);
243 	}
244 
245 	controller = team::CONTROLLER::AI;
246 	controller.parse(cfg["controller"].str());
247 
248 	// TODO: Why do we read disallow observers differently when controller is empty?
249 	if(controller == CONTROLLER::EMPTY) {
250 		disallow_observers = cfg["disallow_observers"].to_bool(true);
251 	}
252 
253 	// override persistence flag if it is explicitly defined in the config
254 	// by default, persistence of a team is set depending on the controller
255 	persistent = cfg["persistent"].to_bool(this->controller == CONTROLLER::HUMAN);
256 
257 	//========================================================
258 	// END OF MESSY CODE
259 
260 	// Share_view and share_maps can't both be enabled,
261 	// so share_view overrides share_maps.
262 	share_vision = cfg["share_vision"].to_enum<team::SHARE_VISION>(team::SHARE_VISION::ALL);
263 	handle_legacy_share_vision(cfg);
264 
265 	LOG_NG << "team_info::team_info(...): team_name: " << team_name << ", share_vision: " << share_vision << ".\n";
266 }
267 
handle_legacy_share_vision(const config & cfg)268 void team::team_info::handle_legacy_share_vision(const config& cfg)
269 {
270 	if(cfg.has_attribute("share_view") || cfg.has_attribute("share_maps")) {
271 		if(cfg["share_view"].to_bool()) {
272 			share_vision = team::SHARE_VISION::ALL;
273 		} else if(cfg["share_maps"].to_bool(true)) {
274 			share_vision = team::SHARE_VISION::SHROUD;
275 		} else {
276 			share_vision = team::SHARE_VISION::NONE;
277 		}
278 	}
279 }
280 
write(config & cfg) const281 void team::team_info::write(config& cfg) const
282 {
283 	cfg["gold"] = gold;
284 	cfg["start_gold"] = start_gold;
285 	cfg["income"] = income;
286 	cfg["team_name"] = team_name;
287 	cfg["user_team_name"] = user_team_name;
288 	cfg["side_name"] = side_name;
289 	cfg["faction"] = faction;
290 	cfg["faction_name"] = faction_name;
291 	cfg["save_id"] = save_id;
292 	cfg["current_player"] = current_player;
293 	cfg["flag"] = flag;
294 	cfg["flag_icon"] = flag_icon;
295 	cfg["id"] = id;
296 	cfg["objectives"] = objectives;
297 	cfg["objectives_changed"] = objectives_changed;
298 	cfg["countdown_time"] = countdown_time;
299 	cfg["action_bonus_count"] = action_bonus_count;
300 	cfg["village_gold"] = income_per_village;
301 	cfg["village_support"] = support_per_village;
302 	cfg["recall_cost"] = recall_cost;
303 	cfg["disallow_observers"] = disallow_observers;
304 	cfg["allow_player"] = allow_player;
305 	cfg["chose_random"] = chose_random;
306 	cfg["no_leader"] = no_leader;
307 	cfg["defeat_condition"] = defeat_condition;
308 	cfg["hidden"] = hidden;
309 	cfg["suppress_end_turn_confirmation"] = no_turn_confirmation;
310 	cfg["scroll_to_leader"] = scroll_to_leader;
311 	cfg["controller"] = controller;
312 	cfg["recruit"] = utils::join(can_recruit);
313 	cfg["share_vision"] = share_vision;
314 
315 	cfg["color"] = color;
316 	cfg["persistent"] = persistent;
317 	cfg["lost"] = lost;
318 	cfg["carryover_percentage"] = carryover_percentage;
319 	cfg["carryover_add"] = carryover_add;
320 	cfg["carryover_bonus"] = carryover_bonus;
321 	cfg["carryover_gold"] = carryover_gold;
322 
323 	if(!variables.empty()) {
324 		cfg.add_child("variables", variables);
325 	}
326 
327 	cfg.add_child("ai", ai::manager::get_singleton().to_config(side));
328 }
329 
team()330 team::team()
331 	: gold_(0)
332 	, villages_()
333 	, shroud_()
334 	, fog_()
335 	, fog_clearer_()
336 	, auto_shroud_updates_(true)
337 	, info_()
338 	, countdown_time_(0)
339 	, action_bonus_count_(0)
340 	, recall_list_()
341 	, last_recruit_()
342 	, enemies_()
343 	, ally_shroud_()
344 	, ally_fog_()
345 	, planned_actions_()
346 {
347 }
348 
~team()349 team::~team()
350 {
351 }
352 
build(const config & cfg,const gamemap & map,int gold)353 void team::build(const config& cfg, const gamemap& map, int gold)
354 {
355 	gold_ = gold;
356 	info_.read(cfg);
357 
358 	fog_.set_enabled(cfg["fog"].to_bool());
359 	fog_.read(cfg["fog_data"]);
360 	shroud_.set_enabled(cfg["shroud"].to_bool());
361 	shroud_.read(cfg["shroud_data"]);
362 	auto_shroud_updates_ = cfg["auto_shroud"].to_bool(auto_shroud_updates_);
363 
364 	LOG_NG << "team::team(...): team_name: " << info_.team_name << ", shroud: " << uses_shroud()
365 		   << ", fog: " << uses_fog() << ".\n";
366 
367 	// Load the WML-cleared fog.
368 	const config& fog_override = cfg.child("fog_override");
369 	if(fog_override) {
370 		const std::vector<map_location> fog_vector
371 				= map.parse_location_range(fog_override["x"], fog_override["y"], true);
372 		fog_clearer_.insert(fog_vector.begin(), fog_vector.end());
373 	}
374 
375 	// To ensure some minimum starting gold,
376 	// gold is the maximum of 'gold' and what is given in the config file
377 	gold_ = std::max(gold, info_.gold);
378 	if(gold_ != info_.gold) {
379 		info_.start_gold = gold;
380 	}
381 
382 	// Old code was doing:
383 	// info_.start_gold = std::to_string(gold) + " (" + info_.start_gold + ")";
384 	// Was it correct?
385 
386 	// Load in the villages the side controls at the start
387 	for(const config& v : cfg.child_range("village")) {
388 		map_location loc(v);
389 		if(map.is_village(loc)) {
390 			villages_.insert(loc);
391 		} else {
392 			WRN_NG << "[side] " << current_player() << " [village] points to a non-village location " << loc
393 				   << std::endl;
394 		}
395 	}
396 
397 	countdown_time_ = cfg["countdown_time"];
398 	action_bonus_count_ = cfg["action_bonus_count"];
399 
400 	planned_actions_.reset(new wb::side_actions());
401 	planned_actions_->set_team_index(info_.side - 1);
402 }
403 
write(config & cfg) const404 void team::write(config& cfg) const
405 {
406 	info_.write(cfg);
407 	cfg["auto_shroud"] = auto_shroud_updates_;
408 	cfg["shroud"] = uses_shroud();
409 	cfg["fog"] = uses_fog();
410 	cfg["gold"] = gold_;
411 
412 	// Write village locations
413 	for(const map_location& loc : villages_) {
414 		loc.write(cfg.add_child("village"));
415 	}
416 
417 	cfg["shroud_data"] = shroud_.write();
418 	cfg["fog_data"] = fog_.write();
419 	if(!fog_clearer_.empty())
420 		write_location_range(fog_clearer_, cfg.add_child("fog_override"));
421 
422 	cfg["countdown_time"] = countdown_time_;
423 	cfg["action_bonus_count"] = action_bonus_count_;
424 }
425 
get_village(const map_location & loc,const int owner_side,game_data * gamedata)426 game_events::pump_result_t team::get_village(const map_location& loc, const int owner_side, game_data* gamedata)
427 {
428 	villages_.insert(loc);
429 	game_events::pump_result_t res;
430 
431 	if(gamedata) {
432 		config::attribute_value& var = gamedata->get_variable("owner_side");
433 		const config::attribute_value old_value = var;
434 		var = owner_side;
435 
436 		// During team building, game_events pump is not guaranteed to exist yet. (At current revision.) We skip capture
437 		// events in this case.
438 		if(resources::game_events) {
439 			res = resources::game_events->pump().fire("capture", loc);
440 		}
441 
442 		if(old_value.blank()) {
443 			gamedata->clear_variable("owner_side");
444 		} else {
445 			var = old_value;
446 		}
447 	}
448 
449 	return res;
450 }
451 
lose_village(const map_location & loc)452 void team::lose_village(const map_location& loc)
453 {
454 	const std::set<map_location>::const_iterator vil = villages_.find(loc);
455 	assert(vil != villages_.end());
456 	villages_.erase(vil);
457 }
458 
set_recruits(const std::set<std::string> & recruits)459 void team::set_recruits(const std::set<std::string>& recruits)
460 {
461 	info_.can_recruit = recruits;
462 	info_.minimum_recruit_price = 0;
463 	ai::manager::get_singleton().raise_recruit_list_changed();
464 }
465 
add_recruit(const std::string & recruit)466 void team::add_recruit(const std::string& recruit)
467 {
468 	info_.can_recruit.insert(recruit);
469 	info_.minimum_recruit_price = 0;
470 	ai::manager::get_singleton().raise_recruit_list_changed();
471 }
472 
minimum_recruit_price() const473 int team::minimum_recruit_price() const
474 {
475 	if(info_.minimum_recruit_price) {
476 		return info_.minimum_recruit_price;
477 	}
478 	int min = 20;
479 	for(std::string recruit : info_.can_recruit) {
480 		const unit_type* ut = unit_types.find(recruit);
481 		if(!ut) {
482 			continue;
483 		} else {
484 			if(ut->cost() < min) {
485 				min = ut->cost();
486 			}
487 		}
488 	}
489 
490 	info_.minimum_recruit_price = min;
491 
492 	return info_.minimum_recruit_price;
493 }
494 
calculate_enemies(size_t index) const495 void team::calculate_enemies(size_t index) const
496 {
497 	if(!resources::gameboard || index >= resources::gameboard->teams().size()) {
498 		return;
499 	}
500 
501 	while(enemies_.size() <= index) {
502 		enemies_.push_back(calculate_is_enemy(enemies_.size()));
503 	}
504 }
505 
calculate_is_enemy(size_t index) const506 bool team::calculate_is_enemy(size_t index) const
507 {
508 	// We're not enemies of ourselves
509 	if(&resources::gameboard->teams()[index] == this) {
510 		return false;
511 	}
512 
513 	// We are friends with anyone who we share a teamname with
514 	std::vector<std::string> our_teams = utils::split(info_.team_name);
515 	std::vector<std::string> their_teams = utils::split(resources::gameboard->teams()[index].info_.team_name);
516 
517 	LOG_NGE << "team " << info_.side << " calculates if it has enemy in team " << index + 1 << "; our team_name ["
518 			<< info_.team_name << "], their team_name is [" << resources::gameboard->teams()[index].info_.team_name
519 			<< "]" << std::endl;
520 
521 	for(const std::string& t : our_teams) {
522 		if(std::find(their_teams.begin(), their_teams.end(), t) != their_teams.end()) {
523 			LOG_NGE << "team " << info_.side << " found same team name [" << t << "] in team " << index + 1
524 					<< std::endl;
525 			return false;
526 		} else {
527 			LOG_NGE << "team " << info_.side << " not found same team name [" << t << "] in team " << index + 1
528 					<< std::endl;
529 		}
530 	}
531 
532 	LOG_NGE << "team " << info_.side << " has enemy in team " << index + 1 << std::endl;
533 	return true;
534 }
535 
536 namespace
537 {
538 class controller_server_choice : public synced_context::server_choice
539 {
540 public:
controller_server_choice(team::CONTROLLER new_controller,const team & team)541 	controller_server_choice(team::CONTROLLER new_controller, const team& team)
542 		: new_controller_(new_controller)
543 		, team_(team)
544 	{
545 	}
546 
547 	/// We are in a game with no mp server and need to do this choice locally
local_choice() const548 	virtual config local_choice() const
549 	{
550 		return config{"controller", new_controller_, "is_local", true};
551 	}
552 
553 	/// tThe request which is sent to the mp server.
request() const554 	virtual config request() const
555 	{
556 		return config{
557 				"new_controller", new_controller_, "old_controller", team_.controller(), "side", team_.side(),
558 		};
559 	}
560 
name() const561 	virtual const char* name() const
562 	{
563 		return "change_controller_wml";
564 	}
565 
566 private:
567 	team::CONTROLLER new_controller_;
568 	const team& team_;
569 };
570 } // end anon namespace
571 
change_controller_by_wml(const std::string & new_controller_string)572 void team::change_controller_by_wml(const std::string& new_controller_string)
573 {
574 	CONTROLLER new_controller;
575 	if(!new_controller.parse(new_controller_string)) {
576 		WRN_NG << "ignored attempt to change controller to " << new_controller_string << std::endl;
577 		return;
578 	}
579 
580 	if(new_controller == CONTROLLER::EMPTY && resources::controller->current_side() == this->side()) {
581 		WRN_NG << "ignored attempt to change the currently playing side's controller to 'null'" << std::endl;
582 		return;
583 	}
584 
585 	config choice = synced_context::ask_server_choice(controller_server_choice(new_controller, *this));
586 	if(!new_controller.parse(choice["controller"])) {
587 		// TODO: this should be more than a ERR_NG message.
588 		// GL-2016SEP02 Oh? So why was ERR_NG defined as warning level? Making the call fit the definition.
589 		WRN_NG << "Received an invalid controller string from the server" << choice["controller"] << std::endl;
590 	}
591 
592 	if(!resources::controller->is_replay()) {
593 		set_local(choice["is_local"].to_bool());
594 	}
595 
596 	if(playsingle_controller* pc =  dynamic_cast<playsingle_controller*>(resources::controller)) {
597 		if(pc->current_side() == side() && new_controller != controller()) {
598 			pc->set_player_type_changed();
599 		}
600 	}
601 
602 	change_controller(new_controller);
603 }
604 
change_team(const std::string & name,const t_string & user_name)605 void team::change_team(const std::string& name, const t_string& user_name)
606 {
607 	info_.team_name = name;
608 
609 	if(!user_name.empty()) {
610 		info_.user_team_name = user_name;
611 	} else {
612 		info_.user_team_name = name;
613 	}
614 
615 	clear_caches();
616 }
617 
clear_caches()618 void team::clear_caches()
619 {
620 	// Reset the cache of allies for all teams
621 	if(resources::gameboard) {
622 		for(auto& t : resources::gameboard->teams()) {
623 			t.enemies_.clear();
624 			t.ally_shroud_.clear();
625 			t.ally_fog_.clear();
626 		}
627 	}
628 }
629 
set_objectives(const t_string & new_objectives,bool silently)630 void team::set_objectives(const t_string& new_objectives, bool silently)
631 {
632 	info_.objectives = new_objectives;
633 
634 	if(!silently) {
635 		info_.objectives_changed = true;
636 	}
637 }
638 
shrouded(const map_location & loc) const639 bool team::shrouded(const map_location& loc) const
640 {
641 	if(!resources::gameboard) {
642 		return shroud_.value(loc.wml_x(), loc.wml_y());
643 	}
644 
645 	return shroud_.shared_value(ally_shroud(resources::gameboard->teams()), loc.wml_x(), loc.wml_y());
646 }
647 
fogged(const map_location & loc) const648 bool team::fogged(const map_location& loc) const
649 {
650 	if(shrouded(loc)) {
651 		return true;
652 	}
653 
654 	// Check for an override of fog.
655 	if(fog_clearer_.count(loc) > 0) {
656 		return false;
657 	}
658 
659 	if(!resources::gameboard) {
660 		return fog_.value(loc.wml_x(), loc.wml_y());
661 	}
662 
663 	return fog_.shared_value(ally_fog(resources::gameboard->teams()), loc.wml_x(), loc.wml_y());
664 }
665 
ally_shroud(const std::vector<team> & teams) const666 const std::vector<const team::shroud_map*>& team::ally_shroud(const std::vector<team>& teams) const
667 {
668 	if(ally_shroud_.empty()) {
669 		for(size_t i = 0; i < teams.size(); ++i) {
670 			if(!is_enemy(i + 1) && (&(teams[i]) == this || teams[i].share_view() || teams[i].share_maps())) {
671 				ally_shroud_.push_back(&(teams[i].shroud_));
672 			}
673 		}
674 	}
675 
676 	return ally_shroud_;
677 }
678 
ally_fog(const std::vector<team> & teams) const679 const std::vector<const team::shroud_map*>& team::ally_fog(const std::vector<team>& teams) const
680 {
681 	if(ally_fog_.empty()) {
682 		for(size_t i = 0; i < teams.size(); ++i) {
683 			if(!is_enemy(i + 1) && (&(teams[i]) == this || teams[i].share_view())) {
684 				ally_fog_.push_back(&(teams[i].fog_));
685 			}
686 		}
687 	}
688 
689 	return ally_fog_;
690 }
691 
knows_about_team(size_t index) const692 bool team::knows_about_team(size_t index) const
693 {
694 	const team& t = resources::gameboard->teams()[index];
695 
696 	// We know about our own team
697 	if(this == &t) {
698 		return true;
699 	}
700 
701 	// If we aren't using shroud or fog, then we know about everyone
702 	if(!uses_shroud() && !uses_fog()) {
703 		return true;
704 	}
705 
706 	// We don't know about enemies
707 	if(is_enemy(index + 1)) {
708 		return false;
709 	}
710 
711 	// We know our human allies.
712 	if(t.is_human()) {
713 		return true;
714 	}
715 
716 	// We know about allies we're sharing maps with
717 	if(share_maps() && t.uses_shroud()) {
718 		return true;
719 	}
720 
721 	// We know about allies we're sharing view with
722 	if(share_view() && (t.uses_fog() || t.uses_shroud())) {
723 		return true;
724 	}
725 
726 	return false;
727 }
728 
729 /**
730  * Removes the record of hexes that were cleared of fog via WML.
731  * @param[in] hexes	The hexes to no longer keep clear.
732  */
remove_fog_override(const std::set<map_location> & hexes)733 void team::remove_fog_override(const std::set<map_location>& hexes)
734 {
735 	// Take a set difference.
736 	std::vector<map_location> result(fog_clearer_.size());
737 	std::vector<map_location>::iterator result_end =
738 		std::set_difference(fog_clearer_.begin(), fog_clearer_.end(), hexes.begin(), hexes.end(), result.begin());
739 
740 	// Put the result into fog_clearer_.
741 	fog_clearer_.clear();
742 	fog_clearer_.insert(result.begin(), result_end);
743 }
744 
validate_side(int side)745 void validate_side(int side)
746 {
747 	if(!resources::gameboard) {
748 		return;
749 	}
750 
751 	if(side < 1 || side > static_cast<int>(resources::gameboard->teams().size())) {
752 		throw game::game_error("invalid side(" + std::to_string(side) + ") found in unit definition");
753 	}
754 }
755 
clear(int x,int y)756 bool team::shroud_map::clear(int x, int y)
757 {
758 	if(enabled_ == false || x < 0 || y < 0) {
759 		return false;
760 	}
761 
762 	if(x >= static_cast<int>(data_.size())) {
763 		data_.resize(x + 1);
764 	}
765 
766 	if(y >= static_cast<int>(data_[x].size())) {
767 		data_[x].resize(y + 1);
768 	}
769 
770 	if(data_[x][y] == false) {
771 		data_[x][y] = true;
772 		return true;
773 	}
774 
775 	return false;
776 }
777 
place(int x,int y)778 void team::shroud_map::place(int x, int y)
779 {
780 	if(enabled_ == false || x < 0 || y < 0) {
781 		return;
782 	}
783 
784 	if(x >= static_cast<int>(data_.size())) {
785 		DBG_NG << "Couldn't place shroud on invalid x coordinate: (" << x << ", " << y
786 			   << ") - max x: " << data_.size() - 1 << "\n";
787 	} else if(y >= static_cast<int>(data_[x].size())) {
788 		DBG_NG << "Couldn't place shroud on invalid y coordinate: (" << x << ", " << y
789 			   << ") - max y: " << data_[x].size() - 1 << "\n";
790 	} else {
791 		data_[x][y] = false;
792 	}
793 }
794 
reset()795 void team::shroud_map::reset()
796 {
797 	if(enabled_ == false) {
798 		return;
799 	}
800 
801 	for(auto& i : data_) {
802 		std::fill(i.begin(), i.end(), false);
803 	}
804 }
805 
value(int x,int y) const806 bool team::shroud_map::value(int x, int y) const
807 {
808 	if(!enabled_) {
809 		return false;
810 	}
811 
812 	// Locations for which we have no data are assumed to still be covered.
813 	if(x < 0 || x >= static_cast<int>(data_.size())) {
814 		return true;
815 	}
816 
817 	if(y < 0 || y >= static_cast<int>(data_[x].size())) {
818 		return true;
819 	}
820 
821 	// data_ stores whether or not a location has been cleared, while
822 	// we want to return whether or not a location is covered.
823 	return !data_[x][y];
824 }
825 
shared_value(const std::vector<const shroud_map * > & maps,int x,int y) const826 bool team::shroud_map::shared_value(const std::vector<const shroud_map*>& maps, int x, int y) const
827 {
828 	if(!enabled_) {
829 		return false;
830 	}
831 
832 	// A quick abort:
833 	if(x < 0 || y < 0) {
834 		return true;
835 	}
836 
837 	// A tile is uncovered if it is uncovered on any shared map.
838 	for(const shroud_map* const shared_map : maps) {
839 		if(shared_map->enabled_ && !shared_map->value(x, y)) {
840 			return false;
841 		}
842 	}
843 
844 	return true;
845 }
846 
write() const847 std::string team::shroud_map::write() const
848 {
849 	std::stringstream shroud_str;
850 	for(const auto& sh : data_) {
851 		shroud_str << '|';
852 
853 		for(bool i : sh) {
854 			shroud_str << (i ? '1' : '0');
855 		}
856 
857 		shroud_str << '\n';
858 	}
859 
860 	return shroud_str.str();
861 }
862 
read(const std::string & str)863 void team::shroud_map::read(const std::string& str)
864 {
865 	data_.clear();
866 
867 	for(const char sh : str) {
868 		if(sh == '|') {
869 			data_.resize(data_.size() + 1);
870 		}
871 
872 		if(data_.empty() == false) {
873 			if(sh == '1') {
874 				data_.back().push_back(true);
875 			} else if(sh == '0') {
876 				data_.back().push_back(false);
877 			}
878 		}
879 	}
880 }
881 
merge(const std::string & str)882 void team::shroud_map::merge(const std::string& str)
883 {
884 	int x = 0, y = 0;
885 	for(size_t i = 1; i < str.length(); ++i) {
886 		if(str[i] == '|') {
887 			y = 0;
888 			x++;
889 		} else if(str[i] == '1') {
890 			clear(x, y);
891 			y++;
892 		} else if(str[i] == '0') {
893 			y++;
894 		}
895 	}
896 }
897 
copy_from(const std::vector<const shroud_map * > & maps)898 bool team::shroud_map::copy_from(const std::vector<const shroud_map*>& maps)
899 {
900 	if(enabled_ == false) {
901 		return false;
902 	}
903 
904 	bool cleared = false;
905 	for(const shroud_map* m : maps) {
906 		if(m->enabled_ == false) {
907 			continue;
908 		}
909 
910 		const std::vector<std::vector<bool>>& v = m->data_;
911 		for(size_t x = 0; x != v.size(); ++x) {
912 			for(size_t y = 0; y != v[x].size(); ++y) {
913 				if(v[x][y]) {
914 					cleared |= clear(x, y);
915 				}
916 			}
917 		}
918 	}
919 
920 	return cleared;
921 }
922 
get_side_color_range(int side)923 const color_range team::get_side_color_range(int side)
924 {
925 	std::string index = get_side_color_id(side);
926 	std::map<std::string, color_range>::iterator gp = game_config::team_rgb_range.find(index);
927 
928 	if(gp != game_config::team_rgb_range.end()) {
929 		return (gp->second);
930 	}
931 
932 	return color_range({255, 0, 0}, {255, 255, 255}, {0, 0, 0}, {255, 0, 0});
933 }
934 
get_side_color(int side)935 color_t team::get_side_color(int side)
936 {
937 	return get_side_color_range(side).mid();
938 }
939 
get_minimap_color(int side)940 color_t team::get_minimap_color(int side)
941 {
942 	// Note: use mid() instead of rep() unless
943 	// high contrast is needed over a map or minimap!
944 	return get_side_color_range(side).rep();
945 }
946 
get_side_color_id(unsigned side)947 std::string team::get_side_color_id(unsigned side)
948 {
949 	try {
950 		const unsigned index = side - 1;
951 
952 		// If no gameboard (and by extension, team list) is available, use the default side color.
953 		if(!resources::gameboard) {
954 			return game_config::default_colors.at(index);
955 		}
956 
957 		// Else, try to fetch the color from the side's config.
958 		const std::string& side_color = resources::gameboard->teams().at(index).color();
959 
960 		if(!side_color.empty()) {
961 			return side_color;
962 		}
963 
964 		// If the side color data was empty, fall back to the default color. This should only
965 		// happen if the side data hadn't been initialized yet, which is the case if this function
966 		// is being called to set up said side data. :P
967 		return game_config::default_colors.at(index);
968 	} catch(const std::out_of_range&) {
969 		// Side index was invalid! Coloring will fail!
970 		return "";
971 	}
972 }
973 
get_side_color_id_from_config(const config & cfg)974 std::string team::get_side_color_id_from_config(const config& cfg)
975 {
976 	const config::attribute_value& c = cfg["color"];
977 
978 	// If no color key or value was provided, use the given color for that side.
979 	// If outside a game context (ie, where a list of teams has been constructed),
980 	// this will just be the side's default color.
981 	if(c.blank() || c.empty()) {
982 		return get_side_color_id(cfg["side"].to_unsigned());
983 	}
984 
985 	// Do the same as above for numeric color key values.
986 	if(unsigned side = c.to_unsigned()) {
987 		return get_side_color_id(side);
988 	}
989 
990 	// Else, we should have a color id at this point. Return it.
991 	return c.str();
992 }
993 
get_side_highlight_pango(int side)994 std::string team::get_side_highlight_pango(int side)
995 {
996 	return get_side_color_range(side).mid().to_hex_string();
997 }
998 
log_recruitable() const999 void team::log_recruitable() const
1000 {
1001 	LOG_NG << "Adding recruitable units: \n";
1002 	for(const std::string& recruit : info_.can_recruit) {
1003 		LOG_NG << recruit << std::endl;
1004 	}
1005 
1006 	LOG_NG << "Added all recruitable units\n";
1007 }
1008 
to_config() const1009 config team::to_config() const
1010 {
1011 	config cfg;
1012 	config& result = cfg.add_child("side");
1013 	write(result);
1014 	return result;
1015 }
1016 
allied_human_teams() const1017 std::string team::allied_human_teams() const
1018 {
1019 	std::vector<int> res;
1020 	for(const team& t : resources::gameboard->teams()) {
1021 		if(!t.is_enemy(this->side()) && t.is_human()) {
1022 			res.push_back(t.side());
1023 		}
1024 	}
1025 
1026 	return utils::join(res);
1027 }
1028