The Battle for Wesnoth  1.19.17+dev
types.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Handle unit-type specific attributes, animations, advancement.
19  */
20 
21 #include "units/types.hpp"
22 
24 #include "game_config.hpp"
25 #include "game_errors.hpp" //thrown sometimes
26 #include "language.hpp" // for string_table
27 #include "log.hpp"
29 #include "units/abilities.hpp"
30 #include "units/animation.hpp"
31 #include "units/unit.hpp"
32 
34 
35 #include <boost/range/algorithm_ext/erase.hpp>
36 
37 #include <array>
38 #include <locale>
39 #include <vector>
40 
41 static lg::log_domain log_config("config");
42 #define ERR_CF LOG_STREAM(err, log_config)
43 #define WRN_CF LOG_STREAM(warn, log_config)
44 #define LOG_CONFIG LOG_STREAM(info, log_config)
45 #define DBG_CF LOG_STREAM(debug, log_config)
46 
47 static lg::log_domain log_unit("unit");
48 #define DBG_UT LOG_STREAM(debug, log_unit)
49 #define WRN_UT LOG_STREAM(warn, log_unit)
50 #define LOG_UT LOG_STREAM(info, log_unit)
51 #define ERR_UT LOG_STREAM(err, log_unit)
52 
53 /* ** unit_type ** */
54 
55 unit_type::unit_type(default_ctor_t, const config& cfg, const std::string & parent_id)
56  : cfg_(nullptr)
57  , built_cfg_()
58  , id_(cfg.has_attribute("id") ? cfg["id"].str() : parent_id)
59  , debug_id_()
60  , parent_id_(!parent_id.empty() ? parent_id : id_)
61  , base_unit_id_()
62  , type_name_()
63  , description_()
64  , hitpoints_(0)
65  , hp_bar_scaling_(0.0)
66  , xp_bar_scaling_(0.0)
67  , level_(0)
68  , recall_cost_()
69  , movement_(0)
70  , vision_(-1)
71  , jamming_(0)
72  , max_attacks_(0)
73  , cost_(0)
74  , usage_()
75  , undead_variation_()
76  , image_()
77  , icon_()
78  , small_profile_()
79  , profile_()
80  , flag_rgb_()
81  , num_traits_(0)
82  , gender_types_()
83  , variations_()
84  , default_variation_()
85  , variation_name_()
86  , race_(&unit_race::null_race)
87  , abilities_()
88  , adv_abilities_()
89  , zoc_(false)
90  , hide_help_(false)
91  , do_not_list_()
92  , advances_to_()
93  , advancements_(cfg.child_range("advancement"))
94  , experience_needed_(0)
95  , alignment_(unit_alignments::type::neutral)
96  , movement_type_()
97  , possible_traits_()
98  , genders_()
99  , animations_()
100  , build_status_(NOT_BUILT)
101 {
102  if(auto base_unit = cfg.optional_child("base_unit")) {
103  base_unit_id_ = base_unit["id"].str();
104  LOG_UT << "type '" << id_ << "' has base unit '" << base_unit_id_ << "'";
105  }
106  check_id(id_);
108 }
109 
110 unit_type::unit_type(const config& cfg, const std::string & parent_id)
111  : unit_type(default_ctor_t(), cfg, parent_id)
112 {
113  cfg_ = &cfg;
114 }
115 
116 unit_type::unit_type(config&& cfg, const std::string & parent_id)
117  : unit_type(default_ctor_t(), cfg, parent_id)
118 {
119  built_cfg_ = std::make_unique<config>(std::move(cfg));
120 }
121 
123 {
124 }
125 
127  : id(cfg["id"])
128  , name(cfg["name"].t_str())
129  , name_inactive(cfg["name_inactive"].t_str())
130  , female_name(cfg["female_name"].t_str())
131  , female_name_inactive(cfg["female_name_inactive"].t_str())
132  , description(cfg["description"].t_str())
133  , description_inactive(cfg["description_inactive"].t_str())
134  , affect_self(cfg["affect_self"].to_bool())
135  , affect_allies(cfg["affect_allies"].to_bool())
136  , affect_enemies(cfg["affect_enemies"].to_bool())
137  , cumulative(cfg["cumulative"].to_bool())
138 {
139 }
140 
141 /**
142  * Load data into an empty unit_type (build to FULL).
143  */
145  const movement_type_map& mv_types, const race_map& races, const config_array_view& traits)
146 {
147  // Don't build twice.
148  if(FULL <= build_status_) {
149  return;
150  }
151 
152  // Make sure we are built to the preceding build level.
153  build_help_index(mv_types, races, traits);
154 
155  for(int i = 0; i < 2; ++i) {
156  if(gender_types_[i]) {
157  gender_types_[i]->build_full(mv_types, races, traits);
158  }
159  }
160 
161  if(race_ != &unit_race::null_race) {
162  if(undead_variation_.empty()) {
164  }
165  }
166 
167  zoc_ = get_cfg()["zoc"].to_bool(level_ > 0);
168 
170 
171  hp_bar_scaling_ = get_cfg()["hp_bar_scaling"].to_double(game_config::hp_bar_scaling);
172  xp_bar_scaling_ = get_cfg()["xp_bar_scaling"].to_double(game_config::xp_bar_scaling);
173 
174  // Propagate the build to the variations.
175  for(variations_map::value_type& variation : variations_) {
176  variation.second.build_full(mv_types, races, traits);
177  }
178 
179  // Deprecation messages, only seen when unit is parsed for the first time.
180 
182 }
183 
184 /**
185  * Partially load data into an empty unit_type (build to HELP_INDEXED).
186  */
188  const movement_type_map& mv_types, const race_map& races, const config_array_view& traits)
189 {
190  // Don't build twice.
191  if(HELP_INDEXED <= build_status_) {
192  return;
193  }
194 
195  // Make sure we are built to the preceding build level.
196  build_created();
197 
198  const config& cfg = get_cfg();
199 
200  type_name_ = cfg["name"].t_str();
201  description_ = cfg["description"].t_str();
202  hitpoints_ = cfg["hitpoints"].to_int(1);
203  level_ = cfg["level"].to_int();
204  recall_cost_ = cfg["recall_cost"].to_int(-1);
205  movement_ = cfg["movement"].to_int(1);
206  vision_ = cfg["vision"].to_int(-1);
207  jamming_ = cfg["jamming"].to_int(0);
208  max_attacks_ = cfg["attacks"].to_int(1);
209  usage_ = cfg["usage"].str();
210  undead_variation_ = cfg["undead_variation"].str();
211  default_variation_ = cfg["variation"].str();
212  icon_ = cfg["image_icon"].str();
213  small_profile_ = cfg["small_profile"].str();
214  profile_ = cfg["profile"].str();
215  flag_rgb_ = cfg["flag_rgb"].str();
216  do_not_list_ = cfg["do_not_list"].to_bool(false);
217 
218  for(const config& sn : cfg.child_range("special_note")) {
219  special_notes_.push_back(sn["note"].t_str());
220  }
221 
223 
224  alignment_ = unit_alignments::get_enum(cfg["alignment"].str()).value_or(unit_alignments::type::neutral);
225 
226  for(int i = 0; i < 2; ++i) {
227  if(gender_types_[i]) {
228  gender_types_[i]->build_help_index(mv_types, races, traits);
229  }
230  }
231 
232  for(auto& pair : variations_) {
233  pair.second.build_help_index(mv_types, races, traits);
234  }
235 
236  const race_map::const_iterator race_it = races.find(cfg["race"]);
237  if(race_it != races.end()) {
238  race_ = &race_it->second;
239  } else {
241  }
242 
243  // if num_traits is not defined, we use the num_traits from race
244  num_traits_ = cfg["num_traits"].to_int(race_->num_traits());
245 
246  for(const std::string& g : utils::split(cfg["gender"])) {
247  genders_.push_back(string_gender(g));
248  }
249 
250  // For simplicity in other parts of the code, we must have at least one gender.
251  if(genders_.empty()) {
252  genders_.push_back(unit_race::MALE);
253  }
254 
255  auto abilities = abilities_cfg();
256  for(const auto [key, cfg] : abilities.all_children_view()) {
257  config subst_cfg(cfg);
258  subst_cfg["name"] = unit_abilities::substitute_variables(cfg["name"], key, cfg);
259  subst_cfg["female_name"] = unit_abilities::substitute_variables(cfg["female_name"], key, cfg);
260  subst_cfg["description"] = unit_abilities::substitute_variables(cfg["description"], key, cfg);
261  subst_cfg["name_inactive"] = unit_abilities::substitute_variables(cfg["name_inactive"], key, cfg);
262  subst_cfg["female_name_inactive"] = unit_abilities::substitute_variables(cfg["female_name_inactive"], key, cfg);
263  subst_cfg["description_inactive"] = unit_abilities::substitute_variables(cfg["description_inactive"], key, cfg);
264  abilities_.emplace_back(subst_cfg);
265  }
266 
267  for(const config& adv : cfg.child_range("advancement")) {
268  for(const config& effect : adv.child_range("effect")) {
269  auto abil_cfg = unit_type_data::add_registry_entries(effect, "abilities", unit_types.abilities());
270  if(!abil_cfg.empty() || effect["apply_to"] != "new_ability") {
271  continue;
272  }
273 
274  for(const auto [key, cfg] : abil_cfg.all_children_view()) {
275  adv_abilities_.emplace_back(cfg);
276  }
277  }
278  }
279 
280  // Set the movement type.
281  const std::string move_type = cfg["movement_type"];
282  movement_type_id_ = move_type;
283  const movement_type_map::const_iterator find_it = mv_types.find(move_type);
284 
285  if(find_it != mv_types.end()) {
286  DBG_UT << "inheriting from movement_type '" << move_type << "'";
287  movement_type_ = find_it->second;
288  } else if(!move_type.empty()) {
289  DBG_UT << "movement_type '" << move_type << "' not found";
290  }
291 
292  // Override parts of the movement type with what is in our config.
294 
295  for(const config& t : traits) {
296  possible_traits_.add_child("trait", t);
297  }
298 
299  if(race_ != &unit_race::null_race) {
300  if(!race_->uses_global_traits()) {
302  }
303 
304  if(cfg["ignore_race_traits"].to_bool()) {
306  } else {
307  for(const config& t : race_->additional_traits()) {
308  if(alignment_ != unit_alignments::type::neutral || t["id"] != "fearless")
309  possible_traits_.add_child("trait", t);
310  }
311  }
312 
313  if(undead_variation_.empty()) {
315  }
316  }
317 
318  // Insert any traits that are just for this unit type
319  for(const config& trait : cfg.child_range("trait")) {
320  possible_traits_.add_child("trait", trait);
321  }
322 
323  hide_help_ = cfg["hide_help"].to_bool();
324 
326 }
327 
328 /**
329  * Load the most needed data into an empty unit_type (build to CREATE).
330  * This creates the gender-specific types (if needed) and also defines how much
331  * experience is needed to advance as well as what this advances to.
332  */
334 {
335  // Don't build twice.
336  if(CREATED <= build_status_) {
337  return;
338  }
339 
340 
341  for(unsigned i = 0; i < gender_types_.size(); ++i) {
342  if(gender_types_[i]) {
343  gender_types_[i]->build_created();
344  }
345  }
346 
347  for(auto& pair : variations_) {
348  pair.second.build_created();
349  }
350 
351 
352  const config& cfg = get_cfg();
353 
354  const std::string& advances_to_val = cfg["advances_to"];
355  if(advances_to_val != "null" && !advances_to_val.empty()) {
356  advances_to_ = utils::split(advances_to_val);
357  }
358 
359 
360  type_name_ = cfg["name"].t_str();
361  variation_name_ = cfg["variation_name"].t_str();
362 
363  DBG_UT << "unit_type '" << log_id() << "' advances to : " << advances_to_val;
364 
365  experience_needed_ = cfg["experience"].to_int(500);
366  cost_ = cfg["cost"].to_int(1);
367 
368  //needed by the editor.
369  image_ = cfg["image"].str();
371 }
372 
373 /**
374  * Performs a build of this to the indicated stage.
375  */
377  const movement_type_map& movement_types,
378  const race_map& races,
379  const config_array_view& traits)
380 {
381  DBG_UT << "Building unit type " << log_id() << ", level " << status;
382 
383  switch(status) {
384  case NOT_BUILT:
385  // Already done in the constructor.
386  return;
387 
388  case CREATED:
389  // Build the basic data.
390  build_created();
391  return;
392 
393  case VARIATIONS: // Implemented as part of HELP_INDEXED
394  case HELP_INDEXED:
395  // Build the data needed to feed the help index.
396  build_help_index(movement_types, races, traits);
397  return;
398 
399  case FULL:
400  build_full(movement_types, races, traits);
401  return;
402 
403  default:
404  ERR_UT << "Build of unit_type to unrecognized status (" << status << ") requested.";
405  // Build as much as possible.
406  build_full(movement_types, races, traits);
407  return;
408  }
409 }
410 
411 const unit_type& unit_type::get_gender_unit_type(const std::string& gender) const
412 {
413  if(gender == unit_race::s_female) {
415  } else if(gender == unit_race::s_male) {
417  }
418 
419  return *this;
420 }
421 
423 {
424  const std::size_t i = gender;
425  if(i < gender_types_.size() && gender_types_[i] != nullptr) {
426  return *gender_types_[i];
427  }
428 
429  return *this;
430 }
431 
432 const unit_type& unit_type::get_variation(const std::string& id) const
433 {
434  const variations_map::const_iterator i = variations_.find(id);
435  if(i != variations_.end()) {
436  return i->second;
437  }
438 
439  return *this;
440 }
441 
443 {
444  if(description_.empty()) {
445  return (_("No description available."));
446  } else {
447  return description_;
448  }
449 }
450 
451 std::vector<t_string> unit_type::special_notes() const {
453 }
454 
455 static void append_special_note(std::vector<t_string>& notes, const t_string& new_note) {
456  if(new_note.empty()) return;
457  std::string_view note_plain = new_note.c_str();
458  utils::trim(note_plain);
459  if(note_plain.empty()) return;
460  if(utils::contains(notes, new_note)) return;
461  notes.push_back(new_note);
462 }
463 
464 std::vector<t_string> combine_special_notes(const std::vector<t_string>& direct, const config& abilities, const const_attack_itors& attacks, const movetype& mt)
465 {
466  std::vector<t_string> notes;
467  for(const auto& note : direct) {
468  append_special_note(notes, note);
469  }
470  for(const auto [key, cfg] : abilities.all_children_view()) {
471  if(cfg.has_attribute("special_note")) {
472  append_special_note(notes, cfg["special_note"].t_str());
473  }
474  }
475  for(const auto& attack : attacks) {
476  for(const auto [key, cfg] : attack.specials().all_children_view()) {
477  if(cfg.has_attribute("special_note")) {
478  append_special_note(notes, cfg["special_note"].t_str());
479  }
480  }
481  if(auto attack_type_note = string_table.find("special_note_damage_type_" + attack.type()); attack_type_note != string_table.end()) {
482  append_special_note(notes, attack_type_note->second);
483  }
484  }
485  for(const auto& move_note : mt.special_notes()) {
486  append_special_note(notes, move_note);
487  }
488  return notes;
489 }
490 
491 const std::vector<unit_animation>& unit_type::animations() const
492 {
493  if(animations_.empty()) {
495  }
496 
497  return animations_;
498 }
499 
501 {
502  if(!attacks_cache_.empty()) {
504  }
505 
506  for(const config& att : get_cfg().child_range("attack")) {
507  attacks_cache_.emplace_back(new attack_type(att));
508  }
509 
511 }
512 
513 namespace
514 {
515 int experience_modifier = 100;
516 }
517 
519  : old_value_(experience_modifier)
520 {
521  experience_modifier = modifier;
522 }
523 
525 {
526  experience_modifier = old_value_;
527 }
528 
530 {
531  return experience_modifier;
532 }
533 
534 int unit_type::experience_needed(bool with_acceleration) const
535 {
536  if(with_acceleration) {
537  int exp = (experience_needed_ * experience_modifier + 50) / 100;
538  if(exp < 1) {
539  exp = 1;
540  }
541 
542  return exp;
543  }
544 
545  return experience_needed_;
546 }
547 
548 bool unit_type::has_ability_by_id(const std::string& ability) const
549 {
550  auto abil = abilities_cfg();
551  if(!abil.empty()) {
552  for(const auto [key, cfg] : abil.all_children_view()) {
553  if(cfg["id"] == ability) {
554  return true;
555  }
556  }
557  }
558 
559  return false;
560 }
561 
562 std::vector<std::string> unit_type::get_ability_list() const
563 {
564  std::vector<std::string> res;
565 
566  auto abilities = abilities_cfg();
567  if(abilities.empty()) {
568  return res;
569  }
570 
571  for(const auto [key, cfg] : abilities.all_children_view()) {
572  std::string id = cfg["id"];
573  if(!id.empty()) {
574  res.push_back(std::move(id));
575  }
576  }
577 
578  return res;
579 }
580 
583 }
584 
586 {
587  return hide_help_ || unit_types.hide_help(id_, race_->id());
588 }
589 
590 
591 static void advancement_tree_internal(const std::string& id, std::set<std::string>& tree)
592 {
593  const unit_type* ut = unit_types.find(id);
594  if(!ut) {
595  return;
596  }
597 
598  for(const std::string& adv : ut->advances_to()) {
599  if(tree.insert(adv).second) {
600  // insertion succeed, expand the new type
601  advancement_tree_internal(adv, tree);
602  }
603  }
604 }
605 
606 std::set<std::string> unit_type::advancement_tree() const
607 {
608  std::set<std::string> tree;
610  return tree;
611 }
612 
613 const std::vector<std::string> unit_type::advances_from() const
614 {
615  // Currently not needed (only help call us and already did it)/
617 
618  std::vector<std::string> adv_from;
619  for(const unit_type_data::unit_type_map::value_type& ut : unit_types.types()) {
620  for(const std::string& adv : ut.second.advances_to()) {
621  if(adv == id_) {
622  adv_from.push_back(ut.second.id());
623  }
624  }
625  }
626 
627  return adv_from;
628 }
629 
630 // This function is only meant to return the likely state a given status
631 // for a new recruit of this type. It should not be used to check if
632 // a particular unit has it, use get_state(status_name) for that.
633 bool unit_type::musthave_status(const std::string& status_name) const
634 {
635  // Statuses default to absent.
636  bool current_status = false;
637 
638  // Look at all of the "musthave" traits to see if the
639  // status gets changed. In the unlikely event it gets changed
640  // multiple times, we want to try to do it in the same order
641  // that unit::apply_modifications does things.
642  for(const config& mod : possible_traits()) {
643  if(mod["availability"] != "musthave") {
644  continue;
645  }
646 
647  for(const config& effect : mod.child_range("effect")) {
648  // See if the effect only applies to
649  // certain unit types But don't worry
650  // about gender checks, since we don't
651  // know what the gender of the
652  // hypothetical recruit is.
653  const std::string& ut = effect["unit_type"];
654 
655  if(!ut.empty()) {
656  if(!utils::contains(utils::split(ut), id())) {
657  continue;
658  }
659  }
660 
661  // We're only interested in status changes.
662  if(effect["apply_to"] != "status") {
663  continue;
664  }
665 
666  if(effect["add"] == status_name) {
667  current_status = true;
668  }
669 
670  if(effect["remove"] == status_name) {
671  current_status = false;
672  }
673  }
674  }
675 
676  return current_status;
677 }
678 
679 const std::string& unit_type::flag_rgb() const
680 {
681  return flag_rgb_.empty() ? game_config::unit_rgb : flag_rgb_;
682 }
683 
685 {
686  if(num_traits() == 0) {
687  return false;
688  }
689 
690  for(const auto& cfg : possible_traits()) {
691  const config::attribute_value& availability = cfg["availability"];
692  if(availability.blank()) {
693  return true;
694  }
695 
696  if(availability.str() != "musthave") {
697  return true;
698  }
699  }
700 
701  return false;
702 }
703 
705 {
706  return utils::contains(genders_, gender);
707 }
708 
709 std::vector<std::string> unit_type::variations() const
710 {
711  std::vector<std::string> retval;
712  retval.reserve(variations_.size());
713 
714  for(const variations_map::value_type& val : variations_) {
715  retval.push_back(val.first);
716  }
717 
718  return retval;
719 }
720 
721 bool unit_type::has_variation(const std::string& variation_id) const
722 {
723  return variations_.find(variation_id) != variations_.end();
724 }
725 
727 {
728  for(const variations_map::value_type& val : variations_) {
729  if(!val.second.hide_help()) {
730  return true;
731  }
732  }
733 
734  return false;
735 }
736 
737 int unit_type::resistance_against(const std::string& damage_name, bool attacker) const
738 {
739  int resistance = movement_type_.resistance_against(damage_name);
740  unit_ability_list resistance_abilities;
741  auto abilities = abilities_cfg();
742 
743  for(const config& cfg : abilities.child_range("resistance")) {
744  if(!cfg["affect_self"].to_bool(true)) {
745  continue;
746  }
747 
748  if(!resistance_filter_matches(cfg, attacker, damage_name, 100 - resistance)) {
749  continue;
750  }
751 
753  }
754 
755  if(!resistance_abilities.empty()) {
756  unit_abilities::effect resist_effect(resistance_abilities, 100 - resistance);
757 
758  resistance = 100 - std::min<int>(
759  resist_effect.get_composite_value(),
760  resistance_abilities.highest("max_value").first
761  );
762  }
763 
764  return resistance;
765 }
766 
768  const config& cfg, bool attacker, const std::string& damage_name, int res) const
769 {
770  if(!(cfg["active_on"].empty() ||
771  (attacker && cfg["active_on"] == "offense") ||
772  (!attacker && cfg["active_on"] == "defense"))
773  ) {
774  return false;
775  }
776 
777  const std::string& apply_to = cfg["apply_to"];
778 
779  if(!apply_to.empty()) {
780  if(damage_name != apply_to) {
781  if(apply_to.find(',') != std::string::npos && apply_to.find(damage_name) != std::string::npos) {
782  if(!utils::contains(utils::split(apply_to), damage_name)) {
783  return false;
784  }
785  } else {
786  return false;
787  }
788  }
789  }
790 
792  return false;
793  }
794 
795  return true;
796 }
797 
798 /** Implementation detail of unit_type::alignment_description */
799 
801 {
802  if(gender == unit_race::FEMALE) {
803  switch(align)
804  {
805  case unit_alignments::type::lawful:
806  return _("female^lawful");
807  case unit_alignments::type::neutral:
808  return _("female^neutral");
809  case unit_alignments::type::chaotic:
810  return _("female^chaotic");
811  case unit_alignments::type::liminal:
812  return _("female^liminal");
813  default:
814  return _("female^lawful");
815  }
816  } else {
817  switch(align)
818  {
819  case unit_alignments::type::lawful:
820  return _("lawful");
821  case unit_alignments::type::neutral:
822  return _("neutral");
823  case unit_alignments::type::chaotic:
824  return _("chaotic");
825  case unit_alignments::type::liminal:
826  return _("liminal");
827  default:
828  return _("lawful");
829  }
830  }
831 }
832 
833 /* ** unit_type_data ** */
834 
836  : types_()
837  , movement_types_()
838  , races_()
839  , hide_help_all_(false)
840  , hide_help_type_()
841  , hide_help_race_()
842  , units_cfg_()
843  , build_status_(unit_type::NOT_BUILT)
844 {
845 }
846 
847 
848 // Helpers for set_config()
849 
850 namespace
851 {
852 /**
853  * Spits out an error message and throws a config::error.
854  * Called when apply_base_unit() detects a cycle.
855  * (This exists merely to take the error message out of that function.)
856  */
857 void throw_base_unit_recursion_error(const std::vector<std::string>& base_tree, const std::string& base_id)
858 {
859  std::stringstream ss;
860  ss << "[base_unit] recursion loop in [unit_type] ";
861 
862  for(const std::string& step : base_tree) {
863  ss << step << "->";
864  }
865 
866  ss << base_id;
867  ERR_CF << ss.str();
868 
869  throw config::error(ss.str());
870 }
871 
872 /**
873  * Insert a new value into a movetype, possibly calculating the value based on
874  * the existing values in the target movetype.
875  */
876 void patch_movetype(movetype& mt,
877  const std::string& type_to_patch,
878  const std::string& new_key,
879  const std::string& formula_str,
880  int default_val,
881  bool replace)
882 {
883  LOG_CONFIG << "Patching " << new_key << " into movetype." << type_to_patch;
884  config mt_cfg;
885  mt.write(mt_cfg, false);
886 
887  if(!replace && mt_cfg.child_or_empty(type_to_patch).has_attribute(new_key)) {
888  // Don't replace if this type already exists in the config
889  return;
890  }
891 
892  // Make movement_costs.flat, defense.castle, etc available to the formula.
893  // The formula uses config_callables which take references to data in mt;
894  // the enclosing scope is to run all the destructors before calling mt's
895  // non-const merge() function. Probably unnecessary, but I'd rather write
896  // it this way than debug it afterwards.
897  config temp_cfg;
898  {
899  // Instances of wfl::config_callable take a reference to a config,
900  // which means that the "cumulative_values" variable below needs to be
901  // copied so that movement costs aren't overwritten by vision costs
902  // before the formula is evaluated.
903  std::list<config> config_copies;
904 
905  auto formula = wfl::formula(formula_str);
906  wfl::map_formula_callable original;
907 
908  // These three need to follow movetype's fallback system, where values for
909  // movement costs are used for vision too.
910  const std::array fallback_children {"movement_costs", "vision_costs", "jamming_costs"};
911  config cumulative_values;
912  for(const auto& x : fallback_children) {
913  if(mt_cfg.has_child(x)) {
914  cumulative_values.merge_with(mt_cfg.mandatory_child(x));
915  }
916  config_copies.emplace_back(cumulative_values);
917  auto val = std::make_shared<wfl::config_callable>(config_copies.back());
918  original.add(x, val);
919 
920  // Allow "flat" to work as "vision_costs.flat" when patching vision_costs, etc
921  if(type_to_patch == x) {
922  original.set_fallback(val);
923  }
924  }
925 
926  // These don't need the fallback system
927  const std::array child_names {"defense", "resistance"};
928  for(const auto& x : child_names) {
929  if(mt_cfg.has_child(x)) {
930  const auto& subtag = mt_cfg.mandatory_child(x);
931  auto val = std::make_shared<wfl::config_callable>(subtag);
932  original.add(x, val);
933 
934  // Allow "arcane" to work as well as "resistance.arcane", etc
935  if(type_to_patch == x) {
936  original.set_fallback(val);
937  }
938  }
939  }
940 
941  try {
942  temp_cfg[new_key] = formula.evaluate(original).as_int(default_val);
943  } catch(const wfl::formula_error&) {
944  ERR_CF << "Error evaluating movetype formula: " << formula_str;
945  return;
946  }
947  }
948  mt.merge(temp_cfg, type_to_patch, true);
949 }
950 } // unnamed namespace
951 
952 /**
953  * Modifies the provided config by merging all base units into it.
954  * The @a base_tree parameter is used for detecting and reporting
955  * cycles of base units and in particular to prevent infinite loops.
956  */
957 
958 void unit_type_data::apply_base_unit(unit_type& type, std::vector<std::string>& base_tree)
959 {
960  // Nothing to do.
961  if(type.base_unit_id_.empty()) {
962  return;
963  }
964 
965  // Detect recursion so the WML author is made aware of an error.
966  if(utils::contains(base_tree, type.base_unit_id_)) {
967  throw_base_unit_recursion_error(base_tree, type.base_unit_id_);
968  }
969 
970  // Find the base unit.
971  const unit_type_map::iterator itor = types_.find(type.base_unit_id_);
972  if(itor != types_.end()) {
973 
974  unit_type& base_type = itor->second;
975 
976  // Make sure the base unit has had its base units accounted for.
977  base_tree.push_back(type.base_unit_id_);
978 
979  apply_base_unit(base_type, base_tree);
980 
981  base_tree.pop_back();
982 
983  // Merge the base unit "under" our config.
984  type.writable_cfg().inherit_from(base_type.get_cfg());
985  }
986  else {
987  ERR_CF << "[base_unit]: unit type not found: " << type.base_unit_id_;
988  throw config::error("unit type not found: " + type.base_unit_id_);
989  }
990 }
991 
992 /**
993  * Handles inheritance for configs of [male], [female], and [variation].
994  * Also removes gendered children, as those serve no purpose.
995  * @a default_inherit is the default value for inherit=.
996  */
997 std::unique_ptr<unit_type> unit_type::create_sub_type(const config& var_cfg, bool default_inherit)
998 {
999  config var_copy = var_cfg;
1000  if(var_cfg["inherit"].to_bool(default_inherit)) {
1001  var_copy.inherit_from(get_cfg());
1002  }
1003 
1004  var_copy.clear_children("male");
1005  var_copy.clear_children("female");
1006 
1007  return std::make_unique<unit_type>(std::move(var_copy), parent_id());
1008 }
1009 
1010 /**
1011  * Processes [variation] tags of @a ut_cfg, handling inheritance and
1012  * child clearing.
1013  */
1015 {
1016  // Most unit types do not have variations.
1017  if(!get_cfg().has_child("variation")) {
1018  return;
1019  }
1020 
1021  // Handle each variation's inheritance.
1022  for(const config& var_cfg : get_cfg().child_range("variation")) {
1023 
1024  std::unique_ptr<unit_type> var = create_sub_type(var_cfg, false);
1025 
1026  var->built_cfg_->remove_children("variation");
1027  var->variation_id_ = var_cfg["variation_id"].str();
1028  var->debug_id_ = debug_id_ + " [" + var->variation_id_ + "]";
1029 
1031  bool success;
1032  std::tie(ut, success) = variations_.emplace(var_cfg["variation_id"].str(), std::move(*var));
1033  if(!success) {
1034  ERR_CF << "Skipping duplicate unit variation ID: '" << var_cfg["variation_id"]
1035  << "' of unit_type '" << get_cfg()["id"] << "'";
1036  }
1037  }
1038 
1039 
1040 }
1041 
1042 
1044 {
1045  // Complete the gender-specific children of the config.
1046  if(auto male_cfg = get_cfg().optional_child("male")) {
1047  gender_types_[0] = create_sub_type(*male_cfg, true);
1048  gender_types_[0]->fill_variations();
1049  }
1050 
1051  if(auto female_cfg = get_cfg().optional_child("female")) {
1052  gender_types_[1] = create_sub_type(*female_cfg, true);
1053  gender_types_[1]->fill_variations();
1054  }
1055 
1056  // Complete the variation-defining children of the config.
1057  fill_variations();
1058 
1060 }
1061 
1063  const config& base_cfg,
1064  const std::string& registry_name,
1065  const std::map<std::string, config>& registry
1066 )
1067 {
1068  config to_append = base_cfg.child_or_empty("abilities");
1069  if(base_cfg.has_attribute(registry_name)) {
1070  std::vector<std::string> abil_ids_list = utils::split(base_cfg[registry_name].str());
1071  for(const std::string& id : abil_ids_list) {
1072  auto registry_entry = registry.find(id);
1073  if(registry_entry != registry.end()) {
1074  to_append.append_children(registry_entry->second);
1075  } else {
1076  WRN_UT << "ID ‘" << id << "’ does not exist in registry for ‘" << registry_name << "’, skipping.";
1077  }
1078  }
1079  }
1080  return to_append;
1081 }
1082 
1083 /**
1084  * Resets all data based on the provided config.
1085  * This includes some processing of the config, such as expanding base units.
1086  * A pointer to the config is stored, so the config must be persistent.
1087  */
1089 {
1090  LOG_UT << "unit_type_data::set_config, nunits: " << cfg.child_range("unit_type").size();
1091 
1092  clear();
1093  units_cfg_ = cfg;
1094 
1095  for(const config& mt : cfg.child_range("movetype")) {
1096  movement_types_.emplace(mt["name"].str(), movetype(mt));
1097 
1099  }
1100 
1101  for(const config& r : cfg.child_range("race")) {
1102  const unit_race race(r);
1103  races_.emplace(race.id(), race);
1104 
1106  }
1107 
1108  for(const auto& abil_cfg : cfg.child_range("abilities")) {
1109  for(const auto& [key, child_cfg] : abil_cfg.get().all_children_range()) {
1110  const std::string& id = child_cfg["unique_id"].str(child_cfg["id"]);
1111  if(abilities_registry_.find(id) == abilities_registry_.end()) {
1112  abilities_registry_.try_emplace(id, config(key, child_cfg));
1113  } else {
1114  WRN_UT << "Ability with id ‘" << id << "’ already exists, not adding.";
1115  }
1116  }
1117  }
1118 
1119  // Movetype resistance patching
1120  DBG_CF << "Start of movetype patching";
1121  for(const config& r : cfg.child_range("resistance_defaults")) {
1122  const std::string& dmg_type = r["id"];
1123 
1124  for(const auto& [mt, value] : r.attribute_range()) {
1125  if(mt == "id" || mt == "default" || movement_types_.find(mt) == movement_types_.end()) {
1126  continue;
1127  }
1128 
1129  DBG_CF << "Patching specific movetype " << mt;
1130  patch_movetype(movement_types_[mt], "resistance", dmg_type, value, 100, true);
1131  }
1132 
1133  if(r.has_attribute("default")) {
1134  for(movement_type_map::value_type& mt : movement_types_) {
1135  // Don't apply a default if a value is explicitly specified.
1136  if(r.has_attribute(mt.first)) {
1137  continue;
1138  }
1139  // The "none" movetype expects everything to have the default value (to be UNREACHABLE)
1140  if(mt.first == "none") {
1141  continue;
1142  }
1143 
1144  patch_movetype(mt.second, "resistance", dmg_type, r["default"], 100, false);
1145  }
1146  }
1147  }
1148  DBG_CF << "Split between resistance and cost patching";
1149 
1150  // Movetype move/defend patching
1151  for(const config& terrain : cfg.child_range("terrain_defaults")) {
1152  const std::string& ter_type = terrain["id"];
1153 
1154  struct ter_defs_to_movetype
1155  {
1156  /** The data to read from is in [terrain_defaults][subtag], and corresponds to [movetype][subtag] */
1157  std::string subtag;
1158  /** Deprecated names used in 1.14.0's [terrain_defaults]. For [defense] the name didn't change. */
1159  std::string alias;
1160  int default_val;
1161  };
1162  const std::array terrain_info_tags{
1163  ter_defs_to_movetype{{"movement_costs"}, {"movement"}, movetype::UNREACHABLE},
1164  ter_defs_to_movetype{{"vision_costs"}, {"vision"}, movetype::UNREACHABLE},
1165  ter_defs_to_movetype{{"jamming_costs"}, {"jamming"}, movetype::UNREACHABLE},
1166  ter_defs_to_movetype{{"defense"}, {"defense"}, 100}
1167  };
1168 
1169  for(const auto& cost_type : terrain_info_tags) {
1170  const std::string* src_tag = nullptr;
1171  if(terrain.has_child(cost_type.subtag)) {
1172  src_tag = &cost_type.subtag;
1173  }
1174  else if(terrain.has_child(cost_type.alias)) {
1175  // Check for the deprecated name, no deprecation warnings are printed.
1176  src_tag = &cost_type.alias;
1177  }
1178  if(!src_tag) {
1179  continue;
1180  }
1181 
1182  const config& info = terrain.mandatory_child(*src_tag);
1183 
1184  for(const auto& [mt, value] : info.attribute_range()) {
1185  if(mt == "default" || movement_types_.find(mt) == movement_types_.end()) {
1186  continue;
1187  }
1188 
1189  patch_movetype(
1190  movement_types_[mt], cost_type.subtag, ter_type, value, cost_type.default_val, true);
1191  }
1192 
1193  if(info.has_attribute("default")) {
1194  for(movement_type_map::value_type& mt : movement_types_) {
1195  // Don't apply a default if a value is explicitly specified.
1196  if(info.has_attribute(mt.first)) {
1197  continue;
1198  }
1199  // The "none" movetype expects everything to have the default value
1200  if(mt.first == "none") {
1201  continue;
1202  }
1203 
1204  patch_movetype(
1205  mt.second, cost_type.subtag, ter_type, info["default"], cost_type.default_val, false);
1206  }
1207  }
1208  }
1209  }
1210  DBG_CF << "End of movetype patching";
1211 
1212  for(const config& ut : cfg.child_range("unit_type")) {
1213  // Every type is required to have an id.
1214  std::string id = ut["id"].str();
1215  if(id.empty()) {
1216  ERR_CF << "[unit_type] with empty id=, ignoring:\n" << ut.debug();
1217  continue;
1218  }
1219 
1220  if(types_.emplace(id, unit_type(ut)).second) {
1221  LOG_CONFIG << "added " << id << " to unit_type list (unit_type_data.unit_types)";
1222  } else {
1223  ERR_CF << "Multiple [unit_type]s with id=" << id << " encountered.";
1224  }
1225  }
1226 
1227  // Apply base units.
1228  for(auto& type : types_) {
1229  std::vector<std::string> base_tree(1, type.second.id());
1230  apply_base_unit(type.second, base_tree);
1231 
1233  }
1234 
1235  //handle [male], [female], [variation]
1236  for(auto& type : types_) {
1237  type.second.fill_variations_and_gender();
1238 
1240  }
1241 
1242  // Build all unit types. (This was not done within the loop for performance.)
1244 
1245  // Suppress some unit types (presumably used as base units) from the help.
1246  if(auto hide_help = cfg.optional_child("hide_help")) {
1247  hide_help_all_ = hide_help["all"].to_bool();
1249  }
1250  DBG_UT << "Finished creating unit types";
1251 }
1252 
1254 {
1255  ut.build(status, movement_types_, races_, units_cfg().child_range("trait"));
1256 }
1257 
1258 /**
1259  * Finds a unit_type by its id() and makes sure it is built to the specified level.
1260  */
1261 const unit_type* unit_type_data::find(const std::string& key, unit_type::BUILD_STATUS status) const
1262 {
1263  if(key.empty() || key == "random") {
1264  return nullptr;
1265  }
1266 
1267  DBG_CF << "trying to find " << key << " in unit_type list (unit_type_data.unit_types)";
1268  const unit_type_map::iterator itor = types_.find(key);
1269 
1270  // This might happen if units of another era are requested (for example for savegames)
1271  if(itor == types_.end()) {
1272  DBG_CF << "unable to find " << key << " in unit_type list (unit_type_data.unit_types)";
1273  return nullptr;
1274  }
1275 
1276  // Make sure the unit_type is built to the requested level.
1277  build_unit_type(itor->second, status);
1278 
1279  return &itor->second;
1280 }
1281 
1282 void unit_type_data::check_types(const std::vector<std::string>& types) const
1283 {
1284  for(const std::string& type : types) {
1285  if(!find(type)) {
1286  throw game::game_error("unknown unit type: " + type);
1287  }
1288  }
1289 }
1290 
1292 {
1293  types_.clear();
1294  movement_types_.clear();
1295  races_.clear();
1296  abilities_registry_.clear();
1298 
1299  hide_help_all_ = false;
1300  hide_help_race_.clear();
1301  hide_help_type_.clear();
1302 }
1303 
1305 {
1306  // Nothing to do if already built to the requested level.
1307  if(status <= build_status_) {
1308  return;
1309  }
1310 
1311  for(const auto& type : types_) {
1312  build_unit_type(type.second, status);
1313 
1315  }
1316 
1317  build_status_ = status;
1318 }
1319 
1321 {
1322  hide_help_race_.emplace_back();
1323  hide_help_type_.emplace_back();
1324 
1325  std::vector<std::string> races = utils::split(cfg["race"]);
1326  hide_help_race_.back().insert(races.begin(), races.end());
1327 
1328  std::vector<std::string> types = utils::split(cfg["type"]);
1329  hide_help_type_.back().insert(types.begin(), types.end());
1330 
1331  std::vector<std::string> trees = utils::split(cfg["type_adv_tree"]);
1332  hide_help_type_.back().insert(trees.begin(), trees.end());
1333 
1334  for(const std::string& t_id : trees) {
1335  unit_type_map::iterator ut = types_.find(t_id);
1336 
1337  if(ut != types_.end()) {
1338  std::set<std::string> adv_tree = ut->second.advancement_tree();
1339  hide_help_type_.back().insert(adv_tree.begin(), adv_tree.end());
1340  }
1341  }
1342 
1343  // We recursively call all the imbricated [not] tags
1344  if(auto cfgnot = cfg.optional_child("not")) {
1345  read_hide_help(*cfgnot);
1346  }
1347 }
1348 
1349 bool unit_type_data::hide_help(const std::string& type, const std::string& race) const
1350 {
1351  bool res = hide_help_all_;
1352  int lvl = hide_help_all_ ? 1 : 0; // first level is covered by 'all=yes'
1353 
1354  // supposed to be equal but let's be cautious
1355  int lvl_nb = std::min(hide_help_race_.size(), hide_help_type_.size());
1356 
1357  for(; lvl < lvl_nb; ++lvl) {
1358  if(hide_help_race_[lvl].count(race) || hide_help_type_[lvl].count(type)) {
1359  res = !res; // each level is a [not]
1360  }
1361  }
1362 
1363  return res;
1364 }
1365 
1366 const unit_race* unit_type_data::find_race(const std::string& key) const
1367 {
1368  race_map::const_iterator i = races_.find(key);
1369  return i != races_.end() ? &i->second : nullptr;
1370 }
1371 
1373 {
1374  build_created();
1375  if(auto p_setxp = cfg.get("set_experience")) {
1376  experience_needed_ = p_setxp->to_int();
1377  }
1378  if(auto attr = cfg.get("set_advances_to")) {
1379  advances_to_ = utils::split(attr->str());
1380  }
1381  if(auto attr = cfg.get("set_cost")) {
1382  cost_ = attr->to_int(1);
1383  }
1384  if(auto attr = cfg.get("add_advancement")) {
1385  for(const auto& str : utils::split(attr->str())) {
1386  if(!utils::contains(advances_to_, str)) {
1387  advances_to_.push_back(str);
1388  }
1389  }
1390  }
1391  if(auto attr = cfg.get("remove_advancement")) {
1392  for(const auto& str : utils::split(attr->str())) {
1393  boost::remove_erase(advances_to_, str);
1394  }
1395  }
1396 
1397  if(cfg.has_child("advancement")) {
1398  advancements_ = cfg.child_range("advancement");
1399  }
1400 
1401  // apply recursively to subtypes.
1402  for(int gender = 0; gender <= 1; ++gender) {
1403  if(!gender_types_[gender]) {
1404  continue;
1405  }
1406  gender_types_[gender]->apply_scenario_fix(cfg);
1407  std::string gender_str = gender == 0 ? "male" : "female";
1408  if(cfg.has_child(gender_str)) {
1409  auto gender_cfg = cfg.optional_child(gender_str);
1410  if(gender_cfg){
1411  gender_types_[gender]->apply_scenario_fix(*gender_cfg);
1412  }
1413  }
1414  }
1415 
1416  if(get_cfg().has_child("variation")) {
1417  // Make sure the variations are created.
1419  for (auto& cv : cfg.child_range("variation")){
1420  for(auto& [variation_id, variation_type] : variations_) {
1421  if(cv["variation_id"] == variation_id) {
1422  variation_type.apply_scenario_fix(cv);
1423  }
1424  }
1425  }
1426  }
1427 }
1428 
1430 {
1431  unit_type_map::iterator itor = types_.find(cfg["type"].str());
1432  // This might happen if units of another era are requested (for example for savegames)
1433  if(itor != types_.end()) {
1434  itor->second.apply_scenario_fix(cfg);
1435  }
1436  else {
1437  // should we give an error message?
1438  }
1439 }
1440 
1442 {
1443  advances_to_.clear();
1444  const std::string& advances_to_val = get_cfg()["advances_to"];
1445  if(advances_to_val != "null" && !advances_to_val.empty()) {
1446  advances_to_ = utils::split(advances_to_val);
1447  }
1448  experience_needed_ = get_cfg()["experience"].to_int(500);
1449  cost_ = get_cfg()["cost"].to_int(1);
1450 
1451  // apply recursively to subtypes.
1452  for(int gender = 0; gender <= 1; ++gender) {
1453  if(!gender_types_[gender]) {
1454  continue;
1455  }
1456  gender_types_[gender]->remove_scenario_fixes();
1457  }
1458  for(auto& v : variations_) {
1459  v.second.remove_scenario_fixes();
1460  }
1461  advancements_ = get_cfg().child_range("advancement");
1462 }
1463 
1465 {
1466  for(auto& pair : types_) {
1467  pair.second.remove_scenario_fixes();
1468  }
1469 }
1470 
1471 void unit_type::check_id(std::string& id)
1472 {
1473  assert(!id.empty());
1474 
1475  // We don't allow leading whitepaces.
1476  if(id[0] == ' ') {
1477  throw error("Found unit type id with a leading whitespace \"" + id + "\"");
1478  }
1479 
1480  if(id == "random" || id == "null") {
1481  throw error("Found unit type using a 'random' or 'null' as an id");
1482  }
1483 
1484  bool gave_warning = false;
1485 
1486  for(std::size_t pos = 0; pos < id.size(); ++pos) {
1487  const char c = id[pos];
1488  const bool valid = std::isalnum(c, std::locale::classic()) || c == '_' || c == ' ';
1489 
1490  if(!valid) {
1491  if(!gave_warning) {
1492  ERR_UT << "Found unit type id with invalid characters: \"" << id << "\"";
1493  gave_warning = true;
1494  }
1495 
1496  id[pos] = '_';
1497  }
1498  }
1499 }
1500 
1502 
1503 void adjust_profile(std::string& profile)
1504 {
1505  // Create a temp copy
1506  std::string temp = profile;
1507 
1508  static const std::string path_adjust = "/transparent";
1509  const std::string::size_type offset = profile.find_last_of('/', profile.find('~'));
1510 
1511  // If the path already refers to /transparent...
1512  if(profile.find(path_adjust) != std::string::npos && offset != std::string::npos) {
1513  if(!image::exists(profile)) {
1514  profile.replace(profile.find(path_adjust), path_adjust.length(), "");
1515  }
1516 
1517  return;
1518  }
1519 
1520  // else, check for the file with /transparent appended...
1521  offset != std::string::npos ? temp.insert(offset, path_adjust) : temp = path_adjust + temp;
1522 
1523  // and use that path if it exists.
1524  if(image::exists(temp)) {
1525  profile = temp;
1526  }
1527 }
double t
Definition: astarsearch.cpp:63
double g
Definition: astarsearch.cpp:63
boost::iterator_range< boost::indirect_iterator< attack_list::const_iterator > > const_attack_itors
attack_itors make_attack_itors(attack_list &atks)
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
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:157
config & add_child(std::string_view key)
Definition: config.cpp:435
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:379
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
child_itors child_range(std::string_view key)
Definition: config.cpp:267
const attribute_value * get(std::string_view key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:664
void clear_children(T... keys)
Definition: config.hpp:601
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1096
void inherit_from(const config &c)
Merge config 'c' into this config, preserving this config's values.
Definition: config.cpp:1146
bool has_attribute(std::string_view key) const
Definition: config.cpp:156
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition: config.cpp:311
const config & child_or_empty(std::string_view key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:389
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:166
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:361
void clear()
Definition: config.cpp:801
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static game_config_view wrap(const config &cfg)
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
The basic "size" of the unit - flying, small land, large land, etc.
Definition: movetype.hpp:44
static const int UNREACHABLE
Magic value that signifies a hex is unreachable.
Definition: movetype.hpp:174
void write(config &cfg, bool include_notes) const
Writes the movement type data to the provided config.
Definition: movetype.cpp:903
void merge(const config &new_cfg, bool overwrite=true)
Merges the given config over the existing data, the config should have zero or more children named "m...
Definition: movetype.cpp:859
int resistance_against(const std::string &damage_type) const
Returns the vulnerability to the indicated damage type (higher means takes more damage).
Definition: movetype.hpp:292
const std::vector< t_string > & special_notes() const
Contents of any [special_note] tags.
Definition: movetype.hpp:343
bool empty() const
Definition: tstring.hpp:199
const char * c_str() const
Definition: tstring.hpp:205
int get_composite_value() const
Definition: abilities.hpp:64
void emplace_back(T &&... args)
Definition: unit.hpp:100
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:67
bool empty() const
Definition: unit.hpp:89
static void fill_initial_animations(std::vector< unit_animation > &animations, const config &cfg)
Definition: animation.cpp:486
const std::string & id() const
Definition: race.hpp:35
bool uses_global_traits() const
Definition: race.cpp:120
static const unit_race null_race
Dummy race used when a race is not yet known.
Definition: race.hpp:69
const std::string & undead_variation() const
Definition: race.hpp:49
unsigned int num_traits() const
Definition: race.cpp:135
const config::const_child_itors & additional_traits() const
Definition: race.cpp:125
static const std::string s_female
Standard string id (not translatable) for FEMALE.
Definition: race.hpp:29
static const std::string s_male
Standard string id (not translatable) for MALE.
Definition: race.hpp:30
@ FEMALE
Definition: race.hpp:28
@ MALE
Definition: race.hpp:28
static config add_registry_entries(const config &base_cfg, const std::string &registry_name, const std::map< std::string, config > &registry)
Definition: types.cpp:1062
const std::map< std::string, config > & abilities() const
Definition: types.hpp:404
void set_config(const game_config_view &cfg)
Resets all data based on the provided config.
Definition: types.cpp:1088
unit_type::BUILD_STATUS build_status_
Definition: types.hpp:452
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:1261
bool hide_help(const std::string &type_id, const std::string &race_id) const
Checks if the [hide_help] tag contains these IDs.
Definition: types.cpp:1349
void clear()
Definition: types.cpp:1291
std::map< std::string, config > abilities_registry_
Definition: types.hpp:442
void remove_scenario_fixes()
Definition: types.cpp:1464
const race_map & races() const
Definition: types.hpp:402
bool hide_help_all_
True if [hide_help] contains a 'all=yes' at its root.
Definition: types.hpp:445
race_map races_
Definition: types.hpp:440
void build_all(unit_type::BUILD_STATUS status)
Makes sure the all unit_types are built to the specified level.
Definition: types.cpp:1304
void apply_base_unit(unit_type &type, std::vector< std::string > &base_tree)
Modifies the provided config by merging all base units into it.
Definition: types.cpp:958
unit_type_map types_
Definition: types.hpp:438
const unit_race * find_race(const std::string &) const
Definition: types.cpp:1366
void check_types(const std::vector< std::string > &types) const
Definition: types.cpp:1282
movement_type_map movement_types_
Definition: types.hpp:439
game_config_view units_cfg_
Definition: types.hpp:451
void build_unit_type(const unit_type &ut, unit_type::BUILD_STATUS status) const
Makes sure the provided unit_type is built to the specified level.
Definition: types.cpp:1253
const game_config_view & units_cfg() const
Definition: types.hpp:450
const unit_type_map & types() const
Definition: types.hpp:392
void read_hide_help(const config &cfg)
Parses the [hide_help] tag.
Definition: types.cpp:1320
std::vector< std::set< std::string > > hide_help_type_
Definition: types.hpp:447
std::vector< std::set< std::string > > hide_help_race_
Definition: types.hpp:448
void apply_scenario_fix(const config &cfg)
Definition: types.cpp:1429
A single unit type that the player may recruit.
Definition: types.hpp:43
std::string default_variation_
Definition: types.hpp:351
t_string description_
Definition: types.hpp:326
std::string image_
Definition: types.hpp:340
std::vector< unit_animation > animations_
Definition: types.hpp:377
const std::vector< std::string > advances_from() const
A vector of unit_type ids that can advance to this unit_type.
Definition: types.cpp:613
unit_alignments::type alignment_
Definition: types.hpp:367
int jamming_
Definition: types.hpp:334
int recall_cost_
Definition: types.hpp:331
const std::string & parent_id() const
The id of the original type from which this (variation) descended.
Definition: types.hpp:148
int cost_
Definition: types.hpp:336
config::const_child_itors advancements_
Definition: types.hpp:363
void fill_variations()
Processes [variation] tags of ut_cfg, handling inheritance and child clearing.
Definition: types.cpp:1014
static std::string alignment_description(unit_alignments::type align, unit_race::GENDER gender=unit_race::MALE)
Implementation detail of unit_type::alignment_description.
Definition: types.cpp:800
std::vector< t_string > special_notes_
Definition: types.hpp:327
const unit_type & get_gender_unit_type(const std::string &gender) const
Returns a gendered variant of this unit_type.
Definition: types.cpp:411
variations_map variations_
Definition: types.hpp:350
void build_full(const movement_type_map &movement_types, const race_map &races, const config_array_view &traits)
Load data into an empty unit_type (build to FULL).
Definition: types.cpp:144
int level_
Definition: types.hpp:330
const std::string & variation_id() const
The id of this variation; empty if it's a gender variation or a base unit.
Definition: types.hpp:150
const unit_race * race_
Never nullptr, but may point to the null race.
Definition: types.hpp:356
std::string parent_id_
The id of the top ancestor of this unit_type.
Definition: types.hpp:322
std::string flag_rgb_
Definition: types.hpp:344
std::string undead_variation_
Definition: types.hpp:338
bool has_random_traits() const
Definition: types.cpp:684
std::string movement_type_id_
Definition: types.hpp:369
std::vector< ability_metadata > abilities_
Definition: types.hpp:358
void fill_variations_and_gender()
Definition: types.cpp:1043
const movetype & movement_type() const
Definition: types.hpp:192
bool show_variations_in_help() const
Whether the unit type has at least one help-visible variation.
Definition: types.cpp:726
int max_attacks_
Definition: types.hpp:335
std::string debug_id_
A suffix for id_, used when logging messages.
Definition: types.hpp:320
std::array< std::unique_ptr< unit_type >, 2 > gender_types_
Definition: types.hpp:348
bool has_ability_by_id(const std::string &ability) const
Definition: types.cpp:548
std::string small_profile_
Definition: types.hpp:342
t_string variation_name_
Definition: types.hpp:353
const unit_type & get_variation(const std::string &id) const
Definition: types.cpp:432
const_attack_itors attacks() const
Definition: types.cpp:500
const std::vector< std::string > & advances_to() const
A vector of unit_type ids that this unit_type can advance to.
Definition: types.hpp:118
void apply_scenario_fix(const config &cfg)
Definition: types.cpp:1372
int vision_
Definition: types.hpp:333
int resistance_against(const std::string &damage_name, bool attacker) const
Gets resistance while considering custom WML abilities.
Definition: types.cpp:737
bool has_variation(const std::string &variation_id) const
Definition: types.cpp:721
bool has_gender_variation(const unit_race::GENDER gender) const
Definition: types.cpp:704
bool hide_help_
Definition: types.hpp:360
t_string unit_description() const
Definition: types.cpp:442
static void check_id(std::string &id)
Validate the id argument.
Definition: types.cpp:1471
BUILD_STATUS
Records the status of the lazy building of unit types.
Definition: types.hpp:77
@ CREATED
Definition: types.hpp:77
@ NOT_BUILT
Definition: types.hpp:77
@ HELP_INDEXED
Definition: types.hpp:77
@ FULL
Definition: types.hpp:77
@ VARIATIONS
Definition: types.hpp:77
std::string id_
Definition: types.hpp:318
std::string usage_
Definition: types.hpp:337
const std::vector< unit_animation > & animations() const
Definition: types.cpp:491
std::vector< std::string > variations() const
Definition: types.cpp:709
bool hide_help() const
Definition: types.cpp:585
const std::string & flag_rgb() const
Definition: types.cpp:679
std::vector< t_string > special_notes() const
Returns all notes that should be displayed in the help page for this type, including those found in a...
Definition: types.cpp:451
attack_list attacks_cache_
Definition: types.hpp:316
unit_type_error error
Definition: types.hpp:49
double xp_bar_scaling_
Definition: types.hpp:329
std::vector< std::string > advances_to_
Definition: types.hpp:362
const config * cfg_
Definition: types.hpp:313
std::string base_unit_id_
from [base_unit]
Definition: types.hpp:324
double hp_bar_scaling_
Definition: types.hpp:329
config possible_traits_
Definition: types.hpp:372
const std::string log_id() const
A variant on id() that is more descriptive, for use with message logging.
Definition: types.hpp:146
std::vector< unit_race::GENDER > genders_
Definition: types.hpp:374
bool zoc_
Definition: types.hpp:360
int experience_needed(bool with_acceleration=true) const
Definition: types.cpp:534
bool musthave_status(const std::string &status) const
Definition: types.cpp:633
unit_type(default_ctor_t, const config &cfg, const std::string &parent_id)
Definition: types.cpp:55
int experience_needed_
Definition: types.hpp:364
movetype movement_type_
Definition: types.hpp:370
std::string icon_
Definition: types.hpp:341
int hitpoints_
Definition: types.hpp:328
bool do_not_list_
Definition: types.hpp:360
void build(BUILD_STATUS status, const movement_type_map &movement_types, const race_map &races, const config_array_view &traits)
Performs a build of this to the indicated stage.
Definition: types.cpp:376
std::vector< std::string > get_ability_list() const
Definition: types.cpp:562
config abilities_cfg() const
Definition: types.cpp:581
bool resistance_filter_matches(const config &cfg, bool attacker, const std::string &damage_name, int res) const
Identical to unit::resistance_filter_matches.
Definition: types.cpp:767
void remove_scenario_fixes()
Definition: types.cpp:1441
std::unique_ptr< unit_type > create_sub_type(const config &var_cfg, bool default_inherit)
Handles inheritance for configs of [male], [female], and [variation].
Definition: types.cpp:997
void build_help_index(const movement_type_map &movement_types, const race_map &races, const config_array_view &traits)
Partially load data into an empty unit_type (build to HELP_INDEXED).
Definition: types.cpp:187
BUILD_STATUS build_status_
Definition: types.hpp:379
config::const_child_itors possible_traits() const
Definition: types.hpp:234
void build_created()
Load the most needed data into an empty unit_type (build to CREATE).
Definition: types.cpp:333
std::unique_ptr< config > built_cfg_
Definition: types.hpp:315
std::vector< ability_metadata > adv_abilities_
Definition: types.hpp:358
t_string type_name_
Definition: types.hpp:325
~unit_type()
Definition: types.cpp:122
const config & get_cfg() const
Definition: types.hpp:280
int movement_
Definition: types.hpp:332
unsigned int num_traits() const
Definition: types.hpp:138
unsigned int num_traits_
Definition: types.hpp:346
std::set< std::string > advancement_tree() const
Get the advancement tree.
Definition: types.cpp:606
std::string profile_
Definition: types.hpp:343
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
void set_fallback(const_formula_callable_ptr fallback)
Definition: callable.hpp:265
const config * cfg
std::size_t i
Definition: function.cpp:1032
std::vector< std::reference_wrapper< const config > > config_array_view
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
auto string_table
Definition: language.hpp:68
Standard logging facilities (interface).
double hp_bar_scaling
Definition: game_config.cpp:65
std::string unit_rgb
static void add_color_info(const game_config_view &v, bool build_defaults)
double xp_bar_scaling
Definition: game_config.cpp:66
retval
Default window/dialog return values.
Definition: retval.hpp:30
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:844
logger & info()
Definition: log.cpp:351
std::string substitute_variables(const std::string &str, const std::string &tag_name, const config &ability_or_special)
Substitute gettext variables in name and description of abilities and specials.
Definition: abilities.cpp:2399
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:2378
void trim(std::string_view &s)
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
std::vector< std::string > split(const config_attribute_value &val)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
unit_race::GENDER string_gender(const std::string &str, unit_race::GENDER def)
Definition: race.cpp:147
std::map< std::string, unit_race > race_map
Definition: race.hpp:104
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
static const map_location & null_location()
Definition: location.hpp:103
The base template for associating string values with enum values.
Definition: enum_base.hpp:33
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
unit_experience_accelerator(int modifier)
Definition: types.cpp:518
static int get_acceleration()
Definition: types.cpp:529
ability_metadata(const config &cfg)
Definition: types.cpp:126
mock_char c
#define LOG_CONFIG
Definition: types.cpp:44
std::vector< t_string > combine_special_notes(const std::vector< t_string > &direct, const config &abilities, const const_attack_itors &attacks, const movetype &mt)
Common logic for unit_type::special_notes() and unit::special_notes().
Definition: types.cpp:464
#define WRN_UT
Definition: types.cpp:49
static lg::log_domain log_unit("unit")
#define DBG_CF
Definition: types.cpp:45
unit_type_data unit_types
Definition: types.cpp:1501
#define LOG_UT
Definition: types.cpp:50
#define DBG_UT
Definition: types.cpp:48
static void advancement_tree_internal(const std::string &id, std::set< std::string > &tree)
Definition: types.cpp:591
#define ERR_UT
Definition: types.cpp:51
#define ERR_CF
Definition: types.cpp:42
void adjust_profile(std::string &profile)
Definition: types.cpp:1503
static void append_special_note(std::vector< t_string > &notes, const t_string &new_note)
Definition: types.cpp:455
static lg::log_domain log_config("config")
std::map< std::string, movetype > movement_type_map
Definition: types.hpp:33