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