The Battle for Wesnoth  1.17.10+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);
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  utils::erase_if(resistance_abilities, [&](const unit_ability& i) {
1659  return !resistance_filter_matches(*i.ability_cfg, attacker, damage_name, 100-res);
1660  });
1661 
1662  if(!resistance_abilities.empty()) {
1663  unit_abilities::effect resist_effect(resistance_abilities, 100-res);
1664 
1665  res = 100 - std::min<int>(
1666  resist_effect.get_composite_value(),
1667  resistance_abilities.highest("max_value").first
1668  );
1669  }
1670 
1671  return res;
1672 }
1673 
1674 std::map<std::string, std::string> unit::advancement_icons() const
1675 {
1676  std::map<std::string,std::string> temp;
1677  if(!can_advance()) {
1678  return temp;
1679  }
1680 
1681  if(!advances_to_.empty()) {
1682  std::ostringstream tooltip;
1683  const std::string& image = game_config::images::level;
1684 
1685  for(const std::string& s : advances_to()) {
1686  if(!s.empty()) {
1687  tooltip << s << std::endl;
1688  }
1689  }
1690 
1691  temp[image] = tooltip.str();
1692  }
1693 
1694  for(const config& adv : get_modification_advances()) {
1695  const std::string& image = adv["image"];
1696  if(image.empty()) {
1697  continue;
1698  }
1699 
1700  std::ostringstream tooltip;
1701  tooltip << temp[image];
1702 
1703  const std::string& tt = adv["description"];
1704  if(!tt.empty()) {
1705  tooltip << tt << std::endl;
1706  }
1707 
1708  temp[image] = tooltip.str();
1709  }
1710 
1711  return(temp);
1712 }
1713 
1714 std::vector<std::pair<std::string, std::string>> unit::amla_icons() const
1715 {
1716  std::vector<std::pair<std::string, std::string>> temp;
1717  std::pair<std::string, std::string> icon; // <image,tooltip>
1718 
1719  for(const config& adv : get_modification_advances()) {
1720  icon.first = adv["icon"].str();
1721  icon.second = adv["description"].str();
1722 
1723  for(unsigned j = 0, j_count = modification_count("advancement", adv["id"]); j < j_count; ++j) {
1724  temp.push_back(icon);
1725  }
1726  }
1727 
1728  return(temp);
1729 }
1730 
1731 std::vector<config> unit::get_modification_advances() const
1732 {
1733  std::vector<config> res;
1734  for(const config& adv : modification_advancements()) {
1735  if(adv["strict_amla"].to_bool() && !advances_to_.empty()) {
1736  continue;
1737  }
1738  if(const config& filter = adv.child("filter")) {
1739  if(!unit_filter(vconfig(filter)).matches(*this, loc_)) {
1740  continue;
1741  }
1742  }
1743 
1744  if(modification_count("advancement", adv["id"]) >= static_cast<unsigned>(adv["max_times"].to_int(1))) {
1745  continue;
1746  }
1747 
1748  std::vector<std::string> temp_require = utils::split(adv["require_amla"]);
1749  std::vector<std::string> temp_exclude = utils::split(adv["exclude_amla"]);
1750 
1751  if(temp_require.empty() && temp_exclude.empty()) {
1752  res.push_back(adv);
1753  continue;
1754  }
1755 
1756  std::sort(temp_require.begin(), temp_require.end());
1757  std::sort(temp_exclude.begin(), temp_exclude.end());
1758 
1759  std::vector<std::string> uniq_require, uniq_exclude;
1760 
1761  std::unique_copy(temp_require.begin(), temp_require.end(), std::back_inserter(uniq_require));
1762  std::unique_copy(temp_exclude.begin(), temp_exclude.end(), std::back_inserter(uniq_exclude));
1763 
1764  bool exclusion_found = false;
1765  for(const std::string& s : uniq_exclude) {
1766  int max_num = std::count(temp_exclude.begin(), temp_exclude.end(), s);
1767  int mod_num = modification_count("advancement", s);
1768  if(mod_num >= max_num) {
1769  exclusion_found = true;
1770  break;
1771  }
1772  }
1773 
1774  if(exclusion_found) {
1775  continue;
1776  }
1777 
1778  bool requirements_done = true;
1779  for(const std::string& s : uniq_require) {
1780  int required_num = std::count(temp_require.begin(), temp_require.end(), s);
1781  int mod_num = modification_count("advancement", s);
1782  if(required_num > mod_num) {
1783  requirements_done = false;
1784  break;
1785  }
1786  }
1787 
1788  if(requirements_done) {
1789  res.push_back(adv);
1790  }
1791  }
1792 
1793  return res;
1794 }
1795 
1796 void unit::set_advancements(std::vector<config> advancements)
1797 {
1799  advancements_ = advancements;
1800 }
1801 
1802 const std::string& unit::type_id() const
1803 {
1804  return type_->id();
1805 }
1806 
1807 void unit::set_big_profile(const std::string& value)
1808 {
1810  profile_ = value;
1812 }
1813 
1814 std::size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
1815 {
1816  std::size_t res = 0;
1817  for(const config& item : modifications_.child_range(mod_type)) {
1818  if(item["id"] == id) {
1819  ++res;
1820  }
1821  }
1822 
1823  // For backwards compatibility, if asked for "advancement", also count "advance"
1824  if(mod_type == "advancement") {
1825  res += modification_count("advance", id);
1826  }
1827 
1828  return res;
1829 }
1830 
1831 const std::set<std::string> unit::builtin_effects {
1832  "alignment", "attack", "defense", "ellipse", "experience", "fearless",
1833  "halo", "healthy", "hitpoints", "image_mod", "jamming", "jamming_costs",
1834  "loyal", "max_attacks", "max_experience", "movement", "movement_costs",
1835  "new_ability", "new_advancement", "new_animation", "new_attack", "overlay", "profile",
1836  "recall_cost", "remove_ability", "remove_advancement", "remove_attacks", "resistance",
1837  "status", "type", "variation", "vision", "vision_costs", "zoc"
1838 };
1839 
1840 std::string unit::describe_builtin_effect(std::string apply_to, const config& effect)
1841 {
1842  if(apply_to == "attack") {
1843  std::vector<t_string> attack_names;
1844 
1845  std::string desc;
1846  for(attack_ptr a : attacks_) {
1847  bool affected = a->describe_modification(effect, &desc);
1848  if(affected && !desc.empty()) {
1849  attack_names.emplace_back(a->name(), "wesnoth-units");
1850  }
1851  }
1852  if(!attack_names.empty()) {
1853  utils::string_map symbols;
1854  symbols["attack_list"] = utils::format_conjunct_list("", attack_names);
1855  symbols["effect_description"] = desc;
1856  return VGETTEXT("$attack_list|: $effect_description", symbols);
1857  }
1858  } else if(apply_to == "hitpoints") {
1859  const std::string& increase_total = effect["increase_total"];
1860  if(!increase_total.empty()) {
1861  return VGETTEXT(
1862  "<span color=\"$color\">$number_or_percent</span> HP",
1863  {{"number_or_percent", utils::print_modifier(increase_total)}, {"color", increase_total[0] == '-' ? "#f00" : "#0f0"}});
1864  }
1865  } else {
1866  const std::string& increase = effect["increase"];
1867  if(increase.empty()) {
1868  return "";
1869  }
1870  if(apply_to == "movement") {
1871  return VNGETTEXT(
1872  "<span color=\"$color\">$number_or_percent</span> move",
1873  "<span color=\"$color\">$number_or_percent</span> moves",
1874  std::stoi(increase),
1875  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1876  } else if(apply_to == "vision") {
1877  return VGETTEXT(
1878  "<span color=\"$color\">$number_or_percent</span> vision",
1879  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1880  } else if(apply_to == "jamming") {
1881  return VGETTEXT(
1882  "<span color=\"$color\">$number_or_percent</span> jamming",
1883  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1884  } else if(apply_to == "max_experience") {
1885  // Unlike others, decreasing experience is a *GOOD* thing
1886  return VGETTEXT(
1887  "<span color=\"$color\">$number_or_percent</span> XP to advance",
1888  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
1889  } else if(apply_to == "max_attacks") {
1890  return VNGETTEXT(
1891  "<span color=\"$color\">$number_or_percent</span> attack per turn",
1892  "<span color=\"$color\">$number_or_percent</span> attacks per turn",
1893  std::stoi(increase),
1894  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1895  } else if(apply_to == "recall_cost") {
1896  // Unlike others, decreasing recall cost is a *GOOD* thing
1897  return VGETTEXT(
1898  "<span color=\"$color\">$number_or_percent</span> cost to recall",
1899  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
1900  }
1901  }
1902  return "";
1903 }
1904 
1905 void unit::apply_builtin_effect(std::string apply_to, const config& effect)
1906 {
1907  appearance_changed_ = true;
1908  if(apply_to == "fearless") {
1910  is_fearless_ = effect["set"].to_bool(true);
1911  } else if(apply_to == "healthy") {
1913  is_healthy_ = effect["set"].to_bool(true);
1914  } else if(apply_to == "profile") {
1915  if(const config::attribute_value* v = effect.get("portrait")) {
1916  set_big_profile((*v).str());
1917  }
1918 
1919  if(const config::attribute_value* v = effect.get("small_portrait")) {
1920  set_small_profile((*v).str());
1921  }
1922 
1923  if(const config::attribute_value* v = effect.get("description")) {
1924  description_ = *v;
1925  }
1926 
1927  if(config::const_child_itors cfg_range = effect.child_range("special_note")) {
1928  for(const config& c : cfg_range) {
1929  if(!c["remove"].to_bool()) {
1930  special_notes_.emplace_back(c["note"].t_str());
1931  } else {
1932  auto iter = std::find(special_notes_.begin(), special_notes_.end(), c["note"].t_str());
1933  if(iter != special_notes_.end()) {
1934  special_notes_.erase(iter);
1935  }
1936  }
1937  }
1938  }
1939  } else if(apply_to == "new_attack") {
1941  attacks_.emplace_back(new attack_type(effect));
1942  } else if(apply_to == "remove_attacks") {
1944  auto iter = std::remove_if(attacks_.begin(), attacks_.end(), [&effect](attack_ptr a) {
1945  return a->matches_filter(effect);
1946  });
1947 
1948  attacks_.erase(iter, attacks_.end());
1949  } else if(apply_to == "attack") {
1951  for(attack_ptr a : attacks_) {
1952  a->apply_modification(effect);
1953  }
1954  } else if(apply_to == "hitpoints") {
1955  LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_;
1956  const std::string& increase_hp = effect["increase"];
1957  const std::string& increase_total = effect["increase_total"];
1958  const std::string& set_hp = effect["set"];
1959  const std::string& set_total = effect["set_total"];
1960 
1961  // If the hitpoints are allowed to end up greater than max hitpoints
1962  const bool violate_max = effect["violate_maximum"].to_bool();
1963 
1964  if(!set_hp.empty()) {
1965  if(set_hp.back() == '%') {
1966  hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
1967  } else {
1968  hit_points_ = lexical_cast_default<int>(set_hp);
1969  }
1970  }
1971 
1972  if(!set_total.empty()) {
1973  if(set_total.back() == '%') {
1974  set_max_hitpoints(lexical_cast_default<int>(set_total)*max_hit_points_/100);
1975  } else {
1976  set_max_hitpoints(lexical_cast_default<int>(set_total));
1977  }
1978  }
1979 
1980  if(!increase_total.empty()) {
1981  // A percentage on the end means increase by that many percent
1982  set_max_hitpoints(utils::apply_modifier(max_hit_points_, increase_total));
1983  }
1984 
1985  if(max_hit_points_ < 1)
1986  set_max_hitpoints(1);
1987 
1988  if(effect["heal_full"].to_bool()) {
1989  heal_fully();
1990  }
1991 
1992  if(!increase_hp.empty()) {
1994  }
1995 
1996  LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_;
1997  if(hit_points_ > max_hit_points_ && !violate_max) {
1998  LOG_UT << "resetting hp to max";
2000  }
2001 
2002  if(hit_points_ < 1) {
2003  hit_points_ = 1;
2004  }
2005  } else if(apply_to == "movement") {
2006  const bool apply_to_vision = effect["apply_to_vision"].to_bool(true);
2007 
2008  // Unlink vision from movement, regardless of whether we'll increment both or not
2009  if(vision_ < 0) {
2011  }
2012 
2013  const int old_max = max_movement_;
2014 
2015  const std::string& increase = effect["increase"];
2016  if(!increase.empty()) {
2018  }
2019 
2020  set_total_movement(effect["set"].to_int(max_movement_));
2021 
2022  if(movement_ > max_movement_) {
2024  }
2025 
2026  if(apply_to_vision) {
2027  vision_ = std::max(0, vision_ + max_movement_ - old_max);
2028  }
2029  } else if(apply_to == "vision") {
2030  // Unlink vision from movement, regardless of which one we're about to change.
2031  if(vision_ < 0) {
2033  }
2034 
2035  const std::string& increase = effect["increase"];
2036  if(!increase.empty()) {
2037  vision_ = utils::apply_modifier(vision_, increase, 1);
2038  }
2039 
2040  vision_ = effect["set"].to_int(vision_);
2041  } else if(apply_to == "jamming") {
2042  const std::string& increase = effect["increase"];
2043 
2044  if(!increase.empty()) {
2045  jamming_ = utils::apply_modifier(jamming_, increase, 1);
2046  }
2047 
2048  jamming_ = effect["set"].to_int(jamming_);
2049  } else if(apply_to == "experience") {
2050  const std::string& increase = effect["increase"];
2051  const std::string& set = effect["set"];
2052 
2053  if(!set.empty()) {
2054  if(set.back() == '%') {
2055  experience_ = lexical_cast_default<int>(set)*max_experience_/100;
2056  } else {
2057  experience_ = lexical_cast_default<int>(set);
2058  }
2059  }
2060 
2061  if(increase.empty() == false) {
2063  }
2064  } else if(apply_to == "max_experience") {
2065  const std::string& increase = effect["increase"];
2066  const std::string& set = effect["set"];
2067 
2068  if(set.empty() == false) {
2069  if(set.back() == '%') {
2070  set_max_experience(lexical_cast_default<int>(set)*max_experience_/100);
2071  } else {
2072  set_max_experience(lexical_cast_default<int>(set));
2073  }
2074  }
2075 
2076  if(increase.empty() == false) {
2078  }
2079  } else if(apply_to == upkeep_loyal::type()) {
2080  upkeep_ = upkeep_loyal{};
2081  } else if(apply_to == "status") {
2082  const std::string& add = effect["add"];
2083  const std::string& remove = effect["remove"];
2084 
2085  for(const std::string& to_add : utils::split(add))
2086  {
2087  set_state(to_add, true);
2088  }
2089 
2090  for(const std::string& to_remove : utils::split(remove))
2091  {
2092  set_state(to_remove, false);
2093  }
2094  } else if(std::find(movetype::effects.cbegin(), movetype::effects.cend(), apply_to) != movetype::effects.cend()) {
2095  // "movement_costs", "vision_costs", "jamming_costs", "defense", "resistance"
2096  if(const config& ap = effect.child(apply_to)) {
2098  movement_type_.merge(ap, apply_to, effect["replace"].to_bool());
2099  }
2100  } else if(apply_to == "zoc") {
2101  if(const config::attribute_value* v = effect.get("value")) {
2103  emit_zoc_ = v->to_bool();
2104  }
2105  } else if(apply_to == "new_ability") {
2106  if(const config& ab_effect = effect.child("abilities")) {
2108  config to_append;
2109  for(const config::any_child ab : ab_effect.all_children_range()) {
2110  if(!has_ability_by_id(ab.cfg["id"])) {
2111  to_append.add_child(ab.key, ab.cfg);
2112  }
2113  }
2114  abilities_.append(to_append);
2115  }
2116  } else if(apply_to == "remove_ability") {
2117  if(const config& ab_effect = effect.child("abilities")) {
2118  for(const config::any_child ab : ab_effect.all_children_range()) {
2119  remove_ability_by_id(ab.cfg["id"]);
2120  }
2121  }
2122  } else if(apply_to == "image_mod") {
2123  LOG_UT << "applying image_mod";
2124  std::string mod = effect["replace"];
2125  if(!mod.empty()){
2126  image_mods_ = mod;
2127  }
2128  LOG_UT << "applying image_mod";
2129  mod = effect["add"].str();
2130  if(!mod.empty()){
2131  if(!image_mods_.empty()) {
2132  image_mods_ += '~';
2133  }
2134 
2135  image_mods_ += mod;
2136  }
2137 
2139  LOG_UT << "applying image_mod";
2140  } else if(apply_to == "new_animation") {
2141  anim_comp_->apply_new_animation_effect(effect);
2142  } else if(apply_to == "ellipse") {
2143  set_image_ellipse(effect["ellipse"]);
2144  } else if(apply_to == "halo") {
2145  set_image_halo(effect["halo"]);
2146  } else if(apply_to == "overlay") {
2147  const std::string& add = effect["add"];
2148  const std::string& replace = effect["replace"];
2149  const std::string& remove = effect["remove"];
2150 
2151  if(!add.empty()) {
2152  for(const auto& to_add : utils::parenthetical_split(add, ',')) {
2153  overlays_.push_back(to_add);
2154  }
2155  }
2156  if(!remove.empty()) {
2157  for(const auto& to_remove : utils::parenthetical_split(remove, ',')) {
2158  overlays_.erase(std::remove(overlays_.begin(), overlays_.end(), to_remove), overlays_.end());
2159  }
2160  }
2161  if(add.empty() && remove.empty() && !replace.empty()) {
2162  overlays_ = utils::parenthetical_split(replace, ',');
2163  }
2164  } else if(apply_to == "new_advancement") {
2165  const std::string& types = effect["types"];
2166  const bool replace = effect["replace"].to_bool(false);
2168 
2169  if(!types.empty()) {
2170  if(replace) {
2172  } else {
2173  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2174  std::copy(temp_advances.begin(), temp_advances.end(), std::back_inserter(advances_to_));
2175  }
2176  }
2177 
2178  if(effect.has_child("advancement")) {
2179  if(replace) {
2180  advancements_.clear();
2181  }
2182 
2183  for(const config& adv : effect.child_range("advancement")) {
2184  advancements_.push_back(adv);
2185  }
2186  }
2187  } else if(apply_to == "remove_advancement") {
2188  const std::string& types = effect["types"];
2189  const std::string& amlas = effect["amlas"];
2191 
2192  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2194  for(const std::string& unit : temp_advances) {
2195  iter = std::find(advances_to_.begin(), advances_to_.end(), unit);
2196  if(iter != advances_to_.end()) {
2197  advances_to_.erase(iter);
2198  }
2199  }
2200 
2201  temp_advances = utils::parenthetical_split(amlas, ',');
2202 
2203  for(int i = advancements_.size() - 1; i >= 0; i--) {
2204  if(std::find(temp_advances.begin(), temp_advances.end(), advancements_[i]["id"]) != temp_advances.end()) {
2205  advancements_.erase(advancements_.begin() + i);
2206  }
2207  }
2208  } else if(apply_to == "alignment") {
2209  auto new_align = unit_alignments::get_enum(effect["set"].str());
2210  if(new_align) {
2211  set_alignment(*new_align);
2212  }
2213  } else if(apply_to == "max_attacks") {
2214  const std::string& increase = effect["increase"];
2215 
2216  if(!increase.empty()) {
2218  }
2219  } else if(apply_to == "recall_cost") {
2220  const std::string& increase = effect["increase"];
2221  const std::string& set = effect["set"];
2222  const int recall_cost = recall_cost_ < 0 ? resources::gameboard->teams().at(side_).recall_cost() : recall_cost_;
2223 
2224  if(!set.empty()) {
2225  if(set.back() == '%') {
2226  recall_cost_ = lexical_cast_default<int>(set)*recall_cost/100;
2227  } else {
2228  recall_cost_ = lexical_cast_default<int>(set);
2229  }
2230  }
2231 
2232  if(!increase.empty()) {
2233  recall_cost_ = utils::apply_modifier(recall_cost, increase, 1);
2234  }
2235  } else if(effect["apply_to"] == "variation") {
2236  const unit_type* base_type = unit_types.find(type().parent_id());
2237  assert(base_type != nullptr);
2238  const std::string& variation_id = effect["name"];
2239  if(variation_id.empty() || base_type->get_gender_unit_type(gender_).has_variation(variation_id)) {
2240  variation_ = variation_id;
2241  advance_to(*base_type);
2242  if(effect["heal_full"].to_bool(false)) {
2243  heal_fully();
2244  }
2245  } else {
2246  WRN_UT << "unknown variation '" << variation_id << "' (name=) in [effect]apply_to=variation, ignoring";
2247  }
2248  } else if(effect["apply_to"] == "type") {
2249  std::string prev_type = effect["prev_type"];
2250  if(prev_type.empty()) {
2251  prev_type = type().parent_id();
2252  }
2253  const std::string& new_type_id = effect["name"];
2254  const unit_type* new_type = unit_types.find(new_type_id);
2255  if(new_type) {
2256  advance_to(*new_type);
2257  preferences::encountered_units().insert(new_type_id);
2258  if(effect["heal_full"].to_bool(false)) {
2259  heal_fully();
2260  }
2261  } else {
2262  WRN_UT << "unknown type '" << new_type_id << "' (name=) in [effect]apply_to=type, ignoring";
2263  }
2264  }
2265 }
2266 
2267 void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
2268 {
2269  bool generate_description = mod["generate_description"].to_bool(true);
2270 
2271  if(no_add == false) {
2272  modifications_.add_child(mod_type, mod);
2273  }
2274 
2275  bool set_poisoned = false; // Tracks if the poisoned state was set after the type or variation was changed.
2276  config type_effect, variation_effect;
2277  std::vector<t_string> effects_description;
2278  for(const config& effect : mod.child_range("effect")) {
2279  // Apply SUF.
2280  if(const config& afilter = effect.child("filter")) {
2281  assert(resources::filter_con);
2282  if(!unit_filter(vconfig(afilter)).matches(*this, loc_)) {
2283  continue;
2284  }
2285  }
2286  const std::string& apply_to = effect["apply_to"];
2287  int times = effect["times"].to_int(1);
2288  t_string description;
2289 
2290  if(effect["times"] == "per level") {
2291  times = level_;
2292  }
2293 
2294  if(times) {
2295  while (times > 0) {
2296  times --;
2297 
2298  bool was_poisoned = get_state(STATE_POISONED);
2299  // Apply unit type/variation changes last to avoid double applying effects on advance.
2300  if(apply_to == "type") {
2301  set_poisoned = false;
2302  type_effect = effect;
2303  continue;
2304  }
2305  if(apply_to == "variation") {
2306  set_poisoned = false;
2307  variation_effect = effect;
2308  continue;
2309  }
2310 
2311  std::string description_component;
2312  if(resources::lua_kernel) {
2313  description_component = resources::lua_kernel->apply_effect(apply_to, *this, effect, true);
2314  } else if(builtin_effects.count(apply_to)) {
2315  // Normally, the built-in effects are dispatched through Lua so that a user
2316  // can override them if desired. However, since they're built-in, we can still
2317  // apply them if the lua kernel is unavailable.
2318  apply_builtin_effect(apply_to, effect);
2319  description_component = describe_builtin_effect(apply_to, effect);
2320  }
2321  if(!times) {
2322  description += description_component;
2323  }
2324  if(!was_poisoned && get_state(STATE_POISONED)) {
2325  set_poisoned = true;
2326  } else if(was_poisoned && !get_state(STATE_POISONED)) {
2327  set_poisoned = false;
2328  }
2329  } // end while
2330  } else { // for times = per level & level = 0 we still need to rebuild the descriptions
2331  if(resources::lua_kernel) {
2332  description += resources::lua_kernel->apply_effect(apply_to, *this, effect, false);
2333  } else if(builtin_effects.count(apply_to)) {
2334  description += describe_builtin_effect(apply_to, effect);
2335  }
2336  }
2337 
2338  if(effect["times"] == "per level" && !times) {
2339  description = VGETTEXT("$effect_description per level", {{"effect_description", description}});
2340  }
2341 
2342  if(!description.empty()) {
2343  effects_description.push_back(description);
2344  }
2345  }
2346  // Apply variations -- only apply if we are adding this for the first time.
2347  if((!type_effect.empty() || !variation_effect.empty()) && no_add == false) {
2348  if(!type_effect.empty()) {
2349  std::string description;
2350  if(resources::lua_kernel) {
2351  description = resources::lua_kernel->apply_effect(type_effect["apply_to"], *this, type_effect, true);
2352  } else if(builtin_effects.count(type_effect["apply_to"])) {
2353  apply_builtin_effect(type_effect["apply_to"], type_effect);
2354  description = describe_builtin_effect(type_effect["apply_to"], type_effect);
2355  }
2356  effects_description.push_back(description);
2357  }
2358  if(!variation_effect.empty()) {
2359  std::string description;
2360  if(resources::lua_kernel) {
2361  description = resources::lua_kernel->apply_effect(variation_effect["apply_to"], *this, variation_effect, true);
2362  } else if(builtin_effects.count(variation_effect["apply_to"])) {
2363  apply_builtin_effect(variation_effect["apply_to"], variation_effect);
2364  description = describe_builtin_effect(variation_effect["apply_to"], variation_effect);
2365  }
2366  effects_description.push_back(description);
2367  }
2368  if(set_poisoned)
2369  // An effect explicitly set the poisoned state, and this
2370  // should override the unit being immune to poison.
2371  set_state(STATE_POISONED, true);
2372  }
2373 
2374  t_string description;
2375 
2376  const t_string& mod_description = mod["description"];
2377  if(!mod_description.empty()) {
2378  description = mod_description;
2379  }
2380 
2381  // Punctuation should be translatable: not all languages use Latin punctuation.
2382  // (However, there maybe is a better way to do it)
2383  if(generate_description && !effects_description.empty()) {
2384  if(!mod_description.empty()) {
2385  description += "\n";
2386  }
2387 
2388  for(const auto& desc_line : effects_description) {
2389  description += desc_line + "\n";
2390  }
2391  }
2392 
2393  // store trait info
2394  if(mod_type == "trait") {
2395  add_trait_description(mod, description);
2396  }
2397 
2398  //NOTE: if not a trait, description is currently not used
2399 }
2400 
2401 void unit::add_trait_description(const config& trait, const t_string& description)
2402 {
2403  const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
2404  const auto& gender_specific_name = trait[gender_string];
2405 
2406  const t_string name = gender_specific_name.empty() ? trait["name"] : gender_specific_name;
2407 
2408  if(!name.empty()) {
2409  trait_names_.push_back(name);
2410  trait_descriptions_.push_back(description);
2411  }
2412 }
2413 
2414 std::string unit::absolute_image() const
2415 {
2416  return type().icon().empty() ? type().image() : type().icon();
2417 }
2418 
2419 std::string unit::default_anim_image() const
2420 {
2421  return type().image().empty() ? type().icon() : type().image();
2422 }
2423 
2425 {
2426  log_scope("apply mods");
2427 
2428  variables_.clear_children("mods");
2429  if(modifications_.has_child("advance")) {
2430  deprecated_message("[advance]", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [advancement] instead.");
2431  }
2433  add_modification(mod.key, mod.cfg, true);
2434  }
2435 }
2436 
2437 bool unit::invisible(const map_location& loc, bool see_all) const
2438 {
2439  if(loc != get_location()) {
2440  DBG_UT << "unit::invisible called: id = " << id() << " loc = " << loc << " get_loc = " << get_location();
2441  }
2442 
2443  // This is a quick condition to check, and it does not depend on the
2444  // location (so might as well bypass the location-based cache).
2445  if(get_state(STATE_UNCOVERED)) {
2446  return false;
2447  }
2448 
2449  // Fetch from cache
2450  /**
2451  * @todo FIXME: We use the cache only when using the default see_all=true
2452  * Maybe add a second cache if the see_all=false become more frequent.
2453  */
2454  if(see_all) {
2455  const auto itor = invisibility_cache_.find(loc);
2456  if(itor != invisibility_cache_.end()) {
2457  return itor->second;
2458  }
2459  }
2460 
2461  // Test hidden status
2462  static const std::string hides("hides");
2463  bool is_inv = get_ability_bool(hides, loc);
2464  if(is_inv){
2465  is_inv = (resources::gameboard ? !resources::gameboard->would_be_discovered(loc, side_,see_all) : true);
2466  }
2467 
2468  if(see_all) {
2469  // Add to caches
2470  if(invisibility_cache_.empty()) {
2471  units_with_cache.push_back(this);
2472  }
2473 
2474  invisibility_cache_[loc] = is_inv;
2475  }
2476 
2477  return is_inv;
2478 }
2479 
2480 bool unit::is_visible_to_team(const team& team, bool const see_all) const
2481 {
2482  const map_location& loc = get_location();
2483  return is_visible_to_team(loc, team, see_all);
2484 }
2485 
2486 bool unit::is_visible_to_team(const map_location& loc, const team& team, bool const see_all) const
2487 {
2488  if(!display::get_singleton()->get_map().on_board(loc)) {
2489  return false;
2490  }
2491 
2492  if(see_all) {
2493  return true;
2494  }
2495 
2496  if(team.is_enemy(side()) && invisible(loc)) {
2497  return false;
2498  }
2499 
2500  // allied planned moves are also visible under fog. (we assume that fake units on the map are always whiteboard markers)
2501  if(!team.is_enemy(side()) && underlying_id_.is_fake()) {
2502  return true;
2503  }
2504 
2505  // when the whiteboard planned unit map is applied, it uses moved the _real_ unit so
2506  // underlying_id_.is_fake() will be false and the check above will not apply.
2507  // TODO: improve this check so that is also works for allied planned units but without
2508  // breaking sp campaigns with allies under fog. We probably need an explicit flag
2509  // is_planned_ in unit that is set by the whiteboard.
2510  if(team.side() == side()) {
2511  return true;
2512  }
2513 
2514  if(team.fogged(loc)) {
2515  return false;
2516  }
2517 
2518  return true;
2519 }
2520 
2522 {
2523  if(underlying_id_.value == 0) {
2525  underlying_id_ = id_manager.next_id();
2526  } else {
2527  underlying_id_ = id_manager.next_fake_id();
2528  }
2529  }
2530 
2531  if(id_.empty() /*&& !underlying_id_.is_fake()*/) {
2532  std::stringstream ss;
2533  ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_.value;
2534  id_ = ss.str();
2535  }
2536 }
2537 
2538 unit& unit::mark_clone(bool is_temporary)
2539 {
2541  if(is_temporary) {
2542  underlying_id_ = ids.next_fake_id();
2543  } else {
2545  underlying_id_ = ids.next_id();
2546  }
2547  else {
2548  underlying_id_ = ids.next_fake_id();
2549  }
2550  std::string::size_type pos = id_.find_last_of('-');
2551  if(pos != std::string::npos && pos+1 < id_.size()
2552  && id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
2553  // this appears to be a duplicate of a generic unit, so give it a new id
2554  WRN_UT << "assigning new id to clone of generic unit " << id_;
2555  id_.clear();
2556  set_underlying_id(ids);
2557  }
2558  }
2559  return *this;
2560 }
2561 
2562 
2564  : u_(const_cast<unit&>(u))
2565  , moves_(u.movement_left(true))
2566 {
2567  if(operate) {
2569  }
2570 }
2571 
2573 {
2574  assert(resources::gameboard);
2575  try {
2576  if(!resources::gameboard->units().has_unit(&u_)) {
2577  /*
2578  * It might be valid that the unit is not in the unit map.
2579  * It might also mean a no longer valid unit will be assigned to.
2580  */
2581  DBG_UT << "The unit to be removed is not in the unit map.";
2582  }
2583 
2585  } catch(...) {
2586  DBG_UT << "Caught exception when destroying unit_movement_resetter: " << utils::get_unknown_exception_type();
2587  }
2588 }
2589 
2590 std::string unit::TC_image_mods() const
2591 {
2592  return formatter() << "~RC(" << flag_rgb() << ">" << team::get_side_color_id(side()) << ")";
2593 }
2594 
2595 std::string unit::image_mods() const
2596 {
2597  if(!image_mods_.empty()) {
2598  return formatter() << "~" << image_mods_ << TC_image_mods();
2599  }
2600 
2601  return TC_image_mods();
2602 }
2603 
2604 // Called by the Lua API after resetting an attack pointer.
2606 {
2607  set_attr_changed(UA_ATTACKS);
2608  auto iter = std::find(attacks_.begin(), attacks_.end(), atk);
2609  if(iter == attacks_.end()) {
2610  return false;
2611  }
2612  attacks_.erase(iter);
2613  return true;
2614 }
2615 
2617 {
2618  if(attacks_left_ == max_attacks_) {
2619  //TODO: add state_not_attacked
2620  }
2621 
2622  set_attacks(0);
2623 }
2624 
2626 {
2627  if(movement_left() == total_movement()) {
2628  set_state(STATE_NOT_MOVED,true);
2629  }
2630 
2631  set_movement(0, true);
2632 }
2633 
2634 void unit::set_hidden(bool state) const
2635 {
2636 // appearance_changed_ = true;
2637  hidden_ = state;
2638  if(!state) {
2639  return;
2640  }
2641 
2642  // TODO: this should really hide the halo, not destroy it
2643  // We need to get rid of haloes immediately to avoid display glitches
2644  anim_comp_->clear_haloes();
2645 }
2646 
2647 void unit::set_image_halo(const std::string& halo)
2648 {
2649  appearance_changed_ = true;
2650  anim_comp_->clear_haloes();
2651  halo_ = halo;
2652 }
2653 
2655 {
2656  if(upkeep.empty()) {
2657  return;
2658  }
2659 
2660  try {
2661  upkeep_ = upkeep.apply_visitor(upkeep_parser_visitor{});
2662  } catch(std::invalid_argument& e) {
2663  WRN_UT << "Found invalid upkeep=\"" << e.what() << "\" in a unit";
2664  upkeep_ = upkeep_full{};
2665  }
2666 }
2667 
2669 {
2670  upkeep = utils::visit(upkeep_type_visitor{}, upkeep_);
2671 }
2672 
2674 {
2675  changed_attributes_.reset();
2676  for(const auto& a_ptr : attacks_) {
2677  a_ptr->set_changed(false);
2678  }
2679 }
2680 
2681 std::vector<t_string> unit::unit_special_notes() const {
2682  return combine_special_notes(special_notes_, abilities(), attacks(), movement_type());
2683 }
2684 
2685 // Filters unimportant stats from the unit config and returns a checksum of
2686 // the remaining config.
2687 std::string get_checksum(const unit& u)
2688 {
2689  config unit_config;
2690  config wcfg;
2691  u.write(unit_config);
2692 
2693  static const std::set<std::string_view> main_keys {
2694  "advances_to",
2695  "alignment",
2696  "cost",
2697  "experience",
2698  "gender",
2699  "hitpoints",
2700  "ignore_race_traits",
2701  "ignore_global_traits",
2702  "level",
2703  "recall_cost",
2704  "max_attacks",
2705  "max_experience",
2706  "max_hitpoints",
2707  "max_moves",
2708  "movement",
2709  "movement_type",
2710  "race",
2711  "random_traits",
2712  "resting",
2713  "undead_variation",
2714  "upkeep",
2715  "zoc"
2716  };
2717 
2718  for(const std::string_view& main_key : main_keys) {
2719  wcfg[main_key] = unit_config[main_key];
2720  }
2721 
2722  static const std::set<std::string_view> attack_keys {
2723  "name",
2724  "type",
2725  "range",
2726  "damage",
2727  "number"
2728  };
2729 
2730  for(const config& att : unit_config.child_range("attack")) {
2731  config& child = wcfg.add_child("attack");
2732 
2733  for(const std::string_view& attack_key : attack_keys) {
2734  child[attack_key] = att[attack_key];
2735  }
2736 
2737  for(const config& spec : att.child_range("specials")) {
2738  config& child_spec = child.add_child("specials", spec);
2739 
2740  child_spec.recursive_clear_value("description");
2741  child_spec.recursive_clear_value("description_inactive");
2742  child_spec.recursive_clear_value("name");
2743  child_spec.recursive_clear_value("name_inactive");
2744  }
2745  }
2746 
2747  for(const config& abi : unit_config.child_range("abilities")) {
2748  config& child = wcfg.add_child("abilities", abi);
2749 
2750  child.recursive_clear_value("description");
2751  child.recursive_clear_value("description_inactive");
2752  child.recursive_clear_value("name");
2753  child.recursive_clear_value("name_inactive");
2754  }
2755 
2756  for(const config& trait : unit_config.child_range("trait")) {
2757  config& child = wcfg.add_child("trait", trait);
2758 
2759  child.recursive_clear_value("description");
2760  child.recursive_clear_value("male_name");
2761  child.recursive_clear_value("female_name");
2762  child.recursive_clear_value("name");
2763  }
2764 
2765  static const std::set<std::string_view> child_keys {
2766  "advance_from",
2767  "defense",
2768  "movement_costs",
2769  "vision_costs",
2770  "jamming_costs",
2771  "resistance"
2772  };
2773 
2774  for(const std::string_view& child_key : child_keys) {
2775  for(const config& c : unit_config.child_range(child_key)) {
2776  wcfg.add_child(child_key, c);
2777  }
2778  }
2779 
2780  DBG_UT << wcfg;
2781 
2782  return wcfg.hash();
2783 }
2784 
2785 void swap(unit& lhs, unit& rhs)
2786 {
2787  lhs.swap(rhs);
2788 }
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:1906
bool appearance_changed_
Definition: unit.hpp:1971
config modifications_
Definition: unit.hpp:1950
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:1933
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:98
void set_big_profile(const std::string &value)
Definition: unit.cpp:1807
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
const config * ability_cfg
The contents of the ability tag, never nullptr.
Definition: unit.hpp:61
int jamming_
Definition: unit.hpp:1897
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:2424
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:869
This class represents a single unit of a specific type.
Definition: unit.hpp:133
const std::string & type_id() const
The id of this unit&#39;s type.
Definition: unit.cpp:1802
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:1876
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:1948
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:1968
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:866
void set_usage(const std::string &usage)
Sets this unit&#39;s usage.
Definition: unit.hpp:695
static manager & get_singleton()
Definition: manager.hpp:145
bool hidden_
Definition: unit.hpp:1947
const std::vector< config > & modification_advancements() const
The raw, unparsed data for modification advancements.
Definition: unit.hpp:326
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:1967
bool resting_
Definition: unit.hpp:1903
std::string id_
Definition: unit.hpp:1864
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:1890
const std::string & big_profile() const
Definition: types.hpp:182
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:468
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:1881
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:502
std::vector< t_string > trait_names_
Definition: unit.hpp:1932
bool generate_name_
Definition: unit.hpp:1963
map_location loc_
Definition: unit.hpp:1851
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:1960
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:706
void set_image_ellipse(const std::string &ellipse)
Set the unit&#39;s ellipse image.
Definition: unit.hpp:1572
void set_small_profile(const std::string &value)
Definition: unit.hpp:599
The unit is a guardian - it won&#39;t move unless a target is sighted.
Definition: unit.hpp:870
config events_
Definition: unit.hpp:1916
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:1936
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:1955
const std::string & variation() const
The ID of the variation of this unit&#39;s type.
Definition: unit.hpp:575
void clear_changed_attributes()
Definition: unit.cpp:2673
int wml_x() const
Definition: location.hpp:153
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:864
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:1958
int experience_needed(bool with_acceleration=true) const
Definition: types.cpp:577
double xp_bar_scaling_
Definition: unit.hpp:1948
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:1945
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:865
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:2414
#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:1953
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:2616
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:1883
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:1425
std::string image_mods_
Definition: unit.hpp:1884
int recall_cost_
Definition: unit.hpp:1878
void set_total_movement(int value)
Definition: unit.hpp:1272
config filter_recall_
Definition: unit.hpp:1917
movetype movement_type_
Definition: unit.hpp:1899
bool canrecruit_
Definition: unit.hpp:1879
const_attr_itors attribute_range() const
Definition: config.cpp:858
bool hold_position_
Definition: unit.hpp:1901
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:1731
std::vector< std::string > recruit_list_
Definition: unit.hpp:1880
int hit_points_
Definition: unit.hpp:1871
int vision_
Definition: unit.hpp:1896
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:1935
state_t
Built-in status effects known to the engine.
Definition: unit.hpp:862
map_location loc_
upkeep_t upkeep_
Definition: unit.hpp:1965
void set_level(int level)
Sets the current level of this unit.
Definition: unit.hpp:568
std::bitset< num_bool_states > known_boolean_states_
Definition: unit.hpp:1912
const unit_type & type() const
This unit&#39;s type, accounting for gender and variation.
Definition: unit.hpp:358
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:544
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:2267
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:1938
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:1921
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:984
#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:1915
Data typedef for unit_ability_list.
Definition: unit.hpp:39
static const std::string & leader_crown()
The path to the leader crown overlay.
Definition: unit.cpp:1039
bool unrenamable_
Definition: unit.hpp:1886
static std::map< std::string, state_t > known_boolean_state_names_
Definition: unit.hpp:1913
int max_experience_
Definition: unit.hpp:1874
std::vector< t_string > special_notes_
Definition: unit.hpp:1956
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:383
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:1724
void set_underlying_id(n_unit::id_manager &id_manager)
Sets the internal ID.
Definition: unit.cpp:2521
int movement_
Definition: unit.hpp:1894
const movetype & movement_type() const
Definition: types.hpp:191
unit_alignments::type alignment() const
Definition: types.hpp:195
bool is_healthy_
Definition: unit.hpp:1938
int max_hit_points_
Definition: unit.hpp:1872
t_string name_
Definition: unit.hpp:1865
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:193
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:2654
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:537
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:484
#define WRN_UT
Definition: unit.cpp:71
const t_string & name() const
Gets this unit&#39;s translatable display name.
Definition: unit.hpp:406
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:241
bool is_enemy(int n) const
Definition: team.hpp:231
int side_
Definition: unit.hpp:1888
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:2681
void set_hidden(bool state) const
Sets whether the unit is hidden on the map.
Definition: unit.cpp:2634
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:2419
std::optional< std::string > halo_
Definition: unit.hpp:1959
const std::vector< std::string > & overlays() const
Get the unit&#39;s overlay images.
Definition: unit.hpp:1600
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:867
void set_advancements(std::vector< config > advancements)
Sets the raw modification advancement option data.
Definition: unit.cpp:1796
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:1895
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:727
std::string role_
Definition: unit.hpp:1923
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:1814
void set_attr_changed(UNIT_ATTRIBUTE attr)
Definition: unit.hpp:186
t_string type_name_
The displayed name of this unit type.
Definition: unit.hpp:1859
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:2538
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:1908
bool invisible(const map_location &loc, bool see_all=true) const
Definition: unit.cpp:2437
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:1862
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 erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:104
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:2647
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:2785
int attacks_left_
Definition: unit.hpp:1905
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:581
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:508
Visitor helper class to fetch the appropriate upkeep value.
Definition: unit.hpp:1141
std::string get_checksum(const unit &u)
Gets a checksum for a unit.
Definition: unit.cpp:2687
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:1351
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:2401
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:615
std::map< std::string, std::string > advancement_icons() const
Gets and image path and and associated description for each advancement option.
Definition: unit.cpp:1674
#define log_scope(description)
Definition: log.hpp:237
std::string halo() const
Definition: types.hpp:183
static std::string type()
Definition: unit.hpp:1135
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:1853
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:1892
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:834
n_unit::unit_id underlying_id_
Definition: unit.hpp:1866
void set_max_hitpoints(int value)
Definition: unit.hpp:513
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::string variation_
Definition: unit.hpp:1869
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:868
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:1924
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:1981
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:2480
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:627
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:1868
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:1856
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:2668
unsigned int num_traits() const
Definition: types.hpp:138
static const std::set< std::string > builtin_effects
Definition: unit.hpp:1518
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:1731
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:1358
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:275
void apply_builtin_effect(std::string type, const config &effect)
Apply a builtin effect to the unit.
Definition: unit.cpp:1905
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:453
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:1840
map_location goto_
Definition: unit.hpp:1936
double hp_bar_scaling() const
Definition: types.hpp:165
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1267
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:346
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:1191
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:1714
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:1171
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:2590
void remove_movement_ai()
Sets the unit to have no moves left for this turn.
Definition: unit.cpp:2625
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:247
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:643
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:1283
bool random_traits_
Definition: unit.hpp:1962
config abilities_
Definition: unit.hpp:1951
bool empty() const
Definition: config.cpp:941
std::bitset< UA_COUNT > changed_attributes_
Definition: unit.hpp:1972
int experience_
Definition: unit.hpp:1873
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition: unit.cpp:2595
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
utils::string_map modification_descriptions_
Definition: unit.hpp:1940
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:1902
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:1929
bool emit_zoc_
Definition: unit.hpp:1919
bool remove_attack(attack_ptr atk)
Remove an attack from the unit.
Definition: unit.cpp:2605
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46