The Battle for Wesnoth  1.17.8+dev
unit.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
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  * Routines to manage units.
19  */
20 
21 #include "units/unit.hpp"
22 
23 #include "ai/manager.hpp"
24 #include "color.hpp"
25 #include "deprecation.hpp"
26 #include "display.hpp"
27 #include "formatter.hpp"
28 #include "formula/string_utils.hpp" // for VGETTEXT
29 #include "game_board.hpp" // for game_board
30 #include "game_config.hpp" // for add_color_info, etc
31 #include "game_data.hpp"
32 #include "game_errors.hpp" // for game_error
33 #include "game_events/manager.hpp" // for add_events
34 #include "game_version.hpp"
35 #include "gettext.hpp" // for N_
36 #include "lexical_cast.hpp"
37 #include "log.hpp" // for LOG_STREAM, logger, etc
38 #include "map/map.hpp" // for gamemap
39 #include "preferences/game.hpp" // for encountered_units
40 #include "random.hpp" // for generator, rng
41 #include "resources.hpp" // for units, gameboard, teams, etc
42 #include "scripting/game_lua_kernel.hpp" // for game_lua_kernel
43 #include "side_filter.hpp" // for side_filter
44 #include "synced_context.hpp"
45 #include "team.hpp" // for team, get_teams, etc
46 #include "terrain/filter.hpp" // for terrain_filter
47 #include "units/abilities.hpp" // for effect, filter_base_matches
48 #include "units/animation.hpp" // for unit_animation
49 #include "units/animation_component.hpp" // for unit_animation_component
50 #include "units/filter.hpp"
51 #include "units/formula_manager.hpp" // for unit_formula_manager
52 #include "units/id.hpp"
53 #include "units/map.hpp" // for unit_map, etc
54 #include "units/types.hpp"
55 #include "variable.hpp" // for vconfig, etc
56 
57 #include <cassert> // for assert
58 #include <cstdlib> // for rand
59 #include <exception> // for exception
60 #include <functional>
61 #include <iterator> // for back_insert_iterator, etc
62 #include <new> // for operator new
63 #include <ostream> // for operator<<, basic_ostream, etc
64 #include <string_view>
65 
66 namespace t_translation { struct terrain_code; }
67 
68 static lg::log_domain log_unit("unit");
69 #define DBG_UT LOG_STREAM(debug, log_unit)
70 #define LOG_UT LOG_STREAM(info, log_unit)
71 #define WRN_UT LOG_STREAM(warn, log_unit)
72 #define ERR_UT LOG_STREAM(err, log_unit)
73 
74 namespace
75 {
76  // "advance" only kept around for backwards compatibility; only "advancement" should be used
77  const std::set<std::string_view> ModificationTypes { "advancement", "advance", "trait", "object" };
78 
79  /**
80  * Pointers to units which have data in their internal caches. The
81  * destructor of an unit removes itself from the cache, so the pointers are
82  * always valid.
83  */
84  static std::vector<const unit*> units_with_cache;
85 
86  static const std::string leader_crown_path = "misc/leader-crown.png";
87  static const std::set<std::string_view> internalized_attrs {
88  "type",
89  "id",
90  "name",
91  "gender",
92  "random_gender",
93  "variation",
94  "role",
95  "ai_special",
96  "side",
97  "underlying_id",
98  "overlays",
99  "facing",
100  "race",
101  "level",
102  "recall_cost",
103  "undead_variation",
104  "max_attacks",
105  "attacks_left",
106  "alpha",
107  "zoc",
108  "flying",
109  "cost",
110  "max_hitpoints",
111  "max_moves",
112  "vision",
113  "jamming",
114  "max_experience",
115  "advances_to",
116  "hitpoints",
117  "goto_x",
118  "goto_y",
119  "moves",
120  "experience",
121  "resting",
122  "unrenamable",
123  "alignment",
124  "canrecruit",
125  "extra_recruit",
126  "x",
127  "y",
128  "placement",
129  "parent_type",
130  "description",
131  "usage",
132  "halo",
133  "ellipse",
134  "upkeep",
135  "random_traits",
136  "generate_name",
137  "profile",
138  "small_profile",
139  "fire_event",
140  "passable",
141  "overwrite",
142  "location_id",
143  "hidden",
144  // Useless attributes created when saving units to WML:
145  "flag_rgb",
146  "language_name",
147  "image",
148  "image_icon"
149  };
150 
151  void warn_unknown_attribute(const config::const_attr_itors& cfg)
152  {
153  config::const_attribute_iterator cur = cfg.begin();
154  config::const_attribute_iterator end = cfg.end();
155 
156  auto cur_known = internalized_attrs.begin();
157  auto end_known = internalized_attrs.end();
158 
159  while(cur_known != end_known) {
160  if(cur == end) {
161  return;
162  }
163  int comp = cur->first.compare(*cur_known);
164  if(comp < 0) {
165  WRN_UT << "Unknown attribute '" << cur->first << "' discarded.";
166  ++cur;
167  }
168  else if(comp == 0) {
169  ++cur;
170  ++cur_known;
171  }
172  else {
173  ++cur_known;
174  }
175  }
176 
177  while(cur != end) {
178  WRN_UT << "Unknown attribute '" << cur->first << "' discarded.";
179  ++cur;
180  }
181  }
182 } // end anon namespace
183 
184 /**
185  * Converts a string ID to a unit_type.
186  * Throws a game_error exception if the string does not correspond to a type.
187  */
188 static const unit_type& get_unit_type(const std::string& type_id)
189 {
190  if(type_id.empty()) {
191  throw unit_type::error("creating unit with an empty type field");
192  }
193  std::string new_id = type_id;
194  unit_type::check_id(new_id);
195  const unit_type* i = unit_types.find(new_id);
196  if(!i) throw unit_type::error("unknown unit type: " + type_id);
197  return *i;
198 }
199 
200 static unit_race::GENDER generate_gender(const unit_type& type, bool random_gender)
201 {
202  const std::vector<unit_race::GENDER>& genders = type.genders();
203  assert(genders.size() > 0);
204 
205  if(random_gender == false || genders.size() == 1) {
206  return genders.front();
207  } else {
208  return genders[randomness::generator->get_random_int(0,genders.size()-1)];
209  }
210 }
211 
212 static unit_race::GENDER generate_gender(const unit_type& u_type, const config& cfg)
213 {
214  const std::string& gender = cfg["gender"];
215  if(!gender.empty()) {
216  return string_gender(gender);
217  }
218 
219  return generate_gender(u_type, cfg["random_gender"].to_bool());
220 }
221 
222 // Copy constructor
223 unit::unit(const unit& o)
224  : std::enable_shared_from_this<unit>()
225  , loc_(o.loc_)
226  , advances_to_(o.advances_to_)
227  , type_(o.type_)
228  , type_name_(o.type_name_)
229  , race_(o.race_)
230  , id_(o.id_)
231  , name_(o.name_)
232  , underlying_id_(o.underlying_id_)
233  , undead_variation_(o.undead_variation_)
234  , variation_(o.variation_)
235  , hit_points_(o.hit_points_)
236  , max_hit_points_(o.max_hit_points_)
237  , experience_(o.experience_)
238  , max_experience_(o.max_experience_)
239  , level_(o.level_)
240  , recall_cost_(o.recall_cost_)
241  , canrecruit_(o.canrecruit_)
242  , recruit_list_(o.recruit_list_)
243  , alignment_(o.alignment_)
244  , flag_rgb_(o.flag_rgb_)
245  , image_mods_(o.image_mods_)
246  , unrenamable_(o.unrenamable_)
247  , side_(o.side_)
248  , gender_(o.gender_)
249  , formula_man_(new unit_formula_manager(o.formula_manager()))
250  , movement_(o.movement_)
251  , max_movement_(o.max_movement_)
252  , vision_(o.vision_)
253  , jamming_(o.jamming_)
254  , movement_type_(o.movement_type_)
255  , hold_position_(o.hold_position_)
256  , end_turn_(o.end_turn_)
257  , resting_(o.resting_)
258  , attacks_left_(o.attacks_left_)
259  , max_attacks_(o.max_attacks_)
260  , states_(o.states_)
261  , known_boolean_states_(o.known_boolean_states_)
262  , variables_(o.variables_)
263  , events_(o.events_)
264  , filter_recall_(o.filter_recall_)
265  , emit_zoc_(o.emit_zoc_)
266  , overlays_(o.overlays_)
267  , role_(o.role_)
268  , attacks_(o.attacks_)
269  , facing_(o.facing_)
270  , trait_names_(o.trait_names_)
271  , trait_descriptions_(o.trait_descriptions_)
272  , unit_value_(o.unit_value_)
273  , goto_(o.goto_)
274  , interrupted_move_(o.interrupted_move_)
275  , is_fearless_(o.is_fearless_)
276  , is_healthy_(o.is_healthy_)
277  , modification_descriptions_(o.modification_descriptions_)
278  , anim_comp_(new unit_animation_component(*this, *o.anim_comp_))
279  , hidden_(o.hidden_)
280  , hp_bar_scaling_(o.hp_bar_scaling_)
281  , xp_bar_scaling_(o.xp_bar_scaling_)
282  , modifications_(o.modifications_)
283  , abilities_(o.abilities_)
284  , advancements_(o.advancements_)
285  , description_(o.description_)
286  , special_notes_(o.special_notes_)
287  , usage_(o.usage_)
288  , halo_(o.halo_)
289  , ellipse_(o.ellipse_)
290  , random_traits_(o.random_traits_)
291  , generate_name_(o.generate_name_)
292  , upkeep_(o.upkeep_)
293  , profile_(o.profile_)
294  , small_profile_(o.small_profile_)
295  , changed_attributes_(o.changed_attributes_)
296  , invisibility_cache_()
297 {
298  // Copy the attacks rather than just copying references
299  for(auto& a : attacks_) {
300  a.reset(new attack_type(*a));
301  }
302 }
303 
305  : std::enable_shared_from_this<unit>()
306  , loc_()
307  , advances_to_()
308  , type_(nullptr)
309  , type_name_()
310  , race_(&unit_race::null_race)
311  , id_()
312  , name_()
313  , underlying_id_(0)
315  , variation_()
316  , hit_points_(1)
317  , max_hit_points_(1)
318  , experience_(0)
319  , max_experience_(1)
320  , level_(0)
321  , recall_cost_(-1)
322  , canrecruit_(false)
323  , recruit_list_()
324  , alignment_()
325  , flag_rgb_()
326  , image_mods_()
327  , unrenamable_(false)
328  , side_(0)
329  , gender_(unit_race::NUM_GENDERS)
331  , movement_(0)
332  , max_movement_(0)
333  , vision_(-1)
334  , jamming_(0)
335  , movement_type_()
336  , hold_position_(false)
337  , end_turn_(false)
338  , resting_(false)
339  , attacks_left_(0)
340  , max_attacks_(0)
341  , states_()
343  , variables_()
344  , events_()
345  , filter_recall_()
346  , emit_zoc_(0)
347  , overlays_()
348  , role_()
349  , attacks_()
350  , facing_(map_location::NDIRECTIONS)
351  , trait_names_()
353  , unit_value_()
354  , goto_()
356  , is_fearless_(false)
357  , is_healthy_(false)
360  , hidden_(false)
361  , hp_bar_scaling_(0)
362  , xp_bar_scaling_(0)
363  , modifications_()
364  , abilities_()
365  , advancements_()
366  , description_()
367  , special_notes_()
368  , usage_()
369  , halo_()
370  , ellipse_()
371  , random_traits_(true)
372  , generate_name_(true)
373  , upkeep_(upkeep_full{})
376 {
377 }
378 
379 void unit::init(const config& cfg, bool use_traits, const vconfig* vcfg)
380 {
381  loc_ = map_location(cfg["x"], cfg["y"], wml_loc());
382  type_ = &get_unit_type(cfg["parent_type"].blank() ? cfg["type"].str() : cfg["parent_type"].str());
384  id_ = cfg["id"].str();
385  name_ = cfg["name"].t_str();
386  variation_ = cfg["variation"].empty() ? type_->default_variation() : cfg["variation"].str();
387  canrecruit_ = cfg["canrecruit"].to_bool();
388  gender_ = generate_gender(*type_, cfg);
389  role_ = cfg["role"].str();
390  //, facing_(map_location::NDIRECTIONS)
391  //, anim_comp_(new unit_animation_component(*this))
392  hidden_ = cfg["hidden"].to_bool(false);
393  hp_bar_scaling_ = cfg["hp_bar_scaling"].blank() ? type_->hp_bar_scaling() : cfg["hp_bar_scaling"];
394  xp_bar_scaling_ = cfg["xp_bar_scaling"].blank() ? type_->xp_bar_scaling() : cfg["xp_bar_scaling"];
395  random_traits_ = true;
396  generate_name_ = true;
397  side_ = cfg["side"].to_int();
398 
399  if(side_ <= 0) {
400  side_ = 1;
401  }
402 
404  underlying_id_ = n_unit::unit_id(cfg["underlying_id"].to_size_t());
406 
407  if(vcfg) {
408  const vconfig& filter_recall = vcfg->child("filter_recall");
409  if(!filter_recall.null())
410  filter_recall_ = filter_recall.get_config();
411 
412  const vconfig::child_list& events = vcfg->get_children("event");
413  for(const vconfig& e : events) {
414  events_.add_child("event", e.get_config());
415  }
416  } else {
417  filter_recall_ = cfg.child_or_empty("filter_recall");
418 
419  for(const config& unit_event : cfg.child_range("event")) {
420  events_.add_child("event", unit_event);
421  }
422  }
423 
426  }
427 
428  random_traits_ = cfg["random_traits"].to_bool(true);
429  facing_ = map_location::parse_direction(cfg["facing"]);
431 
432  for(const config& mods : cfg.child_range("modifications")) {
434  }
435 
436  generate_name_ = cfg["generate_name"].to_bool(true);
437 
438  // Apply the unit type's data to this unit.
439  advance_to(*type_, use_traits);
440 
441  if(const config::attribute_value* v = cfg.get("overlays")) {
442  auto overlays = utils::parenthetical_split(v->str(), ',');
443  if(overlays.size() > 0) {
444  deprecated_message("[unit]overlays", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "This warning is only triggered by the cases that *do* still work: setting [unit]overlays= works, but the [unit]overlays attribute will always be empty if WML tries to read it.");
445  config effect;
446  config o;
447  effect["apply_to"] = "overlay";
448  effect["add"] = v->str();
449  o.add_child("effect", effect);
450  add_modification("object", o);
451  }
452  }
453 
454  if(const config& variables = cfg.child("variables")) {
456  }
457 
458  if(const config::attribute_value* v = cfg.get("race")) {
459  if(const unit_race *r = unit_types.find_race(*v)) {
460  race_ = r;
461  } else {
463  }
464  }
465 
466  if(const config::attribute_value* v = cfg.get("level")) {
467  set_level(v->to_int(level_));
468  }
469 
470  if(const config::attribute_value* v = cfg.get("undead_variation")) {
471  set_undead_variation(v->str());
472  }
473 
474  if(const config::attribute_value* v = cfg.get("max_attacks")) {
475  set_max_attacks(std::max(0, v->to_int(1)));
476  }
477 
478  if(const config::attribute_value* v = cfg.get("zoc")) {
479  set_emit_zoc(v->to_bool(level_ > 0));
480  }
481 
482  if(const config::attribute_value* v = cfg.get("description")) {
483  description_ = *v;
484  }
485 
486  if(const config::attribute_value* v = cfg.get("cost")) {
487  unit_value_ = *v;
488  }
489 
490  if(const config::attribute_value* v = cfg.get("ellipse")) {
491  set_image_ellipse(*v);
492  }
493 
494  if(const config::attribute_value* v = cfg.get("halo")) {
495  set_image_halo(*v);
496  }
497 
498  if(const config::attribute_value* v = cfg.get("usage")) {
499  set_usage(*v);
500  }
501 
502  if(const config::attribute_value* v = cfg.get("profile")) {
503  set_big_profile(v->str());
504  }
505 
506  if(const config::attribute_value* v = cfg.get("small_profile")) {
507  set_small_profile(v->str());
508  }
509 
510  if(const config::attribute_value* v = cfg.get("max_hitpoints")) {
511  set_max_hitpoints(std::max(1, v->to_int(max_hit_points_)));
512  }
513  if(const config::attribute_value* v = cfg.get("max_moves")) {
514  set_total_movement(std::max(0, v->to_int(max_movement_)));
515  }
516  if(const config::attribute_value* v = cfg.get("max_experience")) {
517  set_max_experience(std::max(1, v->to_int(max_experience_)));
518  }
519 
520  vision_ = cfg["vision"].to_int(vision_);
521  jamming_ = cfg["jamming"].to_int(jamming_);
522 
523  advances_to_t temp_advances = utils::split(cfg["advances_to"]);
524  if(temp_advances.size() == 1 && temp_advances.front() == "null") {
526  } else if(temp_advances.size() >= 1 && !temp_advances.front().empty()) {
527  set_advances_to(temp_advances);
528  }
529 
530  if(const config& ai = cfg.child("ai")) {
531  formula_man_->read(ai);
532  config ai_events;
533  for(config mai : ai.child_range("micro_ai")) {
534  mai.clear_children("filter");
535  mai.add_child("filter")["id"] = id();
536  mai["side"] = side();
537  mai["action"] = "add";
538  ai_events.add_child("micro_ai", mai);
539  }
540  for(config ca : ai.child_range("candidate_action")) {
541  ca.clear_children("filter_own");
542  ca.add_child("filter_own")["id"] = id();
543  // Sticky candidate actions not supported here (they cause a crash because the unit isn't on the map yet)
544  ca.remove_attribute("sticky");
545  std::string stage = "main_loop";
546  if(ca.has_attribute("stage")) {
547  stage = ca["stage"].str();
548  ca.remove_attribute("stage");
549  }
550  config mod{
551  "action", "add",
552  "side", side(),
553  "path", "stage[" + stage + "].candidate_action[]",
554  "candidate_action", ca,
555  };
556  ai_events.add_child("modify_ai", mod);
557  }
558  if(ai_events.all_children_count() > 0) {
560  }
561  }
562 
563  // Don't use the unit_type's attacks if this config has its own defined
564  if(config::const_child_itors cfg_range = cfg.child_range("attack")) {
566  attacks_.clear();
567  for(const config& c : cfg_range) {
568  attacks_.emplace_back(new attack_type(c));
569  }
570  }
571 
572  // Don't use the unit_type's special notes if this config has its own defined
573  if(config::const_child_itors cfg_range = cfg.child_range("special_note")) {
575  special_notes_.clear();
576  for(const config& c : cfg_range) {
577  special_notes_.emplace_back(c["note"].t_str());
578  }
579  }
580 
581  // If cfg specifies [advancement]s, replace this [advancement]s with them.
582  if(cfg.has_child("advancement")) {
584  advancements_.clear();
585  for(const config& adv : cfg.child_range("advancement")) {
586  advancements_.push_back(adv);
587  }
588  }
589 
590  // Don't use the unit_type's abilities if this config has its own defined
591  // Why do we allow multiple [abilities] tags?
592  if(config::const_child_itors cfg_range = cfg.child_range("abilities")) {
594  abilities_.clear();
595  for(const config& abilities : cfg_range) {
597  }
598  }
599 
600  if(const config::attribute_value* v = cfg.get("alignment")) {
602  auto new_align = unit_alignments::get_enum(v->str());
603  if(new_align) {
604  alignment_ = *new_align;
605  }
606  }
607 
608  // Adjust the unit's defense, movement, vision, jamming, resistances, and
609  // flying status if this config has its own defined.
610  if(cfg.has_child("movement_costs")
611  || cfg.has_child("vision_costs")
612  || cfg.has_child("jamming_costs")
613  || cfg.has_child("defense")
614  || cfg.has_child("resistance")
615  || cfg.has_attribute("flying"))
616  {
618  }
619 
620  movement_type_.merge(cfg);
621 
622  if(const config& status_flags = cfg.child("status")) {
623  for(const config::attribute &st : status_flags.attribute_range()) {
624  if(st.second.to_bool()) {
625  set_state(st.first, true);
626  }
627  }
628  }
629 
630  if(cfg["ai_special"] == "guardian") {
631  set_state(STATE_GUARDIAN, true);
632  }
633 
634  if(const config::attribute_value* v = cfg.get("invulnerable")) {
635  set_state("invulnerable", v->to_bool());
636  }
637 
638  goto_.set_wml_x(cfg["goto_x"].to_int());
639  goto_.set_wml_y(cfg["goto_y"].to_int());
640 
641  attacks_left_ = std::max(0, cfg["attacks_left"].to_int(max_attacks_));
642 
643  movement_ = std::max(0, cfg["moves"].to_int(max_movement_));
644  // we allow negative hitpoints, one of the reasons is that a unit
645  // might be stored+unstored during a attack related event before it
646  // dies when it has negative hp and when dont want the event to
647  // change the unit hp when it was not intended.
648  hit_points_ = cfg["hitpoints"].to_int(max_hit_points_);
649 
650  experience_ = cfg["experience"];
651  resting_ = cfg["resting"].to_bool();
652  unrenamable_ = cfg["unrenamable"].to_bool();
653 
654  // We need to check to make sure that the cfg is not blank and if it
655  // isn't pull that value otherwise it goes with the default of -1.
656  if(!cfg["recall_cost"].blank()) {
657  recall_cost_ = cfg["recall_cost"].to_int(recall_cost_);
658  }
659 
660  generate_name();
661 
662  parse_upkeep(cfg["upkeep"]);
663 
664  set_recruits(utils::split(cfg["extra_recruit"]));
665 
666  warn_unknown_attribute(cfg.attribute_range());
667 
668 #if 0
669  // Debug unit animations for units as they appear in game
670  for(const auto& anim : anim_comp_->animations_) {
671  std::cout << anim.debug() << std::endl;
672  }
673 #endif
674 }
675 
677 {
678  for(auto& u : units_with_cache) {
679  u->clear_visibility_cache();
680  }
681 
682  units_with_cache.clear();
683 }
684 
685 void unit::init(const unit_type& u_type, int side, bool real_unit, unit_race::GENDER gender, const std::string& variation)
686 {
687  type_ = &u_type;
689  variation_ = variation.empty() ? type_->default_variation() : variation;
690  side_ = side;
691  gender_ = gender != unit_race::NUM_GENDERS ? gender : generate_gender(u_type, real_unit);
693  upkeep_ = upkeep_full{};
694 
695  // Apply the unit type's data to this unit.
696  advance_to(u_type, real_unit);
697 
698  if(real_unit) {
699  generate_name();
700  }
701 
703 
704  // Set these after traits and modifications have set the maximums.
708 }
709 
711 {
712  try {
713  anim_comp_->clear_haloes();
714 
715  // Remove us from the status cache
716  auto itor = std::find(units_with_cache.begin(), units_with_cache.end(), this);
717 
718  if(itor != units_with_cache.end()) {
719  units_with_cache.erase(itor);
720  }
721  } catch(const std::exception & e) {
722  ERR_UT << "Caught exception when destroying unit: " << e.what();
723  } catch(...) {
724  DBG_UT << "Caught general exception when destroying unit: " << utils::get_unknown_exception_type();
725  }
726 }
727 
728 /**
729  * Swap, for copy and swap idiom
730  */
731 void unit::swap(unit & o)
732 {
733  using std::swap;
734 
735  // Don't swap reference count, or it will be incorrect...
736  swap(loc_, o.loc_);
738  swap(type_, o.type_);
740  swap(race_, o.race_);
741  swap(id_, o.id_);
742  swap(name_, o.name_);
750  swap(level_, o.level_);
758  swap(side_, o.side_);
759  swap(gender_, o.gender_);
763  swap(vision_, o.vision_);
764  swap(jamming_, o.jamming_);
768  swap(resting_, o.resting_);
771  swap(states_, o.states_);
774  swap(events_, o.events_);
778  swap(role_, o.role_);
779  swap(attacks_, o.attacks_);
780  swap(facing_, o.facing_);
784  swap(goto_, o.goto_);
790  swap(hidden_, o.hidden_);
793 }
794 
796 {
797  if(!name_.empty() || !generate_name_) {
798  return;
799  }
801  generate_name_ = false;
802 }
803 
804 void unit::generate_traits(bool must_have_only)
805 {
806  LOG_UT << "Generating a trait for unit type " << type().log_id() << " with must_have_only " << must_have_only;
807  const unit_type& u_type = type();
808 
809  // Calculate the unit's traits
810  config::const_child_itors current_traits = modifications_.child_range("trait");
811  std::vector<const config*> candidate_traits;
812 
813  for(const config& t : u_type.possible_traits()) {
814  // Skip the trait if the unit already has it.
815  const std::string& tid = t["id"];
816  bool already = false;
817  for(const config& mod : current_traits) {
818  if(mod["id"] == tid) {
819  already = true;
820  break;
821  }
822  }
823 
824  if(already) {
825  continue;
826  }
827 
828  // Add the trait if it is mandatory.
829  const std::string& avl = t["availability"];
830  if(avl == "musthave") {
831  modifications_.add_child("trait", t);
832  current_traits = modifications_.child_range("trait");
833  continue;
834  }
835 
836  // The trait is still available, mark it as a candidate for randomizing.
837  // For leaders, only traits with availability "any" are considered.
838  if(!must_have_only && (!can_recruit() || avl == "any")) {
839  candidate_traits.push_back(&t);
840  }
841  }
842 
843  if(must_have_only) return;
844 
845  // Now randomly fill out to the number of traits required or until
846  // there aren't any more traits.
847  int nb_traits = current_traits.size();
848  int max_traits = u_type.num_traits();
849  for(; nb_traits < max_traits && !candidate_traits.empty(); ++nb_traits)
850  {
851  int num = randomness::generator->get_random_int(0,candidate_traits.size()-1);
852  modifications_.add_child("trait", *candidate_traits[num]);
853  candidate_traits.erase(candidate_traits.begin() + num);
854  }
855 
856  // Once random traits are added, don't do it again.
857  // Such as when restoring a saved character.
858  random_traits_ = false;
859 }
860 
861 std::vector<std::string> unit::get_traits_list() const
862 {
863  std::vector<std::string> res;
864 
865  for(const config& mod : modifications_.child_range("trait"))
866  {
867  // Make sure to return empty id trait strings as otherwise
868  // names will not match in length (Bug #21967)
869  res.push_back(mod["id"]);
870  }
871  return res;
872 }
873 
874 
875 /**
876  * Advances this unit to the specified type.
877  * Experience is left unchanged.
878  * Current hit point total is left unchanged unless it would violate max HP.
879  * Assumes gender_ and variation_ are set to their correct values.
880  */
881 void unit::advance_to(const unit_type& u_type, bool use_traits)
882 {
883  appearance_changed_ = true;
884  // For reference, the type before this advancement.
885  const unit_type& old_type = type();
886  // Adjust the new type for gender and variation.
888  // In case u_type was already a variation, make sure our variation is set correctly.
889  variation_ = new_type.variation_id();
890 
891  // Reset the scalar values first
892  trait_names_.clear();
893  trait_descriptions_.clear();
894  is_fearless_ = false;
895  is_healthy_ = false;
896  image_mods_.clear();
897  overlays_.clear();
898  ellipse_.reset();
899 
900  // Clear modification-related caches
902 
903 
904  if(!new_type.usage().empty()) {
905  set_usage(new_type.usage());
906  }
907 
908  set_image_halo(new_type.halo());
909  if(!new_type.ellipse().empty()) {
910  set_image_ellipse(new_type.ellipse());
911  }
912 
913  generate_name_ &= new_type.generate_name();
914  abilities_ = new_type.abilities_cfg();
915  advancements_.clear();
916 
917  for(const config& advancement : new_type.advancements()) {
918  advancements_.push_back(advancement);
919  }
920 
921  // If unit has specific profile, remember it and keep it after advancing
922  if(small_profile_.empty() || small_profile_ == old_type.small_profile()) {
923  small_profile_ = new_type.small_profile();
924  }
925 
926  if(profile_.empty() || profile_ == old_type.big_profile()) {
927  profile_ = new_type.big_profile();
928  }
929  // NOTE: There should be no need to access old_cfg (or new_cfg) after this
930  // line. Particularly since the swap might have affected old_cfg.
931 
932  advances_to_ = new_type.advances_to();
933 
934  race_ = new_type.race();
935  type_ = &new_type;
936  type_name_ = new_type.type_name();
937  description_ = new_type.unit_description();
939  undead_variation_ = new_type.undead_variation();
940  max_experience_ = new_type.experience_needed(true);
941  level_ = new_type.level();
942  recall_cost_ = new_type.recall_cost();
943  alignment_ = new_type.alignment();
944  max_hit_points_ = new_type.hitpoints();
945  hp_bar_scaling_ = new_type.hp_bar_scaling();
946  xp_bar_scaling_ = new_type.xp_bar_scaling();
947  max_movement_ = new_type.movement();
948  vision_ = new_type.vision(true);
949  jamming_ = new_type.jamming();
950  movement_type_ = new_type.movement_type();
951  emit_zoc_ = new_type.has_zoc();
952  attacks_.clear();
953  std::transform(new_type.attacks().begin(), new_type.attacks().end(), std::back_inserter(attacks_), [](const attack_type& atk) {
954  return std::make_shared<attack_type>(atk);
955  });
956  unit_value_ = new_type.cost();
957 
958  max_attacks_ = new_type.max_attacks();
959 
960  flag_rgb_ = new_type.flag_rgb();
961 
962  upkeep_ = upkeep_full{};
963  parse_upkeep(new_type.get_cfg()["upkeep"]);
964 
965  anim_comp_->reset_after_advance(&new_type);
966 
967  if(random_traits_) {
968  generate_traits(!use_traits);
969  } else {
970  // This will add any "musthave" traits to the new unit that it doesn't already have.
971  // This covers the Dark Sorcerer advancing to Lich and gaining the "undead" trait,
972  // but random and/or optional traits are not added,
973  // and neither are inappropriate traits removed.
974  generate_traits(true);
975  }
976 
977  // Apply modifications etc, refresh the unit.
978  // This needs to be after type and gender are fixed,
979  // since there can be filters on the modifications
980  // that may result in different effects after the advancement.
982 
983  // Now that modifications are done modifying traits, check if poison should
984  // be cleared.
985  if(get_state("unpoisonable")) {
986  set_state(STATE_POISONED, false);
987  }
988  if(get_state("unslowable")) {
989  set_state(STATE_SLOWED, false);
990  }
991  if(get_state("unpetrifiable")) {
992  set_state(STATE_PETRIFIED, false);
993  }
994 
995  // Now that modifications are done modifying the maximum hit points,
996  // enforce this maximum.
999  }
1000 
1001  // In case the unit carries EventWML, apply it now
1004  }
1005  bool bool_small_profile = get_attr_changed(UA_SMALL_PROFILE);
1006  bool bool_profile = get_attr_changed(UA_PROFILE);
1008  if(bool_small_profile && small_profile_ != new_type.small_profile()) {
1010  }
1011 
1012  if(bool_profile && profile_ != new_type.big_profile()) {
1014  }
1015 }
1016 
1017 std::string unit::big_profile() const
1018 {
1019  if(!profile_.empty() && profile_ != "unit_image") {
1020  return profile_;
1021  }
1022 
1023  return absolute_image();
1024 }
1025 
1026 std::string unit::small_profile() const
1027 {
1028  if(!small_profile_.empty() && small_profile_ != "unit_image") {
1029  return small_profile_;
1030  }
1031 
1032  if(!profile_.empty() && small_profile_ != "unit_image" && profile_ != "unit_image") {
1033  return profile_;
1034  }
1035 
1036  return absolute_image();
1037 }
1038 
1039 const std::string& unit::leader_crown()
1040 {
1041  return leader_crown_path;
1042 }
1043 
1044 const std::string& unit::flag_rgb() const
1045 {
1046  return flag_rgb_.empty() ? game_config::unit_rgb : flag_rgb_;
1047 }
1048 
1050 {
1051  double unit_energy = 0.0;
1052  color_t energy_color {0,0,0,255};
1053 
1054  if(max_hitpoints > 0) {
1055  unit_energy = static_cast<double>(hitpoints)/static_cast<double>(max_hitpoints);
1056  }
1057 
1058  if(1.0 == unit_energy) {
1059  energy_color.r = 33;
1060  energy_color.g = 225;
1061  energy_color.b = 0;
1062  } else if(unit_energy > 1.0) {
1063  energy_color.r = 100;
1064  energy_color.g = 255;
1065  energy_color.b = 100;
1066  } else if(unit_energy >= 0.75) {
1067  energy_color.r = 170;
1068  energy_color.g = 255;
1069  energy_color.b = 0;
1070  } else if(unit_energy >= 0.5) {
1071  energy_color.r = 255;
1072  energy_color.g = 175;
1073  energy_color.b = 0;
1074  } else if(unit_energy >= 0.25) {
1075  energy_color.r = 255;
1076  energy_color.g = 155;
1077  energy_color.b = 0;
1078  } else {
1079  energy_color.r = 255;
1080  energy_color.g = 0;
1081  energy_color.b = 0;
1082  }
1083 
1084  return energy_color;
1085 }
1086 
1088 {
1089  return hp_color_impl(hitpoints(), max_hitpoints());
1090 }
1091 
1092 color_t unit::hp_color(int new_hitpoints) const
1093 {
1094  return hp_color_impl(new_hitpoints, hitpoints());
1095 }
1096 
1098 {
1099  return hp_color_impl(1, 1);
1100 }
1101 
1102 color_t unit::xp_color(int xp_to_advance, bool can_advance, bool has_amla)
1103 {
1104  const color_t near_advance_color {255,255,255,255};
1105  const color_t mid_advance_color {150,255,255,255};
1106  const color_t far_advance_color {0,205,205,255};
1107  const color_t normal_color {0,160,225,255};
1108  const color_t near_amla_color {225,0,255,255};
1109  const color_t mid_amla_color {169,30,255,255};
1110  const color_t far_amla_color {139,0,237,255};
1111  const color_t amla_color {170,0,255,255};
1112 
1113  const bool near_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience;
1114  const bool mid_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience*2;
1115  const bool far_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience*3;
1116 
1117  color_t color = normal_color;
1118  if(can_advance){
1119  if(near_advance){
1120  color=near_advance_color;
1121  } else if(mid_advance){
1122  color=mid_advance_color;
1123  } else if(far_advance){
1124  color=far_advance_color;
1125  }
1126  } else if(has_amla){
1127  if(near_advance){
1128  color=near_amla_color;
1129  } else if(mid_advance){
1130  color=mid_amla_color;
1131  } else if(far_advance){
1132  color=far_amla_color;
1133  } else {
1134  color=amla_color;
1135  }
1136  }
1137 
1138  return(color);
1139 }
1140 
1142 {
1143  bool major_amla = false;
1144  bool has_amla = false;
1145  for(const config& adv:get_modification_advances()){
1146  major_amla |= adv["major_amla"].to_bool();
1147  has_amla = true;
1148  }
1149  //TODO: calculating has_amla and major_amla can be a quite slow operation, we should cache these two values somehow.
1150  return xp_color(experience_to_advance(), !advances_to().empty() || major_amla, has_amla);
1151 }
1152 
1153 void unit::set_recruits(const std::vector<std::string>& recruits)
1154 {
1155  unit_types.check_types(recruits);
1157 }
1158 
1159 const std::vector<std::string> unit::advances_to_translated() const
1160 {
1161  std::vector<std::string> result;
1162  for(const std::string& adv_type_id : advances_to_) {
1163  if(const unit_type* adv_type = unit_types.find(adv_type_id)) {
1164  result.push_back(adv_type->type_name());
1165  } else {
1166  WRN_UT << "unknown unit in advances_to list of type "
1167  << type().log_id() << ": " << adv_type_id;
1168  }
1169  }
1170 
1171  return result;
1172 }
1173 
1174 void unit::set_advances_to(const std::vector<std::string>& advances_to)
1175 {
1177  unit_types.check_types(advances_to);
1179 }
1180 
1181 void unit::set_movement(int moves, bool unit_action)
1182 {
1183  // If this was because the unit acted, clear its "not acting" flags.
1184  if(unit_action) {
1185  end_turn_ = hold_position_ = false;
1186  }
1187 
1188  movement_ = std::max<int>(0, moves);
1189 }
1190 
1191 /**
1192  * Determines if @a mod_dur "matches" @a goal_dur.
1193  * If goal_dur is not empty, they match if they are equal.
1194  * If goal_dur is empty, they match if mod_dur is neither empty nor "forever".
1195  * Helper function for expire_modifications().
1196  */
1197 inline bool mod_duration_match(const std::string& mod_dur, const std::string& goal_dur)
1198 {
1199  if(goal_dur.empty()) {
1200  // Default is all temporary modifications.
1201  return !mod_dur.empty() && mod_dur != "forever";
1202  }
1203 
1204  return mod_dur == goal_dur;
1205 }
1206 
1207 void unit::expire_modifications(const std::string& duration)
1208 {
1209  // If any modifications expire, then we will need to rebuild the unit.
1210  const unit_type* rebuild_from = nullptr;
1211  int hp = hit_points_;
1212  int mp = movement_;
1213  // Loop through all types of modifications.
1214  for(const auto& mod_name : ModificationTypes) {
1215  // Loop through all modifications of this type.
1216  // Looping in reverse since we may delete the current modification.
1217  for(int j = modifications_.child_count(mod_name)-1; j >= 0; --j)
1218  {
1219  const config& mod = modifications_.child(mod_name, j);
1220 
1221  if(mod_duration_match(mod["duration"], duration)) {
1222  // If removing this mod means reverting the unit's type:
1223  if(const config::attribute_value* v = mod.get("prev_type")) {
1224  rebuild_from = &get_unit_type(v->str());
1225  }
1226  // Else, if we have not already specified a type to build from:
1227  else if(rebuild_from == nullptr) {
1228  rebuild_from = &type();
1229  }
1230 
1231  modifications_.remove_child(mod_name, j);
1232  }
1233  }
1234  }
1235 
1236  if(rebuild_from != nullptr) {
1237  anim_comp_->clear_haloes();
1238  advance_to(*rebuild_from);
1239  hit_points_ = hp;
1240  movement_ = std::min(mp, max_movement_);
1241  }
1242 }
1243 
1245 {
1246  expire_modifications("turn");
1247 
1251  set_state(STATE_UNCOVERED, false);
1252 }
1253 
1255 {
1256  expire_modifications("turn end");
1257 
1258  set_state(STATE_SLOWED,false);
1260  resting_ = false;
1261  }
1262 
1263  set_state(STATE_NOT_MOVED,false);
1264  // Clear interrupted move
1266 }
1267 
1269 {
1270  // Set the goto-command to be going to no-where
1271  goto_ = map_location();
1272 
1273  // Expire all temporary modifications.
1275 
1276  heal_fully();
1277  set_state(STATE_SLOWED, false);
1278  set_state(STATE_POISONED, false);
1279  set_state(STATE_PETRIFIED, false);
1280  set_state(STATE_GUARDIAN, false);
1281 }
1282 
1283 void unit::heal(int amount)
1284 {
1285  int max_hp = max_hitpoints();
1286  if(hit_points_ < max_hp) {
1287  hit_points_ += amount;
1288 
1289  if(hit_points_ > max_hp) {
1290  hit_points_ = max_hp;
1291  }
1292  }
1293 
1294  if(hit_points_<1) {
1295  hit_points_ = 1;
1296  }
1297 }
1298 
1299 const std::set<std::string> unit::get_states() const
1300 {
1301  std::set<std::string> all_states = states_;
1302  for(const auto& state : known_boolean_state_names_) {
1303  if(get_state(state.second)) {
1304  all_states.insert(state.first);
1305  }
1306  }
1307 
1308  // Backwards compatibility for not_living. Don't remove before 1.12
1309  if(all_states.count("undrainable") && all_states.count("unpoisonable") && all_states.count("unplagueable")) {
1310  all_states.insert("not_living");
1311  }
1312 
1313  return all_states;
1314 }
1315 
1316 bool unit::get_state(const std::string& state) const
1317 {
1318  state_t known_boolean_state_id = get_known_boolean_state_id(state);
1319  if(known_boolean_state_id!=STATE_UNKNOWN){
1320  return get_state(known_boolean_state_id);
1321  }
1322 
1323  // Backwards compatibility for not_living. Don't remove before 1.12
1324  if(state == "not_living") {
1325  return
1326  get_state("undrainable") &&
1327  get_state("unpoisonable") &&
1328  get_state("unplagueable");
1329  }
1330 
1331  return states_.find(state) != states_.end();
1332 }
1333 
1334 void unit::set_state(state_t state, bool value)
1335 {
1336  known_boolean_states_[state] = value;
1337 }
1338 
1339 bool unit::get_state(state_t state) const
1340 {
1341  return known_boolean_states_[state];
1342 }
1343 
1345 {
1346  auto i = known_boolean_state_names_.find(state);
1347  if(i != known_boolean_state_names_.end()) {
1348  return i->second;
1349  }
1350 
1351  return STATE_UNKNOWN;
1352 }
1353 
1354 std::map<std::string, unit::state_t> unit::known_boolean_state_names_ {
1355  {"slowed", STATE_SLOWED},
1356  {"poisoned", STATE_POISONED},
1357  {"petrified", STATE_PETRIFIED},
1358  {"uncovered", STATE_UNCOVERED},
1359  {"not_moved", STATE_NOT_MOVED},
1360  {"unhealable", STATE_UNHEALABLE},
1361  {"guardian", STATE_GUARDIAN},
1362 };
1363 
1364 void unit::set_state(const std::string& state, bool value)
1365 {
1366  appearance_changed_ = true;
1367  state_t known_boolean_state_id = get_known_boolean_state_id(state);
1368  if(known_boolean_state_id != STATE_UNKNOWN) {
1369  set_state(known_boolean_state_id, value);
1370  return;
1371  }
1372 
1373  // Backwards compatibility for not_living. Don't remove before 1.12
1374  if(state == "not_living") {
1375  set_state("undrainable", value);
1376  set_state("unpoisonable", value);
1377  set_state("unplagueable", value);
1378  }
1379 
1380  if(value) {
1381  states_.insert(state);
1382  } else {
1383  states_.erase(state);
1384  }
1385 }
1386 
1387 bool unit::has_ability_by_id(const std::string& ability) const
1388 {
1389  for(const config::any_child ab : abilities_.all_children_range()) {
1390  if(ab.cfg["id"] == ability) {
1391  return true;
1392  }
1393  }
1394 
1395  return false;
1396 }
1397 
1398 void unit::remove_ability_by_id(const std::string& ability)
1399 {
1402  while (i != abilities_.ordered_end()) {
1403  if(i->cfg["id"] == ability) {
1404  i = abilities_.erase(i);
1405  } else {
1406  ++i;
1407  }
1408  }
1409 }
1410 
1412 {
1413  for(const auto& a_ptr : attacks_) {
1414  if(a_ptr->get_changed()) {
1415  return true;
1416  }
1417 
1418  }
1419  return false;
1420 }
1421 void unit::write(config& cfg, bool write_all) const
1422 {
1423  config back;
1424  auto write_subtag = [&](const std::string& key, const config& child)
1425  {
1426  cfg.clear_children(key);
1427 
1428  if(!child.empty()) {
1429  cfg.add_child(key, child);
1430  } else {
1431  back.add_child(key, child);
1432  }
1433  };
1434 
1435  if(write_all || get_attr_changed(UA_MOVEMENT_TYPE)) {
1436  movement_type_.write(cfg);
1437  }
1438  if(write_all || get_attr_changed(UA_SMALL_PROFILE)) {
1439  cfg["small_profile"] = small_profile_;
1440  }
1441  if(write_all || get_attr_changed(UA_PROFILE)) {
1442  cfg["profile"] = profile_;
1443  }
1444  if(description_ != type().unit_description()) {
1445  cfg["description"] = description_;
1446  }
1447  if(write_all || get_attr_changed(UA_NOTES)) {
1448  for(const t_string& note : special_notes_) {
1449  cfg.add_child("special_note")["note"] = note;
1450  }
1451  }
1452 
1453  if(halo_) {
1454  cfg["halo"] = *halo_;
1455  }
1456 
1457  if(ellipse_) {
1458  cfg["ellipse"] = *ellipse_;
1459  }
1460 
1461  if(usage_) {
1462  cfg["usage"] = *usage_;
1463  }
1464 
1465  write_upkeep(cfg["upkeep"]);
1466 
1467  cfg["hitpoints"] = hit_points_;
1468  if(write_all || get_attr_changed(UA_MAX_HP)) {
1469  cfg["max_hitpoints"] = max_hit_points_;
1470  }
1471  cfg["image_icon"] = type().icon();
1472  cfg["image"] = type().image();
1473  cfg["random_traits"] = random_traits_;
1474  cfg["generate_name"] = generate_name_;
1475  cfg["experience"] = experience_;
1476  if(write_all || get_attr_changed(UA_MAX_XP)) {
1477  cfg["max_experience"] = max_experience_;
1478  }
1479  cfg["recall_cost"] = recall_cost_;
1480 
1481  cfg["side"] = side_;
1482 
1483  cfg["type"] = type_id();
1484 
1485  if(type_id() != type().parent_id()) {
1486  cfg["parent_type"] = type().parent_id();
1487  }
1488 
1489  // Support for unit formulas in [ai] and unit-specific variables in [ai] [vars]
1490  formula_man_->write(cfg);
1491 
1492  cfg["gender"] = gender_string(gender_);
1493  cfg["variation"] = variation_;
1494  cfg["role"] = role_;
1495 
1496  config status_flags;
1497  for(const std::string& state : get_states()) {
1498  status_flags[state] = true;
1499  }
1500 
1501  write_subtag("variables", variables_);
1502  write_subtag("filter_recall", filter_recall_);
1503  write_subtag("status", status_flags);
1504 
1505  cfg.clear_children("events");
1506  cfg.append(events_);
1507 
1508  // Overlays are exported as the modifications that add them, not as an overlays= value,
1509  // however removing the key breaks the Gui Debug Tools.
1510  // \todo does anything depend on the key's value, other than the re-import code in unit::init?
1511  cfg["overlays"] = "";
1512 
1513  cfg["name"] = name_;
1514  cfg["id"] = id_;
1515  cfg["underlying_id"] = underlying_id_.value;
1516 
1517  if(can_recruit()) {
1518  cfg["canrecruit"] = true;
1519  }
1520 
1521  cfg["extra_recruit"] = utils::join(recruit_list_);
1522 
1523  cfg["facing"] = map_location::write_direction(facing_);
1524 
1525  cfg["goto_x"] = goto_.wml_x();
1526  cfg["goto_y"] = goto_.wml_y();
1527 
1528  cfg["moves"] = movement_;
1529  if(write_all || get_attr_changed(UA_MAX_MP)) {
1530  cfg["max_moves"] = max_movement_;
1531  }
1532  cfg["vision"] = vision_;
1533  cfg["jamming"] = jamming_;
1534 
1535  cfg["resting"] = resting_;
1536 
1537  if(write_all || get_attr_changed(UA_ADVANCE_TO)) {
1538  cfg["advances_to"] = utils::join(advances_to_);
1539  }
1540 
1541  cfg["race"] = race_->id();
1542  cfg["language_name"] = type_name_;
1543  cfg["undead_variation"] = undead_variation_;
1544  if(write_all || get_attr_changed(UA_LEVEL)) {
1545  cfg["level"] = level_;
1546  }
1547  if(write_all || get_attr_changed(UA_ALIGNMENT)) {
1548  cfg["alignment"] = unit_alignments::get_string(alignment_);
1549  }
1550  cfg["flag_rgb"] = flag_rgb_;
1551  cfg["unrenamable"] = unrenamable_;
1552 
1553  cfg["attacks_left"] = attacks_left_;
1554  if(write_all || get_attr_changed(UA_MAX_AP)) {
1555  cfg["max_attacks"] = max_attacks_;
1556  }
1557  if(write_all || get_attr_changed(UA_ZOC)) {
1558  cfg["zoc"] = emit_zoc_;
1559  }
1560  cfg["hidden"] = hidden_;
1561 
1562  if(write_all || get_attr_changed(UA_ATTACKS) || get_attacks_changed()) {
1563  cfg.clear_children("attack");
1564  for(attack_ptr i : attacks_) {
1565  i->write(cfg.add_child("attack"));
1566  }
1567  }
1568 
1569  cfg["cost"] = unit_value_;
1570 
1571  write_subtag("modifications", modifications_);
1572  if(write_all || get_attr_changed(UA_ABILITIES)) {
1573  write_subtag("abilities", abilities_);
1574  }
1575  if(write_all || get_attr_changed(UA_ADVANCEMENTS)) {
1576  cfg.clear_children("advancement");
1577  for(const config& advancement : advancements_) {
1578  if(!advancement.empty()) {
1579  cfg.add_child("advancement", advancement);
1580  }
1581  }
1582  }
1583  cfg.append(back);
1584 }
1585 
1587 {
1588  if(dir != map_location::NDIRECTIONS && dir != facing_) {
1589  appearance_changed_ = true;
1590  facing_ = dir;
1591  }
1592  // Else look at yourself (not available so continue to face the same direction)
1593 }
1594 
1595 int unit::upkeep() const
1596 {
1597  // Leaders do not incur upkeep.
1598  if(can_recruit()) {
1599  return 0;
1600  }
1601 
1602  return utils::visit(upkeep_value_visitor{*this}, upkeep_);
1603 }
1604 
1605 bool unit::loyal() const
1606 {
1607  return utils::holds_alternative<upkeep_loyal>(upkeep_);
1608 }
1609 
1611 {
1612  int def = movement_type_.defense_modifier(terrain);
1613 #if 0
1614  // A [defense] ability is too costly and doesn't take into account target locations.
1615  // Left as a comment in case someone ever wonders why it isn't a good idea.
1616  unit_ability_list defense_abilities = get_abilities("defense");
1617  if(!defense_abilities.empty()) {
1618  unit_abilities::effect defense_effect(defense_abilities, def, false);
1619  def = defense_effect.get_composite_value();
1620  }
1621 #endif
1622  return def;
1623 }
1624 
1625 bool unit::resistance_filter_matches(const config& cfg, bool attacker, const std::string& damage_name, int res) const
1626 {
1627  if(!(cfg["active_on"].empty() || (attacker && cfg["active_on"] == "offense") || (!attacker && cfg["active_on"] == "defense"))) {
1628  return false;
1629  }
1630 
1631  const std::string& apply_to = cfg["apply_to"];
1632  if(!apply_to.empty()) {
1633  if(damage_name != apply_to) {
1634  if(apply_to.find(',') != std::string::npos &&
1635  apply_to.find(damage_name) != std::string::npos) {
1636  const std::vector<std::string>& vals = utils::split(apply_to);
1637  if(std::find(vals.begin(),vals.end(),damage_name) == vals.end()) {
1638  return false;
1639  }
1640  } else {
1641  return false;
1642  }
1643  }
1644  }
1645 
1646  if(!unit_abilities::filter_base_matches(cfg, res)) {
1647  return false;
1648  }
1649 
1650  return true;
1651 }
1652 
1653 int unit::resistance_against(const std::string& damage_name,bool attacker,const map_location& loc, const_attack_ptr weapon, const_attack_ptr opp_weapon) const
1654 {
1655  int res = movement_type_.resistance_against(damage_name);
1656 
1657  unit_ability_list resistance_abilities = get_abilities_weapons("resistance",loc, weapon, opp_weapon);
1658  for(unit_ability_list::iterator i = resistance_abilities.begin(); i != resistance_abilities.end();) {
1659  if(!resistance_filter_matches(*i->ability_cfg, attacker, damage_name, 100-res)) {
1660  i = resistance_abilities.erase(i);
1661  } else {
1662  ++i;
1663  }
1664  }
1665 
1666  if(!resistance_abilities.empty()) {
1667  unit_abilities::effect resist_effect(resistance_abilities, 100-res, false);
1668 
1669  res = 100 - std::min<int>(
1670  resist_effect.get_composite_value(),
1671  resistance_abilities.highest("max_value").first
1672  );
1673  }
1674 
1675  return res;
1676 }
1677 
1678 std::map<std::string, std::string> unit::advancement_icons() const
1679 {
1680  std::map<std::string,std::string> temp;
1681  if(!can_advance()) {
1682  return temp;
1683  }
1684 
1685  if(!advances_to_.empty()) {
1686  std::ostringstream tooltip;
1687  const std::string& image = game_config::images::level;
1688 
1689  for(const std::string& s : advances_to()) {
1690  if(!s.empty()) {
1691  tooltip << s << std::endl;
1692  }
1693  }
1694 
1695  temp[image] = tooltip.str();
1696  }
1697 
1698  for(const config& adv : get_modification_advances()) {
1699  const std::string& image = adv["image"];
1700  if(image.empty()) {
1701  continue;
1702  }
1703 
1704  std::ostringstream tooltip;
1705  tooltip << temp[image];
1706 
1707  const std::string& tt = adv["description"];
1708  if(!tt.empty()) {
1709  tooltip << tt << std::endl;
1710  }
1711 
1712  temp[image] = tooltip.str();
1713  }
1714 
1715  return(temp);
1716 }
1717 
1718 std::vector<std::pair<std::string, std::string>> unit::amla_icons() const
1719 {
1720  std::vector<std::pair<std::string, std::string>> temp;
1721  std::pair<std::string, std::string> icon; // <image,tooltip>
1722 
1723  for(const config& adv : get_modification_advances()) {
1724  icon.first = adv["icon"].str();
1725  icon.second = adv["description"].str();
1726 
1727  for(unsigned j = 0, j_count = modification_count("advancement", adv["id"]); j < j_count; ++j) {
1728  temp.push_back(icon);
1729  }
1730  }
1731 
1732  return(temp);
1733 }
1734 
1735 std::vector<config> unit::get_modification_advances() const
1736 {
1737  std::vector<config> res;
1738  for(const config& adv : modification_advancements()) {
1739  if(adv["strict_amla"].to_bool() && !advances_to_.empty()) {
1740  continue;
1741  }
1742  if(const config& filter = adv.child("filter")) {
1743  if(!unit_filter(vconfig(filter)).matches(*this, loc_)) {
1744  continue;
1745  }
1746  }
1747 
1748  if(modification_count("advancement", adv["id"]) >= static_cast<unsigned>(adv["max_times"].to_int(1))) {
1749  continue;
1750  }
1751 
1752  std::vector<std::string> temp_require = utils::split(adv["require_amla"]);
1753  std::vector<std::string> temp_exclude = utils::split(adv["exclude_amla"]);
1754 
1755  if(temp_require.empty() && temp_exclude.empty()) {
1756  res.push_back(adv);
1757  continue;
1758  }
1759 
1760  std::sort(temp_require.begin(), temp_require.end());
1761  std::sort(temp_exclude.begin(), temp_exclude.end());
1762 
1763  std::vector<std::string> uniq_require, uniq_exclude;
1764 
1765  std::unique_copy(temp_require.begin(), temp_require.end(), std::back_inserter(uniq_require));
1766  std::unique_copy(temp_exclude.begin(), temp_exclude.end(), std::back_inserter(uniq_exclude));
1767 
1768  bool exclusion_found = false;
1769  for(const std::string& s : uniq_exclude) {
1770  int max_num = std::count(temp_exclude.begin(), temp_exclude.end(), s);
1771  int mod_num = modification_count("advancement", s);
1772  if(mod_num >= max_num) {
1773  exclusion_found = true;
1774  break;
1775  }
1776  }
1777 
1778  if(exclusion_found) {
1779  continue;
1780  }
1781 
1782  bool requirements_done = true;
1783  for(const std::string& s : uniq_require) {
1784  int required_num = std::count(temp_require.begin(), temp_require.end(), s);
1785  int mod_num = modification_count("advancement", s);
1786  if(required_num > mod_num) {
1787  requirements_done = false;
1788  break;
1789  }
1790  }
1791 
1792  if(requirements_done) {
1793  res.push_back(adv);
1794  }
1795  }
1796 
1797  return res;
1798 }
1799 
1800 void unit::set_advancements(std::vector<config> advancements)
1801 {
1803  advancements_ = advancements;
1804 }
1805 
1806 const std::string& unit::type_id() const
1807 {
1808  return type_->id();
1809 }
1810 
1811 void unit::set_big_profile(const std::string& value)
1812 {
1814  profile_ = value;
1816 }
1817 
1818 std::size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
1819 {
1820  std::size_t res = 0;
1821  for(const config& item : modifications_.child_range(mod_type)) {
1822  if(item["id"] == id) {
1823  ++res;
1824  }
1825  }
1826 
1827  // For backwards compatibility, if asked for "advancement", also count "advance"
1828  if(mod_type == "advancement") {
1829  res += modification_count("advance", id);
1830  }
1831 
1832  return res;
1833 }
1834 
1835 const std::set<std::string> unit::builtin_effects {
1836  "alignment", "attack", "defense", "ellipse", "experience", "fearless",
1837  "halo", "healthy", "hitpoints", "image_mod", "jamming", "jamming_costs",
1838  "loyal", "max_attacks", "max_experience", "movement", "movement_costs",
1839  "new_ability", "new_advancement", "new_animation", "new_attack", "overlay", "profile",
1840  "recall_cost", "remove_ability", "remove_advancement", "remove_attacks", "resistance",
1841  "status", "type", "variation", "vision", "vision_costs", "zoc"
1842 };
1843 
1844 std::string unit::describe_builtin_effect(std::string apply_to, const config& effect)
1845 {
1846  if(apply_to == "attack") {
1847  std::vector<t_string> attack_names;
1848 
1849  std::string desc;
1850  for(attack_ptr a : attacks_) {
1851  bool affected = a->describe_modification(effect, &desc);
1852  if(affected && !desc.empty()) {
1853  attack_names.emplace_back(a->name(), "wesnoth-units");
1854  }
1855  }
1856  if(!attack_names.empty()) {
1857  utils::string_map symbols;
1858  symbols["attack_list"] = utils::format_conjunct_list("", attack_names);
1859  symbols["effect_description"] = desc;
1860  return VGETTEXT("$attack_list|: $effect_description", symbols);
1861  }
1862  } else if(apply_to == "hitpoints") {
1863  const std::string& increase_total = effect["increase_total"];
1864  if(!increase_total.empty()) {
1865  return VGETTEXT(
1866  "<span color=\"$color\">$number_or_percent</span> HP",
1867  {{"number_or_percent", utils::print_modifier(increase_total)}, {"color", increase_total[0] == '-' ? "#f00" : "#0f0"}});
1868  }
1869  } else {
1870  const std::string& increase = effect["increase"];
1871  if(increase.empty()) {
1872  return "";
1873  }
1874  if(apply_to == "movement") {
1875  return VNGETTEXT(
1876  "<span color=\"$color\">$number_or_percent</span> move",
1877  "<span color=\"$color\">$number_or_percent</span> moves",
1878  std::stoi(increase),
1879  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1880  } else if(apply_to == "vision") {
1881  return VGETTEXT(
1882  "<span color=\"$color\">$number_or_percent</span> vision",
1883  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1884  } else if(apply_to == "jamming") {
1885  return VGETTEXT(
1886  "<span color=\"$color\">$number_or_percent</span> jamming",
1887  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1888  } else if(apply_to == "max_experience") {
1889  // Unlike others, decreasing experience is a *GOOD* thing
1890  return VGETTEXT(
1891  "<span color=\"$color\">$number_or_percent</span> XP to advance",
1892  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
1893  } else if(apply_to == "max_attacks") {
1894  return VNGETTEXT(
1895  "<span color=\"$color\">$number_or_percent</span> attack per turn",
1896  "<span color=\"$color\">$number_or_percent</span> attacks per turn",
1897  std::stoi(increase),
1898  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1899  } else if(apply_to == "recall_cost") {
1900  // Unlike others, decreasing recall cost is a *GOOD* thing
1901  return VGETTEXT(
1902  "<span color=\"$color\">$number_or_percent</span> cost to recall",
1903  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
1904  }
1905  }
1906  return "";
1907 }
1908 
1909 void unit::apply_builtin_effect(std::string apply_to, const config& effect)
1910 {
1911  appearance_changed_ = true;
1912  if(apply_to == "fearless") {
1914  is_fearless_ = effect["set"].to_bool(true);
1915  } else if(apply_to == "healthy") {
1917  is_healthy_ = effect["set"].to_bool(true);
1918  } else if(apply_to == "profile") {
1919  if(const config::attribute_value* v = effect.get("portrait")) {
1920  set_big_profile((*v).str());
1921  }
1922 
1923  if(const config::attribute_value* v = effect.get("small_portrait")) {
1924  set_small_profile((*v).str());
1925  }
1926 
1927  if(const config::attribute_value* v = effect.get("description")) {
1928  description_ = *v;
1929  }
1930 
1931  if(config::const_child_itors cfg_range = effect.child_range("special_note")) {
1932  for(const config& c : cfg_range) {
1933  if(!c["remove"].to_bool()) {
1934  special_notes_.emplace_back(c["note"].t_str());
1935  } else {
1936  auto iter = std::find(special_notes_.begin(), special_notes_.end(), c["note"].t_str());
1937  if(iter != special_notes_.end()) {
1938  special_notes_.erase(iter);
1939  }
1940  }
1941  }
1942  }
1943  } else if(apply_to == "new_attack") {
1945  attacks_.emplace_back(new attack_type(effect));
1946  } else if(apply_to == "remove_attacks") {
1948  auto iter = std::remove_if(attacks_.begin(), attacks_.end(), [&effect](attack_ptr a) {
1949  return a->matches_filter(effect);
1950  });
1951 
1952  attacks_.erase(iter, attacks_.end());
1953  } else if(apply_to == "attack") {
1955  for(attack_ptr a : attacks_) {
1956  a->apply_modification(effect);
1957  }
1958  } else if(apply_to == "hitpoints") {
1959  LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_;
1960  const std::string& increase_hp = effect["increase"];
1961  const std::string& increase_total = effect["increase_total"];
1962  const std::string& set_hp = effect["set"];
1963  const std::string& set_total = effect["set_total"];
1964 
1965  // If the hitpoints are allowed to end up greater than max hitpoints
1966  const bool violate_max = effect["violate_maximum"].to_bool();
1967 
1968  if(!set_hp.empty()) {
1969  if(set_hp.back() == '%') {
1970  hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
1971  } else {
1972  hit_points_ = lexical_cast_default<int>(set_hp);
1973  }
1974  }
1975 
1976  if(!set_total.empty()) {
1977  if(set_total.back() == '%') {
1978  set_max_hitpoints(lexical_cast_default<int>(set_total)*max_hit_points_/100);
1979  } else {
1980  set_max_hitpoints(lexical_cast_default<int>(set_total));
1981  }
1982  }
1983 
1984  if(!increase_total.empty()) {
1985  // A percentage on the end means increase by that many percent
1986  set_max_hitpoints(utils::apply_modifier(max_hit_points_, increase_total));
1987  }
1988 
1989  if(max_hit_points_ < 1)
1990  set_max_hitpoints(1);
1991 
1992  if(effect["heal_full"].to_bool()) {
1993  heal_fully();
1994  }
1995 
1996  if(!increase_hp.empty()) {
1998  }
1999 
2000  LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_;
2001  if(hit_points_ > max_hit_points_ && !violate_max) {
2002  LOG_UT << "resetting hp to max";
2004  }
2005 
2006  if(hit_points_ < 1) {
2007  hit_points_ = 1;
2008  }
2009  } else if(apply_to == "movement") {
2010  const bool apply_to_vision = effect["apply_to_vision"].to_bool(true);
2011 
2012  // Unlink vision from movement, regardless of whether we'll increment both or not
2013  if(vision_ < 0) {
2015  }
2016 
2017  const int old_max = max_movement_;
2018 
2019  const std::string& increase = effect["increase"];
2020  if(!increase.empty()) {
2022  }
2023 
2024  set_total_movement(effect["set"].to_int(max_movement_));
2025 
2026  if(movement_ > max_movement_) {
2028  }
2029 
2030  if(apply_to_vision) {
2031  vision_ = std::max(0, vision_ + max_movement_ - old_max);
2032  }
2033  } else if(apply_to == "vision") {
2034  // Unlink vision from movement, regardless of which one we're about to change.
2035  if(vision_ < 0) {
2037  }
2038 
2039  const std::string& increase = effect["increase"];
2040  if(!increase.empty()) {
2041  vision_ = utils::apply_modifier(vision_, increase, 1);
2042  }
2043 
2044  vision_ = effect["set"].to_int(vision_);
2045  } else if(apply_to == "jamming") {
2046  const std::string& increase = effect["increase"];
2047 
2048  if(!increase.empty()) {
2049  jamming_ = utils::apply_modifier(jamming_, increase, 1);
2050  }
2051 
2052  jamming_ = effect["set"].to_int(jamming_);
2053  } else if(apply_to == "experience") {
2054  const std::string& increase = effect["increase"];
2055  const std::string& set = effect["set"];
2056 
2057  if(!set.empty()) {
2058  if(set.back() == '%') {
2059  experience_ = lexical_cast_default<int>(set)*max_experience_/100;
2060  } else {
2061  experience_ = lexical_cast_default<int>(set);
2062  }
2063  }
2064 
2065  if(increase.empty() == false) {
2067  }
2068  } else if(apply_to == "max_experience") {
2069  const std::string& increase = effect["increase"];
2070  const std::string& set = effect["set"];
2071 
2072  if(set.empty() == false) {
2073  if(set.back() == '%') {
2074  set_max_experience(lexical_cast_default<int>(set)*max_experience_/100);
2075  } else {
2076  set_max_experience(lexical_cast_default<int>(set));
2077  }
2078  }
2079 
2080  if(increase.empty() == false) {
2082  }
2083  } else if(apply_to == upkeep_loyal::type()) {
2084  upkeep_ = upkeep_loyal{};
2085  } else if(apply_to == "status") {
2086  const std::string& add = effect["add"];
2087  const std::string& remove = effect["remove"];
2088 
2089  for(const std::string& to_add : utils::split(add))
2090  {
2091  set_state(to_add, true);
2092  }
2093 
2094  for(const std::string& to_remove : utils::split(remove))
2095  {
2096  set_state(to_remove, false);
2097  }
2098  } else if(std::find(movetype::effects.cbegin(), movetype::effects.cend(), apply_to) != movetype::effects.cend()) {
2099  // "movement_costs", "vision_costs", "jamming_costs", "defense", "resistance"
2100  if(const config& ap = effect.child(apply_to)) {
2102  movement_type_.merge(ap, apply_to, effect["replace"].to_bool());
2103  }
2104  } else if(apply_to == "zoc") {
2105  if(const config::attribute_value* v = effect.get("value")) {
2107  emit_zoc_ = v->to_bool();
2108  }
2109  } else if(apply_to == "new_ability") {
2110  if(const config& ab_effect = effect.child("abilities")) {
2112  config to_append;
2113  for(const config::any_child ab : ab_effect.all_children_range()) {
2114  if(!has_ability_by_id(ab.cfg["id"])) {
2115  to_append.add_child(ab.key, ab.cfg);
2116  }
2117  }
2118  abilities_.append(to_append);
2119  }
2120  } else if(apply_to == "remove_ability") {
2121  if(const config& ab_effect = effect.child("abilities")) {
2122  for(const config::any_child ab : ab_effect.all_children_range()) {
2123  remove_ability_by_id(ab.cfg["id"]);
2124  }
2125  }
2126  } else if(apply_to == "image_mod") {
2127  LOG_UT << "applying image_mod";
2128  std::string mod = effect["replace"];
2129  if(!mod.empty()){
2130  image_mods_ = mod;
2131  }
2132  LOG_UT << "applying image_mod";
2133  mod = effect["add"].str();
2134  if(!mod.empty()){
2135  if(!image_mods_.empty()) {
2136  image_mods_ += '~';
2137  }
2138 
2139  image_mods_ += mod;
2140  }
2141 
2143  LOG_UT << "applying image_mod";
2144  } else if(apply_to == "new_animation") {
2145  anim_comp_->apply_new_animation_effect(effect);
2146  } else if(apply_to == "ellipse") {
2147  set_image_ellipse(effect["ellipse"]);
2148  } else if(apply_to == "halo") {
2149  set_image_halo(effect["halo"]);
2150  } else if(apply_to == "overlay") {
2151  const std::string& add = effect["add"];
2152  const std::string& replace = effect["replace"];
2153  const std::string& remove = effect["remove"];
2154 
2155  if(!add.empty()) {
2156  for(const auto& to_add : utils::parenthetical_split(add, ',')) {
2157  overlays_.push_back(to_add);
2158  }
2159  }
2160  if(!remove.empty()) {
2161  for(const auto& to_remove : utils::parenthetical_split(remove, ',')) {
2162  overlays_.erase(std::remove(overlays_.begin(), overlays_.end(), to_remove), overlays_.end());
2163  }
2164  }
2165  if(add.empty() && remove.empty() && !replace.empty()) {
2166  overlays_ = utils::parenthetical_split(replace, ',');
2167  }
2168  } else if(apply_to == "new_advancement") {
2169  const std::string& types = effect["types"];
2170  const bool replace = effect["replace"].to_bool(false);
2172 
2173  if(!types.empty()) {
2174  if(replace) {
2176  } else {
2177  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2178  std::copy(temp_advances.begin(), temp_advances.end(), std::back_inserter(advances_to_));
2179  }
2180  }
2181 
2182  if(effect.has_child("advancement")) {
2183  if(replace) {
2184  advancements_.clear();
2185  }
2186 
2187  for(const config& adv : effect.child_range("advancement")) {
2188  advancements_.push_back(adv);
2189  }
2190  }
2191  } else if(apply_to == "remove_advancement") {
2192  const std::string& types = effect["types"];
2193  const std::string& amlas = effect["amlas"];
2195 
2196  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2198  for(const std::string& unit : temp_advances) {
2199  iter = std::find(advances_to_.begin(), advances_to_.end(), unit);
2200  if(iter != advances_to_.end()) {
2201  advances_to_.erase(iter);
2202  }
2203  }
2204 
2205  temp_advances = utils::parenthetical_split(amlas, ',');
2206 
2207  for(int i = advancements_.size() - 1; i >= 0; i--) {
2208  if(std::find(temp_advances.begin(), temp_advances.end(), advancements_[i]["id"]) != temp_advances.end()) {
2209  advancements_.erase(advancements_.begin() + i);
2210  }
2211  }
2212  } else if(apply_to == "alignment") {
2213  auto new_align = unit_alignments::get_enum(effect["set"].str());
2214  if(new_align) {
2215  set_alignment(*new_align);
2216  }
2217  } else if(apply_to == "max_attacks") {
2218  const std::string& increase = effect["increase"];
2219 
2220  if(!increase.empty()) {
2222  }
2223  } else if(apply_to == "recall_cost") {
2224  const std::string& increase = effect["increase"];
2225  const std::string& set = effect["set"];
2226  const int recall_cost = recall_cost_ < 0 ? resources::gameboard->teams().at(side_).recall_cost() : recall_cost_;
2227 
2228  if(!set.empty()) {
2229  if(set.back() == '%') {
2230  recall_cost_ = lexical_cast_default<int>(set)*recall_cost/100;
2231  } else {
2232  recall_cost_ = lexical_cast_default<int>(set);
2233  }
2234  }
2235 
2236  if(!increase.empty()) {
2237  recall_cost_ = utils::apply_modifier(recall_cost, increase, 1);
2238  }
2239  } else if(effect["apply_to"] == "variation") {
2240  const unit_type* base_type = unit_types.find(type().parent_id());
2241  assert(base_type != nullptr);
2242  const std::string& variation_id = effect["name"];
2243  if(variation_id.empty() || base_type->get_gender_unit_type(gender_).has_variation(variation_id)) {
2244  variation_ = variation_id;
2245  advance_to(*base_type);
2246  if(effect["heal_full"].to_bool(false)) {
2247  heal_fully();
2248  }
2249  } else {
2250  WRN_UT << "unknown variation '" << variation_id << "' (name=) in [effect]apply_to=variation, ignoring";
2251  }
2252  } else if(effect["apply_to"] == "type") {
2253  std::string prev_type = effect["prev_type"];
2254  if(prev_type.empty()) {
2255  prev_type = type().parent_id();
2256  }
2257  const std::string& new_type_id = effect["name"];
2258  const unit_type* new_type = unit_types.find(new_type_id);
2259  if(new_type) {
2260  advance_to(*new_type);
2261  preferences::encountered_units().insert(new_type_id);
2262  if(effect["heal_full"].to_bool(false)) {
2263  heal_fully();
2264  }
2265  } else {
2266  WRN_UT << "unknown type '" << new_type_id << "' (name=) in [effect]apply_to=type, ignoring";
2267  }
2268  }
2269 }
2270 
2271 void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
2272 {
2273  bool generate_description = mod["generate_description"].to_bool(true);
2274 
2275  if(no_add == false) {
2276  modifications_.add_child(mod_type, mod);
2277  }
2278 
2279  bool set_poisoned = false; // Tracks if the poisoned state was set after the type or variation was changed.
2280  config type_effect, variation_effect;
2281  std::vector<t_string> effects_description;
2282  for(const config& effect : mod.child_range("effect")) {
2283  // Apply SUF.
2284  if(const config& afilter = effect.child("filter")) {
2285  assert(resources::filter_con);
2286  if(!unit_filter(vconfig(afilter)).matches(*this, loc_)) {
2287  continue;
2288  }
2289  }
2290  const std::string& apply_to = effect["apply_to"];
2291  int times = effect["times"].to_int(1);
2292  t_string description;
2293 
2294  if(effect["times"] == "per level") {
2295  times = level_;
2296  }
2297 
2298  if(times) {
2299  while (times > 0) {
2300  times --;
2301 
2302  bool was_poisoned = get_state(STATE_POISONED);
2303  // Apply unit type/variation changes last to avoid double applying effects on advance.
2304  if(apply_to == "type") {
2305  set_poisoned = false;
2306  type_effect = effect;
2307  continue;
2308  }
2309  if(apply_to == "variation") {
2310  set_poisoned = false;
2311  variation_effect = effect;
2312  continue;
2313  }
2314 
2315  std::string description_component;
2316  if(resources::lua_kernel) {
2317  description_component = resources::lua_kernel->apply_effect(apply_to, *this, effect, true);
2318  } else if(builtin_effects.count(apply_to)) {
2319  // Normally, the built-in effects are dispatched through Lua so that a user
2320  // can override them if desired. However, since they're built-in, we can still
2321  // apply them if the lua kernel is unavailable.
2322  apply_builtin_effect(apply_to, effect);
2323  description_component = describe_builtin_effect(apply_to, effect);
2324  }
2325  if(!times) {
2326  description += description_component;
2327  }
2328  if(!was_poisoned && get_state(STATE_POISONED)) {
2329  set_poisoned = true;
2330  } else if(was_poisoned && !get_state(STATE_POISONED)) {
2331  set_poisoned = false;
2332  }
2333  } // end while
2334  } else { // for times = per level & level = 0 we still need to rebuild the descriptions
2335  if(resources::lua_kernel) {
2336  description += resources::lua_kernel->apply_effect(apply_to, *this, effect, false);
2337  } else if(builtin_effects.count(apply_to)) {
2338  description += describe_builtin_effect(apply_to, effect);
2339  }
2340  }
2341 
2342  if(effect["times"] == "per level" && !times) {
2343  description = VGETTEXT("$effect_description per level", {{"effect_description", description}});
2344  }
2345 
2346  if(!description.empty()) {
2347  effects_description.push_back(description);
2348  }
2349  }
2350  // Apply variations -- only apply if we are adding this for the first time.
2351  if((!type_effect.empty() || !variation_effect.empty()) && no_add == false) {
2352  if(!type_effect.empty()) {
2353  std::string description;
2354  if(resources::lua_kernel) {
2355  description = resources::lua_kernel->apply_effect(type_effect["apply_to"], *this, type_effect, true);
2356  } else if(builtin_effects.count(type_effect["apply_to"])) {
2357  apply_builtin_effect(type_effect["apply_to"], type_effect);
2358  description = describe_builtin_effect(type_effect["apply_to"], type_effect);
2359  }
2360  effects_description.push_back(description);
2361  }
2362  if(!variation_effect.empty()) {
2363  std::string description;
2364  if(resources::lua_kernel) {
2365  description = resources::lua_kernel->apply_effect(variation_effect["apply_to"], *this, variation_effect, true);
2366  } else if(builtin_effects.count(variation_effect["apply_to"])) {
2367  apply_builtin_effect(variation_effect["apply_to"], variation_effect);
2368  description = describe_builtin_effect(variation_effect["apply_to"], variation_effect);
2369  }
2370  effects_description.push_back(description);
2371  }
2372  if(set_poisoned)
2373  // An effect explicitly set the poisoned state, and this
2374  // should override the unit being immune to poison.
2375  set_state(STATE_POISONED, true);
2376  }
2377 
2378  t_string description;
2379 
2380  const t_string& mod_description = mod["description"];
2381  if(!mod_description.empty()) {
2382  description = mod_description;
2383  }
2384 
2385  // Punctuation should be translatable: not all languages use Latin punctuation.
2386  // (However, there maybe is a better way to do it)
2387  if(generate_description && !effects_description.empty()) {
2388  if(!mod_description.empty()) {
2389  description += "\n";
2390  }
2391 
2392  for(const auto& desc_line : effects_description) {
2393  description += desc_line + "\n";
2394  }
2395  }
2396 
2397  // store trait info
2398  if(mod_type == "trait") {
2399  add_trait_description(mod, description);
2400  }
2401 
2402  //NOTE: if not a trait, description is currently not used
2403 }
2404 
2405 void unit::add_trait_description(const config& trait, const t_string& description)
2406 {
2407  const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
2408  const auto& gender_specific_name = trait[gender_string];
2409 
2410  const t_string name = gender_specific_name.empty() ? trait["name"] : gender_specific_name;
2411 
2412  if(!name.empty()) {
2413  trait_names_.push_back(name);
2414  trait_descriptions_.push_back(description);
2415  }
2416 }
2417 
2418 std::string unit::absolute_image() const
2419 {
2420  return type().icon().empty() ? type().image() : type().icon();
2421 }
2422 
2423 std::string unit::default_anim_image() const
2424 {
2425  return type().image().empty() ? type().icon() : type().image();
2426 }
2427 
2429 {
2430  log_scope("apply mods");
2431 
2432  variables_.clear_children("mods");
2433  if(modifications_.has_child("advance")) {
2434  deprecated_message("[advance]", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [advancement] instead.");
2435  }
2437  add_modification(mod.key, mod.cfg, true);
2438  }
2439 }
2440 
2441 bool unit::invisible(const map_location& loc, bool see_all) const
2442 {
2443  if(loc != get_location()) {
2444  DBG_UT << "unit::invisible called: id = " << id() << " loc = " << loc << " get_loc = " << get_location();
2445  }
2446 
2447  // This is a quick condition to check, and it does not depend on the
2448  // location (so might as well bypass the location-based cache).
2449  if(get_state(STATE_UNCOVERED)) {
2450  return false;
2451  }
2452 
2453  // Fetch from cache
2454  /**
2455  * @todo FIXME: We use the cache only when using the default see_all=true
2456  * Maybe add a second cache if the see_all=false become more frequent.
2457  */
2458  if(see_all) {
2459  const auto itor = invisibility_cache_.find(loc);
2460  if(itor != invisibility_cache_.end()) {
2461  return itor->second;
2462  }
2463  }
2464 
2465  // Test hidden status
2466  static const std::string hides("hides");
2467  bool is_inv = get_ability_bool(hides, loc);
2468  if(is_inv){
2469  is_inv = (resources::gameboard ? !resources::gameboard->would_be_discovered(loc, side_,see_all) : true);
2470  }
2471 
2472  if(see_all) {
2473  // Add to caches
2474  if(invisibility_cache_.empty()) {
2475  units_with_cache.push_back(this);
2476  }
2477 
2478  invisibility_cache_[loc] = is_inv;
2479  }
2480 
2481  return is_inv;
2482 }
2483 
2484 bool unit::is_visible_to_team(const team& team, bool const see_all) const
2485 {
2486  const map_location& loc = get_location();
2487  return is_visible_to_team(loc, team, see_all);
2488 }
2489 
2490 bool unit::is_visible_to_team(const map_location& loc, const team& team, bool const see_all) const
2491 {
2492  if(!display::get_singleton()->get_map().on_board(loc)) {
2493  return false;
2494  }
2495 
2496  if(see_all) {
2497  return true;
2498  }
2499 
2500  if(team.is_enemy(side()) && invisible(loc)) {
2501  return false;
2502  }
2503 
2504  // allied planned moves are also visible under fog. (we assume that fake units on the map are always whiteboard markers)
2505  if(!team.is_enemy(side()) && underlying_id_.is_fake()) {
2506  return true;
2507  }
2508 
2509  // when the whiteboard planned unit map is applied, it uses moved the _real_ unit so
2510  // underlying_id_.is_fake() will be false and the check above will not apply.
2511  // TODO: improve this check so that is also works for allied planned units but without
2512  // breaking sp campaigns with allies under fog. We probably need an explicit flag
2513  // is_planned_ in unit that is set by the whiteboard.
2514  if(team.side() == side()) {
2515  return true;
2516  }
2517 
2518  if(team.fogged(loc)) {
2519  return false;
2520  }
2521 
2522  return true;
2523 }
2524 
2526 {
2527  if(underlying_id_.value == 0) {
2529  underlying_id_ = id_manager.next_id();
2530  } else {
2531  underlying_id_ = id_manager.next_fake_id();
2532  }
2533  }
2534 
2535  if(id_.empty() /*&& !underlying_id_.is_fake()*/) {
2536  std::stringstream ss;
2537  ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_.value;
2538  id_ = ss.str();
2539  }
2540 }
2541 
2542 unit& unit::mark_clone(bool is_temporary)
2543 {
2545  if(is_temporary) {
2546  underlying_id_ = ids.next_fake_id();
2547  } else {
2549  underlying_id_ = ids.next_id();
2550  }
2551  else {
2552  underlying_id_ = ids.next_fake_id();
2553  }
2554  std::string::size_type pos = id_.find_last_of('-');
2555  if(pos != std::string::npos && pos+1 < id_.size()
2556  && id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
2557  // this appears to be a duplicate of a generic unit, so give it a new id
2558  WRN_UT << "assigning new id to clone of generic unit " << id_;
2559  id_.clear();
2560  set_underlying_id(ids);
2561  }
2562  }
2563  return *this;
2564 }
2565 
2566 
2568  : u_(const_cast<unit&>(u))
2569  , moves_(u.movement_left(true))
2570 {
2571  if(operate) {
2573  }
2574 }
2575 
2577 {
2578  assert(resources::gameboard);
2579  try {
2580  if(!resources::gameboard->units().has_unit(&u_)) {
2581  /*
2582  * It might be valid that the unit is not in the unit map.
2583  * It might also mean a no longer valid unit will be assigned to.
2584  */
2585  DBG_UT << "The unit to be removed is not in the unit map.";
2586  }
2587 
2589  } catch(...) {
2590  DBG_UT << "Caught exception when destroying unit_movement_resetter: " << utils::get_unknown_exception_type();
2591  }
2592 }
2593 
2594 std::string unit::TC_image_mods() const
2595 {
2596  return formatter() << "~RC(" << flag_rgb() << ">" << team::get_side_color_id(side()) << ")";
2597 }
2598 
2599 std::string unit::image_mods() const
2600 {
2601  if(!image_mods_.empty()) {
2602  return formatter() << "~" << image_mods_ << TC_image_mods();
2603  }
2604 
2605  return TC_image_mods();
2606 }
2607 
2608 // Called by the Lua API after resetting an attack pointer.
2610 {
2611  set_attr_changed(UA_ATTACKS);
2612  auto iter = std::find(attacks_.begin(), attacks_.end(), atk);
2613  if(iter == attacks_.end()) {
2614  return false;
2615  }
2616  attacks_.erase(iter);
2617  return true;
2618 }
2619 
2621 {
2622  if(attacks_left_ == max_attacks_) {
2623  //TODO: add state_not_attacked
2624  }
2625 
2626  set_attacks(0);
2627 }
2628 
2630 {
2631  if(movement_left() == total_movement()) {
2632  set_state(STATE_NOT_MOVED,true);
2633  }
2634 
2635  set_movement(0, true);
2636 }
2637 
2638 void unit::set_hidden(bool state) const
2639 {
2640 // appearance_changed_ = true;
2641  hidden_ = state;
2642  if(!state) {
2643  return;
2644  }
2645 
2646  // TODO: this should really hide the halo, not destroy it
2647  // We need to get rid of haloes immediately to avoid display glitches
2648  anim_comp_->clear_haloes();
2649 }
2650 
2651 void unit::set_image_halo(const std::string& halo)
2652 {
2653  appearance_changed_ = true;
2654  anim_comp_->clear_haloes();
2655  halo_ = halo;
2656 }
2657 
2659 {
2660  if(upkeep.empty()) {
2661  return;
2662  }
2663 
2664  try {
2665  upkeep_ = upkeep.apply_visitor(upkeep_parser_visitor{});
2666  } catch(std::invalid_argument& e) {
2667  WRN_UT << "Found invalid upkeep=\"" << e.what() << "\" in a unit";
2668  upkeep_ = upkeep_full{};
2669  }
2670 }
2671 
2673 {
2674  upkeep = utils::visit(upkeep_type_visitor{}, upkeep_);
2675 }
2676 
2678 {
2679  changed_attributes_.reset();
2680  for(const auto& a_ptr : attacks_) {
2681  a_ptr->set_changed(false);
2682  }
2683 }
2684 
2685 std::vector<t_string> unit::unit_special_notes() const {
2686  return combine_special_notes(special_notes_, abilities(), attacks(), movement_type());
2687 }
2688 
2689 // Filters unimportant stats from the unit config and returns a checksum of
2690 // the remaining config.
2691 std::string get_checksum(const unit& u)
2692 {
2693  config unit_config;
2694  config wcfg;
2695  u.write(unit_config);
2696 
2697  static const std::set<std::string_view> main_keys {
2698  "advances_to",
2699  "alignment",
2700  "cost",
2701  "experience",
2702  "gender",
2703  "hitpoints",
2704  "ignore_race_traits",
2705  "ignore_global_traits",
2706  "level",
2707  "recall_cost",
2708  "max_attacks",
2709  "max_experience",
2710  "max_hitpoints",
2711  "max_moves",
2712  "movement",
2713  "movement_type",
2714  "race",
2715  "random_traits",
2716  "resting",
2717  "undead_variation",
2718  "upkeep",
2719  "zoc"
2720  };
2721 
2722  for(const std::string_view& main_key : main_keys) {
2723  wcfg[main_key] = unit_config[main_key];
2724  }
2725 
2726  static const std::set<std::string_view> attack_keys {
2727  "name",
2728  "type",
2729  "range",
2730  "damage",
2731  "number"
2732  };
2733 
2734  for(const config& att : unit_config.child_range("attack")) {
2735  config& child = wcfg.add_child("attack");
2736 
2737  for(const std::string_view& attack_key : attack_keys) {
2738  child[attack_key] = att[attack_key];
2739  }
2740 
2741  for(const config& spec : att.child_range("specials")) {
2742  config& child_spec = child.add_child("specials", spec);
2743 
2744  child_spec.recursive_clear_value("description");
2745  child_spec.recursive_clear_value("description_inactive");
2746  child_spec.recursive_clear_value("name");
2747  child_spec.recursive_clear_value("name_inactive");
2748  }
2749  }
2750 
2751  for(const config& abi : unit_config.child_range("abilities")) {
2752  config& child = wcfg.add_child("abilities", abi);
2753 
2754  child.recursive_clear_value("description");
2755  child.recursive_clear_value("description_inactive");
2756  child.recursive_clear_value("name");
2757  child.recursive_clear_value("name_inactive");
2758  }
2759 
2760  for(const config& trait : unit_config.child_range("trait")) {
2761  config& child = wcfg.add_child("trait", trait);
2762 
2763  child.recursive_clear_value("description");
2764  child.recursive_clear_value("male_name");
2765  child.recursive_clear_value("female_name");
2766  child.recursive_clear_value("name");
2767  }
2768 
2769  static const std::set<std::string_view> child_keys {
2770  "advance_from",
2771  "defense",
2772  "movement_costs",
2773  "vision_costs",
2774  "jamming_costs",
2775  "resistance"
2776  };
2777 
2778  for(const std::string_view& child_key : child_keys) {
2779  for(const config& c : unit_config.child_range(child_key)) {
2780  wcfg.add_child(child_key, c);
2781  }
2782  }
2783 
2784  DBG_UT << wcfg;
2785 
2786  return wcfg.hash();
2787 }
2788 
2789 void swap(unit& lhs, unit& rhs)
2790 {
2791  lhs.swap(rhs);
2792 }
void set_wml_y(int v)
Definition: location.hpp:157
unit_id next_fake_id()
Definition: id.cpp:35
int defense_modifier(const t_translation::terrain_code &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:292
bool empty() const
Tests for an attribute that either was never set or was set to "".
void remove()
Removes a tip.
Definition: tooltip.cpp:111
const std::string & parent_id() const
The id of the original type from which this (variation) descended.
Definition: types.hpp:148
void set_movement(int moves, bool unit_action=false)
Set this unit&#39;s remaining movement to moves.
Definition: unit.cpp:1181
int max_attacks_
Definition: unit.hpp:1893
bool appearance_changed_
Definition: unit.hpp:1958
config modifications_
Definition: unit.hpp:1937
std::string apply_effect(const std::string &name, unit &u, const config &cfg, bool need_apply)
static DIRECTION parse_direction(const std::string &str)
Definition: location.cpp:66
std::vector< t_string > trait_descriptions_
Definition: unit.hpp:1920
bool empty() const
Definition: unit.hpp:92
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:97
void set_big_profile(const std::string &value)
Definition: unit.cpp:1811
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:978
static state_t get_known_boolean_state_id(const std::string &state)
Convert a string status effect ID to a built-in status effect ID.
Definition: unit.cpp:1344
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:1246
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:86
int jamming_
Definition: unit.hpp:1884
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:288
std::map< std::string, t_string > string_map
void apply_modifications()
Re-apply all saved modifications.
Definition: unit.cpp:2428
void clear_children(T... keys)
Definition: config.hpp:583
void recursive_clear_value(config_key_type key)
Definition: config.cpp:690
The unit cannot be healed.
Definition: unit.hpp:856
This class represents a single unit of a specific type.
Definition: unit.hpp:120
const std::string & type_id() const
The id of this unit&#39;s type.
Definition: unit.cpp:1806
std::string big_profile() const
An optional profile image displays when this unit is &#39;speaking&#39; via [message].
Definition: unit.cpp:1017
int level_
Definition: unit.hpp:1863
Interfaces for manipulating version numbers of engine, add-ons, etc.
bool generate_name() const
Definition: types.hpp:185
double hp_bar_scaling_
Definition: unit.hpp:1935
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:269
std::string small_profile_
Definition: unit.hpp:1955
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
void advance_to(const unit_type &t, bool use_traits=false)
Advances this unit to another type.
Definition: unit.cpp:881
void new_turn()
Refresh unit for the beginning of a turn.
Definition: unit.cpp:1244
The unit is petrified - it cannot move or be attacked.
Definition: unit.hpp:853
void set_usage(const std::string &usage)
Sets this unit&#39;s usage.
Definition: unit.hpp:682
static manager & get_singleton()
Definition: manager.hpp:145
bool hidden_
Definition: unit.hpp:1934
const std::vector< config > & modification_advancements() const
The raw, unparsed data for modification advancements.
Definition: unit.hpp:313
unit_id next_id()
returns id for unit that is created
Definition: id.cpp:28
std::vector< t_string > combine_special_notes(const std::vector< t_string > direct, const config &abilities, const_attack_itors attacks, const movetype &mt)
Common logic for unit_type::special_notes() and unit::special_notes().
Definition: types.cpp:507
std::string profile_
Definition: unit.hpp:1954
bool resting_
Definition: unit.hpp:1890
std::string id_
Definition: unit.hpp:1851
iterator erase(const iterator &erase_it)
Definition: unit.hpp:98
const std::string & id() const
Definition: race.hpp:35
config::const_child_itors events() const
Definition: types.hpp:242
Variant for storing WML attributes.
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
Definition: unit.cpp:1316
unit_race::GENDER gender_
Definition: unit.hpp:1877
const std::string & big_profile() const
Definition: types.hpp:182
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:455
void check_types(const std::vector< std::string > &types) const
Definition: types.cpp:1267
const std::vector< std::string > & advances_to() const
A vector of unit_type ids that this unit_type can advance to.
Definition: types.hpp:118
unit_alignments::type alignment_
Definition: unit.hpp:1868
static game_config_view wrap(const config &cfg)
static id_manager & global_instance()
Definition: id.hpp:61
New lexcical_cast header.
bool has_attribute(config_key_type key) const
Definition: config.cpp:211
#define a
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:489
std::vector< t_string > trait_names_
Definition: unit.hpp:1919
bool generate_name_
Definition: unit.hpp:1950
map_location loc_
Definition: unit.hpp:1838
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:394
const std::string & flag_rgb() const
Definition: types.cpp:720
unsigned child_count(config_key_type key) const
Definition: config.cpp:372
std::optional< std::string > ellipse_
Definition: unit.hpp:1947
child_itors child_range(config_key_type key)
Definition: config.cpp:344
config & variables()
Gets any user-defined variables this unit &#39;owns&#39;.
Definition: unit.hpp:693
void set_image_ellipse(const std::string &ellipse)
Set the unit&#39;s ellipse image.
Definition: unit.hpp:1559
void set_small_profile(const std::string &value)
Definition: unit.hpp:586
The unit is a guardian - it won&#39;t move unless a target is sighted.
Definition: unit.hpp:857
config events_
Definition: unit.hpp:1903
attribute_map::value_type attribute
Definition: config.hpp:221
void generate_traits(bool must_have_only=false)
Applies mandatory traits (e.g.
Definition: unit.cpp:804
map_location interrupted_move_
Definition: unit.hpp:1923
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:780
std::string unit_rgb
t_string description_
Definition: unit.hpp:1942
const std::string & variation() const
The ID of the variation of this unit&#39;s type.
Definition: unit.hpp:562
void clear_changed_attributes()
Definition: unit.cpp:2677
int wml_x() const
Definition: location.hpp:153
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:851
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
std::optional< std::string > usage_
Definition: unit.hpp:1945
int experience_needed(bool with_acceleration=true) const
Definition: types.cpp:577
double xp_bar_scaling_
Definition: unit.hpp:1935
unit_type_data unit_types
Definition: types.cpp:1465
void set_wml_x(int v)
Definition: location.hpp:156
std::unique_ptr< unit_animation_component > anim_comp_
Definition: unit.hpp:1932
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:852
const config & get_cfg() const
Definition: types.hpp:283
STL namespace.
int resistance_against(const attack_type &attack) const
Returns the resistance against the indicated attack.
Definition: movetype.hpp:296
std::string absolute_image() const
The name of the file to game_display (used in menus).
Definition: unit.cpp:2418
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
std::vector< config > advancements_
Definition: unit.hpp:1940
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition: filter.hpp:127
const std::string & variation_id() const
The id of this variation; empty if it&#39;s a gender variation or a base unit.
Definition: types.hpp:150
void clear()
Definition: config.cpp:920
void remove_attacks_ai()
Set the unit to have no attacks left for this turn.
Definition: unit.cpp:2620
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:223
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:875
void write(config &cfg) const
Writes the movement type data to the provided config.
Definition: movetype.cpp:922
std::string flag_rgb_
Definition: unit.hpp:1870
void set_facing(map_location::DIRECTION dir) const
The this unit&#39;s facing.
Definition: unit.cpp:1586
void set_interrupted_move(const map_location &interrupted_move)
Set the target location of the unit&#39;s interrupted move.
Definition: unit.hpp:1412
std::string image_mods_
Definition: unit.hpp:1871
int recall_cost_
Definition: unit.hpp:1865
void set_total_movement(int value)
Definition: unit.hpp:1259
config filter_recall_
Definition: unit.hpp:1904
movetype movement_type_
Definition: unit.hpp:1886
bool canrecruit_
Definition: unit.hpp:1866
const_attr_itors attribute_range() const
Definition: config.cpp:858
bool hold_position_
Definition: unit.hpp:1888
A single unit type that the player may recruit.
Definition: types.hpp:45
void write(config &cfg, bool write_all=true) const
Serializes the current unit metadata values.
Definition: unit.cpp:1421
game_data * gamedata
Definition: resources.cpp:23
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:1750
std::vector< std::string > recruit_list_
Definition: unit.hpp:1867
int hit_points_
Definition: unit.hpp:1858
int vision_
Definition: unit.hpp:1883
std::vector< unit_ability >::iterator iterator
Definition: unit.hpp:83
void append_active_ai_for_side(ai::side_number side, const config &cfg)
Appends AI parameters to active AI of the given side.
Definition: manager.cpp:672
int unit_value_
Definition: unit.hpp:1922
state_t
Built-in status effects known to the engine.
Definition: unit.hpp:849
map_location loc_
upkeep_t upkeep_
Definition: unit.hpp:1952
void set_level(int level)
Sets the current level of this unit.
Definition: unit.hpp:555
std::bitset< num_bool_states > known_boolean_states_
Definition: unit.hpp:1899
const unit_type & type() const
This unit&#39;s type, accounting for gender and variation.
Definition: unit.hpp:345
color_t hp_color() const
Color for this unit&#39;s current hitpoints.
Definition: unit.cpp:1087
unsigned int experience_to_advance() const
The number of experience points this unit needs to level up, or 0 if current XP > max XP...
Definition: unit.hpp:531
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:52
void heal(int amount)
Heal the unit.
Definition: unit.cpp:1283
void add_modification(const std::string &type, const config &modification, bool no_add=false)
Add a new modification to the unit.
Definition: unit.cpp:2271
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit&#39;s defense on a given terrain.
Definition: unit.cpp:1610
static void clear_status_caches()
Clear this unit status cache for all units.
Definition: unit.cpp:676
bool is_fearless_
Definition: unit.hpp:1925
void set_advances_to(const std::vector< std::string > &advances_to)
Sets this unit&#39;s advancement options.
Definition: unit.cpp:1174
const_all_children_iterator ordered_end() const
Definition: config.cpp:968
child_list get_children(const std::string &key) const
Definition: variable.cpp:226
std::vector< std::string > overlays_
Definition: unit.hpp:1908
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:75
void set_max_attacks(int value)
Definition: unit.hpp:971
#define LOG_UT
Definition: unit.cpp:70
A small explanation about what&#39;s going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:61
config variables_
Definition: unit.hpp:1902
static const std::string & leader_crown()
The path to the leader crown overlay.
Definition: unit.cpp:1039
bool unrenamable_
Definition: unit.hpp:1873
static std::map< std::string, state_t > known_boolean_state_names_
Definition: unit.hpp:1900
int max_experience_
Definition: unit.hpp:1861
std::vector< t_string > special_notes_
Definition: unit.hpp:1943
const std::vector< unit_race::GENDER > & genders() const
The returned vector will not be empty, provided this has been built to the HELP_INDEXED status...
Definition: types.hpp:251
const std::string & id() const
Gets this unit&#39;s id.
Definition: unit.hpp:370
void swap(unit &)
Swap, for copy and swap idiom.
Definition: unit.cpp:731
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:30
int cost() const
Definition: types.hpp:175
void set_state(const std::string &state, bool value)
Set whether the unit is affected by a status effect.
Definition: unit.cpp:1364
int vision() const
Definition: types.hpp:170
static const unit_race null_race
Dummy race used when a race is not yet known.
Definition: race.hpp:69
const config & abilities() const
Definition: unit.hpp:1711
void set_underlying_id(n_unit::id_manager &id_manager)
Sets the internal ID.
Definition: unit.cpp:2525
int movement_
Definition: unit.hpp:1881
const movetype & movement_type() const
Definition: types.hpp:191
unit_alignments::type alignment() const
Definition: types.hpp:195
bool is_healthy_
Definition: unit.hpp:1925
int max_hit_points_
Definition: unit.hpp:1859
t_string name_
Definition: unit.hpp:1852
void set_recruits(const std::vector< std::string > &recruits)
Sets the recruit list.
Definition: unit.cpp:1153
std::ostringstream wrapper.
Definition: formatter.hpp:39
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:141
bool get_attr_changed(UNIT_ATTRIBUTE attr) const
Definition: unit.hpp:180
void new_scenario()
Refresh unit for the beginning of a new scenario.
Definition: unit.cpp:1268
int wml_y() const
Definition: location.hpp:154
filter_context * filter_con
Definition: resources.cpp:24
void parse_upkeep(const config::attribute_value &upkeep)
Definition: unit.cpp:2658
const std::vector< std::string > advances_to_translated() const
Gets the names of the possible types this unit can advance to on level-up.
Definition: unit.cpp:1159
const std::string & usage() const
Definition: types.hpp:178
void set_max_experience(int value)
Definition: unit.hpp:524
color_t xp_color() const
Color for this unit&#39;s XP.
Definition: unit.cpp:1141
unsigned all_children_count() const
Definition: config.cpp:384
game_board * gameboard
Definition: resources.cpp:21
std::string ellipse() const
Definition: types.hpp:184
static const std::set< std::string > effects
The set of applicable effects for movement types.
Definition: movetype.hpp:344
void set_alignment(unit_alignments::type alignment)
Sets the alignment of this unit.
Definition: unit.hpp:471
#define WRN_UT
Definition: unit.cpp:71
const t_string & name() const
Gets this unit&#39;s translatable display name.
Definition: unit.hpp:393
unit_race::GENDER string_gender(const std::string &str, unit_race::GENDER def)
Definition: race.cpp:152
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
int upkeep() const
Gets the amount of gold this unit costs a side per turn.
Definition: unit.cpp:1595
int kill_experience
Definition: game_config.cpp:58
std::vector< std::string > advances_to_t
Definition: unit.hpp:228
bool is_enemy(int n) const
Definition: team.hpp:231
int side_
Definition: unit.hpp:1875
std::size_t value
Definition: id.hpp:27
bool get_attacks_changed() const
Definition: unit.cpp:1411
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:144
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands...
std::vector< t_string > unit_special_notes() const
The unit&#39;s special notes.
Definition: unit.cpp:2685
void set_hidden(bool state) const
Sets whether the unit is hidden on the map.
Definition: unit.cpp:2638
map_display and display: classes which take care of displaying the map and game-data on the screen...
std::string default_anim_image() const
The default image to use for animation frames with no defined image.
Definition: unit.cpp:2423
std::optional< std::string > halo_
Definition: unit.hpp:1946
const std::vector< std::string > & overlays() const
Get the unit&#39;s overlay images.
Definition: unit.hpp:1587
boost::iterator_range< const_attribute_iterator > const_attr_itors
Definition: config.hpp:281
std::string small_profile() const
An optional profile image to display in Help.
Definition: unit.cpp:1026
The unit is uncovered - it was hiding but has been spotted.
Definition: unit.hpp:854
void set_advancements(std::vector< config > advancements)
Sets the raw modification advancement option data.
Definition: unit.cpp:1800
bool mod_duration_match(const std::string &mod_dur, const std::string &goal_dur)
Determines if mod_dur "matches" goal_dur.
Definition: unit.cpp:1197
static color_t hp_color_max()
Definition: unit.cpp:1097
static void add_color_info(const game_config_view &v, bool build_defaults)
game_events::manager * game_events
Definition: resources.cpp:25
int max_movement_
Definition: unit.hpp:1882
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:727
std::string role_
Definition: unit.hpp:1910
static bool is_synced()
void expire_modifications(const std::string &duration)
Clears those modifications whose duration has expired.
Definition: unit.cpp:1207
std::size_t modification_count(const std::string &type, const std::string &id) const
Count modifications of a particular type.
Definition: unit.cpp:1818
void set_attr_changed(UNIT_ATTRIBUTE attr)
Definition: unit.hpp:173
t_string type_name_
The displayed name of this unit type.
Definition: unit.hpp:1846
const std::string & gender_string(unit_race::GENDER gender)
Definition: race.cpp:142
std::string flag_rgb
static std::string get_side_color_id(unsigned side)
Definition: team.cpp:969
double xp_bar_scaling() const
Definition: types.hpp:166
unit & mark_clone(bool is_temporary)
Mark this unit as clone so it can be inserted to unit_map.
Definition: unit.cpp:2542
bool is_fake() const
Definition: id.hpp:29
bool loyal() const
Gets whether this unit is loyal - ie, it costs no upkeep.
Definition: unit.cpp:1605
Encapsulates the map of the game.
Definition: location.hpp:38
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:205
std::set< std::string > states_
Definition: unit.hpp:1895
bool invisible(const map_location &loc, bool see_all=true) const
Definition: unit.cpp:2441
const std::string & undead_variation() const
Info on the type of unit that the unit reanimates as.
Definition: types.hpp:136
#define DBG_UT
Definition: unit.cpp:69
const unit_race * race_
Never nullptr, but may point to the null race.
Definition: unit.hpp:1849
const std::string log_id() const
A variant on id() that is more descriptive, for use with message logging.
Definition: types.hpp:146
bool get_ability_bool(const std::string &tag_name, const map_location &loc) const
Checks whether this unit currently possesses or is affected by a given ability.
Definition: abilities.cpp:150
const unit_race * find_race(const std::string &) const
Definition: types.cpp:1352
void remove_ability_by_id(const std::string &ability)
Removes a unit&#39;s abilities with a specific ID.
Definition: unit.cpp:1398
bool has_zoc() const
Definition: types.hpp:228
void set_image_halo(const std::string &halo)
Set the unit&#39;s halo image.
Definition: unit.cpp:2651
void generate_name()
Generates a random race-appropriate name if one has not already been provided.
Definition: unit.cpp:795
void swap(unit &lhs, unit &rhs)
Implement non-member swap function for std::swap (calls unit::swap).
Definition: unit.cpp:2789
int attacks_left_
Definition: unit.hpp:1892
std::size_t i
Definition: function.cpp:967
int recall_cost() const
Definition: types.hpp:168
void set_undead_variation(const std::string &value)
The ID of the undead variation (ie, dwarf, swimmer) of this unit.
Definition: unit.hpp:568
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:495
Visitor helper class to fetch the appropriate upkeep value.
Definition: unit.hpp:1128
std::string get_checksum(const unit &u)
Gets a checksum for a unit.
Definition: unit.cpp:2691
static constexpr std::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
static map_location::DIRECTION s
const config & abilities_cfg() const
Definition: types.hpp:236
unit()=delete
void set_emit_zoc(bool val)
Sets the raw zone-of-control flag.
Definition: unit.hpp:1338
void add_trait_description(const config &trait, const t_string &description)
Register a trait&#39;s name and its description for the UI&#39;s use.
Definition: unit.cpp:2405
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:602
iterator begin()
Definition: unit.hpp:86
std::map< std::string, std::string > advancement_icons() const
Gets and image path and and associated description for each advancement option.
Definition: unit.cpp:1678
#define log_scope(description)
Definition: log.hpp:235
std::string halo() const
Definition: types.hpp:183
static std::string type()
Definition: unit.hpp:1122
int get_composite_value() const
Definition: abilities.hpp:47
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:40
std::vector< std::string > advances_to_
Definition: unit.hpp:1840
void add_events(const config::const_child_itors &cfgs, game_lua_kernel &lk, const std::string &type=std::string())
Definition: manager.cpp:148
const std::string & icon() const
Definition: types.hpp:180
void validate_side(int side)
Definition: team.cpp:754
std::vector< std::string > get_traits_list() const
Gets a list of the traits this unit currently has.
Definition: unit.cpp:861
int level() const
Definition: types.hpp:167
std::unique_ptr< unit_formula_manager > formula_man_
Definition: unit.hpp:1879
std::set< std::string > & encountered_units()
Definition: game.cpp:917
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
Definition: random.hpp:52
void heal_fully()
Fully heal the unit, restoring it to max hitpoints.
Definition: unit.hpp:821
n_unit::unit_id underlying_id_
Definition: unit.hpp:1853
void set_max_hitpoints(int value)
Definition: unit.hpp:500
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::string variation_
Definition: unit.hpp:1856
const std::string & small_profile() const
Definition: types.hpp:181
int hitpoints() const
Definition: types.hpp:164
The unit has not moved.
Definition: unit.hpp:855
int jamming() const
Definition: types.hpp:173
unit_movement_resetter(const unit_movement_resetter &)=delete
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:61
attack_list attacks_
Definition: unit.hpp:1911
int movement() const
Definition: types.hpp:169
std::map< map_location, bool > invisibility_cache_
Hold the visibility status cache for a unit, when not uncovered.
Definition: unit.hpp:1968
std::shared_ptr< attack_type > attack_ptr
Definition: ptr.hpp:33
config & add_child(config_key_type key)
Definition: config.cpp:514
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2484
Handling of system events.
Definition: manager.hpp:43
const std::vector< std::string > & recruits() const
The type IDs of the other units this unit may recruit, if possible.
Definition: unit.hpp:614
const std::string & flag_rgb() const
Get the source color palette to use when recoloring the unit&#39;s image.
Definition: unit.cpp:1044
std::string undead_variation_
Definition: unit.hpp:1855
const_all_children_iterator ordered_begin() const
Definition: config.cpp:958
config::const_child_itors possible_traits() const
Definition: types.hpp:233
const std::string & image() const
Definition: types.hpp:179
static const unit_type & get_unit_type(const std::string &type_id)
Converts a string ID to a unit_type.
Definition: unit.cpp:188
Definition: display.hpp:45
const unit_type * type_
Never nullptr.
Definition: unit.hpp:1843
static unit_race::GENDER generate_gender(const unit_type &type, bool random_gender)
Definition: unit.cpp:200
bool empty() const
Definition: tstring.hpp:187
unit_type_error error
Definition: types.hpp:52
void init(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Definition: unit.cpp:379
void write_upkeep(config::attribute_value &upkeep) const
Definition: unit.cpp:2672
unsigned int num_traits() const
Definition: types.hpp:138
static const std::set< std::string > builtin_effects
Definition: unit.hpp:1505
n_unit::id_manager & unit_id_manager()
Definition: game_board.hpp:80
double t
Definition: astarsearch.cpp:65
const unit_type & get_variation(const std::string &id) const
Definition: types.cpp:474
std::vector< std::string > split(const config_attribute_value &val)
int apply_modifier(const int number, const std::string &amount, const int minimum)
std::vector< config > get_modification_advances() const
Gets any non-typed advanced options set by modifications.
Definition: unit.cpp:1735
int max_attacks() const
Definition: types.hpp:174
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1345
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
const unit_race * race() const
Never returns nullptr, but may point to the null race.
Definition: types.hpp:279
Functions to load and save images from/to disk.
void adjust_profile(std::string &profile)
Definition: types.cpp:1467
const config & get_config() const
Definition: variable.hpp:75
Standard logging facilities (interface).
bool can_advance() const
Checks whether this unit has any options to advance to.
Definition: unit.hpp:262
void apply_builtin_effect(std::string type, const config &effect)
Apply a builtin effect to the unit.
Definition: unit.cpp:1909
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
t_string unit_description() const
A detailed description of this unit.
Definition: unit.hpp:440
const unit_type & get_gender_unit_type(std::string gender) const
Returns a gendered variant of this unit_type.
Definition: types.cpp:453
static void check_id(std::string &id)
Validate the id argument.
Definition: types.cpp:1439
game_lua_kernel * lua_kernel
Definition: resources.cpp:26
std::string describe_builtin_effect(std::string type, const config &effect)
Construct a string describing a built-in effect.
Definition: unit.cpp:1844
map_location goto_
Definition: unit.hpp:1923
double hp_bar_scaling() const
Definition: types.hpp:165
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1254
const map_location goto_
Definition: move.cpp:312
bool fogged(const map_location &loc) const
Definition: team.cpp:657
std::vector< t_string > direct_special_notes() const
Returns only the notes defined by [unit_type][special_note] tags, excluding any that would be found f...
Definition: types.hpp:158
#define e
int side() const
The side this unit belongs to.
Definition: unit.hpp:333
void end_turn()
Refresh unit for the end of a turn.
Definition: unit.cpp:1254
static color_t hp_color_impl(int hitpoints, int max_hitpoints)
Definition: unit.cpp:1049
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:465
unit_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit&#39;s active abilities of a particular type if it were on a specified location.
Definition: abilities.cpp:189
const_attack_itors attacks() const
Definition: types.cpp:543
Visitor helper class to parse the upkeep value from a config.
Definition: unit.hpp:1178
int resistance_against(const std::string &damage_name, bool attacker, const map_location &loc, const_attack_ptr weapon=nullptr, const_attack_ptr opp_weapon=nullptr) const
The unit&#39;s resistance against a given damage type.
Definition: unit.cpp:1653
unit_ability_list get_abilities_weapons(const std::string &tag_name, const map_location &loc, const_attack_ptr weapon=nullptr, const_attack_ptr opp_weapon=nullptr) const
Definition: abilities.cpp:230
std::vector< vconfig > child_list
Definition: variable.hpp:78
std::vector< std::pair< std::string, std::string > > amla_icons() const
Gets the image and description data for modification advancements.
Definition: unit.cpp:1718
int side() const
Definition: team.hpp:176
t_string unit_description() const
Definition: types.cpp:484
#define ERR_UT
Definition: unit.cpp:72
Visitor helper struct to fetch the upkeep type flag if applicable, or the the value otherwise...
Definition: unit.hpp:1158
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
bool null() const
Definition: variable.hpp:72
mock_char c
bool resistance_filter_matches(const config &cfg, bool attacker, const std::string &damage_name, int res) const
Definition: unit.cpp:1625
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
std::string TC_image_mods() const
Constructs a recolor (RC) IPF string for this unit&#39;s team color.
Definition: unit.cpp:2594
void remove_movement_ai()
Sets the unit to have no moves left for this turn.
Definition: unit.cpp:2629
const std::set< std::string > get_states() const
Get the status effects currently affecting the unit.
Definition: unit.cpp:1299
const advances_to_t & advances_to() const
Gets the possible types this unit can advance to on level-up.
Definition: unit.hpp:234
virtual ~unit()
Definition: unit.cpp:710
std::string print_modifier(const std::string &mod)
Add a "+" or replace the "-" par Unicode minus.
int recall_cost() const
How much gold it costs to recall this unit, or -1 if the side&#39;s default recall cost is used...
Definition: unit.hpp:630
static std::string write_direction(DIRECTION dir)
Definition: location.cpp:141
bool has_variation(const std::string &variation_id) const
Definition: types.cpp:757
void remove_child(config_key_type key, unsigned index)
Definition: config.cpp:732
bool would_be_discovered(const map_location &loc, int side_num, bool see_all=true)
Given a location and a side number, indicates whether an invisible unit of that side at that location...
static rng & default_instance()
Definition: random.cpp:74
std::string hash() const
Definition: config.cpp:1392
static lg::log_domain log_unit("unit")
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1270
bool random_traits_
Definition: unit.hpp:1949
config abilities_
Definition: unit.hpp:1938
bool empty() const
Definition: config.cpp:941
std::bitset< UA_COUNT > changed_attributes_
Definition: unit.hpp:1959
int experience_
Definition: unit.hpp:1860
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition: unit.cpp:2599
bool has_ability_by_id(const std::string &ability) const
Check if the unit has an ability of a specific ID.
Definition: unit.cpp:1387
iterator end()
Definition: unit.hpp:88
utils::string_map modification_descriptions_
Definition: unit.hpp:1927
config::const_child_itors advancements() const
Definition: types.hpp:239
std::string generate_name(GENDER gender) const
Definition: race.cpp:114
bool end_turn_
Definition: unit.hpp:1889
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:70
const std::string & default_variation() const
Definition: types.hpp:176
std::string tooltip
Shown when hovering over an entry in the filter&#39;s drop-down list.
Definition: manager.cpp:219
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414
std::vector< std::string > parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
map_location::DIRECTION facing_
Definition: unit.hpp:1916
bool emit_zoc_
Definition: unit.hpp:1906
bool remove_attack(attack_ptr atk)
Remove an attack from the unit.
Definition: unit.cpp:2609
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46