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