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