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