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