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