The Battle for Wesnoth  1.17.4+dev
team.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
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_cond(defeat_condition::type::no_leader_left)
147  , proxy_controller(side_proxy_controller::type::human)
148  , share_vision(team_shared_vision::type::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_cond = defeat_condition::get_enum(cfg["defeat_condition"].str()).value_or(defeat_condition::type::no_leader_left);
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 = side_controller::get_enum(cfg["controller"].str()).value_or(side_controller::type::ai);
247 
248  // TODO: Why do we read disallow observers differently when controller is empty?
249  if(controller == side_controller::type::none) {
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 == side_controller::type::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 = team_shared_vision::get_enum(cfg["share_vision"].str()).value_or(team_shared_vision::type::all);
264 
265  LOG_NG << "team_info::team_info(...): team_name: " << team_name << ", share_vision: " << team_shared_vision::get_string(share_vision) << ".\n";
266 }
267 
269 {
270  if(cfg.has_attribute("share_view") || cfg.has_attribute("share_maps")) {
271  if(cfg["share_view"].to_bool()) {
272  share_vision = team_shared_vision::type::all;
273  } else if(cfg["share_maps"].to_bool(true)) {
275  } else {
276  share_vision = team_shared_vision::type::none;
277  }
278  }
279 }
280 
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::get_string(defeat_cond);
308  cfg["hidden"] = hidden;
309  cfg["suppress_end_turn_confirmation"] = no_turn_confirmation;
310  cfg["scroll_to_leader"] = scroll_to_leader;
311  cfg["controller"] = side_controller::get_string(controller);
312  cfg["recruit"] = utils::join(can_recruit);
313  cfg["share_vision"] = team_shared_vision::get_string(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 
328 }
329 
331  : gold_(0)
332  , villages_()
333  , shroud_()
334  , fog_()
335  , fog_clearer_()
336  , auto_shroud_updates_(true)
337  , info_()
338  , countdown_time_(0)
340  , recall_list_()
341  , last_recruit_()
342  , enemies_()
343  , ally_shroud_()
344  , ally_fog_()
345  , planned_actions_()
346 {
347 }
348 
350 {
351 }
352 
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) {
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 
404 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 
426 void team::fix_villages(const gamemap &map)
427 {
428  for (auto it = villages_.begin(); it != villages_.end(); ) {
429  if (map.is_village(*it)) {
430  ++it;
431  }
432  else {
433  it = villages_.erase(it);
434  }
435  }
436 }
437 
439 {
440  villages_.insert(loc);
442 
443  if(gamedata) {
444  config::attribute_value& var = gamedata->get_variable("owner_side");
445  const config::attribute_value old_value = var;
446  var = owner_side;
447 
448  // During team building, game_events pump is not guaranteed to exist yet. (At current revision.) We skip capture
449  // events in this case.
451  res = resources::game_events->pump().fire("capture", loc);
452  }
453 
454  if(old_value.blank()) {
455  gamedata->clear_variable("owner_side");
456  } else {
457  var = old_value;
458  }
459  }
460 
461  return res;
462 }
463 
465 {
466  const std::set<map_location>::const_iterator vil = villages_.find(loc);
467  assert(vil != villages_.end());
468  villages_.erase(vil);
469 }
470 
471 void team::set_recruits(const std::set<std::string>& recruits)
472 {
476 }
477 
478 void team::add_recruit(const std::string& recruit)
479 {
480  info_.can_recruit.insert(recruit);
483 }
484 
486 {
489  }
490  int min = 20;
491  for(std::string recruit : info_.can_recruit) {
492  const unit_type* ut = unit_types.find(recruit);
493  if(!ut) {
494  continue;
495  } else {
496  if(ut->cost() < min) {
497  min = ut->cost();
498  }
499  }
500  }
501 
503 
505 }
506 
507 void team::calculate_enemies(std::size_t index) const
508 {
509  if(!resources::gameboard || index >= resources::gameboard->teams().size()) {
510  return;
511  }
512 
513  while(enemies_.size() <= index) {
514  enemies_.push_back(calculate_is_enemy(enemies_.size()));
515  }
516 }
517 
518 bool team::calculate_is_enemy(std::size_t index) const
519 {
520  // We're not enemies of ourselves
521  if(&resources::gameboard->teams()[index] == this) {
522  return false;
523  }
524 
525  // We are friends with anyone who we share a teamname with
526  std::vector<std::string> our_teams = utils::split(info_.team_name);
527  std::vector<std::string> their_teams = utils::split(resources::gameboard->teams()[index].info_.team_name);
528 
529  LOG_NGE << "team " << info_.side << " calculates if it has enemy in team " << index + 1 << "; our team_name ["
530  << info_.team_name << "], their team_name is [" << resources::gameboard->teams()[index].info_.team_name
531  << "]" << std::endl;
532 
533  for(const std::string& t : our_teams) {
534  if(std::find(their_teams.begin(), their_teams.end(), t) != their_teams.end()) {
535  LOG_NGE << "team " << info_.side << " found same team name [" << t << "] in team " << index + 1
536  << std::endl;
537  return false;
538  } else {
539  LOG_NGE << "team " << info_.side << " not found same team name [" << t << "] in team " << index + 1
540  << std::endl;
541  }
542  }
543 
544  LOG_NGE << "team " << info_.side << " has enemy in team " << index + 1 << std::endl;
545  return true;
546 }
547 
548 namespace
549 {
550 class controller_server_choice : public synced_context::server_choice
551 {
552 public:
553  controller_server_choice(side_controller::type new_controller, const team& team)
554  : new_controller_(new_controller)
555  , team_(team)
556  {
557  }
558 
559  /** We are in a game with no mp server and need to do this choice locally */
560  virtual config local_choice() const
561  {
562  return config{"controller", side_controller::get_string(new_controller_), "is_local", true};
563  }
564 
565  /** The request which is sent to the mp server. */
566  virtual config request() const
567  {
568  return config{
569  "new_controller", side_controller::get_string(new_controller_), "old_controller", side_controller::get_string(team_.controller()), "side", team_.side(),
570  };
571  }
572 
573  virtual const char* name() const
574  {
575  return "change_controller_wml";
576  }
577 
578 private:
579  side_controller::type new_controller_;
580  const team& team_;
581 };
582 } // end anon namespace
583 
584 void team::change_controller_by_wml(const std::string& new_controller_string)
585 {
586  auto new_controller = side_controller::get_enum(new_controller_string);
587  if(!new_controller) {
588  WRN_NG << "ignored attempt to change controller to " << new_controller_string << std::endl;
589  return;
590  }
591 
592  if(new_controller == side_controller::type::none && resources::controller->current_side() == this->side()) {
593  WRN_NG << "ignored attempt to change the currently playing side's controller to 'null'" << std::endl;
594  return;
595  }
596 
597  config choice = synced_context::ask_server_choice(controller_server_choice(*new_controller, *this));
598  if(!side_controller::get_enum(choice["controller"].str())) {
599  WRN_NG << "Received an invalid controller string from the server" << choice["controller"] << std::endl;
600  } else {
601  new_controller = side_controller::get_enum(choice["controller"].str());
602  }
603 
604  if(!resources::controller->is_replay()) {
605  set_local(choice["is_local"].to_bool());
606  }
607 
608  if(playsingle_controller* pc = dynamic_cast<playsingle_controller*>(resources::controller)) {
609  if(pc->current_side() == side() && new_controller != controller()) {
610  pc->set_player_type_changed();
611  }
612  }
613 
614  change_controller(*new_controller);
615 }
616 
617 void team::change_team(const std::string& name, const t_string& user_name)
618 {
619  info_.team_name = name;
620 
621  if(!user_name.empty()) {
622  info_.user_team_name = user_name;
623  } else {
624  info_.user_team_name = name;
625  }
626 
627  clear_caches();
628 }
629 
631 {
632  // Reset the cache of allies for all teams
634  for(auto& t : resources::gameboard->teams()) {
635  t.enemies_.clear();
636  t.ally_shroud_.clear();
637  t.ally_fog_.clear();
638  }
639  }
640 }
641 
642 void team::set_objectives(const t_string& new_objectives, bool silently)
643 {
644  info_.objectives = new_objectives;
645 
646  if(!silently) {
647  info_.objectives_changed = true;
648  }
649 }
650 
651 bool team::shrouded(const map_location& loc) const
652 {
653  if(!resources::gameboard) {
654  return shroud_.value(loc.wml_x(), loc.wml_y());
655  }
656 
657  return shroud_.shared_value(ally_shroud(resources::gameboard->teams()), loc.wml_x(), loc.wml_y());
658 }
659 
660 bool team::fogged(const map_location& loc) const
661 {
662  if(shrouded(loc)) {
663  return true;
664  }
665 
666  // Check for an override of fog.
667  if(fog_clearer_.count(loc) > 0) {
668  return false;
669  }
670 
671  if(!resources::gameboard) {
672  return fog_.value(loc.wml_x(), loc.wml_y());
673  }
674 
675  return fog_.shared_value(ally_fog(resources::gameboard->teams()), loc.wml_x(), loc.wml_y());
676 }
677 
678 const std::vector<const shroud_map*>& team::ally_shroud(const std::vector<team>& teams) const
679 {
680  if(ally_shroud_.empty()) {
681  for(std::size_t i = 0; i < teams.size(); ++i) {
682  if(!is_enemy(i + 1) && (&(teams[i]) == this || teams[i].share_view() || teams[i].share_maps())) {
683  ally_shroud_.push_back(&(teams[i].shroud_));
684  }
685  }
686  }
687 
688  return ally_shroud_;
689 }
690 
691 const std::vector<const shroud_map*>& team::ally_fog(const std::vector<team>& teams) const
692 {
693  if(ally_fog_.empty()) {
694  for(std::size_t i = 0; i < teams.size(); ++i) {
695  if(!is_enemy(i + 1) && (&(teams[i]) == this || teams[i].share_view())) {
696  ally_fog_.push_back(&(teams[i].fog_));
697  }
698  }
699  }
700 
701  return ally_fog_;
702 }
703 
704 bool team::knows_about_team(std::size_t index) const
705 {
706  const team& t = resources::gameboard->teams()[index];
707 
708  // We know about our own team
709  if(this == &t) {
710  return true;
711  }
712 
713  // If we aren't using shroud or fog, then we know about everyone
714  if(!uses_shroud() && !uses_fog()) {
715  return true;
716  }
717 
718  // We don't know about enemies
719  if(is_enemy(index + 1)) {
720  return false;
721  }
722 
723  // We know our human allies.
724  if(t.is_human()) {
725  return true;
726  }
727 
728  // We know about allies we're sharing maps with
729  if(share_maps() && t.uses_shroud()) {
730  return true;
731  }
732 
733  // We know about allies we're sharing view with
734  if(share_view() && (t.uses_fog() || t.uses_shroud())) {
735  return true;
736  }
737 
738  return false;
739 }
740 
741 /**
742  * Removes the record of hexes that were cleared of fog via WML.
743  * @param[in] hexes The hexes to no longer keep clear.
744  */
745 void team::remove_fog_override(const std::set<map_location>& hexes)
746 {
747  // Take a set difference.
748  std::vector<map_location> result(fog_clearer_.size());
750  std::set_difference(fog_clearer_.begin(), fog_clearer_.end(), hexes.begin(), hexes.end(), result.begin());
751 
752  // Put the result into fog_clearer_.
753  fog_clearer_.clear();
754  fog_clearer_.insert(result.begin(), result_end);
755 }
756 
758 {
759  if(!resources::gameboard) {
760  return;
761  }
762 
763  if(side < 1 || side > static_cast<int>(resources::gameboard->teams().size())) {
764  throw game::game_error("invalid side(" + std::to_string(side) + ") found in unit definition");
765  }
766 }
767 
768 int shroud_map::width() const
769 {
770  return data_.size();
771 }
772 
774 {
775  if(data_.size() == 0) return 0;
776  return std::max_element(data_.begin(), data_.end(), [](const auto& a, const auto& b) {
777  return a.size() < b.size();
778  })->size();
779 }
780 
781 bool shroud_map::clear(int x, int y)
782 {
783  if(enabled_ == false || x < 0 || y < 0) {
784  return false;
785  }
786 
787  if(x >= static_cast<int>(data_.size())) {
788  data_.resize(x + 1);
789  }
790 
791  if(y >= static_cast<int>(data_[x].size())) {
792  data_[x].resize(y + 1);
793  }
794 
795  if(data_[x][y] == false) {
796  data_[x][y] = true;
797  return true;
798  }
799 
800  return false;
801 }
802 
803 void shroud_map::place(int x, int y)
804 {
805  if(enabled_ == false || x < 0 || y < 0) {
806  return;
807  }
808 
809  if(x >= static_cast<int>(data_.size())) {
810  DBG_NG << "Couldn't place shroud on invalid x coordinate: (" << x << ", " << y
811  << ") - max x: " << data_.size() - 1 << "\n";
812  } else if(y >= static_cast<int>(data_[x].size())) {
813  DBG_NG << "Couldn't place shroud on invalid y coordinate: (" << x << ", " << y
814  << ") - max y: " << data_[x].size() - 1 << "\n";
815  } else {
816  data_[x][y] = false;
817  }
818 }
819 
821 {
822  if(enabled_ == false) {
823  return;
824  }
825 
826  for(auto& i : data_) {
827  std::fill(i.begin(), i.end(), false);
828  }
829 }
830 
831 bool shroud_map::value(int x, int y) const
832 {
833  if(!enabled_) {
834  return false;
835  }
836 
837  // Locations for which we have no data are assumed to still be covered.
838  if(x < 0 || x >= static_cast<int>(data_.size())) {
839  return true;
840  }
841 
842  if(y < 0 || y >= static_cast<int>(data_[x].size())) {
843  return true;
844  }
845 
846  // data_ stores whether or not a location has been cleared, while
847  // we want to return whether or not a location is covered.
848  return !data_[x][y];
849 }
850 
851 bool shroud_map::shared_value(const std::vector<const shroud_map*>& maps, int x, int y) const
852 {
853  if(!enabled_) {
854  return false;
855  }
856 
857  // A quick abort:
858  if(x < 0 || y < 0) {
859  return true;
860  }
861 
862  // A tile is uncovered if it is uncovered on any shared map.
863  for(const shroud_map* const shared_map : maps) {
864  if(shared_map->enabled_ && !shared_map->value(x, y)) {
865  return false;
866  }
867  }
868 
869  return true;
870 }
871 
872 std::string shroud_map::write() const
873 {
874  std::stringstream shroud_str;
875  for(const auto& sh : data_) {
876  shroud_str << '|';
877 
878  for(bool i : sh) {
879  shroud_str << (i ? '1' : '0');
880  }
881 
882  shroud_str << '\n';
883  }
884 
885  return shroud_str.str();
886 }
887 
888 void shroud_map::read(const std::string& str)
889 {
890  data_.clear();
891 
892  for(const char sh : str) {
893  if(sh == '|') {
894  data_.resize(data_.size() + 1);
895  }
896 
897  if(data_.empty() == false) {
898  if(sh == '1') {
899  data_.back().push_back(true);
900  } else if(sh == '0') {
901  data_.back().push_back(false);
902  }
903  }
904  }
905 }
906 
907 void shroud_map::merge(const std::string& str)
908 {
909  int x = 0, y = 0;
910  for(std::size_t i = 1; i < str.length(); ++i) {
911  if(str[i] == '|') {
912  y = 0;
913  x++;
914  } else if(str[i] == '1') {
915  clear(x, y);
916  y++;
917  } else if(str[i] == '0') {
918  y++;
919  }
920  }
921 }
922 
923 bool shroud_map::copy_from(const std::vector<const shroud_map*>& maps)
924 {
925  if(enabled_ == false) {
926  return false;
927  }
928 
929  bool cleared = false;
930  for(const shroud_map* m : maps) {
931  if(m->enabled_ == false) {
932  continue;
933  }
934 
935  const std::vector<std::vector<bool>>& v = m->data_;
936  for(std::size_t x = 0; x != v.size(); ++x) {
937  for(std::size_t y = 0; y != v[x].size(); ++y) {
938  if(v[x][y]) {
939  cleared |= clear(x, y);
940  }
941  }
942  }
943  }
944 
945  return cleared;
946 }
947 
949 {
950  std::string index = get_side_color_id(side);
952 
953  if(gp != game_config::team_rgb_range.end()) {
954  return (gp->second);
955  }
956 
957  return color_range({255, 0, 0}, {255, 255, 255}, {0, 0, 0}, {255, 0, 0});
958 }
959 
961 {
962  return get_side_color_range(side).mid();
963 }
964 
966 {
967  // Note: use mid() instead of rep() unless
968  // high contrast is needed over a map or minimap!
969  return get_side_color_range(side).rep();
970 }
971 
972 std::string team::get_side_color_id(unsigned side)
973 {
974  try {
975  const unsigned index = side - 1;
976 
977  // If no gameboard (and by extension, team list) is available, use the default side color.
978  if(!resources::gameboard) {
979  return game_config::default_colors.at(index);
980  }
981 
982  // Else, try to fetch the color from the side's config.
983  const std::string& side_color = resources::gameboard->teams().at(index).color();
984 
985  if(!side_color.empty()) {
986  return side_color;
987  }
988 
989  // If the side color data was empty, fall back to the default color. This should only
990  // happen if the side data hadn't been initialized yet, which is the case if this function
991  // is being called to set up said side data. :P
992  return game_config::default_colors.at(index);
993  } catch(const std::out_of_range&) {
994  // Side index was invalid! Coloring will fail!
995  return "";
996  }
997 }
998 
1000 {
1001  const std::string& color_id = team::get_side_color_id(side);
1002  const auto& rgb_name = game_config::team_rgb_name[color_id];
1003  if(rgb_name.empty())
1004  // TRANSLATORS: $color_id is the internal identifier of a side color, for example, 'lightred'.
1005  // Translate the quotation marks only; leave "color_id" untranslated, as it's a variable name.
1006  return VGETTEXT("“$color_id”", {{ "color_id", color_id }});
1007  else
1008  return rgb_name;
1009 }
1010 
1012 {
1013  const config::attribute_value& c = cfg["color"];
1014 
1015  // If no color key or value was provided, use the given color for that side.
1016  // If outside a game context (ie, where a list of teams has been constructed),
1017  // this will just be the side's default color.
1018  if(c.blank() || c.empty()) {
1019  return get_side_color_id(cfg["side"].to_unsigned());
1020  }
1021 
1022  // Do the same as above for numeric color key values.
1023  if(unsigned side = c.to_unsigned()) {
1024  return get_side_color_id(side);
1025  }
1026 
1027  // Else, we should have a color id at this point. Return it.
1028  return c.str();
1029 }
1030 
1032 {
1033  return get_side_color_range(side).mid().to_hex_string();
1034 }
1035 
1037 {
1038  LOG_NG << "Adding recruitable units: \n";
1039  for(const std::string& recruit : info_.can_recruit) {
1040  LOG_NG << recruit << std::endl;
1041  }
1042 
1043  LOG_NG << "Added all recruitable units\n";
1044 }
1045 
1047 {
1048  config cfg;
1049  config& result = cfg.add_child("side");
1050  write(result);
1051  return result;
1052 }
1053 
1054 std::string team::allied_human_teams() const
1055 {
1056  std::vector<int> res;
1057  for(const team& t : resources::gameboard->teams()) {
1058  if(!t.is_enemy(this->side()) && t.is_human()) {
1059  res.push_back(t.side());
1060  }
1061  }
1062 
1063  return utils::join(res);
1064 }
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:105
int width() const
Definition: team.cpp:768
bool allow_player
Definition: team.hpp:125
static const int default_team_gold_
Definition: team.hpp:146
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:129
std::string last_recruit_
Definition: team.hpp:422
int height() const
Definition: team.cpp:773
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:1247
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:86
bool copy_from(const std::vector< const shroud_map *> &maps)
Definition: team.cpp:923
std::map< std::string, color_range > team_rgb_range
Colors defined by WML [color_range] tags.
unsigned to_unsigned(unsigned def=0) const
int village_support
Definition: game_config.cpp:56
void handle_legacy_share_vision(const config &cfg)
Definition: team.cpp:268
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
void set_objectives(const t_string &new_objectives, bool silently=false)
Definition: team.cpp:642
int carryover_gold
Definition: team.hpp:141
static std::string get_side_color_id_from_config(const config &cfg)
Definition: team.cpp:1011
static manager & get_singleton()
Definition: manager.hpp:145
void reset()
Definition: team.cpp:820
#define DBG_NG
Definition: team.cpp:40
int minimum_recruit_price() const
Definition: team.cpp:485
void write(config &cfg) const
Definition: team.cpp:281
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:948
static bool has_manager()
Definition: manager.hpp:151
int support_per_village
Definition: team.hpp:87
t_string objectives
Definition: team.hpp:109
void calculate_enemies(std::size_t index) const
Definition: team.cpp:507
bool share_view() const
Definition: team.hpp:378
void fix_villages(const gamemap &map)
Definition: team.cpp:426
static const std::set< std::string > attributes
Stores the attributes recognized by [side].
Definition: team.hpp:156
child_itors child_range(config_key_type key)
Definition: config.cpp:344
void clear(const std::string &key)
Definition: general.cpp:192
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:1466
void build(const config &cfg, const gamemap &map, int gold=default_team_gold_)
Definition: team.cpp:353
int minimum_recruit_price
Definition: team.hpp:88
bool clear(int x, int y)
Definition: team.cpp:781
team()
Definition: team.cpp:330
t_string faction_name
Definition: team.hpp:95
std::shared_ptr< wb::side_actions > planned_actions_
Whiteboard planned actions for this team.
Definition: team.hpp:434
std::string flag_icon
Definition: team.hpp:103
bool objectives_changed
< Team&#39;s objectives for the current level.
Definition: team.hpp:114
void add_recruit(const std::string &)
Definition: team.cpp:478
void change_controller(const std::string &new_controller)
Definition: team.hpp:263
std::string faction
Definition: team.hpp:94
int village_support() const
Definition: team.hpp:187
A single unit type that the player may recruit.
Definition: types.hpp:45
double carryover_bonus
Definition: team.hpp:140
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:831
bool calculate_is_enemy(std::size_t index) const
Definition: team.cpp:518
static std::string get_side_highlight_pango(int side)
Definition: team.cpp:1031
std::string flag_icon
#define b
bool uses_fog() const
Definition: team.hpp:306
int gold() const
Definition: team.hpp:177
shroud_map shroud_
Definition: team.hpp:410
static const t_string get_side_color_name_for_UI(unsigned side)
Definition: team.cpp:999
static lg::log_domain log_engine_enemies("engine/enemies")
bool knows_about_team(std::size_t index) const
Definition: team.cpp:704
team_info info_
Definition: team.hpp:416
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:888
std::string team_name
Definition: team.hpp:91
void write(config &cfg) const
Definition: team.cpp:404
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:75
std::string allied_human_teams() const
Definition: team.cpp:1054
void merge(const std::string &shroud_data)
Definition: team.cpp:907
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:126
team_shared_vision::type share_vision
Definition: team.hpp:123
int cost() const
Definition: types.hpp:175
bool is_local
Definition: team.hpp:117
void read(const config &cfg)
Definition: team.cpp:166
bool uses_shroud() const
Definition: team.hpp:305
The base template for associating string values with enum values.
Definition: enum_base.hpp:30
side_controller::type controller() const
Definition: team.hpp:243
config variables
Definition: team.hpp:142
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:260
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:124
bool is_enemy(int n) const
Definition: team.hpp:231
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands...
bool persistent
Definition: team.hpp:134
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
bool auto_shroud_updates_
Definition: team.hpp:414
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:745
std::vector< const shroud_map * > ally_fog_
Definition: team.hpp:429
game_events::manager * game_events
Definition: resources.cpp:25
boost::dynamic_bitset enemies_
Definition: team.hpp:427
static std::string get_side_color_id(unsigned side)
Definition: team.cpp:972
Encapsulates the map of the game.
Definition: location.hpp:38
bool shrouded(const map_location &loc) const
Definition: team.cpp:651
bool shroud()
Definition: game.cpp:542
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:630
std::string current_player
Definition: team.hpp:98
int start_gold
Definition: team.hpp:84
std::size_t i
Definition: function.cpp:967
virtual ~team()
Definition: team.cpp:349
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:137
int countdown_time_
Definition: team.hpp:418
Game configuration data as global variables.
Definition: build_info.cpp:60
std::set< map_location > villages_
Definition: team.hpp:408
int village_income
Definition: game_config.cpp:55
#define LOG_NGE
Definition: team.cpp:47
static std::string get_string(typename T::type key)
Uses the int value of the provided enum to get the associated index of the values array in the implem...
Definition: enum_base.hpp:41
void set_enabled(bool enabled)
Definition: team.hpp:62
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:222
int recall_cost
Definition: team.hpp:89
static t_string from_serialized(const std::string &string)
Definition: tstring.hpp:154
void validate_side(int side)
Definition: team.cpp:757
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:429
recall_list_manager recall_list_
Definition: team.hpp:421
void change_controller_by_wml(const std::string &new_controller)
Definition: team.cpp:584
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:438
bool is_human() const
Definition: team.hpp:252
static color_t get_minimap_color(int side)
Definition: team.cpp:965
bool empty() const
Definition: tstring.hpp:187
const std::vector< const shroud_map * > & ally_fog(const std::vector< team > &teams) const
Definition: team.cpp:691
bool share_maps() const
Definition: team.hpp:377
std::set< map_location > fog_clearer_
Stores hexes that have been cleared of fog via WML.
Definition: team.hpp:412
static std::optional< typename T::type > get_enum(const std::string value)
Convert a string into its enum equivalent.
Definition: enum_base.hpp:52
double t
Definition: astarsearch.cpp:65
t_string side_name
Definition: team.hpp:93
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:464
shroud_map fog_
Definition: team.hpp:410
std::string color
Definition: team.hpp:131
int income_per_village
Definition: team.hpp:86
int gold_
Definition: team.hpp:407
t_string user_team_name
Definition: team.hpp:92
static color_t get_side_color(int side)
Definition: team.cpp:960
bool carryover_add
Definition: team.hpp:138
int action_bonus_count_
Definition: team.hpp:419
int action_bonus_count
Definition: team.hpp:100
void log_recruitable() const
Definition: team.cpp:1036
void place(int x, int y)
Definition: team.cpp:803
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:617
std::set< std::string > can_recruit
Definition: team.hpp:90
bool fogged(const map_location &loc) const
Definition: team.cpp:660
std::string save_id
Definition: team.hpp:96
bool translatable() const
Definition: tstring.hpp:193
game_events::wml_event_pump & pump()
Definition: manager.cpp:230
defeat_condition::type defeat_cond
Definition: team.hpp:118
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:471
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:176
bool shared_value(const std::vector< const shroud_map *> &maps, int x, int y) const
Definition: team.cpp:851
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
std::string write() const
Definition: team.cpp:872
mock_char c
std::string flag
Definition: team.hpp:102
std::map< std::string, t_string > team_rgb_name
side_controller::type controller
Definition: team.hpp:116
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:678
std::string countdown_time
Definition: team.hpp:99
bool scroll_to_leader
Definition: team.hpp:107
bool no_leader
Definition: team.hpp:127
color_t mid() const
Average color shade.
Definition: color_range.hpp:86
const std::set< std::string > & recruits() const
Definition: team.hpp:211
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:1046