The Battle for Wesnoth  1.19.5+dev
team.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 "resources.hpp"
33 #include "serialization/chrono.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"].to_int();
169  income = cfg["income"].to_int();
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"].to_int();
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"].to_int();
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()) {
241  support_per_village = game_config::village_support;
242  } else {
243  support_per_village = lexical_cast_default<int>(village_support, game_config::village_support);
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)) {
274  share_vision = team_shared_vision::type::shroud;
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  auto fog_override = cfg.optional_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_ = chrono::parse_duration<std::chrono::milliseconds>(cfg["countdown_time"]);
397  action_bonus_count_ = cfg["action_bonus_count"].to_int();
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 {
474  // this method gets called from the editor, which obviously has no AI present
477  }
478 }
479 
480 void team::add_recruit(const std::string& recruit)
481 {
482  info_.can_recruit.insert(recruit);
485 }
486 
488 {
491  }
492  int min = 20;
493  for(std::string recruit : info_.can_recruit) {
494  const unit_type* ut = unit_types.find(recruit);
495  if(!ut) {
496  continue;
497  } else {
498  if(ut->cost() < min) {
499  min = ut->cost();
500  }
501  }
502  }
503 
505 
507 }
508 
509 void team::calculate_enemies(std::size_t index) const
510 {
511  if(!resources::gameboard || index >= resources::gameboard->teams().size()) {
512  return;
513  }
514 
515  while(enemies_.size() <= index) {
516  enemies_.push_back(calculate_is_enemy(enemies_.size()));
517  }
518 }
519 
520 bool team::calculate_is_enemy(std::size_t index) const
521 {
522  // We're not enemies of ourselves
523  if(&resources::gameboard->teams()[index] == this) {
524  return false;
525  }
526 
527  // We are friends with anyone who we share a teamname with
528  std::vector<std::string> our_teams = utils::split(info_.team_name);
529  std::vector<std::string> their_teams = utils::split(resources::gameboard->teams()[index].info_.team_name);
530 
531  LOG_NGE << "team " << info_.side << " calculates if it has enemy in team " << index + 1 << "; our team_name ["
532  << info_.team_name << "], their team_name is [" << resources::gameboard->teams()[index].info_.team_name
533  << "]" << std::endl;
534 
535  for(const std::string& t : our_teams) {
536  if(std::find(their_teams.begin(), their_teams.end(), t) != their_teams.end()) {
537  LOG_NGE << "team " << info_.side << " found same team name [" << t << "] in team " << index + 1;
538  return false;
539  } else {
540  LOG_NGE << "team " << info_.side << " not found same team name [" << t << "] in team " << index + 1;
541  }
542  }
543 
544  LOG_NGE << "team " << info_.side << " has enemy in team " << index + 1;
545  return true;
546 }
547 
548 namespace
549 {
550 class controller_server_choice : public synced_context::server_choice
551 {
552 public:
553  controller_server_choice(side_controller::type new_controller, const team& team)
554  : new_controller_(new_controller)
555  , team_(team)
556  {
557  }
558 
559  /** We are in a game with no mp server and need to do this choice locally */
560  virtual config local_choice() const
561  {
562  return config{"controller", side_controller::get_string(new_controller_), "is_local", true};
563  }
564 
565  /** The request which is sent to the mp server. */
566  virtual config request() const
567  {
568  return config{
569  "new_controller", side_controller::get_string(new_controller_), "old_controller", side_controller::get_string(team_.controller()), "side", team_.side(),
570  };
571  }
572 
573  virtual const char* name() const
574  {
575  return "change_controller_wml";
576  }
577 
578 private:
579  side_controller::type new_controller_;
580  const team& team_;
581 };
582 } // end anon namespace
583 
584 void team::change_controller_by_wml(const std::string& new_controller_string)
585 {
586  auto new_controller = side_controller::get_enum(new_controller_string);
587  if(!new_controller) {
588  WRN_NG << "ignored attempt to change controller to " << new_controller_string;
589  return;
590  }
591 
592  if(new_controller == side_controller::type::none && resources::controller->current_side() == this->side()) {
593  WRN_NG << "ignored attempt to change the currently playing side's controller to 'null'";
594  return;
595  }
596 
597  config choice = synced_context::ask_server_choice(controller_server_choice(*new_controller, *this));
598  if(!side_controller::get_enum(choice["controller"].str())) {
599  WRN_NG << "Received an invalid controller string from the server" << choice["controller"];
600  } else {
601  new_controller = side_controller::get_enum(choice["controller"].str());
602  }
603 
604  if(!resources::controller->is_replay()) {
605  set_local(choice["is_local"].to_bool());
606  }
607 
609  if(pc->current_side() == side() && new_controller != controller()) {
610  pc->set_player_type_changed();
611  }
612  }
613 
614  change_controller(*new_controller);
615 }
616 
617 void team::change_team(const std::string& name, const t_string& user_name)
618 {
619  info_.team_name = name;
620 
621  if(!user_name.empty()) {
622  info_.user_team_name = user_name;
623  } else {
624  info_.user_team_name = name;
625  }
626 
627  clear_caches();
628 }
629 
631 {
632  // Reset the cache of allies for all teams
634  for(auto& t : resources::gameboard->teams()) {
635  t.enemies_.clear();
636  t.ally_shroud_.clear();
637  t.ally_fog_.clear();
638  }
639  }
640 }
641 
642 void team::set_objectives(const t_string& new_objectives, bool silently)
643 {
644  info_.objectives = new_objectives;
645 
646  if(!silently) {
647  info_.objectives_changed = true;
648  }
649 }
650 
651 bool team::shrouded(const map_location& loc) const
652 {
653  if(!resources::gameboard) {
654  return shroud_.value(loc.wml_x(), loc.wml_y());
655  }
656 
657  return shroud_.shared_value(ally_shroud(resources::gameboard->teams()), loc.wml_x(), loc.wml_y());
658 }
659 
660 bool team::fogged(const map_location& loc) const
661 {
662  if(shrouded(loc)) {
663  return true;
664  }
665 
666  // Check for an override of fog.
667  if(fog_clearer_.count(loc) > 0) {
668  return false;
669  }
670 
671  if(!resources::gameboard) {
672  return fog_.value(loc.wml_x(), loc.wml_y());
673  }
674 
675  return fog_.shared_value(ally_fog(resources::gameboard->teams()), loc.wml_x(), loc.wml_y());
676 }
677 
678 const std::vector<const shroud_map*>& team::ally_shroud(const std::vector<team>& teams) const
679 {
680  if(ally_shroud_.empty()) {
681  for(const team& t : teams) {
682  if(!is_enemy(t.side()) && (&t == this || t.share_view() || t.share_maps())) {
683  ally_shroud_.push_back(&t.shroud_);
684  }
685  }
686  }
687 
688  return ally_shroud_;
689 }
690 
691 const std::vector<const shroud_map*>& team::ally_fog(const std::vector<team>& teams) const
692 {
693  if(ally_fog_.empty()) {
694  for(const team& t : teams) {
695  if(!is_enemy(t.side()) && (&t == this || t.share_view())) {
696  ally_fog_.push_back(&t.fog_);
697  }
698  }
699  }
700 
701  return ally_fog_;
702 }
703 
704 bool team::knows_about_team(std::size_t index) const
705 {
706  const team& t = resources::gameboard->teams()[index];
707 
708  // We know about our own team
709  if(this == &t) {
710  return true;
711  }
712 
713  // If we aren't using shroud or fog, then we know about everyone
714  if(!uses_shroud() && !uses_fog()) {
715  return true;
716  }
717 
718  // We don't know about enemies
719  if(is_enemy(index + 1)) {
720  return false;
721  }
722 
723  // We know our human allies.
724  if(t.is_human()) {
725  return true;
726  }
727 
728  // We know about allies we're sharing maps with
729  if(share_maps() && t.uses_shroud()) {
730  return true;
731  }
732 
733  // We know about allies we're sharing view with
734  if(share_view() && (t.uses_fog() || t.uses_shroud())) {
735  return true;
736  }
737 
738  return false;
739 }
740 
741 /**
742  * Removes the record of hexes that were cleared of fog via WML.
743  * @param[in] hexes The hexes to no longer keep clear.
744  */
745 void team::remove_fog_override(const std::set<map_location>& hexes)
746 {
747  // Take a set difference.
748  std::vector<map_location> result(fog_clearer_.size());
750  std::set_difference(fog_clearer_.begin(), fog_clearer_.end(), hexes.begin(), hexes.end(), result.begin());
751 
752  // Put the result into fog_clearer_.
753  fog_clearer_.clear();
754  fog_clearer_.insert(result.begin(), result_end);
755 }
756 
757 void validate_side(int side)
758 {
759  if(!resources::gameboard) {
760  return;
761  }
762 
763  if(side < 1 || side > static_cast<int>(resources::gameboard->teams().size())) {
764  throw game::game_error("invalid side(" + std::to_string(side) + ") found in unit definition");
765  }
766 }
767 
768 int shroud_map::width() const
769 {
770  return data_.size();
771 }
772 
774 {
775  if(data_.size() == 0) return 0;
776  return std::max_element(data_.begin(), data_.end(), [](const auto& a, const auto& b) {
777  return a.size() < b.size();
778  })->size();
779 }
780 
781 bool shroud_map::clear(int x, int y)
782 {
783  if(enabled_ == false || x < 0 || y < 0) {
784  return false;
785  }
786 
787  if(x >= static_cast<int>(data_.size())) {
788  data_.resize(x + 1);
789  }
790 
791  if(y >= static_cast<int>(data_[x].size())) {
792  data_[x].resize(y + 1);
793  }
794 
795  if(data_[x][y] == false) {
796  data_[x][y] = true;
797  return true;
798  }
799 
800  return false;
801 }
802 
803 void shroud_map::place(int x, int y)
804 {
805  if(enabled_ == false || x < 0 || y < 0) {
806  return;
807  }
808 
809  if(x >= static_cast<int>(data_.size())) {
810  DBG_NG << "Couldn't place shroud on invalid x coordinate: (" << x << ", " << y
811  << ") - max x: " << data_.size() - 1;
812  } else if(y >= static_cast<int>(data_[x].size())) {
813  DBG_NG << "Couldn't place shroud on invalid y coordinate: (" << x << ", " << y
814  << ") - max y: " << data_[x].size() - 1;
815  } else {
816  data_[x][y] = false;
817  }
818 }
819 
821 {
822  if(enabled_ == false) {
823  return;
824  }
825 
826  for(auto& i : data_) {
827  std::fill(i.begin(), i.end(), false);
828  }
829 }
830 
831 bool shroud_map::value(int x, int y) const
832 {
833  if(!enabled_) {
834  return false;
835  }
836 
837  // Locations for which we have no data are assumed to still be covered.
838  if(x < 0 || x >= static_cast<int>(data_.size())) {
839  return true;
840  }
841 
842  if(y < 0 || y >= static_cast<int>(data_[x].size())) {
843  return true;
844  }
845 
846  // data_ stores whether or not a location has been cleared, while
847  // we want to return whether or not a location is covered.
848  return !data_[x][y];
849 }
850 
851 bool shroud_map::shared_value(const std::vector<const shroud_map*>& maps, int x, int y) const
852 {
853  if(!enabled_) {
854  return false;
855  }
856 
857  // A quick abort:
858  if(x < 0 || y < 0) {
859  return true;
860  }
861 
862  // A tile is uncovered if it is uncovered on any shared map.
863  for(const shroud_map* const shared_map : maps) {
864  if(shared_map->enabled_ && !shared_map->value(x, y)) {
865  return false;
866  }
867  }
868 
869  return true;
870 }
871 
872 std::string shroud_map::write() const
873 {
874  std::stringstream shroud_str;
875  for(const auto& sh : data_) {
876  shroud_str << '|';
877 
878  for(bool i : sh) {
879  shroud_str << (i ? '1' : '0');
880  }
881 
882  shroud_str << '\n';
883  }
884 
885  return shroud_str.str();
886 }
887 
888 void shroud_map::read(const std::string& str)
889 {
890  data_.clear();
891 
892  for(const char sh : str) {
893  if(sh == '|') {
894  data_.resize(data_.size() + 1);
895  }
896 
897  if(data_.empty() == false) {
898  if(sh == '1') {
899  data_.back().push_back(true);
900  } else if(sh == '0') {
901  data_.back().push_back(false);
902  }
903  }
904  }
905 }
906 
907 void shroud_map::merge(const std::string& str)
908 {
909  int x = 0, y = 0;
910  for(std::size_t i = 1; i < str.length(); ++i) {
911  if(str[i] == '|') {
912  y = 0;
913  x++;
914  } else if(str[i] == '1') {
915  clear(x, y);
916  y++;
917  } else if(str[i] == '0') {
918  y++;
919  }
920  }
921 }
922 
923 bool shroud_map::copy_from(const std::vector<const shroud_map*>& maps)
924 {
925  if(enabled_ == false) {
926  return false;
927  }
928 
929  bool cleared = false;
930  for(const shroud_map* m : maps) {
931  if(m->enabled_ == false) {
932  continue;
933  }
934 
935  const std::vector<std::vector<bool>>& v = m->data_;
936  for(std::size_t x = 0; x != v.size(); ++x) {
937  for(std::size_t y = 0; y != v[x].size(); ++y) {
938  if(v[x][y]) {
939  cleared |= clear(x, y);
940  }
941  }
942  }
943  }
944 
945  return cleared;
946 }
947 
949 {
950  std::string index = get_side_color_id(side);
951  auto gp = game_config::team_rgb_range.find(index);
952 
953  if(gp != game_config::team_rgb_range.end()) {
954  return (gp->second);
955  }
956 
957  return color_range({255, 0, 0}, {255, 255, 255}, {0, 0, 0}, {255, 0, 0});
958 }
959 
961 {
962  return get_side_color_range(side).mid();
963 }
964 
966 {
967  // Note: use mid() instead of rep() unless
968  // high contrast is needed over a map or minimap!
969  return get_side_color_range(side).rep();
970 }
971 
972 std::string team::get_side_color_id(unsigned side)
973 {
974  try {
975  const unsigned index = side - 1;
976 
977  // If no gameboard (and by extension, team list) is available, use the default side color.
978  if(!resources::gameboard) {
980  }
981 
982  // Else, try to fetch the color from the side's config.
983  const std::string& side_color = resources::gameboard->teams().at(index).color();
984 
985  if(!side_color.empty()) {
986  return side_color;
987  }
988 
989  // If the side color data was empty, fall back to the default color. This should only
990  // happen if the side data hadn't been initialized yet, which is the case if this function
991  // is being called to set up said side data. :P
993  } catch(const std::out_of_range&) {
994  // Side index was invalid! Coloring will fail!
995  return "";
996  }
997 }
998 
1000 {
1001  const std::string& color_id = team::get_side_color_id(side);
1002  const auto& rgb_name = game_config::team_rgb_name[color_id];
1003  if(rgb_name.empty())
1004  // TRANSLATORS: $color_id is the internal identifier of a side color, for example, 'lightred'.
1005  // Translate the quotation marks only; leave "color_id" untranslated, as it's a variable name.
1006  return VGETTEXT("“$color_id”", {{ "color_id", color_id }});
1007  else
1008  return rgb_name;
1009 }
1010 
1012 {
1013  const config::attribute_value& c = cfg["color"];
1014 
1015  // If no color key or value was provided, use the given color for that side.
1016  // If outside a game context (ie, where a list of teams has been constructed),
1017  // this will just be the side's default color.
1018  if(c.blank() || c.empty()) {
1019  return get_side_color_id(cfg["side"].to_unsigned());
1020  }
1021 
1022  // Do the same as above for numeric color key values.
1023  if(unsigned side = c.to_unsigned()) {
1024  return get_side_color_id(side);
1025  }
1026 
1027  // Else, we should have a color id at this point. Return it.
1028  return c.str();
1029 }
1030 
1031 std::string team::get_side_highlight_pango(int side)
1032 {
1034 }
1035 
1037 {
1038  LOG_NG << "Adding recruitable units:";
1039  for(const std::string& recruit : info_.can_recruit) {
1040  LOG_NG << recruit;
1041  }
1042 
1043  LOG_NG << "Added all recruitable units";
1044 }
1045 
1047 {
1048  config cfg;
1049  config& result = cfg.add_child("side");
1050  write(result);
1051  return result;
1052 }
1053 
1054 std::string team::allied_human_teams() const
1055 {
1056  std::vector<int> res;
1057  for(const team& t : resources::gameboard->teams()) {
1058  if(!t.is_enemy(this->side()) && t.is_human()) {
1059  res.push_back(t.side());
1060  }
1061  }
1062 
1063  return utils::join(res);
1064 }
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:625
static manager & get_singleton()
Definition: manager.hpp:142
static bool has_manager()
Definition: manager.hpp:148
bool add_ai_for_side_from_file(side_number side, const std::string &file, bool replace=true)
Adds active AI for specified side from file.
Definition: manager.cpp:615
void raise_recruit_list_changed()
Notifies all observers of 'ai_recruit_list_changed' event.
Definition: manager.cpp:453
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:172
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:394
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
child_itors child_range(config_key_type key)
Definition: config.cpp:272
bool empty() const
Definition: config.cpp:849
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:384
config & add_child(config_key_type key)
Definition: config.cpp:440
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
game_events::wml_event_pump & pump()
Definition: manager.cpp:253
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:424
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:923
int height() const
Definition: team.cpp:773
void read(const std::string &shroud_data)
Definition: team.cpp:888
void set_enabled(bool enabled)
Definition: team.hpp:61
std::vector< std::vector< bool > > data_
Definition: team.hpp:67
void place(int x, int y)
Definition: team.cpp:803
int width() const
Definition: team.cpp:768
bool shared_value(const std::vector< const shroud_map * > &maps, int x, int y) const
Definition: team.cpp:851
void merge(const std::string &shroud_data)
Definition: team.cpp:907
bool value(int x, int y) const
Definition: team.cpp:831
std::string write() const
Definition: team.cpp:872
void reset()
Definition: team.cpp:820
bool enabled_
Definition: team.hpp:66
bool clear(int x, int y)
Definition: team.cpp:781
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:161
bool translatable() const
Definition: tstring.hpp:200
bool empty() const
Definition: tstring.hpp:194
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
static std::string get_side_highlight_pango(int side)
Definition: team.cpp:1031
bool uses_shroud() const
Definition: team.hpp:303
int carryover_gold() const
Definition: team.hpp:347
const std::string & color() const
Definition: team.hpp:242
static color_t get_minimap_color(int side)
Definition: team.cpp:965
bool no_turn_confirmation() const
Definition: team.hpp:351
void fix_villages(const gamemap &map)
Definition: team.cpp:425
config & variables()
Definition: team.hpp:348
const std::string & side_name() const
Definition: team.hpp:293
recall_list_manager recall_list_
Definition: team.hpp:419
int side() const
Definition: team.hpp:175
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
const std::string & faction() const
Definition: team.hpp:296
int village_support() const
Definition: team.hpp:186
std::vector< const shroud_map * > ally_shroud_
Definition: team.hpp:427
int recall_cost() const
Definition: team.hpp:180
static const color_range get_side_color_range(int side)
Definition: team.cpp:948
void set_local(bool local)
Definition: team.hpp:258
const std::string & current_player() const
Definition: team.hpp:220
void set_recruits(const std::set< std::string > &recruits)
Definition: team.cpp:470
const std::string & team_name() const
Definition: team.hpp:282
bool objectives_changed() const
Definition: team.hpp:227
bool is_local() const
Definition: team.hpp:247
static std::string get_side_color_id_from_config(const config &cfg)
Definition: team.cpp:1011
std::chrono::milliseconds countdown_time_
Definition: team.hpp:416
bool no_leader() const
Definition: team.hpp:327
bool is_enemy(int n) const
Definition: team.hpp:229
config to_config() const
Definition: team.cpp:1046
team_shared_vision::type share_vision() const
Definition: team.hpp:377
int action_bonus_count() const
Definition: team.hpp:199
shroud_map fog_
Definition: team.hpp:408
bool calculate_is_enemy(std::size_t index) const
Definition: team.cpp:520
bool knows_about_team(std::size_t index) const
Definition: team.cpp:704
const t_string & objectives() const
Definition: team.hpp:226
void change_team(const std::string &name, const t_string &user_name)
Definition: team.cpp:617
int gold() const
Definition: team.hpp:176
static const t_string get_side_color_name_for_UI(unsigned side)
Definition: team.cpp:999
team()
Definition: team.cpp:330
const t_string & faction_name() const
Definition: team.hpp:297
defeat_condition::type defeat_cond() const
Definition: team.hpp:328
void set_objectives(const t_string &new_objectives, bool silently=false)
Definition: team.cpp:642
bool carryover_add() const
Definition: team.hpp:343
void handle_legacy_share_vision(const config &cfg)
Definition: team.hpp:389
int carryover_percentage() const
Definition: team.hpp:341
void write(config &cfg) const
Definition: team.cpp:403
static std::string get_side_color_id(unsigned side)
Definition: team.cpp:972
std::vector< const shroud_map * > ally_fog_
Definition: team.hpp:427
void build(const config &cfg, const gamemap &map, int gold=default_team_gold_)
Definition: team.cpp:353
team_info info_
Definition: team.hpp:414
const std::string & save_id() const
Definition: team.hpp:217
static const int default_team_gold_
Definition: team.hpp:145
void calculate_enemies(std::size_t index) const
Definition: team.cpp:509
void add_recruit(const std::string &)
Definition: team.cpp:480
side_controller::type controller() const
Definition: team.hpp:241
int minimum_recruit_price() const
Definition: team.cpp:487
double carryover_bonus() const
Definition: team.hpp:345
void change_controller(const std::string &new_controller)
Definition: team.hpp:261
virtual ~team()
Definition: team.cpp:349
static void clear_caches()
clear the shroud, fog, and enemies cache for all teams
Definition: team.cpp:630
bool auto_shroud_updates_
Definition: team.hpp:412
bool share_maps() const
Definition: team.hpp:375
std::set< map_location > fog_clearer_
Stores hexes that have been cleared of fog via WML.
Definition: team.hpp:410
std::string allied_human_teams() const
Definition: team.cpp:1054
int action_bonus_count_
Definition: team.hpp:417
bool shrouded(const map_location &loc) const
Definition: team.cpp:651
std::set< map_location > villages_
Definition: team.hpp:406
std::chrono::milliseconds countdown_time() const
Definition: team.hpp:197
int start_gold() const
Definition: team.hpp:177
const std::string & flag_icon() const
Definition: team.hpp:287
shroud_map shroud_
Definition: team.hpp:408
const std::vector< const shroud_map * > & ally_fog(const std::vector< team > &teams) const
Definition: team.cpp:691
bool uses_fog() const
Definition: team.hpp:304
static color_t get_side_color(int side)
Definition: team.cpp:960
static const std::set< std::string > attributes
Stores the attributes recognized by [side].
Definition: team.hpp:155
bool persistent() const
Definition: team.hpp:335
const std::string & flag() const
Definition: team.hpp:286
bool hidden() const
Definition: team.hpp:333
std::string last_recruit_
Definition: team.hpp:420
int gold_
Definition: team.hpp:405
std::shared_ptr< wb::side_actions > planned_actions_
Whiteboard planned actions for this team.
Definition: team.hpp:432
bool share_view() const
Definition: team.hpp:376
const std::set< std::string > & recruits() const
Definition: team.hpp:209
const t_string & user_team_name() const
Definition: team.hpp:283
void lose_village(const map_location &)
Definition: team.cpp:463
boost::dynamic_bitset enemies_
Definition: team.hpp:425
void remove_fog_override(const std::set< map_location > &hexes)
Removes the record of hexes that were cleared of fog via WML.
Definition: team.cpp:745
bool fogged(const map_location &loc) const
Definition: team.cpp:660
bool lost() const
Definition: team.hpp:338
void change_controller_by_wml(const std::string &new_controller)
Definition: team.cpp:584
bool chose_random() const
Definition: team.hpp:395
void log_recruitable() const
Definition: team.cpp:1036
const std::vector< const shroud_map * > & ally_shroud(const std::vector< team > &teams) const
Definition: team.cpp:678
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:1265
A single unit type that the player may recruit.
Definition: types.hpp:43
int cost() const
Definition: types.hpp:172
This internal whiteboard class holds the planned action queues for a team, and offers many utility me...
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1028
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:198
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:403
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:50
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 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:70
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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:59
std::string to_hex_string() const
Returns the stored color in rrggbb hex format.
Definition: color.cpp:88
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
Encapsulates the map of the game.
Definition: location.hpp:45
int wml_y() const
Definition: location.hpp:184
int wml_x() const
Definition: location.hpp:183
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:90
void read(const config &cfg)
Definition: team.cpp:166
t_string user_team_name
Definition: team.hpp:91
int minimum_recruit_price
Definition: team.hpp:87
void handle_legacy_share_vision(const config &cfg)
Definition: team.cpp:268
t_string objectives
Definition: team.hpp:108
int start_gold
Definition: team.hpp:83
void write(config &cfg) const
Definition: team.cpp:281
std::set< std::string > can_recruit
Definition: team.hpp:89
bool objectives_changed
< Team's objectives for the current level.
Definition: team.hpp:113
#define WRN_NG
Definition: team.cpp:42
#define LOG_NGE
Definition: team.cpp:47
static lg::log_domain log_engine("engine")
static lg::log_domain log_engine_enemies("engine/enemies")
#define DBG_NG
Definition: team.cpp:40
void validate_side(int side)
Definition: team.cpp:757
#define LOG_NG
Definition: team.cpp:41
const std::string & gamedata
mock_char c
unit_type_data unit_types
Definition: types.cpp:1500
#define b