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