The Battle for Wesnoth  1.17.14+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(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  {"invulnerable", STATE_INVULNERABLE},
1439 };
1440 
1441 void unit::set_state(const std::string& state, bool value)
1442 {
1443  appearance_changed_ = true;
1444  state_t known_boolean_state_id = get_known_boolean_state_id(state);
1445  if(known_boolean_state_id != STATE_UNKNOWN) {
1446  set_state(known_boolean_state_id, value);
1447  return;
1448  }
1449 
1450  // Backwards compatibility for not_living. Don't remove before 1.12
1451  if(state == "not_living") {
1452  set_state("undrainable", value);
1453  set_state("unpoisonable", value);
1454  set_state("unplagueable", value);
1455  }
1456 
1457  if(value) {
1458  states_.insert(state);
1459  } else {
1460  states_.erase(state);
1461  }
1462 }
1463 
1464 bool unit::has_ability_by_id(const std::string& ability) const
1465 {
1466  for(const config::any_child ab : abilities_.all_children_range()) {
1467  if(ab.cfg["id"] == ability) {
1468  return true;
1469  }
1470  }
1471 
1472  return false;
1473 }
1474 
1475 void unit::remove_ability_by_id(const std::string& ability)
1476 {
1479  while (i != abilities_.ordered_end()) {
1480  if(i->cfg["id"] == ability) {
1481  i = abilities_.erase(i);
1482  } else {
1483  ++i;
1484  }
1485  }
1486 }
1487 
1489 {
1490  for(const auto& a_ptr : attacks_) {
1491  if(a_ptr->get_changed()) {
1492  return true;
1493  }
1494 
1495  }
1496  return false;
1497 }
1498 void unit::write(config& cfg, bool write_all) const
1499 {
1500  config back;
1501  auto write_subtag = [&](const std::string& key, const config& child)
1502  {
1503  cfg.clear_children(key);
1504 
1505  if(!child.empty()) {
1506  cfg.add_child(key, child);
1507  } else {
1508  back.add_child(key, child);
1509  }
1510  };
1511 
1512  if(write_all || get_attr_changed(UA_MOVEMENT_TYPE)) {
1513  movement_type_.write(cfg, false);
1514  }
1515  if(write_all || get_attr_changed(UA_SMALL_PROFILE)) {
1516  cfg["small_profile"] = small_profile_;
1517  }
1518  if(write_all || get_attr_changed(UA_PROFILE)) {
1519  cfg["profile"] = profile_;
1520  }
1521  if(description_ != type().unit_description()) {
1522  cfg["description"] = description_;
1523  }
1524  if(write_all || get_attr_changed(UA_NOTES)) {
1525  for(const t_string& note : special_notes_) {
1526  cfg.add_child("special_note")["note"] = note;
1527  }
1528  }
1529 
1530  if(halo_) {
1531  cfg["halo"] = *halo_;
1532  }
1533 
1534  if(ellipse_) {
1535  cfg["ellipse"] = *ellipse_;
1536  }
1537 
1538  if(usage_) {
1539  cfg["usage"] = *usage_;
1540  }
1541 
1542  write_upkeep(cfg["upkeep"]);
1543 
1544  cfg["hitpoints"] = hit_points_;
1545  if(write_all || get_attr_changed(UA_MAX_HP)) {
1546  cfg["max_hitpoints"] = max_hit_points_;
1547  }
1548  cfg["image_icon"] = type().icon();
1549  cfg["image"] = type().image();
1550  cfg["random_traits"] = random_traits_;
1551  cfg["generate_name"] = generate_name_;
1552  cfg["experience"] = experience_;
1553  if(write_all || get_attr_changed(UA_MAX_XP)) {
1554  cfg["max_experience"] = max_experience_;
1555  }
1556  cfg["recall_cost"] = recall_cost_;
1557 
1558  cfg["side"] = side_;
1559 
1560  cfg["type"] = type_id();
1561 
1562  if(type_id() != type().parent_id()) {
1563  cfg["parent_type"] = type().parent_id();
1564  }
1565 
1566  // Support for unit formulas in [ai] and unit-specific variables in [ai] [vars]
1567  formula_man_->write(cfg);
1568 
1569  cfg["gender"] = gender_string(gender_);
1570  cfg["variation"] = variation_;
1571  cfg["role"] = role_;
1572 
1573  config status_flags;
1574  for(const std::string& state : get_states()) {
1575  status_flags[state] = true;
1576  }
1577 
1578  write_subtag("variables", variables_);
1579  write_subtag("filter_recall", filter_recall_);
1580  write_subtag("status", status_flags);
1581 
1582  cfg.clear_children("events");
1583  cfg.append(events_);
1584 
1585  // Overlays are exported as the modifications that add them, not as an overlays= value,
1586  // however removing the key breaks the Gui Debug Tools.
1587  // \todo does anything depend on the key's value, other than the re-import code in unit::init?
1588  cfg["overlays"] = "";
1589 
1590  cfg["name"] = name_;
1591  cfg["id"] = id_;
1592  cfg["underlying_id"] = underlying_id_.value;
1593 
1594  if(can_recruit()) {
1595  cfg["canrecruit"] = true;
1596  }
1597 
1598  cfg["extra_recruit"] = utils::join(recruit_list_);
1599 
1600  cfg["facing"] = map_location::write_direction(facing_);
1601 
1602  cfg["goto_x"] = goto_.wml_x();
1603  cfg["goto_y"] = goto_.wml_y();
1604 
1605  cfg["moves"] = movement_;
1606  if(write_all || get_attr_changed(UA_MAX_MP)) {
1607  cfg["max_moves"] = max_movement_;
1608  }
1609  cfg["vision"] = vision_;
1610  cfg["jamming"] = jamming_;
1611 
1612  cfg["resting"] = resting_;
1613 
1614  if(write_all || get_attr_changed(UA_ADVANCE_TO)) {
1615  cfg["advances_to"] = utils::join(advances_to_);
1616  }
1617 
1618  cfg["race"] = race_->id();
1619  cfg["language_name"] = type_name_;
1620  cfg["undead_variation"] = undead_variation_;
1621  if(write_all || get_attr_changed(UA_LEVEL)) {
1622  cfg["level"] = level_;
1623  }
1624  if(write_all || get_attr_changed(UA_ALIGNMENT)) {
1625  cfg["alignment"] = unit_alignments::get_string(alignment_);
1626  }
1627  cfg["flag_rgb"] = flag_rgb_;
1628  cfg["unrenamable"] = unrenamable_;
1629 
1630  cfg["attacks_left"] = attacks_left_;
1631  if(write_all || get_attr_changed(UA_MAX_AP)) {
1632  cfg["max_attacks"] = max_attacks_;
1633  }
1634  if(write_all || get_attr_changed(UA_ZOC)) {
1635  cfg["zoc"] = emit_zoc_;
1636  }
1637  cfg["hidden"] = hidden_;
1638 
1639  if(write_all || get_attr_changed(UA_ATTACKS) || get_attacks_changed()) {
1640  cfg.clear_children("attack");
1641  for(attack_ptr i : attacks_) {
1642  i->write(cfg.add_child("attack"));
1643  }
1644  }
1645 
1646  cfg["cost"] = unit_value_;
1647 
1648  write_subtag("modifications", modifications_);
1649  if(write_all || get_attr_changed(UA_ABILITIES)) {
1650  write_subtag("abilities", abilities_);
1651  }
1652  if(write_all || get_attr_changed(UA_ADVANCEMENTS)) {
1653  cfg.clear_children("advancement");
1654  for(const config& advancement : advancements_) {
1655  if(!advancement.empty()) {
1656  cfg.add_child("advancement", advancement);
1657  }
1658  }
1659  }
1660  cfg.append(back);
1661 }
1662 
1664 {
1665  if(dir != map_location::NDIRECTIONS && dir != facing_) {
1666  appearance_changed_ = true;
1667  facing_ = dir;
1668  }
1669  // Else look at yourself (not available so continue to face the same direction)
1670 }
1671 
1672 int unit::upkeep() const
1673 {
1674  // Leaders do not incur upkeep.
1675  if(can_recruit()) {
1676  return 0;
1677  }
1678 
1679  return utils::visit(upkeep_value_visitor{*this}, upkeep_);
1680 }
1681 
1682 bool unit::loyal() const
1683 {
1684  return utils::holds_alternative<upkeep_loyal>(upkeep_);
1685 }
1686 
1688 {
1689  int def = movement_type_.defense_modifier(terrain);
1690 #if 0
1691  // A [defense] ability is too costly and doesn't take into account target locations.
1692  // Left as a comment in case someone ever wonders why it isn't a good idea.
1693  unit_ability_list defense_abilities = get_abilities("defense");
1694  if(!defense_abilities.empty()) {
1695  unit_abilities::effect defense_effect(defense_abilities, def);
1696  def = defense_effect.get_composite_value();
1697  }
1698 #endif
1699  return def;
1700 }
1701 
1702 bool unit::resistance_filter_matches(const config& cfg, bool attacker, const std::string& damage_name, int res) const
1703 {
1704  if(!(cfg["active_on"].empty() || (attacker && cfg["active_on"] == "offense") || (!attacker && cfg["active_on"] == "defense"))) {
1705  return false;
1706  }
1707 
1708  const std::string& apply_to = cfg["apply_to"];
1709  if(!apply_to.empty()) {
1710  if(damage_name != apply_to) {
1711  if(apply_to.find(',') != std::string::npos &&
1712  apply_to.find(damage_name) != std::string::npos) {
1713  const std::vector<std::string>& vals = utils::split(apply_to);
1714  if(std::find(vals.begin(),vals.end(),damage_name) == vals.end()) {
1715  return false;
1716  }
1717  } else {
1718  return false;
1719  }
1720  }
1721  }
1722 
1723  if(!unit_abilities::filter_base_matches(cfg, res)) {
1724  return false;
1725  }
1726 
1727  return true;
1728 }
1729 
1730 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
1731 {
1732  int res = movement_type_.resistance_against(damage_name);
1733 
1734  unit_ability_list resistance_abilities = get_abilities_weapons("resistance",loc, weapon, opp_weapon);
1735  utils::erase_if(resistance_abilities, [&](const unit_ability& i) {
1736  return !resistance_filter_matches(*i.ability_cfg, attacker, damage_name, 100-res);
1737  });
1738 
1739  if(!resistance_abilities.empty()) {
1740  unit_abilities::effect resist_effect(resistance_abilities, 100-res);
1741 
1742  res = 100 - std::min<int>(
1743  resist_effect.get_composite_value(),
1744  resistance_abilities.highest("max_value").first
1745  );
1746  }
1747 
1748  return res;
1749 }
1750 
1751 std::map<std::string, std::string> unit::advancement_icons() const
1752 {
1753  std::map<std::string,std::string> temp;
1754  if(!can_advance()) {
1755  return temp;
1756  }
1757 
1758  if(!advances_to_.empty()) {
1759  std::ostringstream tooltip;
1760  const std::string& image = game_config::images::level;
1761 
1762  for(const std::string& s : advances_to()) {
1763  if(!s.empty()) {
1764  tooltip << s << std::endl;
1765  }
1766  }
1767 
1768  temp[image] = tooltip.str();
1769  }
1770 
1771  for(const config& adv : get_modification_advances()) {
1772  const std::string& image = adv["image"];
1773  if(image.empty()) {
1774  continue;
1775  }
1776 
1777  std::ostringstream tooltip;
1778  tooltip << temp[image];
1779 
1780  const std::string& tt = adv["description"];
1781  if(!tt.empty()) {
1782  tooltip << tt << std::endl;
1783  }
1784 
1785  temp[image] = tooltip.str();
1786  }
1787 
1788  return(temp);
1789 }
1790 
1791 std::vector<std::pair<std::string, std::string>> unit::amla_icons() const
1792 {
1793  std::vector<std::pair<std::string, std::string>> temp;
1794  std::pair<std::string, std::string> icon; // <image,tooltip>
1795 
1796  for(const config& adv : get_modification_advances()) {
1797  icon.first = adv["icon"].str();
1798  icon.second = adv["description"].str();
1799 
1800  for(unsigned j = 0, j_count = modification_count("advancement", adv["id"]); j < j_count; ++j) {
1801  temp.push_back(icon);
1802  }
1803  }
1804 
1805  return(temp);
1806 }
1807 
1808 std::vector<config> unit::get_modification_advances() const
1809 {
1810  std::vector<config> res;
1811  for(const config& adv : modification_advancements()) {
1812  if(adv["strict_amla"].to_bool() && !advances_to_.empty()) {
1813  continue;
1814  }
1815  if(const config& filter = adv.child("filter")) {
1816  if(!unit_filter(vconfig(filter)).matches(*this, loc_)) {
1817  continue;
1818  }
1819  }
1820 
1821  if(modification_count("advancement", adv["id"]) >= static_cast<unsigned>(adv["max_times"].to_int(1))) {
1822  continue;
1823  }
1824 
1825  std::vector<std::string> temp_require = utils::split(adv["require_amla"]);
1826  std::vector<std::string> temp_exclude = utils::split(adv["exclude_amla"]);
1827 
1828  if(temp_require.empty() && temp_exclude.empty()) {
1829  res.push_back(adv);
1830  continue;
1831  }
1832 
1833  std::sort(temp_require.begin(), temp_require.end());
1834  std::sort(temp_exclude.begin(), temp_exclude.end());
1835 
1836  std::vector<std::string> uniq_require, uniq_exclude;
1837 
1838  std::unique_copy(temp_require.begin(), temp_require.end(), std::back_inserter(uniq_require));
1839  std::unique_copy(temp_exclude.begin(), temp_exclude.end(), std::back_inserter(uniq_exclude));
1840 
1841  bool exclusion_found = false;
1842  for(const std::string& s : uniq_exclude) {
1843  int max_num = std::count(temp_exclude.begin(), temp_exclude.end(), s);
1844  int mod_num = modification_count("advancement", s);
1845  if(mod_num >= max_num) {
1846  exclusion_found = true;
1847  break;
1848  }
1849  }
1850 
1851  if(exclusion_found) {
1852  continue;
1853  }
1854 
1855  bool requirements_done = true;
1856  for(const std::string& s : uniq_require) {
1857  int required_num = std::count(temp_require.begin(), temp_require.end(), s);
1858  int mod_num = modification_count("advancement", s);
1859  if(required_num > mod_num) {
1860  requirements_done = false;
1861  break;
1862  }
1863  }
1864 
1865  if(requirements_done) {
1866  res.push_back(adv);
1867  }
1868  }
1869 
1870  return res;
1871 }
1872 
1873 void unit::set_advancements(std::vector<config> advancements)
1874 {
1876  advancements_ = advancements;
1877 }
1878 
1879 const std::string& unit::type_id() const
1880 {
1881  return type_->id();
1882 }
1883 
1884 void unit::set_big_profile(const std::string& value)
1885 {
1887  profile_ = value;
1889 }
1890 
1891 std::size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
1892 {
1893  std::size_t res = 0;
1894  for(const config& item : modifications_.child_range(mod_type)) {
1895  if(item["id"] == id) {
1896  ++res;
1897  }
1898  }
1899 
1900  // For backwards compatibility, if asked for "advancement", also count "advance"
1901  if(mod_type == "advancement") {
1902  res += modification_count("advance", id);
1903  }
1904 
1905  return res;
1906 }
1907 
1908 const std::set<std::string> unit::builtin_effects {
1909  "alignment", "attack", "defense", "ellipse", "experience", "fearless",
1910  "halo", "healthy", "hitpoints", "image_mod", "jamming", "jamming_costs",
1911  "loyal", "max_attacks", "max_experience", "movement", "movement_costs",
1912  "new_ability", "new_advancement", "new_animation", "new_attack", "overlay", "profile",
1913  "recall_cost", "remove_ability", "remove_advancement", "remove_attacks", "resistance",
1914  "status", "type", "variation", "vision", "vision_costs", "zoc"
1915 };
1916 
1917 std::string unit::describe_builtin_effect(std::string apply_to, const config& effect)
1918 {
1919  if(apply_to == "attack") {
1920  std::vector<t_string> attack_names;
1921 
1922  std::string desc;
1923  for(attack_ptr a : attacks_) {
1924  bool affected = a->describe_modification(effect, &desc);
1925  if(affected && !desc.empty()) {
1926  attack_names.emplace_back(a->name(), "wesnoth-units");
1927  }
1928  }
1929  if(!attack_names.empty()) {
1930  utils::string_map symbols;
1931  symbols["attack_list"] = utils::format_conjunct_list("", attack_names);
1932  symbols["effect_description"] = desc;
1933  return VGETTEXT("$attack_list|: $effect_description", symbols);
1934  }
1935  } else if(apply_to == "hitpoints") {
1936  const std::string& increase_total = effect["increase_total"];
1937  if(!increase_total.empty()) {
1938  return VGETTEXT(
1939  "<span color=\"$color\">$number_or_percent</span> HP",
1940  {{"number_or_percent", utils::print_modifier(increase_total)}, {"color", increase_total[0] == '-' ? "#f00" : "#0f0"}});
1941  }
1942  } else {
1943  const std::string& increase = effect["increase"];
1944  if(increase.empty()) {
1945  return "";
1946  }
1947  if(apply_to == "movement") {
1948  return VNGETTEXT(
1949  "<span color=\"$color\">$number_or_percent</span> move",
1950  "<span color=\"$color\">$number_or_percent</span> moves",
1951  std::stoi(increase),
1952  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1953  } else if(apply_to == "vision") {
1954  return VGETTEXT(
1955  "<span color=\"$color\">$number_or_percent</span> vision",
1956  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1957  } else if(apply_to == "jamming") {
1958  return VGETTEXT(
1959  "<span color=\"$color\">$number_or_percent</span> jamming",
1960  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1961  } else if(apply_to == "max_experience") {
1962  // Unlike others, decreasing experience is a *GOOD* thing
1963  return VGETTEXT(
1964  "<span color=\"$color\">$number_or_percent</span> XP to advance",
1965  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
1966  } else if(apply_to == "max_attacks") {
1967  return VNGETTEXT(
1968  "<span color=\"$color\">$number_or_percent</span> attack per turn",
1969  "<span color=\"$color\">$number_or_percent</span> attacks per turn",
1970  std::stoi(increase),
1971  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
1972  } else if(apply_to == "recall_cost") {
1973  // Unlike others, decreasing recall cost is a *GOOD* thing
1974  return VGETTEXT(
1975  "<span color=\"$color\">$number_or_percent</span> cost to recall",
1976  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
1977  }
1978  }
1979  return "";
1980 }
1981 
1982 void unit::apply_builtin_effect(std::string apply_to, const config& effect)
1983 {
1984  appearance_changed_ = true;
1985  if(apply_to == "fearless") {
1987  is_fearless_ = effect["set"].to_bool(true);
1988  } else if(apply_to == "healthy") {
1990  is_healthy_ = effect["set"].to_bool(true);
1991  } else if(apply_to == "profile") {
1992  if(const config::attribute_value* v = effect.get("portrait")) {
1993  set_big_profile((*v).str());
1994  }
1995 
1996  if(const config::attribute_value* v = effect.get("small_portrait")) {
1997  set_small_profile((*v).str());
1998  }
1999 
2000  if(const config::attribute_value* v = effect.get("description")) {
2001  description_ = *v;
2002  }
2003 
2004  if(config::const_child_itors cfg_range = effect.child_range("special_note")) {
2005  for(const config& c : cfg_range) {
2006  if(!c["remove"].to_bool()) {
2007  special_notes_.emplace_back(c["note"].t_str());
2008  } else {
2009  auto iter = std::find(special_notes_.begin(), special_notes_.end(), c["note"].t_str());
2010  if(iter != special_notes_.end()) {
2011  special_notes_.erase(iter);
2012  }
2013  }
2014  }
2015  }
2016  } else if(apply_to == "new_attack") {
2018  attacks_.emplace_back(new attack_type(effect));
2019  } else if(apply_to == "remove_attacks") {
2021  auto iter = std::remove_if(attacks_.begin(), attacks_.end(), [&effect](attack_ptr a) {
2022  return a->matches_filter(effect);
2023  });
2024 
2025  attacks_.erase(iter, attacks_.end());
2026  } else if(apply_to == "attack") {
2028  for(attack_ptr a : attacks_) {
2029  a->apply_modification(effect);
2030  }
2031  } else if(apply_to == "hitpoints") {
2032  LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_;
2033  const std::string& increase_hp = effect["increase"];
2034  const std::string& increase_total = effect["increase_total"];
2035  const std::string& set_hp = effect["set"];
2036  const std::string& set_total = effect["set_total"];
2037 
2038  // If the hitpoints are allowed to end up greater than max hitpoints
2039  const bool violate_max = effect["violate_maximum"].to_bool();
2040 
2041  if(!set_hp.empty()) {
2042  if(set_hp.back() == '%') {
2043  hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
2044  } else {
2045  hit_points_ = lexical_cast_default<int>(set_hp);
2046  }
2047  }
2048 
2049  if(!set_total.empty()) {
2050  if(set_total.back() == '%') {
2051  set_max_hitpoints(lexical_cast_default<int>(set_total)*max_hit_points_/100);
2052  } else {
2053  set_max_hitpoints(lexical_cast_default<int>(set_total));
2054  }
2055  }
2056 
2057  if(!increase_total.empty()) {
2058  // A percentage on the end means increase by that many percent
2059  set_max_hitpoints(utils::apply_modifier(max_hit_points_, increase_total));
2060  }
2061 
2062  if(max_hit_points_ < 1)
2063  set_max_hitpoints(1);
2064 
2065  if(effect["heal_full"].to_bool()) {
2066  heal_fully();
2067  }
2068 
2069  if(!increase_hp.empty()) {
2071  }
2072 
2073  LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_;
2074  if(hit_points_ > max_hit_points_ && !violate_max) {
2075  LOG_UT << "resetting hp to max";
2077  }
2078 
2079  if(hit_points_ < 1) {
2080  hit_points_ = 1;
2081  }
2082  } else if(apply_to == "movement") {
2083  const bool apply_to_vision = effect["apply_to_vision"].to_bool(true);
2084 
2085  // Unlink vision from movement, regardless of whether we'll increment both or not
2086  if(vision_ < 0) {
2088  }
2089 
2090  const int old_max = max_movement_;
2091 
2092  const std::string& increase = effect["increase"];
2093  if(!increase.empty()) {
2095  }
2096 
2097  set_total_movement(effect["set"].to_int(max_movement_));
2098 
2099  if(movement_ > max_movement_) {
2101  }
2102 
2103  if(apply_to_vision) {
2104  vision_ = std::max(0, vision_ + max_movement_ - old_max);
2105  }
2106  } else if(apply_to == "vision") {
2107  // Unlink vision from movement, regardless of which one we're about to change.
2108  if(vision_ < 0) {
2110  }
2111 
2112  const std::string& increase = effect["increase"];
2113  if(!increase.empty()) {
2114  vision_ = utils::apply_modifier(vision_, increase, 1);
2115  }
2116 
2117  vision_ = effect["set"].to_int(vision_);
2118  } else if(apply_to == "jamming") {
2119  const std::string& increase = effect["increase"];
2120 
2121  if(!increase.empty()) {
2122  jamming_ = utils::apply_modifier(jamming_, increase, 1);
2123  }
2124 
2125  jamming_ = effect["set"].to_int(jamming_);
2126  } else if(apply_to == "experience") {
2127  const std::string& increase = effect["increase"];
2128  const std::string& set = effect["set"];
2129 
2130  if(!set.empty()) {
2131  if(set.back() == '%') {
2132  experience_ = lexical_cast_default<int>(set)*max_experience_/100;
2133  } else {
2134  experience_ = lexical_cast_default<int>(set);
2135  }
2136  }
2137 
2138  if(increase.empty() == false) {
2140  }
2141  } else if(apply_to == "max_experience") {
2142  const std::string& increase = effect["increase"];
2143  const std::string& set = effect["set"];
2144 
2145  if(set.empty() == false) {
2146  if(set.back() == '%') {
2147  set_max_experience(lexical_cast_default<int>(set)*max_experience_/100);
2148  } else {
2149  set_max_experience(lexical_cast_default<int>(set));
2150  }
2151  }
2152 
2153  if(increase.empty() == false) {
2155  }
2156  } else if(apply_to == upkeep_loyal::type()) {
2157  upkeep_ = upkeep_loyal{};
2158  } else if(apply_to == "status") {
2159  const std::string& add = effect["add"];
2160  const std::string& remove = effect["remove"];
2161 
2162  for(const std::string& to_add : utils::split(add))
2163  {
2164  set_state(to_add, true);
2165  }
2166 
2167  for(const std::string& to_remove : utils::split(remove))
2168  {
2169  set_state(to_remove, false);
2170  }
2171  } else if(std::find(movetype::effects.cbegin(), movetype::effects.cend(), apply_to) != movetype::effects.cend()) {
2172  // "movement_costs", "vision_costs", "jamming_costs", "defense", "resistance"
2173  if(const config& ap = effect.child(apply_to)) {
2175  movement_type_.merge(ap, apply_to, effect["replace"].to_bool());
2176  }
2177  } else if(apply_to == "zoc") {
2178  if(const config::attribute_value* v = effect.get("value")) {
2180  emit_zoc_ = v->to_bool();
2181  }
2182  } else if(apply_to == "new_ability") {
2183  if(const config& ab_effect = effect.child("abilities")) {
2185  config to_append;
2186  for(const config::any_child ab : ab_effect.all_children_range()) {
2187  if(!has_ability_by_id(ab.cfg["id"])) {
2188  to_append.add_child(ab.key, ab.cfg);
2189  }
2190  }
2191  abilities_.append(to_append);
2192  }
2193  } else if(apply_to == "remove_ability") {
2194  if(const config& ab_effect = effect.child("abilities")) {
2195  for(const config::any_child ab : ab_effect.all_children_range()) {
2196  remove_ability_by_id(ab.cfg["id"]);
2197  }
2198  }
2199  } else if(apply_to == "image_mod") {
2200  LOG_UT << "applying image_mod";
2201  std::string mod = effect["replace"];
2202  if(!mod.empty()){
2203  image_mods_ = mod;
2204  }
2205  LOG_UT << "applying image_mod";
2206  mod = effect["add"].str();
2207  if(!mod.empty()){
2208  if(!image_mods_.empty()) {
2209  image_mods_ += '~';
2210  }
2211 
2212  image_mods_ += mod;
2213  }
2214 
2216  LOG_UT << "applying image_mod";
2217  } else if(apply_to == "new_animation") {
2218  anim_comp_->apply_new_animation_effect(effect);
2219  } else if(apply_to == "ellipse") {
2220  set_image_ellipse(effect["ellipse"]);
2221  } else if(apply_to == "halo") {
2222  set_image_halo(effect["halo"]);
2223  } else if(apply_to == "overlay") {
2224  const std::string& add = effect["add"];
2225  const std::string& replace = effect["replace"];
2226  const std::string& remove = effect["remove"];
2227 
2228  if(!add.empty()) {
2229  for(const auto& to_add : utils::parenthetical_split(add, ',')) {
2230  overlays_.push_back(to_add);
2231  }
2232  }
2233  if(!remove.empty()) {
2234  for(const auto& to_remove : utils::parenthetical_split(remove, ',')) {
2235  overlays_.erase(std::remove(overlays_.begin(), overlays_.end(), to_remove), overlays_.end());
2236  }
2237  }
2238  if(add.empty() && remove.empty() && !replace.empty()) {
2239  overlays_ = utils::parenthetical_split(replace, ',');
2240  }
2241  } else if(apply_to == "new_advancement") {
2242  const std::string& types = effect["types"];
2243  const bool replace = effect["replace"].to_bool(false);
2245 
2246  if(!types.empty()) {
2247  if(replace) {
2249  } else {
2250  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2251  std::copy(temp_advances.begin(), temp_advances.end(), std::back_inserter(advances_to_));
2252  }
2253  }
2254 
2255  if(effect.has_child("advancement")) {
2256  if(replace) {
2257  advancements_.clear();
2258  }
2259 
2260  for(const config& adv : effect.child_range("advancement")) {
2261  advancements_.push_back(adv);
2262  }
2263  }
2264  } else if(apply_to == "remove_advancement") {
2265  const std::string& types = effect["types"];
2266  const std::string& amlas = effect["amlas"];
2268 
2269  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2271  for(const std::string& unit : temp_advances) {
2272  iter = std::find(advances_to_.begin(), advances_to_.end(), unit);
2273  if(iter != advances_to_.end()) {
2274  advances_to_.erase(iter);
2275  }
2276  }
2277 
2278  temp_advances = utils::parenthetical_split(amlas, ',');
2279 
2280  for(int i = advancements_.size() - 1; i >= 0; i--) {
2281  if(std::find(temp_advances.begin(), temp_advances.end(), advancements_[i]["id"]) != temp_advances.end()) {
2282  advancements_.erase(advancements_.begin() + i);
2283  }
2284  }
2285  } else if(apply_to == "alignment") {
2286  auto new_align = unit_alignments::get_enum(effect["set"].str());
2287  if(new_align) {
2288  set_alignment(*new_align);
2289  }
2290  } else if(apply_to == "max_attacks") {
2291  const std::string& increase = effect["increase"];
2292 
2293  if(!increase.empty()) {
2295  }
2296  } else if(apply_to == "recall_cost") {
2297  const std::string& increase = effect["increase"];
2298  const std::string& set = effect["set"];
2299  const int recall_cost = recall_cost_ < 0 ? resources::gameboard->teams().at(side_).recall_cost() : recall_cost_;
2300 
2301  if(!set.empty()) {
2302  if(set.back() == '%') {
2303  recall_cost_ = lexical_cast_default<int>(set)*recall_cost/100;
2304  } else {
2305  recall_cost_ = lexical_cast_default<int>(set);
2306  }
2307  }
2308 
2309  if(!increase.empty()) {
2310  recall_cost_ = utils::apply_modifier(recall_cost, increase, 1);
2311  }
2312  } else if(effect["apply_to"] == "variation") {
2313  const unit_type* base_type = unit_types.find(type().parent_id());
2314  assert(base_type != nullptr);
2315  const std::string& variation_id = effect["name"];
2316  if(variation_id.empty() || base_type->get_gender_unit_type(gender_).has_variation(variation_id)) {
2317  variation_ = variation_id;
2318  advance_to(*base_type);
2319  if(effect["heal_full"].to_bool(false)) {
2320  heal_fully();
2321  }
2322  } else {
2323  WRN_UT << "unknown variation '" << variation_id << "' (name=) in [effect]apply_to=variation, ignoring";
2324  }
2325  } else if(effect["apply_to"] == "type") {
2326  std::string prev_type = effect["prev_type"];
2327  if(prev_type.empty()) {
2328  prev_type = type().parent_id();
2329  }
2330  const std::string& new_type_id = effect["name"];
2331  const unit_type* new_type = unit_types.find(new_type_id);
2332  if(new_type) {
2333  advance_to(*new_type);
2334  preferences::encountered_units().insert(new_type_id);
2335  if(effect["heal_full"].to_bool(false)) {
2336  heal_fully();
2337  }
2338  } else {
2339  WRN_UT << "unknown type '" << new_type_id << "' (name=) in [effect]apply_to=type, ignoring";
2340  }
2341  }
2342 }
2343 
2344 void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
2345 {
2346  bool generate_description = mod["generate_description"].to_bool(true);
2347 
2348  if(no_add == false) {
2349  modifications_.add_child(mod_type, mod);
2350  }
2351 
2352  bool set_poisoned = false; // Tracks if the poisoned state was set after the type or variation was changed.
2353  config type_effect, variation_effect;
2354  std::vector<t_string> effects_description;
2355  for(const config& effect : mod.child_range("effect")) {
2356  // Apply SUF.
2357  if(const config& afilter = effect.child("filter")) {
2358  assert(resources::filter_con);
2359  if(!unit_filter(vconfig(afilter)).matches(*this, loc_)) {
2360  continue;
2361  }
2362  }
2363  const std::string& apply_to = effect["apply_to"];
2364  int times = effect["times"].to_int(1);
2365  t_string description;
2366 
2367  if(effect["times"] == "per level") {
2368  times = level_;
2369  }
2370 
2371  if(times) {
2372  while (times > 0) {
2373  times --;
2374 
2375  bool was_poisoned = get_state(STATE_POISONED);
2376  // Apply unit type/variation changes last to avoid double applying effects on advance.
2377  if(apply_to == "type") {
2378  set_poisoned = false;
2379  type_effect = effect;
2380  continue;
2381  }
2382  if(apply_to == "variation") {
2383  set_poisoned = false;
2384  variation_effect = effect;
2385  continue;
2386  }
2387 
2388  std::string description_component;
2389  if(resources::lua_kernel) {
2390  description_component = resources::lua_kernel->apply_effect(apply_to, *this, effect, true);
2391  } else if(builtin_effects.count(apply_to)) {
2392  // Normally, the built-in effects are dispatched through Lua so that a user
2393  // can override them if desired. However, since they're built-in, we can still
2394  // apply them if the lua kernel is unavailable.
2395  apply_builtin_effect(apply_to, effect);
2396  description_component = describe_builtin_effect(apply_to, effect);
2397  }
2398  if(!times) {
2399  description += description_component;
2400  }
2401  if(!was_poisoned && get_state(STATE_POISONED)) {
2402  set_poisoned = true;
2403  } else if(was_poisoned && !get_state(STATE_POISONED)) {
2404  set_poisoned = false;
2405  }
2406  } // end while
2407  } else { // for times = per level & level = 0 we still need to rebuild the descriptions
2408  if(resources::lua_kernel) {
2409  description += resources::lua_kernel->apply_effect(apply_to, *this, effect, false);
2410  } else if(builtin_effects.count(apply_to)) {
2411  description += describe_builtin_effect(apply_to, effect);
2412  }
2413  }
2414 
2415  if(effect["times"] == "per level" && !times) {
2416  description = VGETTEXT("$effect_description per level", {{"effect_description", description}});
2417  }
2418 
2419  if(!description.empty()) {
2420  effects_description.push_back(description);
2421  }
2422  }
2423  // Apply variations -- only apply if we are adding this for the first time.
2424  if((!type_effect.empty() || !variation_effect.empty()) && no_add == false) {
2425  if(!type_effect.empty()) {
2426  std::string description;
2427  if(resources::lua_kernel) {
2428  description = resources::lua_kernel->apply_effect(type_effect["apply_to"], *this, type_effect, true);
2429  } else if(builtin_effects.count(type_effect["apply_to"])) {
2430  apply_builtin_effect(type_effect["apply_to"], type_effect);
2431  description = describe_builtin_effect(type_effect["apply_to"], type_effect);
2432  }
2433  effects_description.push_back(description);
2434  }
2435  if(!variation_effect.empty()) {
2436  std::string description;
2437  if(resources::lua_kernel) {
2438  description = resources::lua_kernel->apply_effect(variation_effect["apply_to"], *this, variation_effect, true);
2439  } else if(builtin_effects.count(variation_effect["apply_to"])) {
2440  apply_builtin_effect(variation_effect["apply_to"], variation_effect);
2441  description = describe_builtin_effect(variation_effect["apply_to"], variation_effect);
2442  }
2443  effects_description.push_back(description);
2444  }
2445  if(set_poisoned)
2446  // An effect explicitly set the poisoned state, and this
2447  // should override the unit being immune to poison.
2448  set_state(STATE_POISONED, true);
2449  }
2450 
2451  t_string description;
2452 
2453  const t_string& mod_description = mod["description"];
2454  if(!mod_description.empty()) {
2455  description = mod_description;
2456  }
2457 
2458  // Punctuation should be translatable: not all languages use Latin punctuation.
2459  // (However, there maybe is a better way to do it)
2460  if(generate_description && !effects_description.empty()) {
2461  if(!mod_description.empty()) {
2462  description += "\n";
2463  }
2464 
2465  for(const auto& desc_line : effects_description) {
2466  description += desc_line + "\n";
2467  }
2468  }
2469 
2470  // store trait info
2471  if(mod_type == "trait") {
2472  add_trait_description(mod, description);
2473  }
2474 
2475  //NOTE: if not a trait, description is currently not used
2476 }
2477 
2478 void unit::add_trait_description(const config& trait, const t_string& description)
2479 {
2480  const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
2481  const auto& gender_specific_name = trait[gender_string];
2482 
2483  const t_string name = gender_specific_name.empty() ? trait["name"] : gender_specific_name;
2484 
2485  if(!name.empty()) {
2486  trait_names_.push_back(name);
2487  trait_descriptions_.push_back(description);
2488  }
2489 }
2490 
2491 std::string unit::absolute_image() const
2492 {
2493  return type().icon().empty() ? type().image() : type().icon();
2494 }
2495 
2496 std::string unit::default_anim_image() const
2497 {
2498  return type().image().empty() ? type().icon() : type().image();
2499 }
2500 
2502 {
2503  log_scope("apply mods");
2504 
2505  variables_.clear_children("mods");
2506  if(modifications_.has_child("advance")) {
2507  deprecated_message("[advance]", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [advancement] instead.");
2508  }
2510  add_modification(mod.key, mod.cfg, true);
2511  }
2512 }
2513 
2514 bool unit::invisible(const map_location& loc, bool see_all) const
2515 {
2516  if(loc != get_location()) {
2517  DBG_UT << "unit::invisible called: id = " << id() << " loc = " << loc << " get_loc = " << get_location();
2518  }
2519 
2520  // This is a quick condition to check, and it does not depend on the
2521  // location (so might as well bypass the location-based cache).
2522  if(get_state(STATE_UNCOVERED)) {
2523  return false;
2524  }
2525 
2526  // Fetch from cache
2527  /**
2528  * @todo FIXME: We use the cache only when using the default see_all=true
2529  * Maybe add a second cache if the see_all=false become more frequent.
2530  */
2531  if(see_all) {
2532  const auto itor = invisibility_cache_.find(loc);
2533  if(itor != invisibility_cache_.end()) {
2534  return itor->second;
2535  }
2536  }
2537 
2538  // Test hidden status
2539  static const std::string hides("hides");
2540  bool is_inv = get_ability_bool(hides, loc);
2541  if(is_inv){
2542  is_inv = (resources::gameboard ? !resources::gameboard->would_be_discovered(loc, side_,see_all) : true);
2543  }
2544 
2545  if(see_all) {
2546  // Add to caches
2547  if(invisibility_cache_.empty()) {
2548  units_with_cache.push_back(this);
2549  }
2550 
2551  invisibility_cache_[loc] = is_inv;
2552  }
2553 
2554  return is_inv;
2555 }
2556 
2557 bool unit::is_visible_to_team(const team& team, bool const see_all) const
2558 {
2559  const map_location& loc = get_location();
2560  return is_visible_to_team(loc, team, see_all);
2561 }
2562 
2563 bool unit::is_visible_to_team(const map_location& loc, const team& team, bool const see_all) const
2564 {
2565  if(!display::get_singleton()->get_map().on_board(loc)) {
2566  return false;
2567  }
2568 
2569  if(see_all) {
2570  return true;
2571  }
2572 
2573  if(team.is_enemy(side()) && invisible(loc)) {
2574  return false;
2575  }
2576 
2577  // allied planned moves are also visible under fog. (we assume that fake units on the map are always whiteboard markers)
2578  if(!team.is_enemy(side()) && underlying_id_.is_fake()) {
2579  return true;
2580  }
2581 
2582  // when the whiteboard planned unit map is applied, it uses moved the _real_ unit so
2583  // underlying_id_.is_fake() will be false and the check above will not apply.
2584  // TODO: improve this check so that is also works for allied planned units but without
2585  // breaking sp campaigns with allies under fog. We probably need an explicit flag
2586  // is_planned_ in unit that is set by the whiteboard.
2587  if(team.side() == side()) {
2588  return true;
2589  }
2590 
2591  if(team.fogged(loc)) {
2592  return false;
2593  }
2594 
2595  return true;
2596 }
2597 
2599 {
2600  if(underlying_id_.value == 0) {
2602  underlying_id_ = id_manager.next_id();
2603  } else {
2604  underlying_id_ = id_manager.next_fake_id();
2605  }
2606  }
2607 
2608  if(id_.empty() /*&& !underlying_id_.is_fake()*/) {
2609  std::stringstream ss;
2610  ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_.value;
2611  id_ = ss.str();
2612  }
2613 }
2614 
2615 unit& unit::mark_clone(bool is_temporary)
2616 {
2618  if(is_temporary) {
2619  underlying_id_ = ids.next_fake_id();
2620  } else {
2622  underlying_id_ = ids.next_id();
2623  }
2624  else {
2625  underlying_id_ = ids.next_fake_id();
2626  }
2627  std::string::size_type pos = id_.find_last_of('-');
2628  if(pos != std::string::npos && pos+1 < id_.size()
2629  && id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
2630  // this appears to be a duplicate of a generic unit, so give it a new id
2631  WRN_UT << "assigning new id to clone of generic unit " << id_;
2632  id_.clear();
2633  set_underlying_id(ids);
2634  }
2635  }
2636  return *this;
2637 }
2638 
2639 
2641  : u_(const_cast<unit&>(u))
2642  , moves_(u.movement_left(true))
2643 {
2644  if(operate) {
2646  }
2647 }
2648 
2650 {
2651  assert(resources::gameboard);
2652  try {
2653  if(!resources::gameboard->units().has_unit(&u_)) {
2654  /*
2655  * It might be valid that the unit is not in the unit map.
2656  * It might also mean a no longer valid unit will be assigned to.
2657  */
2658  DBG_UT << "The unit to be removed is not in the unit map.";
2659  }
2660 
2662  } catch(...) {
2663  DBG_UT << "Caught exception when destroying unit_movement_resetter: " << utils::get_unknown_exception_type();
2664  }
2665 }
2666 
2667 std::string unit::TC_image_mods() const
2668 {
2669  return formatter() << "~RC(" << flag_rgb() << ">" << team::get_side_color_id(side()) << ")";
2670 }
2671 
2672 std::string unit::image_mods() const
2673 {
2674  if(!image_mods_.empty()) {
2675  return formatter() << "~" << image_mods_ << TC_image_mods();
2676  }
2677 
2678  return TC_image_mods();
2679 }
2680 
2681 // Called by the Lua API after resetting an attack pointer.
2683 {
2684  set_attr_changed(UA_ATTACKS);
2685  auto iter = std::find(attacks_.begin(), attacks_.end(), atk);
2686  if(iter == attacks_.end()) {
2687  return false;
2688  }
2689  attacks_.erase(iter);
2690  return true;
2691 }
2692 
2694 {
2695  if(attacks_left_ == max_attacks_) {
2696  //TODO: add state_not_attacked
2697  }
2698 
2699  set_attacks(0);
2700 }
2701 
2703 {
2704  if(movement_left() == total_movement()) {
2705  set_state(STATE_NOT_MOVED,true);
2706  }
2707 
2708  set_movement(0, true);
2709 }
2710 
2711 void unit::set_hidden(bool state) const
2712 {
2713 // appearance_changed_ = true;
2714  hidden_ = state;
2715  if(!state) {
2716  return;
2717  }
2718 
2719  // TODO: this should really hide the halo, not destroy it
2720  // We need to get rid of haloes immediately to avoid display glitches
2721  anim_comp_->clear_haloes();
2722 }
2723 
2724 void unit::set_image_halo(const std::string& halo)
2725 {
2726  appearance_changed_ = true;
2727  anim_comp_->clear_haloes();
2728  halo_ = halo;
2729 }
2730 
2732 {
2733  if(upkeep.empty()) {
2734  return;
2735  }
2736 
2737  try {
2738  upkeep_ = upkeep.apply_visitor(upkeep_parser_visitor{});
2739  } catch(std::invalid_argument& e) {
2740  WRN_UT << "Found invalid upkeep=\"" << e.what() << "\" in a unit";
2741  upkeep_ = upkeep_full{};
2742  }
2743 }
2744 
2746 {
2747  upkeep = utils::visit(upkeep_type_visitor{}, upkeep_);
2748 }
2749 
2751 {
2752  changed_attributes_.reset();
2753  for(const auto& a_ptr : attacks_) {
2754  a_ptr->set_changed(false);
2755  }
2756 }
2757 
2758 std::vector<t_string> unit::unit_special_notes() const {
2759  return combine_special_notes(special_notes_, abilities(), attacks(), movement_type());
2760 }
2761 
2762 // Filters unimportant stats from the unit config and returns a checksum of
2763 // the remaining config.
2765 {
2766  config unit_config;
2767  config wcfg;
2768  u.write(unit_config);
2769 
2770  static const std::set<std::string_view> main_keys {
2771  "advances_to",
2772  "alignment",
2773  "cost",
2774  "experience",
2775  "gender",
2776  "hitpoints",
2777  "ignore_race_traits",
2778  "ignore_global_traits",
2779  "level",
2780  "recall_cost",
2781  "max_attacks",
2782  "max_experience",
2783  "max_hitpoints",
2784  "max_moves",
2785  "movement",
2786  "movement_type",
2787  "race",
2788  "random_traits",
2789  "resting",
2790  "undead_variation",
2791  "upkeep",
2792  "zoc"
2793  };
2794 
2795  for(const std::string_view& main_key : main_keys) {
2796  wcfg[main_key] = unit_config[main_key];
2797  }
2798 
2799  static const std::set<std::string_view> attack_keys {
2800  "name",
2801  "type",
2802  "range",
2803  "damage",
2804  "number"
2805  };
2806 
2807  for(const config& att : unit_config.child_range("attack")) {
2808  config& child = wcfg.add_child("attack");
2809 
2810  for(const std::string_view& attack_key : attack_keys) {
2811  child[attack_key] = att[attack_key];
2812  }
2813 
2814  for(const config& spec : att.child_range("specials")) {
2815  config& child_spec = child.add_child("specials", spec);
2816 
2817  child_spec.recursive_clear_value("description");
2819  child_spec.recursive_clear_value("description_inactive");
2820  child_spec.recursive_clear_value("name");
2821  child_spec.recursive_clear_value("name_inactive");
2822  }
2823  }
2824  }
2825 
2826  for(const config& abi : unit_config.child_range("abilities")) {
2827  config& child = wcfg.add_child("abilities", abi);
2828 
2829  child.recursive_clear_value("description");
2830  child.recursive_clear_value("description_inactive");
2831  child.recursive_clear_value("name");
2832  child.recursive_clear_value("name_inactive");
2833  }
2834 
2835  for(const config& trait : unit_config.child_range("trait")) {
2836  config& child = wcfg.add_child("trait", trait);
2837 
2838  child.recursive_clear_value("description");
2839  child.recursive_clear_value("male_name");
2840  child.recursive_clear_value("female_name");
2841  child.recursive_clear_value("name");
2842  }
2843 
2844  static const std::set<std::string_view> child_keys {
2845  "advance_from",
2846  "defense",
2847  "movement_costs",
2848  "vision_costs",
2849  "jamming_costs",
2850  "resistance"
2851  };
2852 
2853  for(const std::string_view& child_key : child_keys) {
2854  for(const config& c : unit_config.child_range(child_key)) {
2855  wcfg.add_child(child_key, c);
2856  }
2857  }
2858 
2859  DBG_UT << wcfg;
2860 
2861  return wcfg.hash();
2862 }
2863 
2864 void swap(unit& lhs, unit& rhs)
2865 {
2866  lhs.swap(rhs);
2867 }
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:1908
bool appearance_changed_
Definition: unit.hpp:1973
config modifications_
Definition: unit.hpp:1952
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:1935
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:1884
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:1899
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:2501
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:1879
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:1878
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:1950
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:1970
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:1949
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:1969
bool resting_
Definition: unit.hpp:1905
std::string id_
Definition: unit.hpp:1866
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:1892
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:1883
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:1934
bool generate_name_
Definition: unit.hpp:1965
map_location loc_
Definition: unit.hpp:1853
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:1962
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:1574
void set_small_profile(const std::string &value)
Definition: unit.hpp:599
To set the size of known_boolean_states_.
Definition: unit.hpp:872
config events_
Definition: unit.hpp:1918
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:1938
The unit is a guardian - it won&#39;t move unless a target is sighted.
Definition: unit.hpp:870
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:2025
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:1957
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:2750
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:1960
int experience_needed(bool with_acceleration=true) const
Definition: types.cpp:577
double xp_bar_scaling_
Definition: unit.hpp:1950
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:1947
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:2491
#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:1955
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:2693
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:1885
void set_facing(map_location::DIRECTION dir) const
The this unit&#39;s facing.
Definition: unit.cpp:1663
void set_interrupted_move(const map_location &interrupted_move)
Set the target location of the unit&#39;s interrupted move.
Definition: unit.hpp:1427
std::string image_mods_
Definition: unit.hpp:1886
int recall_cost_
Definition: unit.hpp:1880
void set_total_movement(int value)
Definition: unit.hpp:1274
config filter_recall_
Definition: unit.hpp:1919
movetype movement_type_
Definition: unit.hpp:1901
bool canrecruit_
Definition: unit.hpp:1881
const_attr_itors attribute_range() const
Definition: config.cpp:858
bool hold_position_
Definition: unit.hpp:1903
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:1498
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:1882
int hit_points_
Definition: unit.hpp:1873
int vision_
Definition: unit.hpp:1898
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:1937
state_t
Built-in status effects known to the engine.
Definition: unit.hpp:862
map_location loc_
upkeep_t upkeep_
Definition: unit.hpp:1967
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:1914
const unit_type & type() const
This unit&#39;s type, accounting for gender and variation.
Definition: unit.hpp:358
creating intitial [unit]s, executing toplevel [lua] etc.
Definition: game_data.hpp:74
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:2344
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit&#39;s defense on a given terrain.
Definition: unit.cpp:1687
static void clear_status_caches()
Clear this unit status cache for all units.
Definition: unit.cpp:676
bool is_fearless_
Definition: unit.hpp:1940
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:1923
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:986
#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:1917
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:1888
static std::map< std::string, state_t > known_boolean_state_names_
Definition: unit.hpp:1915
int max_experience_
Definition: unit.hpp:1876
Included some of the flavortext from weapon specials.
std::vector< t_string > special_notes_
Definition: unit.hpp:1958
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:1441
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:1726
void set_underlying_id(n_unit::id_manager &id_manager)
Sets the internal ID.
Definition: unit.cpp:2598
int movement_
Definition: unit.hpp:1896
const movetype & movement_type() const
Definition: types.hpp:191
unit_alignments::type alignment() const
Definition: types.hpp:195
bool is_healthy_
Definition: unit.hpp:1940
int max_hit_points_
Definition: unit.hpp:1874
t_string name_
Definition: unit.hpp:1867
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:2731
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:1672
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:1890
std::size_t value
Definition: id.hpp:27
bool get_attacks_changed() const
Definition: unit.cpp:1488
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:2758
void set_hidden(bool state) const
Sets whether the unit is hidden on the map.
Definition: unit.cpp:2711
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:2764
std::string default_anim_image() const
The default image to use for animation frames with no defined image.
Definition: unit.cpp:2496
std::optional< std::string > halo_
Definition: unit.hpp:1961
const std::vector< std::string > & overlays() const
Get the unit&#39;s overlay images.
Definition: unit.hpp:1602
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:1873
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:1897
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:727
std::string role_
Definition: unit.hpp:1925
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:1891
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:1861
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:2615
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:1682
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:1910
bool invisible(const map_location &loc, bool see_all=true) const
Definition: unit.cpp:2514
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:1864
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:1475
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:2724
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:2864
int attacks_left_
Definition: unit.hpp:1907
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:1143
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:1353
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:2478
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:1751
#define log_scope(description)
Definition: log.hpp:239
std::string halo() const
Definition: types.hpp:183
static std::string type()
Definition: unit.hpp:1137
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:1855
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:1894
std::set< std::string > & encountered_units()
Definition: game.cpp:916
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:1868
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:1871
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:1926
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:1983
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:2557
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:1870
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:1858
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:2745
unsigned int num_traits() const
Definition: types.hpp:138
static const std::set< std::string > builtin_effects
Definition: unit.hpp:1520
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:1808
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:1360
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:1982
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:1917
map_location goto_
Definition: unit.hpp:1938
double hp_bar_scaling() const
Definition: types.hpp:165
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1269
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:1193
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:1730
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:1791
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:1173
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:1702
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:2667
void remove_movement_ai()
Sets the unit to have no moves left for this turn.
Definition: unit.cpp:2702
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:1285
bool random_traits_
Definition: unit.hpp:1964
config abilities_
Definition: unit.hpp:1953
bool empty() const
Definition: config.cpp:941
std::bitset< UA_COUNT > changed_attributes_
Definition: unit.hpp:1974
int experience_
Definition: unit.hpp:1875
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition: unit.cpp:2672
bool has_ability_by_id(const std::string &ability) const
Check if the unit has an ability of a specific ID.
Definition: unit.cpp:1464
utils::string_map modification_descriptions_
Definition: unit.hpp:1942
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:1904
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:1931
bool emit_zoc_
Definition: unit.hpp:1921
bool remove_attack(attack_ptr atk)
Remove an attack from the unit.
Definition: unit.cpp:2682
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46