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