The Battle for Wesnoth  1.17.12+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  config::const_child_itors current_traits = modifications_.child_range("trait");
810 
811  // Handle must-have only at the beginning
812  for(const config& t : u_type.possible_traits()) {
813  // Skip the trait if the unit already has it.
814  const std::string& tid = t["id"];
815  bool already = false;
816  for(const config& mod : current_traits) {
817  if(mod["id"] == tid) {
818  already = true;
819  break;
820  }
821  }
822  if(already) {
823  continue;
824  }
825  // Add the trait if it is mandatory.
826  const std::string& avl = t["availability"];
827  if(avl == "musthave") {
828  modifications_.add_child("trait", t);
829  current_traits = modifications_.child_range("trait");
830  continue;
831  }
832  }
833 
834  if(must_have_only) {
835  return;
836  }
837 
838  std::vector<const config*> candidate_traits;
839  std::vector<std::string> temp_require_traits;
840  std::vector<std::string> temp_exclude_traits;
841 
842  // Now randomly fill out to the number of traits required or until
843  // there aren't any more traits.
844  int nb_traits = current_traits.size();
845  int max_traits = u_type.num_traits();
846  for(; nb_traits < max_traits; ++nb_traits)
847  {
848  current_traits = modifications_.child_range("trait");
849  candidate_traits.clear();
850  for(const config& t : u_type.possible_traits()) {
851  // Skip the trait if the unit already has it.
852  const std::string& tid = t["id"];
853  bool already = false;
854  for(const config& mod : current_traits) {
855  if(mod["id"] == tid) {
856  already = true;
857  break;
858  }
859  }
860 
861  if(already) {
862  continue;
863  }
864  // Skip trait if trait requirements are not met
865  // or trait exclusions are present
866  temp_require_traits = utils::split(t["require_traits"]);
867  temp_exclude_traits = utils::split(t["exclude_traits"]);
868 
869  // See if the unit already has a trait that excludes the current one
870  for(const config& mod : current_traits) {
871  if (mod["exclude_traits"] != "") {
872  for (const auto& c: utils::split(mod["exclude_traits"])) {
873  temp_exclude_traits.push_back(c);
874  }
875  }
876  }
877 
878  // First check for requirements
879  bool trait_req_met = true;
880  for(const std::string& s : temp_require_traits) {
881  bool has_trait = false;
882  for(const config& mod : current_traits) {
883  if (mod["id"] == s)
884  has_trait = true;
885  }
886  if(!has_trait) {
887  trait_req_met = false;
888  break;
889  }
890  }
891  if(!trait_req_met) {
892  continue;
893  }
894 
895  // Now check for exclusionary traits
896  bool trait_exc_met = true;
897 
898  for(const std::string& s : temp_exclude_traits) {
899  bool has_exclusionary_trait = false;
900  for(const config& mod : current_traits) {
901  if (mod["id"] == s)
902  has_exclusionary_trait = true;
903  }
904  if (tid == s) {
905  has_exclusionary_trait = true;
906  }
907  if(has_exclusionary_trait) {
908  trait_exc_met = false;
909  break;
910  }
911  }
912  if(!trait_exc_met) {
913  continue;
914  }
915 
916  const std::string& avl = t["availability"];
917  // The trait is still available, mark it as a candidate for randomizing.
918  // For leaders, only traits with availability "any" are considered.
919  if(!must_have_only && (!can_recruit() || avl == "any")) {
920  candidate_traits.push_back(&t);
921  }
922  }
923  // No traits available anymore? Break
924  if(candidate_traits.empty()) {
925  break;
926  }
927 
928  int num = randomness::generator->get_random_int(0,candidate_traits.size()-1);
929  modifications_.add_child("trait", *candidate_traits[num]);
930  candidate_traits.erase(candidate_traits.begin() + num);
931  }
932  // Once random traits are added, don't do it again.
933  // Such as when restoring a saved character.
934  random_traits_ = false;
935 }
936 
937 std::vector<std::string> unit::get_traits_list() const
938 {
939  std::vector<std::string> res;
940 
941  for(const config& mod : modifications_.child_range("trait"))
942  {
943  // Make sure to return empty id trait strings as otherwise
944  // names will not match in length (Bug #21967)
945  res.push_back(mod["id"]);
946  }
947  return res;
948 }
949 
950 
951 /**
952  * Advances this unit to the specified type.
953  * Experience is left unchanged.
954  * Current hit point total is left unchanged unless it would violate max HP.
955  * Assumes gender_ and variation_ are set to their correct values.
956  */
957 void unit::advance_to(const unit_type& u_type, bool use_traits)
958 {
959  appearance_changed_ = true;
960  // For reference, the type before this advancement.
961  const unit_type& old_type = type();
962  // Adjust the new type for gender and variation.
964  // In case u_type was already a variation, make sure our variation is set correctly.
965  variation_ = new_type.variation_id();
966 
967  // Reset the scalar values first
968  trait_names_.clear();
969  trait_descriptions_.clear();
970  is_fearless_ = false;
971  is_healthy_ = false;
972  image_mods_.clear();
973  overlays_.clear();
974  ellipse_.reset();
975 
976  // Clear modification-related caches
978 
979 
980  if(!new_type.usage().empty()) {
981  set_usage(new_type.usage());
982  }
983 
984  set_image_halo(new_type.halo());
985  if(!new_type.ellipse().empty()) {
986  set_image_ellipse(new_type.ellipse());
987  }
988 
989  generate_name_ &= new_type.generate_name();
990  abilities_ = new_type.abilities_cfg();
991  advancements_.clear();
992 
993  for(const config& advancement : new_type.advancements()) {
994  advancements_.push_back(advancement);
995  }
996 
997  // If unit has specific profile, remember it and keep it after advancing
998  if(small_profile_.empty() || small_profile_ == old_type.small_profile()) {
999  small_profile_ = new_type.small_profile();
1000  }
1001 
1002  if(profile_.empty() || profile_ == old_type.big_profile()) {
1003  profile_ = new_type.big_profile();
1004  }
1005  // NOTE: There should be no need to access old_cfg (or new_cfg) after this
1006  // line. Particularly since the swap might have affected old_cfg.
1007 
1008  advances_to_ = new_type.advances_to();
1009 
1010  race_ = new_type.race();
1011  type_ = &new_type;
1012  type_name_ = new_type.type_name();
1013  description_ = new_type.unit_description();
1014  special_notes_ = new_type.direct_special_notes();
1015  undead_variation_ = new_type.undead_variation();
1016  max_experience_ = new_type.experience_needed(true);
1017  level_ = new_type.level();
1018  recall_cost_ = new_type.recall_cost();
1019  alignment_ = new_type.alignment();
1020  max_hit_points_ = new_type.hitpoints();
1021  hp_bar_scaling_ = new_type.hp_bar_scaling();
1022  xp_bar_scaling_ = new_type.xp_bar_scaling();
1023  max_movement_ = new_type.movement();
1024  vision_ = new_type.vision(true);
1025  jamming_ = new_type.jamming();
1026  movement_type_ = new_type.movement_type();
1027  emit_zoc_ = new_type.has_zoc();
1028  attacks_.clear();
1029  std::transform(new_type.attacks().begin(), new_type.attacks().end(), std::back_inserter(attacks_), [](const attack_type& atk) {
1030  return std::make_shared<attack_type>(atk);
1031  });
1032  unit_value_ = new_type.cost();
1033 
1034  max_attacks_ = new_type.max_attacks();
1035 
1036  flag_rgb_ = new_type.flag_rgb();
1037 
1038  upkeep_ = upkeep_full{};
1039  parse_upkeep(new_type.get_cfg()["upkeep"]);
1040 
1041  anim_comp_->reset_after_advance(&new_type);
1042 
1043  if(random_traits_) {
1044  generate_traits(!use_traits);
1045  } else {
1046  // This will add any "musthave" traits to the new unit that it doesn't already have.
1047  // This covers the Dark Sorcerer advancing to Lich and gaining the "undead" trait,
1048  // but random and/or optional traits are not added,
1049  // and neither are inappropriate traits removed.
1050  generate_traits(true);
1051  }
1052 
1053  // Apply modifications etc, refresh the unit.
1054  // This needs to be after type and gender are fixed,
1055  // since there can be filters on the modifications
1056  // that may result in different effects after the advancement.
1058 
1059  // Now that modifications are done modifying traits, check if poison should
1060  // be cleared.
1061  if(get_state("unpoisonable")) {
1062  set_state(STATE_POISONED, false);
1063  }
1064  if(get_state("unslowable")) {
1065  set_state(STATE_SLOWED, false);
1066  }
1067  if(get_state("unpetrifiable")) {
1068  set_state(STATE_PETRIFIED, false);
1069  }
1070 
1071  // Now that modifications are done modifying the maximum hit points,
1072  // enforce this maximum.
1075  }
1076 
1077  // In case the unit carries EventWML, apply it now
1080  }
1081  bool bool_small_profile = get_attr_changed(UA_SMALL_PROFILE);
1082  bool bool_profile = get_attr_changed(UA_PROFILE);
1084  if(bool_small_profile && small_profile_ != new_type.small_profile()) {
1086  }
1087 
1088  if(bool_profile && profile_ != new_type.big_profile()) {
1090  }
1091 }
1092 
1093 std::string unit::big_profile() const
1094 {
1095  if(!profile_.empty() && profile_ != "unit_image") {
1096  return profile_;
1097  }
1098 
1099  return absolute_image();
1100 }
1101 
1102 std::string unit::small_profile() const
1103 {
1104  if(!small_profile_.empty() && small_profile_ != "unit_image") {
1105  return small_profile_;
1106  }
1107 
1108  if(!profile_.empty() && small_profile_ != "unit_image" && profile_ != "unit_image") {
1109  return profile_;
1110  }
1111 
1112  return absolute_image();
1113 }
1114 
1115 const std::string& unit::leader_crown()
1116 {
1117  return leader_crown_path;
1118 }
1119 
1120 const std::string& unit::flag_rgb() const
1121 {
1122  return flag_rgb_.empty() ? game_config::unit_rgb : flag_rgb_;
1123 }
1124 
1126 {
1127  double unit_energy = 0.0;
1128  color_t energy_color {0,0,0,255};
1129 
1130  if(max_hitpoints > 0) {
1131  unit_energy = static_cast<double>(hitpoints)/static_cast<double>(max_hitpoints);
1132  }
1133 
1134  if(1.0 == unit_energy) {
1135  energy_color.r = 33;
1136  energy_color.g = 225;
1137  energy_color.b = 0;
1138  } else if(unit_energy > 1.0) {
1139  energy_color.r = 100;
1140  energy_color.g = 255;
1141  energy_color.b = 100;
1142  } else if(unit_energy >= 0.75) {
1143  energy_color.r = 170;
1144  energy_color.g = 255;
1145  energy_color.b = 0;
1146  } else if(unit_energy >= 0.5) {
1147  energy_color.r = 255;
1148  energy_color.g = 175;
1149  energy_color.b = 0;
1150  } else if(unit_energy >= 0.25) {
1151  energy_color.r = 255;
1152  energy_color.g = 155;
1153  energy_color.b = 0;
1154  } else {
1155  energy_color.r = 255;
1156  energy_color.g = 0;
1157  energy_color.b = 0;
1158  }
1159 
1160  return energy_color;
1161 }
1162 
1164 {
1165  return hp_color_impl(hitpoints(), max_hitpoints());
1166 }
1167 
1168 color_t unit::hp_color(int new_hitpoints) const
1169 {
1170  return hp_color_impl(new_hitpoints, hitpoints());
1171 }
1172 
1174 {
1175  return hp_color_impl(1, 1);
1176 }
1177 
1178 color_t unit::xp_color(int xp_to_advance, bool can_advance, bool has_amla)
1179 {
1180  const color_t near_advance_color {255,255,255,255};
1181  const color_t mid_advance_color {150,255,255,255};
1182  const color_t far_advance_color {0,205,205,255};
1183  const color_t normal_color {0,160,225,255};
1184  const color_t near_amla_color {225,0,255,255};
1185  const color_t mid_amla_color {169,30,255,255};
1186  const color_t far_amla_color {139,0,237,255};
1187  const color_t amla_color {170,0,255,255};
1188 
1189  const bool near_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience;
1190  const bool mid_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience*2;
1191  const bool far_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience*3;
1192 
1193  color_t color = normal_color;
1194  if(can_advance){
1195  if(near_advance){
1196  color=near_advance_color;
1197  } else if(mid_advance){
1198  color=mid_advance_color;
1199  } else if(far_advance){
1200  color=far_advance_color;
1201  }
1202  } else if(has_amla){
1203  if(near_advance){
1204  color=near_amla_color;
1205  } else if(mid_advance){
1206  color=mid_amla_color;
1207  } else if(far_advance){
1208  color=far_amla_color;
1209  } else {
1210  color=amla_color;
1211  }
1212  }
1213 
1214  return(color);
1215 }
1216 
1218 {
1219  bool major_amla = false;
1220  bool has_amla = false;
1221  for(const config& adv:get_modification_advances()){
1222  major_amla |= adv["major_amla"].to_bool();
1223  has_amla = true;
1224  }
1225  //TODO: calculating has_amla and major_amla can be a quite slow operation, we should cache these two values somehow.
1226  return xp_color(experience_to_advance(), !advances_to().empty() || major_amla, has_amla);
1227 }
1228 
1229 void unit::set_recruits(const std::vector<std::string>& recruits)
1230 {
1231  unit_types.check_types(recruits);
1233 }
1234 
1235 const std::vector<std::string> unit::advances_to_translated() const
1236 {
1237  std::vector<std::string> result;
1238  for(const std::string& adv_type_id : advances_to_) {
1239  if(const unit_type* adv_type = unit_types.find(adv_type_id)) {
1240  result.push_back(adv_type->type_name());
1241  } else {
1242  WRN_UT << "unknown unit in advances_to list of type "
1243  << type().log_id() << ": " << adv_type_id;
1244  }
1245  }
1246 
1247  return result;
1248 }
1249 
1250 void unit::set_advances_to(const std::vector<std::string>& advances_to)
1251 {
1253  unit_types.check_types(advances_to);
1255 }
1256 
1257 void unit::set_movement(int moves, bool unit_action)
1258 {
1259  // If this was because the unit acted, clear its "not acting" flags.
1260  if(unit_action) {
1261  end_turn_ = hold_position_ = false;
1262  }
1263 
1264  movement_ = std::max<int>(0, moves);
1265 }
1266 
1267 /**
1268  * Determines if @a mod_dur "matches" @a goal_dur.
1269  * If goal_dur is not empty, they match if they are equal.
1270  * If goal_dur is empty, they match if mod_dur is neither empty nor "forever".
1271  * Helper function for expire_modifications().
1272  */
1273 inline bool mod_duration_match(const std::string& mod_dur, const std::string& goal_dur)
1274 {
1275  if(goal_dur.empty()) {
1276  // Default is all temporary modifications.
1277  return !mod_dur.empty() && mod_dur != "forever";
1278  }
1279 
1280  return mod_dur == goal_dur;
1281 }
1282 
1283 void unit::expire_modifications(const std::string& duration)
1284 {
1285  // If any modifications expire, then we will need to rebuild the unit.
1286  const unit_type* rebuild_from = nullptr;
1287  int hp = hit_points_;
1288  int mp = movement_;
1289  // Loop through all types of modifications.
1290  for(const auto& mod_name : ModificationTypes) {
1291  // Loop through all modifications of this type.
1292  // Looping in reverse since we may delete the current modification.
1293  for(int j = modifications_.child_count(mod_name)-1; j >= 0; --j)
1294  {
1295  const config& mod = modifications_.child(mod_name, j);
1296 
1297  if(mod_duration_match(mod["duration"], duration)) {
1298  // If removing this mod means reverting the unit's type:
1299  if(const config::attribute_value* v = mod.get("prev_type")) {
1300  rebuild_from = &get_unit_type(v->str());
1301  }
1302  // Else, if we have not already specified a type to build from:
1303  else if(rebuild_from == nullptr) {
1304  rebuild_from = &type();
1305  }
1306 
1307  modifications_.remove_child(mod_name, j);
1308  }
1309  }
1310  }
1311 
1312  if(rebuild_from != nullptr) {
1313  anim_comp_->clear_haloes();
1314  advance_to(*rebuild_from);
1315  hit_points_ = hp;
1316  movement_ = std::min(mp, max_movement_);
1317  }
1318 }
1319 
1321 {
1322  expire_modifications("turn");
1323 
1327  set_state(STATE_UNCOVERED, false);
1328 }
1329 
1331 {
1332  expire_modifications("turn end");
1333 
1334  set_state(STATE_SLOWED,false);
1336  resting_ = false;
1337  }
1338 
1339  set_state(STATE_NOT_MOVED,false);
1340  // Clear interrupted move
1342 }
1343 
1345 {
1346  // Set the goto-command to be going to no-where
1347  goto_ = map_location();
1348 
1349  // Expire all temporary modifications.
1351 
1352  heal_fully();
1353  set_state(STATE_SLOWED, false);
1354  set_state(STATE_POISONED, false);
1355  set_state(STATE_PETRIFIED, false);
1356  set_state(STATE_GUARDIAN, false);
1357 }
1358 
1359 void unit::heal(int amount)
1360 {
1361  int max_hp = max_hitpoints();
1362  if(hit_points_ < max_hp) {
1363  hit_points_ += amount;
1364 
1365  if(hit_points_ > max_hp) {
1366  hit_points_ = max_hp;
1367  }
1368  }
1369 
1370  if(hit_points_<1) {
1371  hit_points_ = 1;
1372  }
1373 }
1374 
1375 const std::set<std::string> unit::get_states() const
1376 {
1377  std::set<std::string> all_states = states_;
1378  for(const auto& state : known_boolean_state_names_) {
1379  if(get_state(state.second)) {
1380  all_states.insert(state.first);
1381  }
1382  }
1383 
1384  // Backwards compatibility for not_living. Don't remove before 1.12
1385  if(all_states.count("undrainable") && all_states.count("unpoisonable") && all_states.count("unplagueable")) {
1386  all_states.insert("not_living");
1387  }
1388 
1389  return all_states;
1390 }
1391 
1392 bool unit::get_state(const std::string& state) const
1393 {
1394  state_t known_boolean_state_id = get_known_boolean_state_id(state);
1395  if(known_boolean_state_id!=STATE_UNKNOWN){
1396  return get_state(known_boolean_state_id);
1397  }
1398 
1399  // Backwards compatibility for not_living. Don't remove before 1.12
1400  if(state == "not_living") {
1401  return
1402  get_state("undrainable") &&
1403  get_state("unpoisonable") &&
1404  get_state("unplagueable");
1405  }
1406 
1407  return states_.find(state) != states_.end();
1408 }
1409 
1410 void unit::set_state(state_t state, bool value)
1411 {
1412  known_boolean_states_[state] = value;
1413 }
1414 
1415 bool unit::get_state(state_t state) const
1416 {
1417  return known_boolean_states_[state];
1418 }
1419 
1421 {
1422  auto i = known_boolean_state_names_.find(state);
1423  if(i != known_boolean_state_names_.end()) {
1424  return i->second;
1425  }
1426 
1427  return STATE_UNKNOWN;
1428 }
1429 
1430 std::map<std::string, unit::state_t> unit::known_boolean_state_names_ {
1431  {"slowed", STATE_SLOWED},
1432  {"poisoned", STATE_POISONED},
1433  {"petrified", STATE_PETRIFIED},
1434  {"uncovered", STATE_UNCOVERED},
1435  {"not_moved", STATE_NOT_MOVED},
1436  {"unhealable", STATE_UNHEALABLE},
1437  {"guardian", STATE_GUARDIAN},
1438 };
1439 
1440 void unit::set_state(const std::string& state, bool value)
1441 {
1442  appearance_changed_ = true;
1443  state_t known_boolean_state_id = get_known_boolean_state_id(state);
1444  if(known_boolean_state_id != STATE_UNKNOWN) {
1445  set_state(known_boolean_state_id, value);
1446  return;
1447  }
1448 
1449  // Backwards compatibility for not_living. Don't remove before 1.12
1450  if(state == "not_living") {
1451  set_state("undrainable", value);
1452  set_state("unpoisonable", value);
1453  set_state("unplagueable", value);
1454  }
1455 
1456  if(value) {
1457  states_.insert(state);
1458  } else {
1459  states_.erase(state);
1460  }
1461 }
1462 
1463 bool unit::has_ability_by_id(const std::string& ability) const
1464 {
1465  for(const config::any_child ab : abilities_.all_children_range()) {
1466  if(ab.cfg["id"] == ability) {
1467  return true;
1468  }
1469  }
1470 
1471  return false;
1472 }
1473 
1474 void unit::remove_ability_by_id(const std::string& ability)
1475 {
1478  while (i != abilities_.ordered_end()) {
1479  if(i->cfg["id"] == ability) {
1480  i = abilities_.erase(i);
1481  } else {
1482  ++i;
1483  }
1484  }
1485 }
1486 
1488 {
1489  for(const auto& a_ptr : attacks_) {
1490  if(a_ptr->get_changed()) {
1491  return true;
1492  }
1493 
1494  }
1495  return false;
1496 }
1497 void unit::write(config& cfg, bool write_all) const
1498 {
1499  config back;
1500  auto write_subtag = [&](const std::string& key, const config& child)
1501  {
1502  cfg.clear_children(key);
1503 
1504  if(!child.empty()) {
1505  cfg.add_child(key, child);
1506  } else {
1507  back.add_child(key, child);
1508  }
1509  };
1510 
1511  if(write_all || get_attr_changed(UA_MOVEMENT_TYPE)) {
1512  movement_type_.write(cfg, false);
1513  }
1514  if(write_all || get_attr_changed(UA_SMALL_PROFILE)) {
1515  cfg["small_profile"] = small_profile_;
1516  }
1517  if(write_all || get_attr_changed(UA_PROFILE)) {
1518  cfg["profile"] = profile_;
1519  }
1520  if(description_ != type().unit_description()) {
1521  cfg["description"] = description_;
1522  }
1523  if(write_all || get_attr_changed(UA_NOTES)) {
1524  for(const t_string& note : special_notes_) {
1525  cfg.add_child("special_note")["note"] = note;
1526  }
1527  }
1528 
1529  if(halo_) {
1530  cfg["halo"] = *halo_;
1531  }
1532 
1533  if(ellipse_) {
1534  cfg["ellipse"] = *ellipse_;
1535  }
1536 
1537  if(usage_) {
1538  cfg["usage"] = *usage_;
1539  }
1540 
1541  write_upkeep(cfg["upkeep"]);
1542 
1543  cfg["hitpoints"] = hit_points_;
1544  if(write_all || get_attr_changed(UA_MAX_HP)) {
1545  cfg["max_hitpoints"] = max_hit_points_;
1546  }
1547  cfg["image_icon"] = type().icon();
1548  cfg["image"] = type().image();
1549  cfg["random_traits"] = random_traits_;
1550  cfg["generate_name"] = generate_name_;
1551  cfg["experience"] = experience_;
1552  if(write_all || get_attr_changed(UA_MAX_XP)) {
1553  cfg["max_experience"] = max_experience_;
1554  }
1555  cfg["recall_cost"] = recall_cost_;
1556 
1557  cfg["side"] = side_;
1558 
1559  cfg["type"] = type_id();
1560 
1561  if(type_id() != type().parent_id()) {
1562  cfg["parent_type"] = type().parent_id();
1563  }
1564 
1565  // Support for unit formulas in [ai] and unit-specific variables in [ai] [vars]
1566  formula_man_->write(cfg);
1567 
1568  cfg["gender"] = gender_string(gender_);
1569  cfg["variation"] = variation_;
1570  cfg["role"] = role_;
1571 
1572  config status_flags;
1573  for(const std::string& state : get_states()) {
1574  status_flags[state] = true;
1575  }
1576 
1577  write_subtag("variables", variables_);
1578  write_subtag("filter_recall", filter_recall_);
1579  write_subtag("status", status_flags);
1580 
1581  cfg.clear_children("events");
1582  cfg.append(events_);
1583 
1584  // Overlays are exported as the modifications that add them, not as an overlays= value,
1585  // however removing the key breaks the Gui Debug Tools.
1586  // \todo does anything depend on the key's value, other than the re-import code in unit::init?
1587  cfg["overlays"] = "";
1588 
1589  cfg["name"] = name_;
1590  cfg["id"] = id_;
1591  cfg["underlying_id"] = underlying_id_.value;
1592 
1593  if(can_recruit()) {
1594  cfg["canrecruit"] = true;
1595  }
1596 
1597  cfg["extra_recruit"] = utils::join(recruit_list_);
1598 
1599  cfg["facing"] = map_location::write_direction(facing_);
1600 
1601  cfg["goto_x"] = goto_.wml_x();
1602  cfg["goto_y"] = goto_.wml_y();
1603 
1604  cfg["moves"] = movement_;
1605  if(write_all || get_attr_changed(UA_MAX_MP)) {
1606  cfg["max_moves"] = max_movement_;
1607  }
1608  cfg["vision"] = vision_;
1609  cfg["jamming"] = jamming_;
1610 
1611  cfg["resting"] = resting_;
1612 
1613  if(write_all || get_attr_changed(UA_ADVANCE_TO)) {
1614  cfg["advances_to"] = utils::join(advances_to_);
1615  }
1616 
1617  cfg["race"] = race_->id();
1618  cfg["language_name"] = type_name_;
1619  cfg["undead_variation"] = undead_variation_;
1620  if(write_all || get_attr_changed(UA_LEVEL)) {
1621  cfg["level"] = level_;
1622  }
1623  if(write_all || get_attr_changed(UA_ALIGNMENT)) {
1624  cfg["alignment"] = unit_alignments::get_string(alignment_);
1625  }
1626  cfg["flag_rgb"] = flag_rgb_;
1627  cfg["unrenamable"] = unrenamable_;
1628 
1629  cfg["attacks_left"] = attacks_left_;
1630  if(write_all || get_attr_changed(UA_MAX_AP)) {
1631  cfg["max_attacks"] = max_attacks_;
1632  }
1633  if(write_all || get_attr_changed(UA_ZOC)) {
1634  cfg["zoc"] = emit_zoc_;
1635  }
1636  cfg["hidden"] = hidden_;
1637 
1638  if(write_all || get_attr_changed(UA_ATTACKS) || get_attacks_changed()) {
1639  cfg.clear_children("attack");
1640  for(attack_ptr i : attacks_) {
1641  i->write(cfg.add_child("attack"));
1642  }
1643  }
1644 
1645  cfg["cost"] = unit_value_;
1646 
1647  write_subtag("modifications", modifications_);
1648  if(write_all || get_attr_changed(UA_ABILITIES)) {
1649  write_subtag("abilities", abilities_);
1650  }
1651  if(write_all || get_attr_changed(UA_ADVANCEMENTS)) {
1652  cfg.clear_children("advancement");
1653  for(const config& advancement : advancements_) {
1654  if(!advancement.empty()) {
1655  cfg.add_child("advancement", advancement);
1656  }
1657  }
1658  }
1659  cfg.append(back);
1660 }
1661 
1663 {
1664  if(dir != map_location::NDIRECTIONS && dir != facing_) {
1665  appearance_changed_ = true;
1666  facing_ = dir;
1667  }
1668  // Else look at yourself (not available so continue to face the same direction)
1669 }
1670 
1671 int unit::upkeep() const
1672 {
1673  // Leaders do not incur upkeep.
1674  if(can_recruit()) {
1675  return 0;
1676  }
1677 
1678  return utils::visit(upkeep_value_visitor{*this}, upkeep_);
1679 }
1680 
1681 bool unit::loyal() const
1682 {
1683  return utils::holds_alternative<upkeep_loyal>(upkeep_);
1684 }
1685 
1687 {
1688  int def = movement_type_.defense_modifier(terrain);
1689 #if 0
1690  // A [defense] ability is too costly and doesn't take into account target locations.
1691  // Left as a comment in case someone ever wonders why it isn't a good idea.
1692  unit_ability_list defense_abilities = get_abilities("defense");
1693  if(!defense_abilities.empty()) {
1694  unit_abilities::effect defense_effect(defense_abilities, def);
1695  def = defense_effect.get_composite_value();
1696  }
1697 #endif
1698  return def;
1699 }
1700 
1701 bool unit::resistance_filter_matches(const config& cfg, bool attacker, const std::string& damage_name, int res) const
1702 {
1703  if(!(cfg["active_on"].empty() || (attacker && cfg["active_on"] == "offense") || (!attacker && cfg["active_on"] == "defense"))) {
1704  return false;
1705  }
1706 
1707  const std::string& apply_to = cfg["apply_to"];
1708  if(!apply_to.empty()) {
1709  if(damage_name != apply_to) {
1710  if(apply_to.find(',') != std::string::npos &&
1711  apply_to.find(damage_name) != std::string::npos) {
1712  const std::vector<std::string>& vals = utils::split(apply_to);
1713  if(std::find(vals.begin(),vals.end(),damage_name) == vals.end()) {
1714  return false;
1715  }
1716  } else {
1717  return false;
1718  }
1719  }
1720  }
1721 
1722  if(!unit_abilities::filter_base_matches(cfg, res)) {
1723  return false;
1724  }
1725 
1726  return true;
1727 }
1728 
1729 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
1730 {
1731  int res = movement_type_.resistance_against(damage_name);
1732 
1733  unit_ability_list resistance_abilities = get_abilities_weapons("resistance",loc, weapon, opp_weapon);
1734  utils::erase_if(resistance_abilities, [&](const unit_ability& i) {
1735  return !resistance_filter_matches(*i.ability_cfg, attacker, damage_name, 100-res);
1736  });
1737 
1738  if(!resistance_abilities.empty()) {
1739  unit_abilities::effect resist_effect(resistance_abilities, 100-res);
1740 
1741  res = 100 - std::min<int>(
1742  resist_effect.get_composite_value(),
1743  resistance_abilities.highest("max_value").first
1744  );
1745  }
1746 
1747  return res;
1748 }
1749 
1750 std::map<std::string, std::string> unit::advancement_icons() const
1751 {
1752  std::map<std::string,std::string> temp;
1753  if(!can_advance()) {
1754  return temp;
1755  }
1756 
1757  if(!advances_to_.empty()) {
1758  std::ostringstream tooltip;
1759  const std::string& image = game_config::images::level;
1760 
1761  for(const std::string& s : advances_to()) {
1762  if(!s.empty()) {
1763  tooltip << s << std::endl;
1764  }
1765  }
1766 
1767  temp[image] = tooltip.str();
1768  }
1769 
1770  for(const config& adv : get_modification_advances()) {
1771  const std::string& image = adv["image"];
1772  if(image.empty()) {
1773  continue;
1774  }
1775 
1776  std::ostringstream tooltip;
1777  tooltip << temp[image];
1778 
1779  const std::string& tt = adv["description"];
1780  if(!tt.empty()) {
1781  tooltip << tt << std::endl;
1782  }
1783 
1784  temp[image] = tooltip.str();
1785  }
1786 
1787  return(temp);
1788 }
1789 
1790 std::vector<std::pair<std::string, std::string>> unit::amla_icons() const
1791 {
1792  std::vector<std::pair<std::string, std::string>> temp;
1793  std::pair<std::string, std::string> icon; // <image,tooltip>
1794 
1795  for(const config& adv : get_modification_advances()) {
1796  icon.first = adv["icon"].str();
1797  icon.second = adv["description"].str();
1798 
1799  for(unsigned j = 0, j_count = modification_count("advancement", adv["id"]); j < j_count; ++j) {
1800  temp.push_back(icon);
1801  }
1802  }
1803 
1804  return(temp);
1805 }
1806 
1807 std::vector<config> unit::get_modification_advances() const
1808 {
1809  std::vector<config> res;
1810  for(const config& adv : modification_advancements()) {
1811  if(adv["strict_amla"].to_bool() && !advances_to_.empty()) {
1812  continue;
1813  }
1814  if(const config& filter = adv.child("filter")) {
1815  if(!unit_filter(vconfig(filter)).matches(*this, loc_)) {
1816  continue;
1817  }
1818  }
1819 
1820  if(modification_count("advancement", adv["id"]) >= static_cast<unsigned>(adv["max_times"].to_int(1))) {
1821  continue;
1822  }
1823 
1824  std::vector<std::string> temp_require = utils::split(adv["require_amla"]);
1825  std::vector<std::string> temp_exclude = utils::split(adv["exclude_amla"]);
1826 
1827  if(temp_require.empty() && temp_exclude.empty()) {
1828  res.push_back(adv);
1829  continue;
1830  }
1831 
1832  std::sort(temp_require.begin(), temp_require.end());
1833  std::sort(temp_exclude.begin(), temp_exclude.end());
1834 
1835  std::vector<std::string> uniq_require, uniq_exclude;
1836 
1837  std::unique_copy(temp_require.begin(), temp_require.end(), std::back_inserter(uniq_require));
1838  std::unique_copy(temp_exclude.begin(), temp_exclude.end(), std::back_inserter(uniq_exclude));
1839 
1840  bool exclusion_found = false;
1841  for(const std::string& s : uniq_exclude) {
1842  int max_num = std::count(temp_exclude.begin(), temp_exclude.end(), s);
1843  int mod_num = modification_count("advancement", s);
1844  if(mod_num >= max_num) {
1845  exclusion_found = true;
1846  break;
1847  }
1848  }
1849 
1850  if(exclusion_found) {
1851  continue;
1852  }
1853 
1854  bool requirements_done = true;
1855  for(const std::string& s : uniq_require) {
1856  int required_num = std::count(temp_require.begin(), temp_require.end(), s);
1857  int mod_num = modification_count("advancement", s);
1858  if(required_num > mod_num) {
1859  requirements_done = false;
1860  break;
1861  }
1862  }
1863 
1864  if(requirements_done) {
1865  res.push_back(adv);
1866  }
1867  }
1868 
1869  return res;
1870 }
1871 
1872 void unit::set_advancements(std::vector<config> advancements)
1873 {
1875  advancements_ = advancements;
1876 }
1877 
1878 const std::string& unit::type_id() const
1879 {
1880  return type_->id();
1881 }
1882 
1883 void unit::set_big_profile(const std::string& value)
1884 {
1886  profile_ = value;
1888 }
1889 
1890 std::size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
1891 {
1892  std::size_t res = 0;
1893  for(const config& item : modifications_.child_range(mod_type)) {
1894  if(item["id"] == id) {
1895  ++res;
1896  }
1897  }
1898 
1899  // For backwards compatibility, if asked for "advancement", also count "advance"
1900  if(mod_type == "advancement") {
1901  res += modification_count("advance", id);
1902  }
1903 
1904  return res;
1905 }
1906 
1907 const std::set<std::string> unit::builtin_effects {
1908  "alignment", "attack", "defense", "ellipse", "experience", "fearless",
1909  "halo", "healthy", "hitpoints", "image_mod", "jamming", "jamming_costs",
1910  "loyal", "max_attacks", "max_experience", "movement", "movement_costs",
1911  "new_ability", "new_advancement", "new_animation", "new_attack", "overlay", "profile",
1912  "recall_cost", "remove_ability", "remove_advancement", "remove_attacks", "resistance",
1913  "status", "type", "variation", "vision", "vision_costs", "zoc"
1914 };
1915 
1916 std::string unit::describe_builtin_effect(std::string apply_to, const config& effect)
1917 {
1918  if(apply_to == "attack") {
1919  std::vector<t_string> attack_names;
1920 
1921  std::string desc;
1922  for(attack_ptr a : attacks_) {
1923  bool affected = a->describe_modification(effect, &desc);
1924  if(affected && !desc.empty()) {
1925  attack_names.emplace_back(a->name(), "wesnoth-units");
1926  }
1927  }
1928  if(!attack_names.empty()) {
1929  utils::string_map symbols;
1930  symbols["attack_list"] = utils::format_conjunct_list("", attack_names);
1931  symbols["effect_description"] = desc;
1932  return VGETTEXT("$attack_list|: $effect_description", symbols);
1933  }
1934  } else if(apply_to == "hitpoints") {
1935  const std::string& increase_total = effect["increase_total"];
1936  if(!increase_total.empty()) {
1937  return VGETTEXT(
1938  "<span color=\"$color\">$number_or_percent</span> HP",
1939  {{"number_or_percent", utils::print_modifier(increase_total)}, {"color", increase_total[0] == '-' ? "#f00" : "#0f0"}});
1940  }
1941  } else {
1942  const std::string& increase = effect["increase"];
1943  if(increase.empty()) {
1944  return "";
1945  }
1946  if(apply_to == "movement") {
1947  return VNGETTEXT(
1948  "<span color=\"$color\">$number_or_percent</span> move",
1949  "<span color=\"$color\">$number_or_percent</span> moves",
1950  std::stoi(increase),
1951  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1952  } else if(apply_to == "vision") {
1953  return VGETTEXT(
1954  "<span color=\"$color\">$number_or_percent</span> vision",
1955  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1956  } else if(apply_to == "jamming") {
1957  return VGETTEXT(
1958  "<span color=\"$color\">$number_or_percent</span> jamming",
1959  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1960  } else if(apply_to == "max_experience") {
1961  // Unlike others, decreasing experience is a *GOOD* thing
1962  return VGETTEXT(
1963  "<span color=\"$color\">$number_or_percent</span> XP to advance",
1964  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
1965  } else if(apply_to == "max_attacks") {
1966  return VNGETTEXT(
1967  "<span color=\"$color\">$number_or_percent</span> attack per turn",
1968  "<span color=\"$color\">$number_or_percent</span> attacks per turn",
1969  std::stoi(increase),
1970  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1971  } else if(apply_to == "recall_cost") {
1972  // Unlike others, decreasing recall cost is a *GOOD* thing
1973  return VGETTEXT(
1974  "<span color=\"$color\">$number_or_percent</span> cost to recall",
1975  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
1976  }
1977  }
1978  return "";
1979 }
1980 
1981 void unit::apply_builtin_effect(std::string apply_to, const config& effect)
1982 {
1983  appearance_changed_ = true;
1984  if(apply_to == "fearless") {
1986  is_fearless_ = effect["set"].to_bool(true);
1987  } else if(apply_to == "healthy") {
1989  is_healthy_ = effect["set"].to_bool(true);
1990  } else if(apply_to == "profile") {
1991  if(const config::attribute_value* v = effect.get("portrait")) {
1992  set_big_profile((*v).str());
1993  }
1994 
1995  if(const config::attribute_value* v = effect.get("small_portrait")) {
1996  set_small_profile((*v).str());
1997  }
1998 
1999  if(const config::attribute_value* v = effect.get("description")) {
2000  description_ = *v;
2001  }
2002 
2003  if(config::const_child_itors cfg_range = effect.child_range("special_note")) {
2004  for(const config& c : cfg_range) {
2005  if(!c["remove"].to_bool()) {
2006  special_notes_.emplace_back(c["note"].t_str());
2007  } else {
2008  auto iter = std::find(special_notes_.begin(), special_notes_.end(), c["note"].t_str());
2009  if(iter != special_notes_.end()) {
2010  special_notes_.erase(iter);
2011  }
2012  }
2013  }
2014  }
2015  } else if(apply_to == "new_attack") {
2017  attacks_.emplace_back(new attack_type(effect));
2018  } else if(apply_to == "remove_attacks") {
2020  auto iter = std::remove_if(attacks_.begin(), attacks_.end(), [&effect](attack_ptr a) {
2021  return a->matches_filter(effect);
2022  });
2023 
2024  attacks_.erase(iter, attacks_.end());
2025  } else if(apply_to == "attack") {
2027  for(attack_ptr a : attacks_) {
2028  a->apply_modification(effect);
2029  }
2030  } else if(apply_to == "hitpoints") {
2031  LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_;
2032  const std::string& increase_hp = effect["increase"];
2033  const std::string& increase_total = effect["increase_total"];
2034  const std::string& set_hp = effect["set"];
2035  const std::string& set_total = effect["set_total"];
2036 
2037  // If the hitpoints are allowed to end up greater than max hitpoints
2038  const bool violate_max = effect["violate_maximum"].to_bool();
2039 
2040  if(!set_hp.empty()) {
2041  if(set_hp.back() == '%') {
2042  hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
2043  } else {
2044  hit_points_ = lexical_cast_default<int>(set_hp);
2045  }
2046  }
2047 
2048  if(!set_total.empty()) {
2049  if(set_total.back() == '%') {
2050  set_max_hitpoints(lexical_cast_default<int>(set_total)*max_hit_points_/100);
2051  } else {
2052  set_max_hitpoints(lexical_cast_default<int>(set_total));
2053  }
2054  }
2055 
2056  if(!increase_total.empty()) {
2057  // A percentage on the end means increase by that many percent
2058  set_max_hitpoints(utils::apply_modifier(max_hit_points_, increase_total));
2059  }
2060 
2061  if(max_hit_points_ < 1)
2062  set_max_hitpoints(1);
2063 
2064  if(effect["heal_full"].to_bool()) {
2065  heal_fully();
2066  }
2067 
2068  if(!increase_hp.empty()) {
2070  }
2071 
2072  LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_;
2073  if(hit_points_ > max_hit_points_ && !violate_max) {
2074  LOG_UT << "resetting hp to max";
2076  }
2077 
2078  if(hit_points_ < 1) {
2079  hit_points_ = 1;
2080  }
2081  } else if(apply_to == "movement") {
2082  const bool apply_to_vision = effect["apply_to_vision"].to_bool(true);
2083 
2084  // Unlink vision from movement, regardless of whether we'll increment both or not
2085  if(vision_ < 0) {
2087  }
2088 
2089  const int old_max = max_movement_;
2090 
2091  const std::string& increase = effect["increase"];
2092  if(!increase.empty()) {
2094  }
2095 
2096  set_total_movement(effect["set"].to_int(max_movement_));
2097 
2098  if(movement_ > max_movement_) {
2100  }
2101 
2102  if(apply_to_vision) {
2103  vision_ = std::max(0, vision_ + max_movement_ - old_max);
2104  }
2105  } else if(apply_to == "vision") {
2106  // Unlink vision from movement, regardless of which one we're about to change.
2107  if(vision_ < 0) {
2109  }
2110 
2111  const std::string& increase = effect["increase"];
2112  if(!increase.empty()) {
2113  vision_ = utils::apply_modifier(vision_, increase, 1);
2114  }
2115 
2116  vision_ = effect["set"].to_int(vision_);
2117  } else if(apply_to == "jamming") {
2118  const std::string& increase = effect["increase"];
2119 
2120  if(!increase.empty()) {
2121  jamming_ = utils::apply_modifier(jamming_, increase, 1);
2122  }
2123 
2124  jamming_ = effect["set"].to_int(jamming_);
2125  } else if(apply_to == "experience") {
2126  const std::string& increase = effect["increase"];
2127  const std::string& set = effect["set"];
2128 
2129  if(!set.empty()) {
2130  if(set.back() == '%') {
2131  experience_ = lexical_cast_default<int>(set)*max_experience_/100;
2132  } else {
2133  experience_ = lexical_cast_default<int>(set);
2134  }
2135  }
2136 
2137  if(increase.empty() == false) {
2139  }
2140  } else if(apply_to == "max_experience") {
2141  const std::string& increase = effect["increase"];
2142  const std::string& set = effect["set"];
2143 
2144  if(set.empty() == false) {
2145  if(set.back() == '%') {
2146  set_max_experience(lexical_cast_default<int>(set)*max_experience_/100);
2147  } else {
2148  set_max_experience(lexical_cast_default<int>(set));
2149  }
2150  }
2151 
2152  if(increase.empty() == false) {
2154  }
2155  } else if(apply_to == upkeep_loyal::type()) {
2156  upkeep_ = upkeep_loyal{};
2157  } else if(apply_to == "status") {
2158  const std::string& add = effect["add"];
2159  const std::string& remove = effect["remove"];
2160 
2161  for(const std::string& to_add : utils::split(add))
2162  {
2163  set_state(to_add, true);
2164  }
2165 
2166  for(const std::string& to_remove : utils::split(remove))
2167  {
2168  set_state(to_remove, false);
2169  }
2170  } else if(std::find(movetype::effects.cbegin(), movetype::effects.cend(), apply_to) != movetype::effects.cend()) {
2171  // "movement_costs", "vision_costs", "jamming_costs", "defense", "resistance"
2172  if(const config& ap = effect.child(apply_to)) {
2174  movement_type_.merge(ap, apply_to, effect["replace"].to_bool());
2175  }
2176  } else if(apply_to == "zoc") {
2177  if(const config::attribute_value* v = effect.get("value")) {
2179  emit_zoc_ = v->to_bool();
2180  }
2181  } else if(apply_to == "new_ability") {
2182  if(const config& ab_effect = effect.child("abilities")) {
2184  config to_append;
2185  for(const config::any_child ab : ab_effect.all_children_range()) {
2186  if(!has_ability_by_id(ab.cfg["id"])) {
2187  to_append.add_child(ab.key, ab.cfg);
2188  }
2189  }
2190  abilities_.append(to_append);
2191  }
2192  } else if(apply_to == "remove_ability") {
2193  if(const config& ab_effect = effect.child("abilities")) {
2194  for(const config::any_child ab : ab_effect.all_children_range()) {
2195  remove_ability_by_id(ab.cfg["id"]);
2196  }
2197  }
2198  } else if(apply_to == "image_mod") {
2199  LOG_UT << "applying image_mod";
2200  std::string mod = effect["replace"];
2201  if(!mod.empty()){
2202  image_mods_ = mod;
2203  }
2204  LOG_UT << "applying image_mod";
2205  mod = effect["add"].str();
2206  if(!mod.empty()){
2207  if(!image_mods_.empty()) {
2208  image_mods_ += '~';
2209  }
2210 
2211  image_mods_ += mod;
2212  }
2213 
2215  LOG_UT << "applying image_mod";
2216  } else if(apply_to == "new_animation") {
2217  anim_comp_->apply_new_animation_effect(effect);
2218  } else if(apply_to == "ellipse") {
2219  set_image_ellipse(effect["ellipse"]);
2220  } else if(apply_to == "halo") {
2221  set_image_halo(effect["halo"]);
2222  } else if(apply_to == "overlay") {
2223  const std::string& add = effect["add"];
2224  const std::string& replace = effect["replace"];
2225  const std::string& remove = effect["remove"];
2226 
2227  if(!add.empty()) {
2228  for(const auto& to_add : utils::parenthetical_split(add, ',')) {
2229  overlays_.push_back(to_add);
2230  }
2231  }
2232  if(!remove.empty()) {
2233  for(const auto& to_remove : utils::parenthetical_split(remove, ',')) {
2234  overlays_.erase(std::remove(overlays_.begin(), overlays_.end(), to_remove), overlays_.end());
2235  }
2236  }
2237  if(add.empty() && remove.empty() && !replace.empty()) {
2238  overlays_ = utils::parenthetical_split(replace, ',');
2239  }
2240  } else if(apply_to == "new_advancement") {
2241  const std::string& types = effect["types"];
2242  const bool replace = effect["replace"].to_bool(false);
2244 
2245  if(!types.empty()) {
2246  if(replace) {
2248  } else {
2249  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2250  std::copy(temp_advances.begin(), temp_advances.end(), std::back_inserter(advances_to_));
2251  }
2252  }
2253 
2254  if(effect.has_child("advancement")) {
2255  if(replace) {
2256  advancements_.clear();
2257  }
2258 
2259  for(const config& adv : effect.child_range("advancement")) {
2260  advancements_.push_back(adv);
2261  }
2262  }
2263  } else if(apply_to == "remove_advancement") {
2264  const std::string& types = effect["types"];
2265  const std::string& amlas = effect["amlas"];
2267 
2268  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2270  for(const std::string& unit : temp_advances) {
2271  iter = std::find(advances_to_.begin(), advances_to_.end(), unit);
2272  if(iter != advances_to_.end()) {
2273  advances_to_.erase(iter);
2274  }
2275  }
2276 
2277  temp_advances = utils::parenthetical_split(amlas, ',');
2278 
2279  for(int i = advancements_.size() - 1; i >= 0; i--) {
2280  if(std::find(temp_advances.begin(), temp_advances.end(), advancements_[i]["id"]) != temp_advances.end()) {
2281  advancements_.erase(advancements_.begin() + i);
2282  }
2283  }
2284  } else if(apply_to == "alignment") {
2285  auto new_align = unit_alignments::get_enum(effect["set"].str());
2286  if(new_align) {
2287  set_alignment(*new_align);
2288  }
2289  } else if(apply_to == "max_attacks") {
2290  const std::string& increase = effect["increase"];
2291 
2292  if(!increase.empty()) {
2294  }
2295  } else if(apply_to == "recall_cost") {
2296  const std::string& increase = effect["increase"];
2297  const std::string& set = effect["set"];
2298  const int recall_cost = recall_cost_ < 0 ? resources::gameboard->teams().at(side_).recall_cost() : recall_cost_;
2299 
2300  if(!set.empty()) {
2301  if(set.back() == '%') {
2302  recall_cost_ = lexical_cast_default<int>(set)*recall_cost/100;
2303  } else {
2304  recall_cost_ = lexical_cast_default<int>(set);
2305  }
2306  }
2307 
2308  if(!increase.empty()) {
2309  recall_cost_ = utils::apply_modifier(recall_cost, increase, 1);
2310  }
2311  } else if(effect["apply_to"] == "variation") {
2312  const unit_type* base_type = unit_types.find(type().parent_id());
2313  assert(base_type != nullptr);
2314  const std::string& variation_id = effect["name"];
2315  if(variation_id.empty() || base_type->get_gender_unit_type(gender_).has_variation(variation_id)) {
2316  variation_ = variation_id;
2317  advance_to(*base_type);
2318  if(effect["heal_full"].to_bool(false)) {
2319  heal_fully();
2320  }
2321  } else {
2322  WRN_UT << "unknown variation '" << variation_id << "' (name=) in [effect]apply_to=variation, ignoring";
2323  }
2324  } else if(effect["apply_to"] == "type") {
2325  std::string prev_type = effect["prev_type"];
2326  if(prev_type.empty()) {
2327  prev_type = type().parent_id();
2328  }
2329  const std::string& new_type_id = effect["name"];
2330  const unit_type* new_type = unit_types.find(new_type_id);
2331  if(new_type) {
2332  advance_to(*new_type);
2333  preferences::encountered_units().insert(new_type_id);
2334  if(effect["heal_full"].to_bool(false)) {
2335  heal_fully();
2336  }
2337  } else {
2338  WRN_UT << "unknown type '" << new_type_id << "' (name=) in [effect]apply_to=type, ignoring";
2339  }
2340  }
2341 }
2342 
2343 void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
2344 {
2345  bool generate_description = mod["generate_description"].to_bool(true);
2346 
2347  if(no_add == false) {
2348  modifications_.add_child(mod_type, mod);
2349  }
2350 
2351  bool set_poisoned = false; // Tracks if the poisoned state was set after the type or variation was changed.
2352  config type_effect, variation_effect;
2353  std::vector<t_string> effects_description;
2354  for(const config& effect : mod.child_range("effect")) {
2355  // Apply SUF.
2356  if(const config& afilter = effect.child("filter")) {
2357  assert(resources::filter_con);
2358  if(!unit_filter(vconfig(afilter)).matches(*this, loc_)) {
2359  continue;
2360  }
2361  }
2362  const std::string& apply_to = effect["apply_to"];
2363  int times = effect["times"].to_int(1);
2364  t_string description;
2365 
2366  if(effect["times"] == "per level") {
2367  times = level_;
2368  }
2369 
2370  if(times) {
2371  while (times > 0) {
2372  times --;
2373 
2374  bool was_poisoned = get_state(STATE_POISONED);
2375  // Apply unit type/variation changes last to avoid double applying effects on advance.
2376  if(apply_to == "type") {
2377  set_poisoned = false;
2378  type_effect = effect;
2379  continue;
2380  }
2381  if(apply_to == "variation") {
2382  set_poisoned = false;
2383  variation_effect = effect;
2384  continue;
2385  }
2386 
2387  std::string description_component;
2388  if(resources::lua_kernel) {
2389  description_component = resources::lua_kernel->apply_effect(apply_to, *this, effect, true);
2390  } else if(builtin_effects.count(apply_to)) {
2391  // Normally, the built-in effects are dispatched through Lua so that a user
2392  // can override them if desired. However, since they're built-in, we can still
2393  // apply them if the lua kernel is unavailable.
2394  apply_builtin_effect(apply_to, effect);
2395  description_component = describe_builtin_effect(apply_to, effect);
2396  }
2397  if(!times) {
2398  description += description_component;
2399  }
2400  if(!was_poisoned && get_state(STATE_POISONED)) {
2401  set_poisoned = true;
2402  } else if(was_poisoned && !get_state(STATE_POISONED)) {
2403  set_poisoned = false;
2404  }
2405  } // end while
2406  } else { // for times = per level & level = 0 we still need to rebuild the descriptions
2407  if(resources::lua_kernel) {
2408  description += resources::lua_kernel->apply_effect(apply_to, *this, effect, false);
2409  } else if(builtin_effects.count(apply_to)) {
2410  description += describe_builtin_effect(apply_to, effect);
2411  }
2412  }
2413 
2414  if(effect["times"] == "per level" && !times) {
2415  description = VGETTEXT("$effect_description per level", {{"effect_description", description}});
2416  }
2417 
2418  if(!description.empty()) {
2419  effects_description.push_back(description);
2420  }
2421  }
2422  // Apply variations -- only apply if we are adding this for the first time.
2423  if((!type_effect.empty() || !variation_effect.empty()) && no_add == false) {
2424  if(!type_effect.empty()) {
2425  std::string description;
2426  if(resources::lua_kernel) {
2427  description = resources::lua_kernel->apply_effect(type_effect["apply_to"], *this, type_effect, true);
2428  } else if(builtin_effects.count(type_effect["apply_to"])) {
2429  apply_builtin_effect(type_effect["apply_to"], type_effect);
2430  description = describe_builtin_effect(type_effect["apply_to"], type_effect);
2431  }
2432  effects_description.push_back(description);
2433  }
2434  if(!variation_effect.empty()) {
2435  std::string description;
2436  if(resources::lua_kernel) {
2437  description = resources::lua_kernel->apply_effect(variation_effect["apply_to"], *this, variation_effect, true);
2438  } else if(builtin_effects.count(variation_effect["apply_to"])) {
2439  apply_builtin_effect(variation_effect["apply_to"], variation_effect);
2440  description = describe_builtin_effect(variation_effect["apply_to"], variation_effect);
2441  }
2442  effects_description.push_back(description);
2443  }
2444  if(set_poisoned)
2445  // An effect explicitly set the poisoned state, and this
2446  // should override the unit being immune to poison.
2447  set_state(STATE_POISONED, true);
2448  }
2449 
2450  t_string description;
2451 
2452  const t_string& mod_description = mod["description"];
2453  if(!mod_description.empty()) {
2454  description = mod_description;
2455  }
2456 
2457  // Punctuation should be translatable: not all languages use Latin punctuation.
2458  // (However, there maybe is a better way to do it)
2459  if(generate_description && !effects_description.empty()) {
2460  if(!mod_description.empty()) {
2461  description += "\n";
2462  }
2463 
2464  for(const auto& desc_line : effects_description) {
2465  description += desc_line + "\n";
2466  }
2467  }
2468 
2469  // store trait info
2470  if(mod_type == "trait") {
2471  add_trait_description(mod, description);
2472  }
2473 
2474  //NOTE: if not a trait, description is currently not used
2475 }
2476 
2477 void unit::add_trait_description(const config& trait, const t_string& description)
2478 {
2479  const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
2480  const auto& gender_specific_name = trait[gender_string];
2481 
2482  const t_string name = gender_specific_name.empty() ? trait["name"] : gender_specific_name;
2483 
2484  if(!name.empty()) {
2485  trait_names_.push_back(name);
2486  trait_descriptions_.push_back(description);
2487  }
2488 }
2489 
2490 std::string unit::absolute_image() const
2491 {
2492  return type().icon().empty() ? type().image() : type().icon();
2493 }
2494 
2495 std::string unit::default_anim_image() const
2496 {
2497  return type().image().empty() ? type().icon() : type().image();
2498 }
2499 
2501 {
2502  log_scope("apply mods");
2503 
2504  variables_.clear_children("mods");
2505  if(modifications_.has_child("advance")) {
2506  deprecated_message("[advance]", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [advancement] instead.");
2507  }
2509  add_modification(mod.key, mod.cfg, true);
2510  }
2511 }
2512 
2513 bool unit::invisible(const map_location& loc, bool see_all) const
2514 {
2515  if(loc != get_location()) {
2516  DBG_UT << "unit::invisible called: id = " << id() << " loc = " << loc << " get_loc = " << get_location();
2517  }
2518 
2519  // This is a quick condition to check, and it does not depend on the
2520  // location (so might as well bypass the location-based cache).
2521  if(get_state(STATE_UNCOVERED)) {
2522  return false;
2523  }
2524 
2525  // Fetch from cache
2526  /**
2527  * @todo FIXME: We use the cache only when using the default see_all=true
2528  * Maybe add a second cache if the see_all=false become more frequent.
2529  */
2530  if(see_all) {
2531  const auto itor = invisibility_cache_.find(loc);
2532  if(itor != invisibility_cache_.end()) {
2533  return itor->second;
2534  }
2535  }
2536 
2537  // Test hidden status
2538  static const std::string hides("hides");
2539  bool is_inv = get_ability_bool(hides, loc);
2540  if(is_inv){
2541  is_inv = (resources::gameboard ? !resources::gameboard->would_be_discovered(loc, side_,see_all) : true);
2542  }
2543 
2544  if(see_all) {
2545  // Add to caches
2546  if(invisibility_cache_.empty()) {
2547  units_with_cache.push_back(this);
2548  }
2549 
2550  invisibility_cache_[loc] = is_inv;
2551  }
2552 
2553  return is_inv;
2554 }
2555 
2556 bool unit::is_visible_to_team(const team& team, bool const see_all) const
2557 {
2558  const map_location& loc = get_location();
2559  return is_visible_to_team(loc, team, see_all);
2560 }
2561 
2562 bool unit::is_visible_to_team(const map_location& loc, const team& team, bool const see_all) const
2563 {
2564  if(!display::get_singleton()->get_map().on_board(loc)) {
2565  return false;
2566  }
2567 
2568  if(see_all) {
2569  return true;
2570  }
2571 
2572  if(team.is_enemy(side()) && invisible(loc)) {
2573  return false;
2574  }
2575 
2576  // allied planned moves are also visible under fog. (we assume that fake units on the map are always whiteboard markers)
2577  if(!team.is_enemy(side()) && underlying_id_.is_fake()) {
2578  return true;
2579  }
2580 
2581  // when the whiteboard planned unit map is applied, it uses moved the _real_ unit so
2582  // underlying_id_.is_fake() will be false and the check above will not apply.
2583  // TODO: improve this check so that is also works for allied planned units but without
2584  // breaking sp campaigns with allies under fog. We probably need an explicit flag
2585  // is_planned_ in unit that is set by the whiteboard.
2586  if(team.side() == side()) {
2587  return true;
2588  }
2589 
2590  if(team.fogged(loc)) {
2591  return false;
2592  }
2593 
2594  return true;
2595 }
2596 
2598 {
2599  if(underlying_id_.value == 0) {
2601  underlying_id_ = id_manager.next_id();
2602  } else {
2603  underlying_id_ = id_manager.next_fake_id();
2604  }
2605  }
2606 
2607  if(id_.empty() /*&& !underlying_id_.is_fake()*/) {
2608  std::stringstream ss;
2609  ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_.value;
2610  id_ = ss.str();
2611  }
2612 }
2613 
2614 unit& unit::mark_clone(bool is_temporary)
2615 {
2617  if(is_temporary) {
2618  underlying_id_ = ids.next_fake_id();
2619  } else {
2621  underlying_id_ = ids.next_id();
2622  }
2623  else {
2624  underlying_id_ = ids.next_fake_id();
2625  }
2626  std::string::size_type pos = id_.find_last_of('-');
2627  if(pos != std::string::npos && pos+1 < id_.size()
2628  && id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
2629  // this appears to be a duplicate of a generic unit, so give it a new id
2630  WRN_UT << "assigning new id to clone of generic unit " << id_;
2631  id_.clear();
2632  set_underlying_id(ids);
2633  }
2634  }
2635  return *this;
2636 }
2637 
2638 
2640  : u_(const_cast<unit&>(u))
2641  , moves_(u.movement_left(true))
2642 {
2643  if(operate) {
2645  }
2646 }
2647 
2649 {
2650  assert(resources::gameboard);
2651  try {
2652  if(!resources::gameboard->units().has_unit(&u_)) {
2653  /*
2654  * It might be valid that the unit is not in the unit map.
2655  * It might also mean a no longer valid unit will be assigned to.
2656  */
2657  DBG_UT << "The unit to be removed is not in the unit map.";
2658  }
2659 
2661  } catch(...) {
2662  DBG_UT << "Caught exception when destroying unit_movement_resetter: " << utils::get_unknown_exception_type();
2663  }
2664 }
2665 
2666 std::string unit::TC_image_mods() const
2667 {
2668  return formatter() << "~RC(" << flag_rgb() << ">" << team::get_side_color_id(side()) << ")";
2669 }
2670 
2671 std::string unit::image_mods() const
2672 {
2673  if(!image_mods_.empty()) {
2674  return formatter() << "~" << image_mods_ << TC_image_mods();
2675  }
2676 
2677  return TC_image_mods();
2678 }
2679 
2680 // Called by the Lua API after resetting an attack pointer.
2682 {
2683  set_attr_changed(UA_ATTACKS);
2684  auto iter = std::find(attacks_.begin(), attacks_.end(), atk);
2685  if(iter == attacks_.end()) {
2686  return false;
2687  }
2688  attacks_.erase(iter);
2689  return true;
2690 }
2691 
2693 {
2694  if(attacks_left_ == max_attacks_) {
2695  //TODO: add state_not_attacked
2696  }
2697 
2698  set_attacks(0);
2699 }
2700 
2702 {
2703  if(movement_left() == total_movement()) {
2704  set_state(STATE_NOT_MOVED,true);
2705  }
2706 
2707  set_movement(0, true);
2708 }
2709 
2710 void unit::set_hidden(bool state) const
2711 {
2712 // appearance_changed_ = true;
2713  hidden_ = state;
2714  if(!state) {
2715  return;
2716  }
2717 
2718  // TODO: this should really hide the halo, not destroy it
2719  // We need to get rid of haloes immediately to avoid display glitches
2720  anim_comp_->clear_haloes();
2721 }
2722 
2723 void unit::set_image_halo(const std::string& halo)
2724 {
2725  appearance_changed_ = true;
2726  anim_comp_->clear_haloes();
2727  halo_ = halo;
2728 }
2729 
2731 {
2732  if(upkeep.empty()) {
2733  return;
2734  }
2735 
2736  try {
2737  upkeep_ = upkeep.apply_visitor(upkeep_parser_visitor{});
2738  } catch(std::invalid_argument& e) {
2739  WRN_UT << "Found invalid upkeep=\"" << e.what() << "\" in a unit";
2740  upkeep_ = upkeep_full{};
2741  }
2742 }
2743 
2745 {
2746  upkeep = utils::visit(upkeep_type_visitor{}, upkeep_);
2747 }
2748 
2750 {
2751  changed_attributes_.reset();
2752  for(const auto& a_ptr : attacks_) {
2753  a_ptr->set_changed(false);
2754  }
2755 }
2756 
2757 std::vector<t_string> unit::unit_special_notes() const {
2758  return combine_special_notes(special_notes_, abilities(), attacks(), movement_type());
2759 }
2760 
2761 // Filters unimportant stats from the unit config and returns a checksum of
2762 // the remaining config.
2764 {
2765  config unit_config;
2766  config wcfg;
2767  u.write(unit_config);
2768 
2769  static const std::set<std::string_view> main_keys {
2770  "advances_to",
2771  "alignment",
2772  "cost",
2773  "experience",
2774  "gender",
2775  "hitpoints",
2776  "ignore_race_traits",
2777  "ignore_global_traits",
2778  "level",
2779  "recall_cost",
2780  "max_attacks",
2781  "max_experience",
2782  "max_hitpoints",
2783  "max_moves",
2784  "movement",
2785  "movement_type",
2786  "race",
2787  "random_traits",
2788  "resting",
2789  "undead_variation",
2790  "upkeep",
2791  "zoc"
2792  };
2793 
2794  for(const std::string_view& main_key : main_keys) {
2795  wcfg[main_key] = unit_config[main_key];
2796  }
2797 
2798  static const std::set<std::string_view> attack_keys {
2799  "name",
2800  "type",
2801  "range",
2802  "damage",
2803  "number"
2804  };
2805 
2806  for(const config& att : unit_config.child_range("attack")) {
2807  config& child = wcfg.add_child("attack");
2808 
2809  for(const std::string_view& attack_key : attack_keys) {
2810  child[attack_key] = att[attack_key];
2811  }
2812 
2813  for(const config& spec : att.child_range("specials")) {
2814  config& child_spec = child.add_child("specials", spec);
2815 
2816  child_spec.recursive_clear_value("description");
2818  child_spec.recursive_clear_value("description_inactive");
2819  child_spec.recursive_clear_value("name");
2820  child_spec.recursive_clear_value("name_inactive");
2821  }
2822  }
2823  }
2824 
2825  for(const config& abi : unit_config.child_range("abilities")) {
2826  config& child = wcfg.add_child("abilities", abi);
2827 
2828  child.recursive_clear_value("description");
2829  child.recursive_clear_value("description_inactive");
2830  child.recursive_clear_value("name");
2831  child.recursive_clear_value("name_inactive");
2832  }
2833 
2834  for(const config& trait : unit_config.child_range("trait")) {
2835  config& child = wcfg.add_child("trait", trait);
2836 
2837  child.recursive_clear_value("description");
2838  child.recursive_clear_value("male_name");
2839  child.recursive_clear_value("female_name");
2840  child.recursive_clear_value("name");
2841  }
2842 
2843  static const std::set<std::string_view> child_keys {
2844  "advance_from",
2845  "defense",
2846  "movement_costs",
2847  "vision_costs",
2848  "jamming_costs",
2849  "resistance"
2850  };
2851 
2852  for(const std::string_view& child_key : child_keys) {
2853  for(const config& c : unit_config.child_range(child_key)) {
2854  wcfg.add_child(child_key, c);
2855  }
2856  }
2857 
2858  DBG_UT << wcfg;
2859 
2860  return wcfg.hash();
2861 }
2862 
2863 void swap(unit& lhs, unit& rhs)
2864 {
2865  lhs.swap(rhs);
2866 }
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:1257
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:1883
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:1420
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:2500
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:1878
std::string big_profile() const
An optional profile image displays when this unit is &#39;speaking&#39; via [message].
Definition: unit.cpp:1093
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:957
void new_turn()
Refresh unit for the beginning of a turn.
Definition: unit.cpp:1320
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:1392
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
unit_checksum_version
Optional parameter for get_checksum to use the algorithm of an older version of Wesnoth, thus preventing spurious OOS warnings while watching old replays.
Definition: unit.hpp:2023
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:2749
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:2490
#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:2692
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:871
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:1662
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:1497
game_data * gamedata
Definition: resources.cpp:23
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:1758
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:1163
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:1359
void add_modification(const std::string &type, const config &modification, bool no_add=false)
Add a new modification to the unit.
Definition: unit.cpp:2343
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit&#39;s defense on a given terrain.
Definition: unit.cpp:1686
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:1250
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:1115
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
Included some of the flavortext from weapon specials.
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:1440
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:2597
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:1229
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:1344
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:2730
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:1235
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:1217
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:347
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:1671
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:1487
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:2757
void set_hidden(bool state) const
Sets whether the unit is hidden on the map.
Definition: unit.cpp:2710
map_display and display: classes which take care of displaying the map and game-data on the screen...
std::string get_checksum(const unit &u, backwards_compatibility::unit_checksum_version version)
Gets a checksum for a unit.
Definition: unit.cpp:2763
std::string default_anim_image() const
The default image to use for animation frames with no defined image.
Definition: unit.cpp:2495
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:1102
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:1872
bool mod_duration_match(const std::string &mod_dur, const std::string &goal_dur)
Determines if mod_dur "matches" goal_dur.
Definition: unit.cpp:1273
static color_t hp_color_max()
Definition: unit.cpp:1173
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:1283
std::size_t modification_count(const std::string &type, const std::string &id) const
Count modifications of a particular type.
Definition: unit.cpp:1890
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:2614
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:1681
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:2513
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:182
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:1474
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:2723
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:2863
int attacks_left_
Definition: unit.hpp:1905
std::size_t i
Definition: function.cpp:968
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
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:2477
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:1750
#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:937
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:2556
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:1120
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:2744
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:1807
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 write(config &cfg, bool include_notes) const
Writes the movement type data to the provided config.
Definition: movetype.cpp:915
void apply_builtin_effect(std::string type, const config &effect)
Apply a builtin effect to the unit.
Definition: unit.cpp:1981
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:1916
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:1330
static color_t hp_color_impl(int hitpoints, int max_hitpoints)
Definition: unit.cpp:1125
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:220
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:1729
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:260
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:1790
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:1701
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:2666
void remove_movement_ai()
Sets the unit to have no moves left for this turn.
Definition: unit.cpp:2701
const std::set< std::string > get_states() const
Get the status effects currently affecting the unit.
Definition: unit.cpp:1375
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:2671
bool has_ability_by_id(const std::string &ability) const
Check if the unit has an ability of a specific ID.
Definition: unit.cpp:1463
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:2681
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46