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