The Battle for Wesnoth  1.17.0-dev
team.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Team-management, allies, setup at start of scenario.
19  */
20 
21 #include "team.hpp"
22 
23 #include "ai/manager.hpp"
24 #include "color.hpp"
25 #include "formula/string_utils.hpp" // for VGETTEXT
26 #include "game_data.hpp"
27 #include "game_events/pump.hpp"
28 #include "lexical_cast.hpp"
29 #include "map/map.hpp"
30 #include "play_controller.hpp"
32 #include "preferences/game.hpp"
33 #include "resources.hpp"
35 #include "synced_context.hpp"
36 #include "units/types.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  "previous_recruits",
78  "save_id",
79  "scroll_to_leader",
80  "share_vision",
81  "share_maps",
82  "share_view",
83  "shroud",
84  "shroud_data",
85  "start_gold",
86  "suppress_end_turn_confirmation",
87  "team_name",
88  "user_team_name",
89  "side_name",
90  "village_gold",
91  "village_support",
92  "is_local",
93  // Multiplayer attributes.
94  "player_id",
95  "is_host",
96  "action_bonus_count",
97  "allow_changes",
98  "allow_player",
99  "color_lock",
100  "countdown_time",
101  "disallow_observers",
102  "faction",
103  "faction_from_recruit",
104  "faction_name",
105  "faction_lock",
106  "gold_lock",
107  "income_lock",
108  "leader",
109  "leader_lock",
110  "random_leader",
111  "team_lock",
112  "terrain_liked",
113  "user_description",
114  "controller_lock",
115  "chose_random",
116  "disallow_shuffle",
117  "description"
118 };
119 
121  : gold(0)
122  , start_gold(0)
123  , income(0)
124  , income_per_village(0)
125  , support_per_village(1)
126  , minimum_recruit_price(0)
127  , recall_cost(0)
128  , can_recruit()
129  , team_name()
130  , user_team_name()
131  , side_name()
132  , faction()
133  , faction_name()
134  , save_id()
135  , current_player()
136  , countdown_time()
137  , action_bonus_count(0)
138  , flag()
139  , flag_icon()
140  , id()
141  , scroll_to_leader(true)
142  , objectives()
143  , objectives_changed(false)
144  , controller()
145  , is_local(true)
146  , defeat_condition(team::DEFEAT_CONDITION::NO_LEADER)
147  , proxy_controller(team::PROXY_CONTROLLER::PROXY_HUMAN)
148  , share_vision(team::SHARE_VISION::ALL)
149  , disallow_observers(false)
150  , allow_player(false)
151  , chose_random(false)
152  , no_leader(true)
153  , hidden(true)
154  , no_turn_confirmation(false)
155  , color()
156  , side(1)
157  , persistent(false)
158  , lost(false)
159  , carryover_percentage(game_config::gold_carryover_percentage)
160  , carryover_add(false)
161  , carryover_bonus(0)
162  , carryover_gold(0)
163 {
164 }
165 
167 {
168  gold = cfg["gold"];
169  income = cfg["income"];
170  team_name = cfg["team_name"].str();
171  user_team_name = cfg["user_team_name"];
172  side_name = cfg["side_name"];
173  faction = cfg["faction"].str();
174  faction_name = cfg["faction_name"];
175  save_id = cfg["save_id"].str();
176  current_player = cfg["current_player"].str();
177  countdown_time = cfg["countdown_time"].str();
178  action_bonus_count = cfg["action_bonus_count"];
179  flag = cfg["flag"].str();
180  flag_icon = cfg["flag_icon"].str();
181  id = cfg["id"].str();
182  scroll_to_leader = cfg["scroll_to_leader"].to_bool(true);
183  objectives = cfg["objectives"];
184  objectives_changed = cfg["objectives_changed"].to_bool();
185  disallow_observers = cfg["disallow_observers"].to_bool();
186  allow_player = cfg["allow_player"].to_bool(true);
187  chose_random = cfg["chose_random"].to_bool(false);
188  no_leader = cfg["no_leader"].to_bool();
189  defeat_condition = cfg["defeat_condition"].to_enum<team::DEFEAT_CONDITION>(team::DEFEAT_CONDITION::NO_LEADER);
190  lost = cfg["lost"].to_bool(false);
191  hidden = cfg["hidden"].to_bool();
192  no_turn_confirmation = cfg["suppress_end_turn_confirmation"].to_bool();
193  side = cfg["side"].to_int(1);
194  carryover_percentage = cfg["carryover_percentage"].to_int(game_config::gold_carryover_percentage);
195  carryover_add = cfg["carryover_add"].to_bool(false);
196  carryover_bonus = cfg["carryover_bonus"].to_double(1);
197  carryover_gold = cfg["carryover_gold"].to_int(0);
198  variables = cfg.child_or_empty("variables");
199  is_local = cfg["is_local"].to_bool(true);
200 
202 
203  // If starting new scenario override settings from [ai] tags
206 
208  if(cfg.has_attribute("ai_config")) {
209  ai::manager::get_singleton().add_ai_for_side_from_file(side, cfg["ai_config"], true);
210  } else {
212  }
213  }
214 
215  std::vector<std::string> recruits = utils::split(cfg["recruit"]);
216  can_recruit.insert(recruits.begin(), recruits.end());
217 
218  // at the start of a scenario "start_gold" is not set, we need to take the
219  // value from the gold setting (or fall back to the gold default)
220  if(!cfg["start_gold"].empty()) {
221  start_gold = cfg["start_gold"];
222  } else if(!cfg["gold"].empty()) {
223  start_gold = gold;
224  } else {
226  }
227 
228  if(team_name.empty()) {
229  team_name = cfg["side"].str();
230  }
231 
232  if(save_id.empty()) {
233  save_id = id;
234  }
235 
236  income_per_village = cfg["village_gold"].to_int(game_config::village_income);
237  recall_cost = cfg["recall_cost"].to_int(game_config::recall_cost);
238 
239  const std::string& village_support = cfg["village_support"];
240  if(village_support.empty()) {
242  } else {
244  }
245 
246  controller = team::CONTROLLER::AI;
247  controller.parse(cfg["controller"].str());
248 
249  // TODO: Why do we read disallow observers differently when controller is empty?
250  if(controller == CONTROLLER::EMPTY) {
251  disallow_observers = cfg["disallow_observers"].to_bool(true);
252  }
253 
254  // override persistence flag if it is explicitly defined in the config
255  // by default, persistence of a team is set depending on the controller
256  persistent = cfg["persistent"].to_bool(this->controller == CONTROLLER::HUMAN);
257 
258  //========================================================
259  // END OF MESSY CODE
260 
261  // Share_view and share_maps can't both be enabled,
262  // so share_view overrides share_maps.
263  share_vision = cfg["share_vision"].to_enum<team::SHARE_VISION>(team::SHARE_VISION::ALL);
265 
266  LOG_NG << "team_info::team_info(...): team_name: " << team_name << ", share_vision: " << share_vision << ".\n";
267 }
268 
270 {
271  if(cfg.has_attribute("share_view") || cfg.has_attribute("share_maps")) {
272  if(cfg["share_view"].to_bool()) {
273  share_vision = team::SHARE_VISION::ALL;
274  } else if(cfg["share_maps"].to_bool(true)) {
275  share_vision = team::SHARE_VISION::SHROUD;
276  } else {
278  }
279  }
280 }
281 
283 {
284  cfg["gold"] = gold;
285  cfg["start_gold"] = start_gold;
286  cfg["income"] = income;
287  cfg["team_name"] = team_name;
288  cfg["user_team_name"] = user_team_name;
289  cfg["side_name"] = side_name;
290  cfg["faction"] = faction;
291  cfg["faction_name"] = faction_name;
292  cfg["save_id"] = save_id;
293  cfg["current_player"] = current_player;
294  cfg["flag"] = flag;
295  cfg["flag_icon"] = flag_icon;
296  cfg["id"] = id;
297  cfg["objectives"] = objectives;
298  cfg["objectives_changed"] = objectives_changed;
299  cfg["countdown_time"] = countdown_time;
300  cfg["action_bonus_count"] = action_bonus_count;
301  cfg["village_gold"] = income_per_village;
302  cfg["village_support"] = support_per_village;
303  cfg["recall_cost"] = recall_cost;
304  cfg["disallow_observers"] = disallow_observers;
305  cfg["allow_player"] = allow_player;
306  cfg["chose_random"] = chose_random;
307  cfg["no_leader"] = no_leader;
308  cfg["defeat_condition"] = defeat_condition;
309  cfg["hidden"] = hidden;
310  cfg["suppress_end_turn_confirmation"] = no_turn_confirmation;
311  cfg["scroll_to_leader"] = scroll_to_leader;
312  cfg["controller"] = controller;
313  cfg["recruit"] = utils::join(can_recruit);
314  cfg["share_vision"] = share_vision;
315 
316  cfg["color"] = color;
317  cfg["persistent"] = persistent;
318  cfg["lost"] = lost;
319  cfg["carryover_percentage"] = carryover_percentage;
320  cfg["carryover_add"] = carryover_add;
321  cfg["carryover_bonus"] = carryover_bonus;
322  cfg["carryover_gold"] = carryover_gold;
323 
324  if(!variables.empty()) {
325  cfg.add_child("variables", variables);
326  }
327 
329 }
330 
332  : gold_(0)
333  , villages_()
334  , shroud_()
335  , fog_()
336  , fog_clearer_()
337  , auto_shroud_updates_(true)
338  , info_()
339  , countdown_time_(0)
341  , recall_list_()
342  , last_recruit_()
343  , enemies_()
344  , ally_shroud_()
345  , ally_fog_()
346  , planned_actions_()
347 {
348 }
349 
351 {
352 }
353 
354 void team::build(const config& cfg, const gamemap& map, int gold)
355 {
356  gold_ = gold;
357  info_.read(cfg);
358 
359  fog_.set_enabled(cfg["fog"].to_bool());
360  fog_.read(cfg["fog_data"]);
361  shroud_.set_enabled(cfg["shroud"].to_bool());
362  shroud_.read(cfg["shroud_data"]);
363  auto_shroud_updates_ = cfg["auto_shroud"].to_bool(auto_shroud_updates_);
364 
365  LOG_NG << "team::team(...): team_name: " << info_.team_name << ", shroud: " << uses_shroud()
366  << ", fog: " << uses_fog() << ".\n";
367 
368  // Load the WML-cleared fog.
369  const config& fog_override = cfg.child("fog_override");
370  if(fog_override) {
371  const std::vector<map_location> fog_vector
372  = map.parse_location_range(fog_override["x"], fog_override["y"], true);
373  fog_clearer_.insert(fog_vector.begin(), fog_vector.end());
374  }
375 
376  // To ensure some minimum starting gold,
377  // gold is the maximum of 'gold' and what is given in the config file
378  gold_ = std::max(gold, info_.gold);
379  if(gold_ != info_.gold) {
381  }
382 
383  // Old code was doing:
384  // info_.start_gold = std::to_string(gold) + " (" + info_.start_gold + ")";
385  // Was it correct?
386 
387  // Load in the villages the side controls at the start
388  for(const config& v : cfg.child_range("village")) {
389  map_location loc(v);
390  if(map.is_village(loc)) {
391  villages_.insert(loc);
392  } else {
393  WRN_NG << "[side] " << current_player() << " [village] points to a non-village location " << loc
394  << std::endl;
395  }
396  }
397 
398  countdown_time_ = cfg["countdown_time"];
399  action_bonus_count_ = cfg["action_bonus_count"];
400 
401  planned_actions_.reset(new wb::side_actions());
402  planned_actions_->set_team_index(info_.side - 1);
403 }
404 
405 void team::write(config& cfg) const
406 {
407  info_.write(cfg);
408  cfg["auto_shroud"] = auto_shroud_updates_;
409  cfg["shroud"] = uses_shroud();
410  cfg["fog"] = uses_fog();
411  cfg["gold"] = gold_;
412 
413  // Write village locations
414  for(const map_location& loc : villages_) {
415  loc.write(cfg.add_child("village"));
416  }
417 
418  cfg["shroud_data"] = shroud_.write();
419  cfg["fog_data"] = fog_.write();
420  if(!fog_clearer_.empty())
421  write_location_range(fog_clearer_, cfg.add_child("fog_override"));
422 
423  cfg["countdown_time"] = countdown_time_;
424  cfg["action_bonus_count"] = action_bonus_count_;
425 }
426 
427 void team::fix_villages(const gamemap &map)
428 {
429  for (auto it = villages_.begin(); it != villages_.end(); ) {
430  if (map.is_village(*it)) {
431  ++it;
432  }
433  else {
434  it = villages_.erase(it);
435  }
436  }
437 }
438 
440 {
441  villages_.insert(loc);
443 
444  if(gamedata) {
445  config::attribute_value& var = gamedata->get_variable("owner_side");
446  const config::attribute_value old_value = var;
447  var = owner_side;
448 
449  // During team building, game_events pump is not guaranteed to exist yet. (At current revision.) We skip capture
450  // events in this case.
452  res = resources::game_events->pump().fire("capture", loc);
453  }
454 
455  if(old_value.blank()) {
456  gamedata->clear_variable("owner_side");
457  } else {
458  var = old_value;
459  }
460  }
461 
462  return res;
463 }
464 
466 {
467  const std::set<map_location>::const_iterator vil = villages_.find(loc);
468  assert(vil != villages_.end());
469  villages_.erase(vil);
470 }
471 
472 void team::set_recruits(const std::set<std::string>& recruits)
473 {
477 }
478 
479 void team::add_recruit(const std::string& recruit)
480 {
481  info_.can_recruit.insert(recruit);
484 }
485 
487 {
490  }
491  int min = 20;
492  for(std::string recruit : info_.can_recruit) {
493  const unit_type* ut = unit_types.find(recruit);
494  if(!ut) {
495  continue;
496  } else {
497  if(ut->cost() < min) {
498  min = ut->cost();
499  }
500  }
501  }
502 
504 
506 }
507 
508 void team::calculate_enemies(std::size_t index) const
509 {
510  if(!resources::gameboard || index >= resources::gameboard->teams().size()) {
511  return;
512  }
513 
514  while(enemies_.size() <= index) {
515  enemies_.push_back(calculate_is_enemy(enemies_.size()));
516  }
517 }
518 
519 bool team::calculate_is_enemy(std::size_t index) const
520 {
521  // We're not enemies of ourselves
522  if(&resources::gameboard->teams()[index] == this) {
523  return false;
524  }
525 
526  // We are friends with anyone who we share a teamname with
527  std::vector<std::string> our_teams = utils::split(info_.team_name);
528  std::vector<std::string> their_teams = utils::split(resources::gameboard->teams()[index].info_.team_name);
529 
530  LOG_NGE << "team " << info_.side << " calculates if it has enemy in team " << index + 1 << "; our team_name ["
531  << info_.team_name << "], their team_name is [" << resources::gameboard->teams()[index].info_.team_name
532  << "]" << std::endl;
533 
534  for(const std::string& t : our_teams) {
535  if(std::find(their_teams.begin(), their_teams.end(), t) != their_teams.end()) {
536  LOG_NGE << "team " << info_.side << " found same team name [" << t << "] in team " << index + 1
537  << std::endl;
538  return false;
539  } else {
540  LOG_NGE << "team " << info_.side << " not found same team name [" << t << "] in team " << index + 1
541  << std::endl;
542  }
543  }
544 
545  LOG_NGE << "team " << info_.side << " has enemy in team " << index + 1 << std::endl;
546  return true;
547 }
548 
549 namespace
550 {
551 class controller_server_choice : public synced_context::server_choice
552 {
553 public:
554  controller_server_choice(team::CONTROLLER new_controller, const team& team)
555  : new_controller_(new_controller)
556  , team_(team)
557  {
558  }
559 
560  /** We are in a game with no mp server and need to do this choice locally */
561  virtual config local_choice() const
562  {
563  return config{"controller", new_controller_, "is_local", true};
564  }
565 
566  /** The request which is sent to the mp server. */
567  virtual config request() const
568  {
569  return config{
570  "new_controller", new_controller_, "old_controller", team_.controller(), "side", team_.side(),
571  };
572  }
573 
574  virtual const char* name() const
575  {
576  return "change_controller_wml";
577  }
578 
579 private:
580  team::CONTROLLER new_controller_;
581  const team& team_;
582 };
583 } // end anon namespace
584 
585 void team::change_controller_by_wml(const std::string& new_controller_string)
586 {
587  CONTROLLER new_controller;
588  if(!new_controller.parse(new_controller_string)) {
589  WRN_NG << "ignored attempt to change controller to " << new_controller_string << std::endl;
590  return;
591  }
592 
593  if(new_controller == CONTROLLER::EMPTY && resources::controller->current_side() == this->side()) {
594  WRN_NG << "ignored attempt to change the currently playing side's controller to 'null'" << std::endl;
595  return;
596  }
597 
598  config choice = synced_context::ask_server_choice(controller_server_choice(new_controller, *this));
599  if(!new_controller.parse(choice["controller"])) {
600  // TODO: this should be more than a ERR_NG message.
601  // GL-2016SEP02 Oh? So why was ERR_NG defined as warning level? Making the call fit the definition.
602  WRN_NG << "Received an invalid controller string from the server" << choice["controller"] << std::endl;
603  }
604 
605  if(!resources::controller->is_replay()) {
606  set_local(choice["is_local"].to_bool());
607  }
608 
609  if(playsingle_controller* pc = dynamic_cast<playsingle_controller*>(resources::controller)) {
610  if(pc->current_side() == side() && new_controller != controller()) {
611  pc->set_player_type_changed();
612  }
613  }
614 
615  change_controller(new_controller);
616 }
617 
618 void team::change_team(const std::string& name, const t_string& user_name)
619 {
620  info_.team_name = name;
621 
622  if(!user_name.empty()) {
623  info_.user_team_name = user_name;
624  } else {
625  info_.user_team_name = name;
626  }
627 
628  clear_caches();
629 }
630 
632 {
633  // Reset the cache of allies for all teams
635  for(auto& t : resources::gameboard->teams()) {
636  t.enemies_.clear();
637  t.ally_shroud_.clear();
638  t.ally_fog_.clear();
639  }
640  }
641 }
642 
643 void team::set_objectives(const t_string& new_objectives, bool silently)
644 {
645  info_.objectives = new_objectives;
646 
647  if(!silently) {
648  info_.objectives_changed = true;
649  }
650 }
651 
652 bool team::shrouded(const map_location& loc) const
653 {
654  if(!resources::gameboard) {
655  return shroud_.value(loc.wml_x(), loc.wml_y());
656  }
657 
658  return shroud_.shared_value(ally_shroud(resources::gameboard->teams()), loc.wml_x(), loc.wml_y());
659 }
660 
661 bool team::fogged(const map_location& loc) const
662 {
663  if(shrouded(loc)) {
664  return true;
665  }
666 
667  // Check for an override of fog.
668  if(fog_clearer_.count(loc) > 0) {
669  return false;
670  }
671 
672  if(!resources::gameboard) {
673  return fog_.value(loc.wml_x(), loc.wml_y());
674  }
675 
676  return fog_.shared_value(ally_fog(resources::gameboard->teams()), loc.wml_x(), loc.wml_y());
677 }
678 
679 const std::vector<const shroud_map*>& team::ally_shroud(const std::vector<team>& teams) const
680 {
681  if(ally_shroud_.empty()) {
682  for(std::size_t i = 0; i < teams.size(); ++i) {
683  if(!is_enemy(i + 1) && (&(teams[i]) == this || teams[i].share_view() || teams[i].share_maps())) {
684  ally_shroud_.push_back(&(teams[i].shroud_));
685  }
686  }
687  }
688 
689  return ally_shroud_;
690 }
691 
692 const std::vector<const shroud_map*>& team::ally_fog(const std::vector<team>& teams) const
693 {
694  if(ally_fog_.empty()) {
695  for(std::size_t i = 0; i < teams.size(); ++i) {
696  if(!is_enemy(i + 1) && (&(teams[i]) == this || teams[i].share_view())) {
697  ally_fog_.push_back(&(teams[i].fog_));
698  }
699  }
700  }
701 
702  return ally_fog_;
703 }
704 
705 bool team::knows_about_team(std::size_t index) const
706 {
707  const team& t = resources::gameboard->teams()[index];
708 
709  // We know about our own team
710  if(this == &t) {
711  return true;
712  }
713 
714  // If we aren't using shroud or fog, then we know about everyone
715  if(!uses_shroud() && !uses_fog()) {
716  return true;
717  }
718 
719  // We don't know about enemies
720  if(is_enemy(index + 1)) {
721  return false;
722  }
723 
724  // We know our human allies.
725  if(t.is_human()) {
726  return true;
727  }
728 
729  // We know about allies we're sharing maps with
730  if(share_maps() && t.uses_shroud()) {
731  return true;
732  }
733 
734  // We know about allies we're sharing view with
735  if(share_view() && (t.uses_fog() || t.uses_shroud())) {
736  return true;
737  }
738 
739  return false;
740 }
741 
742 /**
743  * Removes the record of hexes that were cleared of fog via WML.
744  * @param[in] hexes The hexes to no longer keep clear.
745  */
746 void team::remove_fog_override(const std::set<map_location>& hexes)
747 {
748  // Take a set difference.
749  std::vector<map_location> result(fog_clearer_.size());
751  std::set_difference(fog_clearer_.begin(), fog_clearer_.end(), hexes.begin(), hexes.end(), result.begin());
752 
753  // Put the result into fog_clearer_.
754  fog_clearer_.clear();
755  fog_clearer_.insert(result.begin(), result_end);
756 }
757 
759 {
760  if(!resources::gameboard) {
761  return;
762  }
763 
764  if(side < 1 || side > static_cast<int>(resources::gameboard->teams().size())) {
765  throw game::game_error("invalid side(" + std::to_string(side) + ") found in unit definition");
766  }
767 }
768 
769 int shroud_map::width() const
770 {
771  return data_.size();
772 }
773 
775 {
776  if(data_.size() == 0) return 0;
777  return std::max_element(data_.begin(), data_.end(), [](const auto& a, const auto& b) {
778  return a.size() < b.size();
779  })->size();
780 }
781 
782 bool shroud_map::clear(int x, int y)
783 {
784  if(enabled_ == false || x < 0 || y < 0) {
785  return false;
786  }
787 
788  if(x >= static_cast<int>(data_.size())) {
789  data_.resize(x + 1);
790  }
791 
792  if(y >= static_cast<int>(data_[x].size())) {
793  data_[x].resize(y + 1);
794  }
795 
796  if(data_[x][y] == false) {
797  data_[x][y] = true;
798  return true;
799  }
800 
801  return false;
802 }
803 
804 void shroud_map::place(int x, int y)
805 {
806  if(enabled_ == false || x < 0 || y < 0) {
807  return;
808  }
809 
810  if(x >= static_cast<int>(data_.size())) {
811  DBG_NG << "Couldn't place shroud on invalid x coordinate: (" << x << ", " << y
812  << ") - max x: " << data_.size() - 1 << "\n";
813  } else if(y >= static_cast<int>(data_[x].size())) {
814  DBG_NG << "Couldn't place shroud on invalid y coordinate: (" << x << ", " << y
815  << ") - max y: " << data_[x].size() - 1 << "\n";
816  } else {
817  data_[x][y] = false;
818  }
819 }
820 
822 {
823  if(enabled_ == false) {
824  return;
825  }
826 
827  for(auto& i : data_) {
828  std::fill(i.begin(), i.end(), false);
829  }
830 }
831 
832 bool shroud_map::value(int x, int y) const
833 {
834  if(!enabled_) {
835  return false;
836  }
837 
838  // Locations for which we have no data are assumed to still be covered.
839  if(x < 0 || x >= static_cast<int>(data_.size())) {
840  return true;
841  }
842 
843  if(y < 0 || y >= static_cast<int>(data_[x].size())) {
844  return true;
845  }
846 
847  // data_ stores whether or not a location has been cleared, while
848  // we want to return whether or not a location is covered.
849  return !data_[x][y];
850 }
851 
852 bool shroud_map::shared_value(const std::vector<const shroud_map*>& maps, int x, int y) const
853 {
854  if(!enabled_) {
855  return false;
856  }
857 
858  // A quick abort:
859  if(x < 0 || y < 0) {
860  return true;
861  }
862 
863  // A tile is uncovered if it is uncovered on any shared map.
864  for(const shroud_map* const shared_map : maps) {
865  if(shared_map->enabled_ && !shared_map->value(x, y)) {
866  return false;
867  }
868  }
869 
870  return true;
871 }
872 
873 std::string shroud_map::write() const
874 {
875  std::stringstream shroud_str;
876  for(const auto& sh : data_) {
877  shroud_str << '|';
878 
879  for(bool i : sh) {
880  shroud_str << (i ? '1' : '0');
881  }
882 
883  shroud_str << '\n';
884  }
885 
886  return shroud_str.str();
887 }
888 
889 void shroud_map::read(const std::string& str)
890 {
891  data_.clear();
892 
893  for(const char sh : str) {
894  if(sh == '|') {
895  data_.resize(data_.size() + 1);
896  }
897 
898  if(data_.empty() == false) {
899  if(sh == '1') {
900  data_.back().push_back(true);
901  } else if(sh == '0') {
902  data_.back().push_back(false);
903  }
904  }
905  }
906 }
907 
908 void shroud_map::merge(const std::string& str)
909 {
910  int x = 0, y = 0;
911  for(std::size_t i = 1; i < str.length(); ++i) {
912  if(str[i] == '|') {
913  y = 0;
914  x++;
915  } else if(str[i] == '1') {
916  clear(x, y);
917  y++;
918  } else if(str[i] == '0') {
919  y++;
920  }
921  }
922 }
923 
924 bool shroud_map::copy_from(const std::vector<const shroud_map*>& maps)
925 {
926  if(enabled_ == false) {
927  return false;
928  }
929 
930  bool cleared = false;
931  for(const shroud_map* m : maps) {
932  if(m->enabled_ == false) {
933  continue;
934  }
935 
936  const std::vector<std::vector<bool>>& v = m->data_;
937  for(std::size_t x = 0; x != v.size(); ++x) {
938  for(std::size_t y = 0; y != v[x].size(); ++y) {
939  if(v[x][y]) {
940  cleared |= clear(x, y);
941  }
942  }
943  }
944  }
945 
946  return cleared;
947 }
948 
950 {
951  std::string index = get_side_color_id(side);
953 
954  if(gp != game_config::team_rgb_range.end()) {
955  return (gp->second);
956  }
957 
958  return color_range({255, 0, 0}, {255, 255, 255}, {0, 0, 0}, {255, 0, 0});
959 }
960 
962 {
963  return get_side_color_range(side).mid();
964 }
965 
967 {
968  // Note: use mid() instead of rep() unless
969  // high contrast is needed over a map or minimap!
970  return get_side_color_range(side).rep();
971 }
972 
973 std::string team::get_side_color_id(unsigned side)
974 {
975  try {
976  const unsigned index = side - 1;
977 
978  // If no gameboard (and by extension, team list) is available, use the default side color.
979  if(!resources::gameboard) {
980  return game_config::default_colors.at(index);
981  }
982 
983  // Else, try to fetch the color from the side's config.
984  const std::string& side_color = resources::gameboard->teams().at(index).color();
985 
986  if(!side_color.empty()) {
987  return side_color;
988  }
989 
990  // If the side color data was empty, fall back to the default color. This should only
991  // happen if the side data hadn't been initialized yet, which is the case if this function
992  // is being called to set up said side data. :P
993  return game_config::default_colors.at(index);
994  } catch(const std::out_of_range&) {
995  // Side index was invalid! Coloring will fail!
996  return "";
997  }
998 }
999 
1001 {
1002  const std::string& color_id = team::get_side_color_id(side);
1003  const auto& rgb_name = game_config::team_rgb_name[color_id];
1004  if(rgb_name.empty())
1005  // TRANSLATORS: $color_id is the internal identifier of a side color, for example, 'lightred'.
1006  // Translate the quotation marks only; leave "color_id" untranslated, as it's a variable name.
1007  return VGETTEXT("“$color_id”", {{ "color_id", color_id }});
1008  else
1009  return rgb_name;
1010 }
1011 
1013 {
1014  const config::attribute_value& c = cfg["color"];
1015 
1016  // If no color key or value was provided, use the given color for that side.
1017  // If outside a game context (ie, where a list of teams has been constructed),
1018  // this will just be the side's default color.
1019  if(c.blank() || c.empty()) {
1020  return get_side_color_id(cfg["side"].to_unsigned());
1021  }
1022 
1023  // Do the same as above for numeric color key values.
1024  if(unsigned side = c.to_unsigned()) {
1025  return get_side_color_id(side);
1026  }
1027 
1028  // Else, we should have a color id at this point. Return it.
1029  return c.str();
1030 }
1031 
1033 {
1034  return get_side_color_range(side).mid().to_hex_string();
1035 }
1036 
1038 {
1039  LOG_NG << "Adding recruitable units: \n";
1040  for(const std::string& recruit : info_.can_recruit) {
1041  LOG_NG << recruit << std::endl;
1042  }
1043 
1044  LOG_NG << "Added all recruitable units\n";
1045 }
1046 
1048 {
1049  config cfg;
1050  config& result = cfg.add_child("side");
1051  write(result);
1052  return result;
1053 }
1054 
1055 std::string team::allied_human_teams() const
1056 {
1057  std::vector<int> res;
1058  for(const team& t : resources::gameboard->teams()) {
1059  if(!t.is_enemy(this->side()) && t.is_human()) {
1060  res.push_back(t.side());
1061  }
1062  }
1063 
1064  return utils::join(res);
1065 }
play_controller * controller
Definition: resources.cpp:22
bool empty() const
Tests for an attribute that either was never set or was set to "".
std::string id
Definition: team.hpp:129
int width() const
Definition: team.cpp:769
bool allow_player
Definition: team.hpp:149
static const int default_team_gold_
Definition: team.hpp:170
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
bool no_turn_confirmation
Definition: team.hpp:153
std::string last_recruit_
Definition: team.hpp:448
int height() const
Definition: team.cpp:774
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1263
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:85
DEFEAT_CONDITION defeat_condition
Definition: team.hpp:142
bool copy_from(const std::vector< const shroud_map *> &maps)
Definition: team.cpp:924
std::map< std::string, color_range > team_rgb_range
Colors defined by WML [color_range] tags.
unsigned to_unsigned(unsigned def=0) const
SHARE_VISION share_vision
Definition: team.hpp:147
int village_support
Definition: game_config.cpp:56
void handle_legacy_share_vision(const config &cfg)
Definition: team.cpp:269
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
CONTROLLER controller
Definition: team.hpp:140
void set_objectives(const t_string &new_objectives, bool silently=false)
Definition: team.cpp:643
int carryover_gold
Definition: team.hpp:165
static std::string get_side_color_id_from_config(const config &cfg)
Definition: team.cpp:1012
static manager & get_singleton()
Definition: manager.hpp:145
void reset()
Definition: team.cpp:821
#define DBG_NG
Definition: team.cpp:40
int minimum_recruit_price() const
Definition: team.cpp:486
void write(config &cfg) const
Definition: team.cpp:282
Variant for storing WML attributes.
New lexcical_cast header.
bool has_attribute(config_key_type key) const
Definition: config.cpp:211
#define a
static const color_range get_side_color_range(int side)
Definition: team.cpp:949
static bool has_manager()
Definition: manager.hpp:151
int support_per_village
Definition: team.hpp:111
t_string objectives
Definition: team.hpp:133
void calculate_enemies(std::size_t index) const
Definition: team.cpp:508
bool share_view() const
Definition: team.hpp:403
void fix_villages(const gamemap &map)
Definition: team.cpp:427
static const std::set< std::string > attributes
Stores the attributes recognized by [side].
Definition: team.hpp:180
child_itors child_range(config_key_type key)
Definition: config.cpp:344
void clear(const std::string &key)
Definition: general.cpp:186
const std::string & gamedata
int wml_x() const
Definition: location.hpp:153
#define WRN_NG
Definition: team.cpp:42
unit_type_data unit_types
Definition: types.cpp:1482
void build(const config &cfg, const gamemap &map, int gold=default_team_gold_)
Definition: team.cpp:354
int minimum_recruit_price
Definition: team.hpp:112
bool clear(int x, int y)
Definition: team.cpp:782
team()
Definition: team.cpp:331
t_string faction_name
Definition: team.hpp:119
std::shared_ptr< wb::side_actions > planned_actions_
Whiteboard planned actions for this team.
Definition: team.hpp:460
std::string flag_icon
Definition: team.hpp:127
bool objectives_changed
< Team&#39;s objectives for the current level.
Definition: team.hpp:138
void add_recruit(const std::string &)
Definition: team.cpp:479
void change_controller(const std::string &new_controller)
Definition: team.hpp:287
std::string faction
Definition: team.hpp:118
int village_support() const
Definition: team.hpp:211
A single unit type that the player may recruit.
Definition: types.hpp:45
double carryover_bonus
Definition: team.hpp:164
config::attribute_value & get_variable(const std::string &varname)
throws invalid_variablename_exception if varname is no valid variable name.
Definition: game_data.cpp:63
bool value(int x, int y) const
Definition: team.cpp:832
bool calculate_is_enemy(std::size_t index) const
Definition: team.cpp:519
static std::string get_side_highlight_pango(int side)
Definition: team.cpp:1032
std::string flag_icon
#define b
bool uses_fog() const
Definition: team.hpp:331
int gold() const
Definition: team.hpp:201
shroud_map shroud_
Definition: team.hpp:436
static const t_string get_side_color_name_for_UI(unsigned side)
Definition: team.cpp:1000
static lg::log_domain log_engine_enemies("engine/enemies")
bool knows_about_team(std::size_t index) const
Definition: team.cpp:705
team_info info_
Definition: team.hpp:442
bool add_ai_for_side_from_file(side_number side, const std::string &file, bool replace=true)
Adds active AI for specified side from file.
Definition: manager.cpp:615
void read(const std::string &shroud_data)
Definition: team.cpp:889
std::string team_name
Definition: team.hpp:115
void write(config &cfg) const
Definition: team.cpp:405
void raise_recruit_list_changed()
Notifies all observers of &#39;ai_recruit_list_changed&#39; event.
Definition: manager.cpp:453
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:72
std::string allied_human_teams() const
Definition: team.cpp:1055
void merge(const std::string &shroud_data)
Definition: team.cpp:908
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
bool chose_random
Definition: team.hpp:150
int cost() const
Definition: types.hpp:175
bool is_local
Definition: team.hpp:141
void read(const config &cfg)
Definition: team.cpp:166
bool uses_shroud() const
Definition: team.hpp:330
config variables
Definition: team.hpp:166
int wml_y() const
Definition: location.hpp:154
const int gold_carryover_percentage
Default percentage gold carried over to the next scenario.
Definition: game_config.cpp:64
void set_local(bool local)
Definition: team.hpp:284
bool blank() const
Tests for an attribute that was never set.
game_board * gameboard
Definition: resources.cpp:21
Encapsulates the map of the game.
Definition: map.hpp:171
bool disallow_observers
Definition: team.hpp:148
bool is_enemy(int n) const
Definition: team.hpp:255
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands...
bool persistent
Definition: team.hpp:158
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
bool auto_shroud_updates_
Definition: team.hpp:440
void remove_fog_override(const std::set< map_location > &hexes)
Removes the record of hexes that were cleared of fog via WML.
Definition: team.cpp:746
std::vector< const shroud_map * > ally_fog_
Definition: team.hpp:455
game_events::manager * game_events
Definition: resources.cpp:25
boost::dynamic_bitset enemies_
Definition: team.hpp:453
static std::string get_side_color_id(unsigned side)
Definition: team.cpp:973
Encapsulates the map of the game.
Definition: location.hpp:38
bool shrouded(const map_location &loc) const
Definition: team.cpp:652
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
static void clear_caches()
clear the shroud, fog, and enemies cache for all teams
Definition: team.cpp:631
std::string current_player
Definition: team.hpp:122
int start_gold
Definition: team.hpp:108
std::size_t i
Definition: function.cpp:967
virtual ~team()
Definition: team.cpp:350
void write_location_range(const std::set< map_location > &locs, config &cfg)
Write a set of locations into a config using ranges, adding keys x=x1,..,xn and y=y1a-y1b,..,yna-ynb.
Definition: location.cpp:399
int carryover_percentage
Definition: team.hpp:161
Default, unset return value.
Definition: retval.hpp:32
int countdown_time_
Definition: team.hpp:444
Game configuration data as global variables.
Definition: build_info.cpp:59
std::set< map_location > villages_
Definition: team.hpp:434
int village_income
Definition: game_config.cpp:55
#define LOG_NGE
Definition: team.cpp:47
void set_enabled(bool enabled)
Definition: team.hpp:59
std::string to_hex_string() const
Returns the stored color in rrggbb hex format.
Definition: color.cpp:98
Define the game&#39;s event mechanism.
A color range definition is made of four reference RGB colors, used for calculating conversions from ...
Definition: color_range.hpp:49
const std::string & current_player() const
Definition: team.hpp:246
int recall_cost
Definition: team.hpp:113
static t_string from_serialized(const std::string &string)
Definition: tstring.hpp:154
void validate_side(int side)
Definition: team.cpp:758
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
bool add_ai_for_side_from_config(side_number side, const config &cfg, bool replace=true)
Adds active AI for specified side from cfg.
Definition: manager.cpp:625
std::vector< const shroud_map * > ally_shroud_
Definition: team.hpp:455
recall_list_manager recall_list_
Definition: team.hpp:447
void change_controller_by_wml(const std::string &new_controller)
Definition: team.cpp:585
static lg::log_domain log_engine("engine")
config & add_child(config_key_type key)
Definition: config.cpp:514
#define LOG_NG
Definition: team.cpp:41
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:481
bool is_village(const map_location &loc) const
Definition: map.cpp:66
game_events::pump_result_t get_village(const map_location &, const int owner_side, game_data *fire_event)
Acquires a village from owner_side.
Definition: team.cpp:439
bool is_human() const
Definition: team.hpp:276
static color_t get_minimap_color(int side)
Definition: team.cpp:966
bool empty() const
Definition: tstring.hpp:187
const std::vector< const shroud_map * > & ally_fog(const std::vector< team > &teams) const
Definition: team.cpp:692
bool share_maps() const
Definition: team.hpp:402
std::set< map_location > fog_clearer_
Stores hexes that have been cleared of fog via WML.
Definition: team.hpp:438
double t
Definition: astarsearch.cpp:65
t_string side_name
Definition: team.hpp:117
color_t rep() const
High-contrast shade, intended for the minimap markers.
Definition: color_range.hpp:95
std::vector< std::string > split(const config_attribute_value &val)
void lose_village(const map_location &)
Definition: team.cpp:465
shroud_map fog_
Definition: team.hpp:436
std::string color
Definition: team.hpp:155
int income_per_village
Definition: team.hpp:110
int gold_
Definition: team.hpp:433
t_string user_team_name
Definition: team.hpp:116
static color_t get_side_color(int side)
Definition: team.cpp:961
bool carryover_add
Definition: team.hpp:162
int action_bonus_count_
Definition: team.hpp:445
int action_bonus_count
Definition: team.hpp:124
void log_recruitable() const
Definition: team.cpp:1037
void place(int x, int y)
Definition: team.cpp:804
std::vector< map_location > parse_location_range(const std::string &xvals, const std::string &yvals, bool with_border=false) const
Parses ranges of locations into a vector of locations, using this map&#39;s dimensions as bounds...
Definition: map.cpp:424
void change_team(const std::string &name, const t_string &user_name)
Definition: team.cpp:618
std::set< std::string > can_recruit
Definition: team.hpp:114
bool fogged(const map_location &loc) const
Definition: team.cpp:661
std::string save_id
Definition: team.hpp:120
bool translatable() const
Definition: tstring.hpp:193
game_events::wml_event_pump & pump()
Definition: manager.cpp:230
void clear_variable(const std::string &varname)
Clears attributes config children does nothing if varname is no valid variable name.
Definition: game_data.cpp:115
void set_recruits(const std::set< std::string > &recruits)
Definition: team.cpp:472
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:465
std::vector< std::string > default_colors
std::tuple< bool, bool > pump_result_t
Definition: fwd.hpp:29
int side() const
Definition: team.hpp:200
bool shared_value(const std::vector< const shroud_map *> &maps, int x, int y) const
Definition: team.cpp:852
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
std::string write() const
Definition: team.cpp:873
mock_char c
std::string flag
Definition: team.hpp:126
std::map< std::string, t_string > team_rgb_name
This internal whiteboard class holds the planned action queues for a team, and offers many utility me...
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
bool empty() const
Definition: config.cpp:941
const std::vector< const shroud_map * > & ally_shroud(const std::vector< team > &teams) const
Definition: team.cpp:679
std::string countdown_time
Definition: team.hpp:123
bool scroll_to_leader
Definition: team.hpp:131
bool no_leader
Definition: team.hpp:151
color_t mid() const
Average color shade.
Definition: color_range.hpp:86
const std::set< std::string > & recruits() const
Definition: team.hpp:235
static config ask_server_choice(const server_choice &)
If we are in a mp game, ask the server, otherwise generate the answer ourselves.
std::string str(const std::string &fallback="") const
config to_config() const
Definition: team.cpp:1047
CONTROLLER controller() const
Definition: team.hpp:267