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