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