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