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