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