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