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