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