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