The Battle for Wesnoth  1.19.15+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_upkeep(const team& t) const
690 {
691  // We know about our own team
692  if(this == &t) {
693  return true;
694  }
695 
696  // If we aren't using shroud or fog, then we know about everyone
697  if(!uses_shroud() && !uses_fog()) {
698  return true;
699  }
700 
701  // We don't know about enemies
702  if(is_enemy(t.side())) {
703  return false;
704  }
705 
706  // We know our human allies.
707  if(t.is_human()) {
708  return true;
709  }
710 
711  // We know about allies we're sharing maps with
712  if(share_maps() && t.uses_shroud()) {
713  return true;
714  }
715 
716  // We know about allies we're sharing view with
717  if(share_view() && (t.uses_fog() || t.uses_shroud())) {
718  return true;
719  }
720 
721  return false;
722 }
723 
724 /**
725  * Removes the record of hexes that were cleared of fog via WML.
726  * @param[in] hexes The hexes to no longer keep clear.
727  */
728 void team::remove_fog_override(const std::set<map_location>& hexes)
729 {
730  // Take a set difference.
731  std::vector<map_location> result(fog_clearer_.size());
733  std::set_difference(fog_clearer_.begin(), fog_clearer_.end(), hexes.begin(), hexes.end(), result.begin());
734 
735  // Put the result into fog_clearer_.
736  fog_clearer_.clear();
737  fog_clearer_.insert(result.begin(), result_end);
738 }
739 
740 void validate_side(int side)
741 {
742  if(!resources::gameboard) {
743  return;
744  }
745 
746  if(side < 1 || side > static_cast<int>(resources::gameboard->teams().size())) {
747  throw game::game_error("invalid side(" + std::to_string(side) + ") found in unit definition");
748  }
749 }
750 
751 int shroud_map::width() const
752 {
753  return data_.size();
754 }
755 
757 {
758  if(data_.size() == 0) return 0;
759  return std::max_element(data_.begin(), data_.end(), [](const auto& a, const auto& b) {
760  return a.size() < b.size();
761  })->size();
762 }
763 
764 bool shroud_map::clear(int x, int y)
765 {
766  if(enabled_ == false || x < 0 || y < 0) {
767  return false;
768  }
769 
770  if(x >= static_cast<int>(data_.size())) {
771  data_.resize(x + 1);
772  }
773 
774  if(y >= static_cast<int>(data_[x].size())) {
775  data_[x].resize(y + 1);
776  }
777 
778  if(data_[x][y] == false) {
779  data_[x][y] = true;
780  return true;
781  }
782 
783  return false;
784 }
785 
786 void shroud_map::place(int x, int y)
787 {
788  if(enabled_ == false || x < 0 || y < 0) {
789  return;
790  }
791 
792  if(x >= static_cast<int>(data_.size())) {
793  DBG_NG << "Couldn't place shroud on invalid x coordinate: (" << x << ", " << y
794  << ") - max x: " << data_.size() - 1;
795  } else if(y >= static_cast<int>(data_[x].size())) {
796  DBG_NG << "Couldn't place shroud on invalid y coordinate: (" << x << ", " << y
797  << ") - max y: " << data_[x].size() - 1;
798  } else {
799  data_[x][y] = false;
800  }
801 }
802 
804 {
805  if(enabled_ == false) {
806  return;
807  }
808 
809  for(auto& i : data_) {
810  std::fill(i.begin(), i.end(), false);
811  }
812 }
813 
814 bool shroud_map::value(int x, int y) const
815 {
816  if(!enabled_) {
817  return false;
818  }
819 
820  // Locations for which we have no data are assumed to still be covered.
821  if(x < 0 || x >= static_cast<int>(data_.size())) {
822  return true;
823  }
824 
825  if(y < 0 || y >= static_cast<int>(data_[x].size())) {
826  return true;
827  }
828 
829  // data_ stores whether or not a location has been cleared, while
830  // we want to return whether or not a location is covered.
831  return !data_[x][y];
832 }
833 
834 bool shroud_map::shared_value(const std::vector<const shroud_map*>& maps, int x, int y) const
835 {
836  if(!enabled_) {
837  return false;
838  }
839 
840  // A quick abort:
841  if(x < 0 || y < 0) {
842  return true;
843  }
844 
845  // A tile is uncovered if it is uncovered on any shared map.
846  for(const shroud_map* const shared_map : maps) {
847  if(shared_map->enabled_ && !shared_map->value(x, y)) {
848  return false;
849  }
850  }
851 
852  return true;
853 }
854 
855 std::string shroud_map::write() const
856 {
857  std::stringstream shroud_str;
858  for(const auto& sh : data_) {
859  shroud_str << '|';
860 
861  for(bool i : sh) {
862  shroud_str << (i ? '1' : '0');
863  }
864 
865  shroud_str << '\n';
866  }
867 
868  return shroud_str.str();
869 }
870 
871 void shroud_map::read(const std::string& str)
872 {
873  data_.clear();
874 
875  for(const char sh : str) {
876  if(sh == '|') {
877  data_.resize(data_.size() + 1);
878  }
879 
880  if(data_.empty() == false) {
881  if(sh == '1') {
882  data_.back().push_back(true);
883  } else if(sh == '0') {
884  data_.back().push_back(false);
885  }
886  }
887  }
888 }
889 
890 void shroud_map::merge(const std::string& str)
891 {
892  int x = 0, y = 0;
893  for(std::size_t i = 1; i < str.length(); ++i) {
894  if(str[i] == '|') {
895  y = 0;
896  x++;
897  } else if(str[i] == '1') {
898  clear(x, y);
899  y++;
900  } else if(str[i] == '0') {
901  y++;
902  }
903  }
904 }
905 
906 bool shroud_map::copy_from(const std::vector<const shroud_map*>& maps)
907 {
908  if(enabled_ == false) {
909  return false;
910  }
911 
912  bool cleared = false;
913  for(const shroud_map* m : maps) {
914  if(m->enabled_ == false) {
915  continue;
916  }
917 
918  const std::vector<std::vector<bool>>& v = m->data_;
919  for(std::size_t x = 0; x != v.size(); ++x) {
920  for(std::size_t y = 0; y != v[x].size(); ++y) {
921  if(v[x][y]) {
922  cleared |= clear(x, y);
923  }
924  }
925  }
926  }
927 
928  return cleared;
929 }
930 
932 {
933  std::string index = get_side_color_id(side);
934  auto gp = game_config::team_rgb_range.find(index);
935 
936  if(gp != game_config::team_rgb_range.end()) {
937  return (gp->second);
938  }
939 
940  return color_range({255, 0, 0}, {255, 255, 255}, {0, 0, 0}, {255, 0, 0});
941 }
942 
944 {
945  return get_side_color_range(side).mid();
946 }
947 
949 {
950  // Note: use mid() instead of rep() unless
951  // high contrast is needed over a map or minimap!
952  return get_side_color_range(side).rep();
953 }
954 
955 std::string team::get_side_color_id(unsigned side)
956 {
957  try {
958  const unsigned index = side - 1;
959 
960  // If no gameboard (and by extension, team list) is available, use the default side color.
961  if(!resources::gameboard) {
963  }
964 
965  // Else, try to fetch the color from the side's config.
966  const std::string& side_color = resources::gameboard->teams().at(index).color();
967 
968  if(!side_color.empty()) {
969  return side_color;
970  }
971 
972  // If the side color data was empty, fall back to the default color. This should only
973  // happen if the side data hadn't been initialized yet, which is the case if this function
974  // is being called to set up said side data. :P
976  } catch(const std::out_of_range&) {
977  // Side index was invalid! Coloring will fail!
978  return "";
979  }
980 }
981 
983 {
984  const std::string& color_id = team::get_side_color_id(side);
985  const auto& rgb_name = game_config::team_rgb_name[color_id];
986  if(rgb_name.empty())
987  // TRANSLATORS: $color_id is the internal identifier of a side color, for example, 'lightred'.
988  // Translate the quotation marks only; leave "color_id" untranslated, as it's a variable name.
989  return VGETTEXT("“$color_id”", {{ "color_id", color_id }});
990  else
991  return rgb_name;
992 }
993 
995 {
996  const config::attribute_value& c = cfg["color"];
997 
998  // If no color key or value was provided, use the given color for that side.
999  // If outside a game context (ie, where a list of teams has been constructed),
1000  // this will just be the side's default color.
1001  if(c.blank() || c.empty()) {
1002  return get_side_color_id(cfg["side"].to_unsigned());
1003  }
1004 
1005  // Do the same as above for numeric color key values.
1006  if(unsigned side = c.to_unsigned()) {
1007  return get_side_color_id(side);
1008  }
1009 
1010  // Else, we should have a color id at this point. Return it.
1011  return c.str();
1012 }
1013 
1014 std::string team::get_side_highlight_pango(int side)
1015 {
1017 }
1018 
1020 {
1021  LOG_NG << "Adding recruitable units:";
1022  for(const std::string& recruit : info_.can_recruit) {
1023  LOG_NG << recruit;
1024  }
1025 
1026  LOG_NG << "Added all recruitable units";
1027 }
1028 
1030 {
1031  config cfg;
1032  config& result = cfg.add_child("side");
1033  write(result);
1034  return result;
1035 }
1036 
1037 std::string team::allied_human_teams() const
1038 {
1039  std::vector<int> res;
1040  for(const team& t : resources::gameboard->teams()) {
1041  if(!t.is_enemy(this->side()) && t.is_human()) {
1042  res.push_back(t.side());
1043  }
1044  }
1045 
1046  return utils::join(res);
1047 }
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:906
int height() const
Definition: team.cpp:756
void read(const std::string &shroud_data)
Definition: team.cpp:871
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:786
int width() const
Definition: team.cpp:751
bool shared_value(const std::vector< const shroud_map * > &maps, int x, int y) const
Definition: team.cpp:834
void merge(const std::string &shroud_data)
Definition: team.cpp:890
bool value(int x, int y) const
Definition: team.cpp:814
std::string write() const
Definition: team.cpp:855
void reset()
Definition: team.cpp:803
bool enabled_
Definition: team.hpp:65
bool clear(int x, int y)
Definition: team.cpp:764
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:1014
bool uses_shroud() const
Definition: team.hpp:341
int carryover_gold() const
Definition: team.hpp:386
const std::string & color() const
Definition: team.hpp:280
static color_t get_minimap_color(int side)
Definition: team.cpp:948
void build(const config &cfg, const gamemap &map)
Definition: team.cpp:344
bool no_turn_confirmation() const
Definition: team.hpp:390
void fix_villages(const gamemap &map)
Definition: team.cpp:413
config & variables()
Definition: team.hpp:387
const std::string & side_name() const
Definition: team.hpp:331
recall_list_manager recall_list_
Definition: team.hpp:457
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:465
int recall_cost() const
Definition: team.hpp:195
static const color_range get_side_color_range(int side)
Definition: team.cpp:931
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:994
std::chrono::milliseconds countdown_time_
Definition: team.hpp:454
bool no_leader() const
Definition: team.hpp:366
bool is_enemy(int n) const
Definition: team.hpp:267
config to_config() const
Definition: team.cpp:1029
team_shared_vision::type share_vision() const
Definition: team.hpp:416
int action_bonus_count() const
Definition: team.hpp:235
shroud_map fog_
Definition: team.hpp:446
bool calculate_is_enemy(std::size_t index) const
Definition: team.cpp:505
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:982
team()
Definition: team.cpp:322
const t_string & faction_name() const
Definition: team.hpp:335
defeat_condition::type defeat_cond() const
Definition: team.hpp:367
void set_objectives(const t_string &new_objectives, bool silently=false)
Definition: team.cpp:627
bool carryover_add() const
Definition: team.hpp:382
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:428
int carryover_percentage() const
Definition: team.hpp:380
void write(config &cfg) const
Definition: team.cpp:382
static std::string get_side_color_id(unsigned side)
Definition: team.cpp:955
int raw_income() const
Definition: team.hpp:186
std::vector< const shroud_map * > ally_fog_
Definition: team.hpp:465
team_info info_
Definition: team.hpp:452
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:384
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:450
bool share_maps() const
Definition: team.hpp:414
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:448
std::string allied_human_teams() const
Definition: team.cpp:1037
int action_bonus_count_
Definition: team.hpp:455
bool shrouded(const map_location &loc) const
Definition: team.cpp:636
std::set< map_location > villages_
Definition: team.hpp:444
bool knows_upkeep(const team &other) const
Whether other's count of villages, units, upkeep and income should be visible in the status table.
Definition: team.cpp:689
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:446
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:943
static const std::set< std::string > attributes
Stores the attributes recognized by [side].
Definition: team.hpp:154
bool persistent() const
Definition: team.hpp:374
const std::string & flag() const
Definition: team.hpp:324
bool hidden() const
Definition: team.hpp:372
std::string last_recruit_
Definition: team.hpp:458
std::shared_ptr< wb::side_actions > planned_actions_
Whiteboard planned actions for this team.
Definition: team.hpp:470
bool share_view() const
Definition: team.hpp:415
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:463
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:728
bool fogged(const map_location &loc) const
Definition: team.cpp:645
bool lost() const
Definition: team.hpp:377
void change_controller_by_wml(const std::string &new_controller)
Definition: team.cpp:569
bool chose_random() const
Definition: team.hpp:434
void log_recruitable() const
Definition: team.cpp:1019
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:740
#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