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