The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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
41static 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
47static 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
55unit_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
111unit_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
117unit_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
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
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.
194 return;
195 }
196
197 // Make sure we are built to the preceding build level.
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
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.
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
416const 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
437const 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
456std::vector<t_string> unit_type::special_notes() const {
458}
459
460static 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
469std::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
496const 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
518namespace
519{
520int experience_modifier = 100;
521}
522
524 : old_value_(experience_modifier)
525{
526 experience_modifier = modifier;
527}
528
533
535{
536 return experience_modifier;
537}
538
539int 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
553bool 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
564std::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
581
583{
584 return hide_help_ || unit_types.hide_help(id_, race_->id());
585}
586
587
588static 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
603std::set<std::string> unit_type::advancement_tree() const
604{
605 std::set<std::string> tree;
607 return tree;
608}
609
610const 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.
630bool 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
676const 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
686std::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
698bool 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
714int 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
779std::string unit_type::alignment_description(unit_alignments::type align, unit_race::GENDER gender)
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
829namespace
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 */
836void 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 */
855void 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);
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
937void 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 */
976std::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
1009 variations_map::iterator ut;
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.
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 */
1253const 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
1274void 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
1342bool 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
1359const 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 // Make sure what generic modification was applied to all variations.
1413 for(auto& v : variations_) {
1414 v.second.apply_scenario_fix(cfg);
1415 }
1416 // Apply modification for unique variation.
1417 for (auto& cv : cfg.child_range("variation")) {
1418 for(auto& [variation_id, variation_type] : variations_) {
1419 if(cv["variation_id"] == variation_id) {
1420 variation_type.apply_scenario_fix(cv);
1421 }
1422 }
1423 }
1424 }
1425}
1426
1428{
1429 unit_type_map::iterator itor = types_.find(cfg["type"].str());
1430 // This might happen if units of another era are requested (for example for savegames)
1431 if(itor != types_.end()) {
1432 itor->second.apply_scenario_fix(cfg);
1433 }
1434 else {
1435 // should we give an error message?
1436 }
1437}
1438
1440{
1441 advances_to_.clear();
1442 const std::string& advances_to_val = get_cfg()["advances_to"];
1443 if(advances_to_val != "null" && !advances_to_val.empty()) {
1444 advances_to_ = utils::split(advances_to_val);
1445 }
1446 experience_needed_ = get_cfg()["experience"].to_int(500);
1447 cost_ = get_cfg()["cost"].to_int(1);
1448
1449 // apply recursively to subtypes.
1450 for(int gender = 0; gender <= 1; ++gender) {
1451 if(!gender_types_[gender]) {
1452 continue;
1453 }
1454 gender_types_[gender]->remove_scenario_fixes();
1455 }
1456 for(auto& v : variations_) {
1457 v.second.remove_scenario_fixes();
1458 }
1459 advancements_ = get_cfg().child_range("advancement");
1460}
1461
1463{
1464 for(auto& pair : types_) {
1465 pair.second.remove_scenario_fixes();
1466 }
1467}
1468
1469void unit_type::check_id(std::string& id)
1470{
1471 assert(!id.empty());
1472
1473 // We don't allow leading whitepaces.
1474 if(id[0] == ' ') {
1475 throw error("Found unit type id with a leading whitespace \"" + id + "\"");
1476 }
1477
1478 if(id == "random" || id == "null") {
1479 throw error("Found unit type using a 'random' or 'null' as an id");
1480 }
1481
1482 bool gave_warning = false;
1483
1484 for(std::size_t pos = 0; pos < id.size(); ++pos) {
1485 const char c = id[pos];
1486 const bool valid = std::isalnum(c, std::locale::classic()) || c == '_' || c == ' ';
1487
1488 if(!valid) {
1489 if(!gave_warning) {
1490 ERR_UT << "Found unit type id with invalid characters: \"" << id << "\"";
1491 gave_warning = true;
1492 }
1493
1494 id[pos] = '_';
1495 }
1496 }
1497}
1498
1500
1501void adjust_profile(std::string& profile)
1502{
1503 // Create a temp copy
1504 std::string temp = profile;
1505
1506 static const std::string path_adjust = "/transparent";
1507 const std::string::size_type offset = profile.find_last_of('/', profile.find('~'));
1508
1509 // If the path already refers to /transparent...
1510 if(profile.find(path_adjust) != std::string::npos && offset != std::string::npos) {
1511 if(!image::exists(profile)) {
1512 profile.replace(profile.find(path_adjust), path_adjust.length(), "");
1513 }
1514
1515 return;
1516 }
1517
1518 // else, check for the file with /transparent appended...
1519 offset != std::string::npos ? temp.insert(offset, path_adjust) : temp = path_adjust + temp;
1520
1521 // and use that path if it exists.
1522 if(image::exists(temp)) {
1523 profile = temp;
1524 }
1525}
double t
#define DBG_UT
#define ERR_UT
boost::iterator_range< boost::indirect_iterator< attack_list::const_iterator > > const_attack_itors
attack_itors make_attack_itors(attack_list &atks)
void emplace_back(T &&... args)
std::pair< int, map_location > highest(const std::string &key, int def=0) const
bool empty() const
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
const std::vector< t_string > & special_notes() const
Contents of any [special_note] tags.
Definition movetype.hpp:343
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
bool empty() const
Definition tstring.hpp:200
const char * c_str() const
Definition tstring.hpp:206
int get_composite_value() const
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)
bool uses_global_traits() const
Definition race.cpp:122
const std::string & id() const
Definition race.hpp:35
static const unit_race null_race
Dummy race used when a race is not yet known.
Definition race.hpp:70
unsigned int num_traits() const
Definition race.cpp:137
const config::const_child_itors & additional_traits() const
Definition race.cpp:127
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
const std::string & undead_variation() const
Definition race.hpp:49
@ FEMALE
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:450
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:460
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
std::map< std::string, config > abilities_registry_
Definition types.hpp:449
void remove_scenario_fixes()
Definition types.cpp:1462
bool hide_help_all_
True if [hide_help] contains a 'all=yes' at its root.
Definition types.hpp:453
race_map races_
Definition types.hpp:447
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
const std::map< std::string, config > & abilities() const
Definition types.hpp:410
const race_map & races() const
Definition types.hpp:408
const game_config_view & units_cfg() const
Definition types.hpp:458
unit_type_map types_
Definition types.hpp:445
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:446
game_config_view units_cfg_
Definition types.hpp:459
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
void read_hide_help(const config &cfg)
Parses the [hide_help] tag.
Definition types.cpp:1313
const unit_type_map & types() const
Definition types.hpp:398
std::vector< std::set< std::string > > hide_help_type_
Definition types.hpp:455
std::vector< std::set< std::string > > hide_help_race_
Definition types.hpp:456
void apply_scenario_fix(const config &cfg)
Definition types.cpp:1427
A single unit type that the player may recruit.
Definition types.hpp:43
std::string default_variation_
Definition types.hpp:354
t_string description_
Definition types.hpp:329
std::string image_
Definition types.hpp:343
std::vector< unit_animation > animations_
Definition types.hpp:383
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:373
int jamming_
Definition types.hpp:337
int recall_cost_
Definition types.hpp:334
int cost_
Definition types.hpp:339
config::const_child_itors advancements_
Definition types.hpp:369
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:330
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:353
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:333
const unit_race * race_
Never nullptr, but may point to the null race.
Definition types.hpp:359
std::string parent_id_
The id of the top ancestor of this unit_type.
Definition types.hpp:325
ability_vector abilities_
Definition types.hpp:364
std::string flag_rgb_
Definition types.hpp:347
std::string undead_variation_
Definition types.hpp:341
std::string movement_type_id_
Definition types.hpp:375
void fill_variations_and_gender()
Definition types.cpp:1022
std::vector< ability_metadata > adv_abilities_infos_
Definition types.hpp:362
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:338
std::string debug_id_
A suffix for id_, used when logging messages.
Definition types.hpp:323
std::array< std::unique_ptr< unit_type >, 2 > gender_types_
Definition types.hpp:351
bool has_ability_by_id(const std::string &ability) const
Definition types.cpp:553
std::string small_profile_
Definition types.hpp:345
t_string variation_name_
Definition types.hpp:356
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_type & get_variation(const std::string &id) const
Definition types.cpp:437
const_attack_itors attacks() const
Definition types.cpp:505
void apply_scenario_fix(const config &cfg)
Definition types.cpp:1365
int vision_
Definition types.hpp:336
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:366
t_string unit_description() const
Definition types.cpp:447
const config & get_cfg() const
Definition types.hpp:283
static void check_id(std::string &id)
Validate the id argument.
Definition types.cpp:1469
BUILD_STATUS
Records the status of the lazy building of unit types.
Definition types.hpp:77
@ CREATED
Definition types.hpp:77
@ NOT_BUILT
Definition types.hpp:77
@ HELP_INDEXED
Definition types.hpp:77
@ VARIATIONS
Definition types.hpp:77
const std::string & parent_id() const
The id of the original type from which this (variation) descended.
Definition types.hpp:149
std::string id_
Definition types.hpp:321
std::string usage_
Definition types.hpp:340
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:361
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:319
unit_type_error error
Definition types.hpp:49
double xp_bar_scaling_
Definition types.hpp:332
std::vector< std::string > advances_to_
Definition types.hpp:368
const config * cfg_
Definition types.hpp:316
std::string base_unit_id_
from [base_unit]
Definition types.hpp:327
double hp_bar_scaling_
Definition types.hpp:332
config possible_traits_
Definition types.hpp:378
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:380
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
const ability_vector & abilities() const
Definition types.hpp:117
bool zoc_
Definition types.hpp:366
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:370
movetype movement_type_
Definition types.hpp:376
std::string icon_
Definition types.hpp:344
int hitpoints_
Definition types.hpp:331
bool do_not_list_
Definition types.hpp:366
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:1439
std::unique_ptr< unit_type > create_sub_type(const config &var_cfg, bool default_inherit)
Handles inheritance for configs of [male], [female], and [variation].
Definition types.cpp: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:385
config::const_child_itors possible_traits() const
Definition types.hpp:239
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:318
t_string type_name_
Definition types.hpp:328
~unit_type()
Definition types.cpp:123
int movement_
Definition types.hpp:335
unsigned int num_traits_
Definition types.hpp:349
std::set< std::string > advancement_tree() const
Get the advancement tree.
Definition types.cpp:603
std::string profile_
Definition types.hpp:346
const movetype & movement_type() const
Definition types.hpp:194
map_formula_callable & add(const std::string &key, const variant &value)
Definition callable.hpp:249
void set_fallback(const_formula_callable_ptr fallback)
Definition callable.hpp:261
#define DBG_CF
Definition config.cpp:38
#define ERR_CF
Definition config.cpp:37
const config * cfg
#define LOG_CONFIG
std::vector< std::reference_wrapper< const config > > config_array_view
static std::string _(const char *str)
Definition gettext.hpp:100
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
auto string_table
Definition language.hpp:81
Standard logging facilities (interface).
double hp_bar_scaling
std::string unit_rgb
static void add_color_info(const game_config_view &v, bool build_defaults)
double xp_bar_scaling
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition picture.cpp:855
bool filter_base_matches(const config &cfg, int def)
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:88
std::vector< std::string > split(const config_attribute_value &val)
unit_race::GENDER string_gender(const std::string &str, unit_race::GENDER def)
Definition race.cpp:149
std::map< std::string, unit_race > race_map
Definition race.hpp:106
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
Error used for any general game error, e.g.
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
#define LOG_CONFIG
Definition types.cpp:44
#define WRN_UT
Definition types.cpp:49
static lg::log_domain log_unit("unit")
unit_type_data unit_types
Definition types.cpp:1499
#define LOG_UT
Definition types.cpp:50
static void advancement_tree_internal(const std::string &id, std::set< std::string > &tree)
Definition types.cpp:588
#define ERR_CF
Definition types.cpp:42
void adjust_profile(std::string &profile)
Definition types.cpp:1501
static void append_special_note(std::vector< t_string > &notes, const t_string &new_note)
Definition types.cpp:460
static lg::log_domain log_config("config")
unit_type_data unit_types
Definition types.cpp:1499
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
std::map< std::string, movetype > movement_type_map
Definition types.hpp:35
void adjust_profile(std::string &profile)
Definition types.cpp:1501
#define i
#define g
#define c