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