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