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