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