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_modifications_list(const std::string& mod_type) const
892 {
893  std::vector<std::string> res;
894 
895  for(const config& mod : modifications_.child_range(mod_type)){
896  // Make sure to return empty id trait strings as otherwise
897  // names will not match in length (Bug #21967)
898  res.push_back(mod["id"]);
899  }
900  if(mod_type == "advancement"){
901  for(const config& mod : modifications_.child_range("advance")){
902  res.push_back(mod["id"]);
903  }
904  }
905  return res;
906 }
907 
908 std::size_t unit::modification_count(const std::string& type) const
909 {
910  //return numbers of modifications of same type, same without ID.
911  std::size_t res = modifications_.child_range(type).size();
912  if(type == "advancement"){
913  res += modification_count("advance");
914  }
915 
916  return res;
917 }
918 
919 
920 /**
921  * Advances this unit to the specified type.
922  * Experience is left unchanged.
923  * Current hitpoints/movement/attacks_left is left unchanged unless it would violate their maximum.
924  * Assumes gender_ and variation_ are set to their correct values.
925  */
926 void unit::advance_to(const unit_type& u_type, bool use_traits)
927 {
928  auto ss = stats_storage_resetter(*this, true);
929  appearance_changed_ = true;
930  // For reference, the type before this advancement.
931  const unit_type& old_type = type();
932  // Adjust the new type for gender and variation.
934  // In case u_type was already a variation, make sure our variation is set correctly.
935  variation_ = new_type.variation_id();
936 
937  // Reset the scalar values first
938  trait_names_.clear();
939  trait_descriptions_.clear();
940  trait_nonhidden_ids_.clear();
941  is_fearless_ = false;
942  is_healthy_ = false;
943  image_mods_.clear();
944  overlays_.clear();
945  ellipse_.reset();
946 
947  // Clear modification-related caches
949 
950 
951  if(!new_type.usage().empty()) {
952  set_usage(new_type.usage());
953  }
954 
955  set_image_halo(new_type.halo());
956  if(!new_type.ellipse().empty()) {
957  set_image_ellipse(new_type.ellipse());
958  }
959 
960  generate_name_ &= new_type.generate_name();
961  abilities_ = new_type.abilities_cfg();
962  advancements_.clear();
963 
964  for(const config& advancement : new_type.advancements()) {
965  advancements_.push_back(advancement);
966  }
967 
968  // If unit has specific profile, remember it and keep it after advancing
969  if(small_profile_.empty() || small_profile_ == old_type.small_profile()) {
970  small_profile_ = new_type.small_profile();
971  }
972 
973  if(profile_.empty() || profile_ == old_type.big_profile()) {
974  profile_ = new_type.big_profile();
975  }
976  // NOTE: There should be no need to access old_cfg (or new_cfg) after this
977  // line. Particularly since the swap might have affected old_cfg.
978 
979  advances_to_ = new_type.advances_to();
980 
981  race_ = new_type.race();
982  type_ = &new_type;
983  type_name_ = new_type.type_name();
984  description_ = new_type.unit_description();
986  undead_variation_ = new_type.undead_variation();
987  max_experience_ = new_type.experience_needed(true);
988  level_ = new_type.level();
989  recall_cost_ = new_type.recall_cost();
990  alignment_ = new_type.alignment();
991  max_hit_points_ = new_type.hitpoints();
992  hp_bar_scaling_ = new_type.hp_bar_scaling();
993  xp_bar_scaling_ = new_type.xp_bar_scaling();
994  max_movement_ = new_type.movement();
995  vision_ = new_type.vision(true);
996  jamming_ = new_type.jamming();
997  movement_type_ = new_type.movement_type();
998  emit_zoc_ = new_type.has_zoc();
999  attacks_.clear();
1000  std::transform(new_type.attacks().begin(), new_type.attacks().end(), std::back_inserter(attacks_), [](const attack_type& atk) {
1001  return std::make_shared<attack_type>(atk);
1002  });
1003  unit_value_ = new_type.cost();
1004 
1005  max_attacks_ = new_type.max_attacks();
1006 
1007  flag_rgb_ = new_type.flag_rgb();
1008 
1009  upkeep_ = upkeep_full{};
1010  parse_upkeep(new_type.get_cfg()["upkeep"]);
1011 
1012  anim_comp_->reset_after_advance(&new_type);
1013 
1014  if(random_traits_) {
1015  generate_traits(!use_traits);
1016  } else {
1017  // This will add any "musthave" traits to the new unit that it doesn't already have.
1018  // This covers the Dark Sorcerer advancing to Lich and gaining the "undead" trait,
1019  // but random and/or optional traits are not added,
1020  // and neither are inappropriate traits removed.
1021  generate_traits(true);
1022  }
1023 
1024  // Apply modifications etc, refresh the unit.
1025  // This needs to be after type and gender are fixed,
1026  // since there can be filters on the modifications
1027  // that may result in different effects after the advancement.
1029 
1030  // Now that modifications are done modifying traits, check if poison should
1031  // be cleared.
1032  // Make sure apply_modifications() didn't attempt to heal the unit (for example if the unit has a default amla.).
1033  ss();
1034  if(get_state("unpetrifiable")) {
1035  set_state(STATE_PETRIFIED, false);
1036  }
1037 
1038  // In case the unit carries EventWML, apply it now
1041  }
1042  bool bool_small_profile = get_attr_changed(UA_SMALL_PROFILE);
1043  bool bool_profile = get_attr_changed(UA_PROFILE);
1045  if(bool_small_profile && small_profile_ != new_type.small_profile()) {
1047  }
1048 
1049  if(bool_profile && profile_ != new_type.big_profile()) {
1051  }
1052 }
1053 
1054 std::string unit::big_profile() const
1055 {
1056  if(!profile_.empty() && profile_ != "unit_image") {
1057  return profile_;
1058  }
1059 
1060  return absolute_image();
1061 }
1062 
1063 std::string unit::small_profile() const
1064 {
1065  if(!small_profile_.empty() && small_profile_ != "unit_image") {
1066  return small_profile_;
1067  }
1068 
1069  if(!profile_.empty() && small_profile_ != "unit_image" && profile_ != "unit_image") {
1070  return profile_;
1071  }
1072 
1073  return absolute_image();
1074 }
1075 
1076 const std::string& unit::leader_crown()
1077 {
1078  return leader_crown_path;
1079 }
1080 
1081 const std::string& unit::flag_rgb() const
1082 {
1083  return flag_rgb_.empty() ? game_config::unit_rgb : flag_rgb_;
1084 }
1085 
1086 static color_t hp_color_impl(int hitpoints, int max_hitpoints)
1087 {
1088  double unit_energy = 0.0;
1089  color_t energy_color {0,0,0,255};
1090 
1091  if(max_hitpoints > 0) {
1092  unit_energy = static_cast<double>(hitpoints)/static_cast<double>(max_hitpoints);
1093  }
1094 
1095  if(1.0 == unit_energy) {
1096  energy_color.r = 33;
1097  energy_color.g = 225;
1098  energy_color.b = 0;
1099  } else if(unit_energy > 1.0) {
1100  energy_color.r = 100;
1101  energy_color.g = 255;
1102  energy_color.b = 100;
1103  } else if(unit_energy >= 0.75) {
1104  energy_color.r = 170;
1105  energy_color.g = 255;
1106  energy_color.b = 0;
1107  } else if(unit_energy >= 0.5) {
1108  energy_color.r = 255;
1109  energy_color.g = 175;
1110  energy_color.b = 0;
1111  } else if(unit_energy >= 0.25) {
1112  energy_color.r = 255;
1113  energy_color.g = 155;
1114  energy_color.b = 0;
1115  } else {
1116  energy_color.r = 255;
1117  energy_color.g = 0;
1118  energy_color.b = 0;
1119  }
1120 
1121  return energy_color;
1122 }
1123 
1125 {
1126  return hp_color_impl(hitpoints(), max_hitpoints());
1127 }
1128 
1129 color_t unit::hp_color(int new_hitpoints) const
1130 {
1131  return hp_color_impl(new_hitpoints, hitpoints());
1132 }
1133 
1135 {
1136  return hp_color_impl(1, 1);
1137 }
1138 
1139 color_t unit::xp_color(int xp_to_advance, bool can_advance, bool has_amla)
1140 {
1141  const color_t near_advance_color {255,255,255,255};
1142  const color_t mid_advance_color {150,255,255,255};
1143  const color_t far_advance_color {0,205,205,255};
1144  const color_t normal_color {0,160,225,255};
1145  const color_t near_amla_color {225,0,255,255};
1146  const color_t mid_amla_color {169,30,255,255};
1147  const color_t far_amla_color {139,0,237,255};
1148  const color_t amla_color {170,0,255,255};
1149 
1150  const bool near_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience;
1151  const bool mid_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience*2;
1152  const bool far_advance = static_cast<int>(xp_to_advance) <= game_config::kill_experience*3;
1153 
1154  color_t color = normal_color;
1155  if(can_advance){
1156  if(near_advance){
1157  color=near_advance_color;
1158  } else if(mid_advance){
1159  color=mid_advance_color;
1160  } else if(far_advance){
1161  color=far_advance_color;
1162  }
1163  } else if(has_amla){
1164  if(near_advance){
1165  color=near_amla_color;
1166  } else if(mid_advance){
1167  color=mid_amla_color;
1168  } else if(far_advance){
1169  color=far_amla_color;
1170  } else {
1171  color=amla_color;
1172  }
1173  }
1174 
1175  return(color);
1176 }
1177 
1179 {
1180  bool major_amla = false;
1181  bool has_amla = false;
1182  for(const config& adv:get_modification_advances()){
1183  major_amla |= adv["major_amla"].to_bool();
1184  has_amla = true;
1185  }
1186  //TODO: calculating has_amla and major_amla can be a quite slow operation, we should cache these two values somehow.
1187  return xp_color(experience_to_advance(), !advances_to().empty() || major_amla, has_amla);
1188 }
1189 
1190 void unit::set_recruits(const std::vector<std::string>& recruits)
1191 {
1194 }
1195 
1196 const std::vector<std::string> unit::advances_to_translated() const
1197 {
1198  std::vector<std::string> result;
1199  for(const std::string& adv_type_id : advances_to_) {
1200  if(const unit_type* adv_type = unit_types.find(adv_type_id)) {
1201  result.push_back(adv_type->type_name());
1202  } else {
1203  WRN_UT << "unknown unit in advances_to list of type "
1204  << type().log_id() << ": " << adv_type_id;
1205  }
1206  }
1207 
1208  return result;
1209 }
1210 
1211 void unit::set_advances_to(const std::vector<std::string>& advances_to)
1212 {
1216 }
1217 
1218 void unit::set_movement(int moves, bool unit_action)
1219 {
1220  // If this was because the unit acted, clear its "not acting" flags.
1221  if(unit_action) {
1222  end_turn_ = hold_position_ = false;
1223  }
1224 
1225  movement_ = std::max<int>(0, moves);
1226 }
1227 
1228 /**
1229  * Determines if @a mod_dur "matches" @a goal_dur.
1230  * If goal_dur is not empty, they match if they are equal.
1231  * If goal_dur is empty, they match if mod_dur is neither empty nor "forever".
1232  * Helper function for expire_modifications().
1233  */
1234 inline bool mod_duration_match(const std::string& mod_dur, const std::string& goal_dur)
1235 {
1236  if(goal_dur.empty()) {
1237  // Default is all temporary modifications.
1238  return !mod_dur.empty() && mod_dur != "forever";
1239  }
1240 
1241  return mod_dur == goal_dur;
1242 }
1243 
1244 void unit::expire_modifications(const std::string& duration)
1245 {
1246  // If any modifications expire, then we will need to rebuild the unit.
1247  const unit_type* rebuild_from = nullptr;
1248  // Loop through all types of modifications.
1249  for(const auto& mod_name : ModificationTypes) {
1250  // Loop through all modifications of this type.
1251  // Looping in reverse since we may delete the current modification.
1252  for(int j = modifications_.child_count(mod_name)-1; j >= 0; --j)
1253  {
1254  const config& mod = modifications_.mandatory_child(mod_name, j);
1255 
1256  if(mod_duration_match(mod["duration"], duration)) {
1257  // If removing this mod means reverting the unit's type:
1258  if(const config::attribute_value* v = mod.get("prev_type")) {
1259  rebuild_from = &get_unit_type(v->str());
1260  }
1261  // Else, if we have not already specified a type to build from:
1262  else if(rebuild_from == nullptr) {
1263  rebuild_from = &type();
1264  }
1265 
1266  modifications_.remove_child(mod_name, j);
1267  }
1268  }
1269  }
1270 
1271  if(rebuild_from != nullptr) {
1272  anim_comp_->clear_haloes();
1273  advance_to(*rebuild_from);
1274  }
1275 }
1276 
1278 {
1279  expire_modifications("turn");
1280 
1284  set_state(STATE_UNCOVERED, false);
1285 }
1286 
1288 {
1289  expire_modifications("turn end");
1290 
1291  set_state(STATE_SLOWED,false);
1293  resting_ = false;
1294  }
1295 
1296  set_state(STATE_NOT_MOVED,false);
1297  // Clear interrupted move
1299 }
1300 
1302 {
1303  // Set the goto-command to be going to no-where
1304  goto_ = map_location();
1305 
1306  // Expire all temporary modifications.
1308 
1309  heal_fully();
1310  set_state(STATE_SLOWED, false);
1311  set_state(STATE_POISONED, false);
1312  set_state(STATE_PETRIFIED, false);
1313  set_state(STATE_GUARDIAN, false);
1314 }
1315 
1316 void unit::heal(int amount)
1317 {
1318  int max_hp = max_hitpoints();
1319  if(hit_points_ < max_hp) {
1320  hit_points_ += amount;
1321 
1322  if(hit_points_ > max_hp) {
1323  hit_points_ = max_hp;
1324  }
1325  }
1326 
1327  if(hit_points_<1) {
1328  hit_points_ = 1;
1329  }
1330 }
1331 
1332 const std::set<std::string> unit::get_states() const
1333 {
1334  std::set<std::string> all_states = states_;
1335  for(const auto& state : known_boolean_state_names_) {
1336  if(get_state(state.second)) {
1337  all_states.insert(state.first);
1338  }
1339  }
1340 
1341  // Backwards compatibility for not_living. Don't remove before 1.12
1342  if(all_states.count("undrainable") && all_states.count("unpoisonable") && all_states.count("unplagueable")) {
1343  all_states.insert("not_living");
1344  }
1345 
1346  return all_states;
1347 }
1348 
1349 bool unit::get_state(const std::string& state) const
1350 {
1351  state_t known_boolean_state_id = get_known_boolean_state_id(state);
1352  if(known_boolean_state_id!=STATE_UNKNOWN){
1353  return get_state(known_boolean_state_id);
1354  }
1355 
1356  // Backwards compatibility for not_living. Don't remove before 1.12
1357  if(state == "not_living") {
1358  return
1359  get_state("undrainable") &&
1360  get_state("unpoisonable") &&
1361  get_state("unplagueable");
1362  }
1363 
1364  return states_.find(state) != states_.end();
1365 }
1366 
1367 void unit::set_state(state_t state, bool value)
1368 {
1369  known_boolean_states_[state] = value;
1370 }
1371 
1372 bool unit::get_state(state_t state) const
1373 {
1374  return known_boolean_states_[state];
1375 }
1376 
1378 {
1379  auto i = known_boolean_state_names_.find(state);
1380  if(i != known_boolean_state_names_.end()) {
1381  return i->second;
1382  }
1383 
1384  return STATE_UNKNOWN;
1385 }
1386 
1387 std::map<std::string, unit::state_t> unit::known_boolean_state_names_ {
1388  {"slowed", STATE_SLOWED},
1389  {"poisoned", STATE_POISONED},
1390  {"petrified", STATE_PETRIFIED},
1391  {"uncovered", STATE_UNCOVERED},
1392  {"not_moved", STATE_NOT_MOVED},
1393  {"unhealable", STATE_UNHEALABLE},
1394  {"guardian", STATE_GUARDIAN},
1395  {"invulnerable", STATE_INVULNERABLE},
1396 };
1397 
1398 void unit::set_state(const std::string& state, bool value)
1399 {
1400  appearance_changed_ = true;
1401  state_t known_boolean_state_id = get_known_boolean_state_id(state);
1402  if(known_boolean_state_id != STATE_UNKNOWN) {
1403  set_state(known_boolean_state_id, value);
1404  return;
1405  }
1406 
1407  // Backwards compatibility for not_living. Don't remove before 1.12
1408  if(state == "not_living") {
1409  set_state("undrainable", value);
1410  set_state("unpoisonable", value);
1411  set_state("unplagueable", value);
1412  }
1413 
1414  if(value) {
1415  states_.insert(state);
1416  } else {
1417  states_.erase(state);
1418  }
1419 }
1420 
1421 bool unit::has_ability_by_id(const std::string& ability) const
1422 {
1423  for(const config::any_child ab : abilities_.all_children_range()) {
1424  if(ab.cfg["id"] == ability) {
1425  return true;
1426  }
1427  }
1428 
1429  return false;
1430 }
1431 
1432 void unit::remove_ability_by_id(const std::string& ability)
1433 {
1436  while (i != abilities_.ordered_end()) {
1437  if(i->cfg["id"] == ability) {
1438  i = abilities_.erase(i);
1439  } else {
1440  ++i;
1441  }
1442  }
1443 }
1444 
1445 static bool matches_ability_filter(const config & cfg, const std::string& tag_name, const config & filter)
1446 {
1447  using namespace utils::config_filters;
1448 
1449  if(!filter["affect_adjacent"].empty()){
1450  bool adjacent = cfg.has_child("affect_adjacent");
1451  if(filter["affect_adjacent"].to_bool() != adjacent){
1452  return false;
1453  }
1454  }
1455 
1456  if(!bool_matches_if_present(filter, cfg, "affect_self", true))
1457  return false;
1458 
1459  if(!bool_or_empty(filter, cfg, "affect_allies"))
1460  return false;
1461 
1462  if(!bool_matches_if_present(filter, cfg, "affect_enemies", false))
1463  return false;
1464 
1465  if(!bool_matches_if_present(filter, cfg, "cumulative", false))
1466  return false;
1467 
1468  const std::vector<std::string> filter_type = utils::split(filter["tag_name"]);
1469  if ( !filter_type.empty() && std::find(filter_type.begin(), filter_type.end(), tag_name) == filter_type.end() )
1470  return false;
1471 
1472  if(!string_matches_if_present(filter, cfg, "id", ""))
1473  return false;
1474 
1475  if(!string_matches_if_present(filter, cfg, "apply_to", "self"))
1476  return false;
1477 
1478  if(!string_matches_if_present(filter, cfg, "overwrite_specials", "none"))
1479  return false;
1480 
1481  if(!string_matches_if_present(filter, cfg, "active_on", "both"))
1482  return false;
1483 
1484  //for damage only
1485  if(!string_matches_if_present(filter, cfg, "replacement_type", ""))
1486  return false;
1487 
1488  if(!string_matches_if_present(filter, cfg, "alternative_type", ""))
1489  return false;
1490 
1491  //for plague only
1492  if(!string_matches_if_present(filter, cfg, "type", ""))
1493  return false;
1494 
1495  if(!filter["value"].empty()){
1496  if(tag_name == "drains"){
1497  if(!int_matches_if_present(filter, cfg, "value", 50)){
1498  return false;
1499  }
1500  } else if(tag_name == "berserk"){
1501  if(!int_matches_if_present(filter, cfg, "value", 1)){
1502  return false;
1503  }
1504  } else if(tag_name == "heal_on_hit" || tag_name == "heals" || tag_name == "regenerate" || tag_name == "leadership"){
1505  if(!int_matches_if_present(filter, cfg, "value" , 0)){
1506  return false;
1507  }
1508  } else {
1509  if(!int_matches_if_present(filter, cfg, "value")){
1510  return false;
1511  }
1512  }
1513  }
1514 
1515  if(!int_matches_if_present_or_negative(filter, cfg, "add", "sub"))
1516  return false;
1517 
1518  if(!int_matches_if_present_or_negative(filter, cfg, "sub", "add"))
1519  return false;
1520 
1521  if(!double_matches_if_present(filter, cfg, "multiply"))
1522  return false;
1523 
1524  if(!double_matches_if_present(filter, cfg, "divide"))
1525  return false;
1526 
1527  // Passed all tests.
1528  return true;
1529 }
1530 
1531 bool unit::ability_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const
1532 {
1533  // Handle the basic filter.
1534  bool matches = matches_ability_filter(cfg, tag_name, filter);
1535 
1536  // Handle [and], [or], and [not] with in-order precedence
1537  for (const config::any_child condition : filter.all_children_range() )
1538  {
1539  // Handle [and]
1540  if ( condition.key == "and" )
1541  matches = matches && ability_matches_filter(cfg, tag_name, condition.cfg);
1542 
1543  // Handle [or]
1544  else if ( condition.key == "or" )
1545  matches = matches || ability_matches_filter(cfg, tag_name, condition.cfg);
1546 
1547  // Handle [not]
1548  else if ( condition.key == "not" )
1549  matches = matches && !ability_matches_filter(cfg, tag_name, condition.cfg);
1550  }
1551 
1552  return matches;
1553 }
1554 
1556 {
1559  while (i != abilities_.ordered_end()) {
1560  if(ability_matches_filter(i->cfg, i->key, filter)) {
1561  i = abilities_.erase(i);
1562  } else {
1563  ++i;
1564  }
1565  }
1566 }
1567 
1569 {
1570  for(const auto& a_ptr : attacks_) {
1571  if(a_ptr->get_changed()) {
1572  return true;
1573  }
1574 
1575  }
1576  return false;
1577 }
1578 void unit::write(config& cfg, bool write_all) const
1579 {
1580  config back;
1581  auto write_subtag = [&](const std::string& key, const config& child)
1582  {
1583  cfg.clear_children(key);
1584 
1585  if(!child.empty()) {
1586  cfg.add_child(key, child);
1587  } else {
1588  back.add_child(key, child);
1589  }
1590  };
1591 
1592  if(write_all || get_attr_changed(UA_MOVEMENT_TYPE)) {
1593  movement_type_.write(cfg, false);
1594  }
1595  if(write_all || get_attr_changed(UA_SMALL_PROFILE)) {
1596  cfg["small_profile"] = small_profile_;
1597  }
1598  if(write_all || get_attr_changed(UA_PROFILE)) {
1599  cfg["profile"] = profile_;
1600  }
1601  if(description_ != type().unit_description()) {
1602  cfg["description"] = description_;
1603  }
1604  if(write_all || get_attr_changed(UA_NOTES)) {
1605  for(const t_string& note : special_notes_) {
1606  cfg.add_child("special_note")["note"] = note;
1607  }
1608  }
1609 
1610  if(halo_) {
1611  cfg["halo"] = *halo_;
1612  }
1613 
1614  if(ellipse_) {
1615  cfg["ellipse"] = *ellipse_;
1616  }
1617 
1618  if(usage_) {
1619  cfg["usage"] = *usage_;
1620  }
1621 
1622  write_upkeep(cfg["upkeep"]);
1623 
1624  cfg["hitpoints"] = hit_points_;
1625  if(write_all || get_attr_changed(UA_MAX_HP)) {
1626  cfg["max_hitpoints"] = max_hit_points_;
1627  }
1628  cfg["image_icon"] = type().icon();
1629  cfg["image"] = type().image();
1630  cfg["random_traits"] = random_traits_;
1631  cfg["generate_name"] = generate_name_;
1632  cfg["experience"] = experience_;
1633  if(write_all || get_attr_changed(UA_MAX_XP)) {
1634  cfg["max_experience"] = max_experience_;
1635  }
1636  cfg["recall_cost"] = recall_cost_;
1637 
1638  cfg["side"] = side_;
1639 
1640  cfg["type"] = type_id();
1641 
1642  if(type_id() != type().parent_id()) {
1643  cfg["parent_type"] = type().parent_id();
1644  }
1645 
1646  // Support for unit formulas in [ai] and unit-specific variables in [ai] [vars]
1647  formula_man_->write(cfg);
1648 
1649  cfg["gender"] = gender_string(gender_);
1650  cfg["variation"] = variation_;
1651  cfg["role"] = role_;
1652 
1653  config status_flags;
1654  for(const std::string& state : get_states()) {
1655  status_flags[state] = true;
1656  }
1657 
1658  write_subtag("variables", variables_);
1659  write_subtag("filter_recall", filter_recall_);
1660  write_subtag("status", status_flags);
1661 
1662  cfg.clear_children("events");
1663  cfg.append(events_);
1664 
1665  // Overlays are exported as the modifications that add them, not as an overlays= value,
1666  // however removing the key breaks the Gui Debug Tools.
1667  // \todo does anything depend on the key's value, other than the re-import code in unit::init?
1668  cfg["overlays"] = "";
1669 
1670  cfg["name"] = name_;
1671  cfg["id"] = id_;
1672  cfg["underlying_id"] = underlying_id_.value;
1673 
1674  if(can_recruit()) {
1675  cfg["canrecruit"] = true;
1676  }
1677 
1678  cfg["extra_recruit"] = utils::join(recruit_list_);
1679 
1680  cfg["facing"] = map_location::write_direction(facing_);
1681 
1682  cfg["goto_x"] = goto_.wml_x();
1683  cfg["goto_y"] = goto_.wml_y();
1684 
1685  cfg["moves"] = movement_;
1686  if(write_all || get_attr_changed(UA_MAX_MP)) {
1687  cfg["max_moves"] = max_movement_;
1688  }
1689  cfg["vision"] = vision_;
1690  cfg["jamming"] = jamming_;
1691 
1692  cfg["resting"] = resting_;
1693 
1694  if(write_all || get_attr_changed(UA_ADVANCE_TO)) {
1695  cfg["advances_to"] = utils::join(advances_to_);
1696  }
1697 
1698  cfg["race"] = race_->id();
1699  cfg["language_name"] = type_name_;
1700  cfg["undead_variation"] = undead_variation_;
1701  if(write_all || get_attr_changed(UA_LEVEL)) {
1702  cfg["level"] = level_;
1703  }
1704  if(write_all || get_attr_changed(UA_ALIGNMENT)) {
1705  cfg["alignment"] = unit_alignments::get_string(alignment_);
1706  }
1707  cfg["flag_rgb"] = flag_rgb_;
1708  cfg["unrenamable"] = unrenamable_;
1709 
1710  cfg["attacks_left"] = attacks_left_;
1711  if(write_all || get_attr_changed(UA_MAX_AP)) {
1712  cfg["max_attacks"] = max_attacks_;
1713  }
1714  if(write_all || get_attr_changed(UA_ZOC)) {
1715  cfg["zoc"] = emit_zoc_;
1716  }
1717  cfg["hidden"] = hidden_;
1718 
1719  if(write_all || get_attr_changed(UA_ATTACKS) || get_attacks_changed()) {
1720  cfg.clear_children("attack");
1721  for(attack_ptr i : attacks_) {
1722  i->write(cfg.add_child("attack"));
1723  }
1724  }
1725 
1726  cfg["cost"] = unit_value_;
1727 
1728  write_subtag("modifications", modifications_);
1729  if(write_all || get_attr_changed(UA_ABILITIES)) {
1730  write_subtag("abilities", abilities_);
1731  }
1732  if(write_all || get_attr_changed(UA_ADVANCEMENTS)) {
1733  cfg.clear_children("advancement");
1734  for(const config& advancement : advancements_) {
1735  if(!advancement.empty()) {
1736  cfg.add_child("advancement", advancement);
1737  }
1738  }
1739  }
1740  cfg.append(back);
1741 }
1742 
1744 {
1745  if(dir != map_location::NDIRECTIONS && dir != facing_) {
1746  appearance_changed_ = true;
1747  facing_ = dir;
1748  }
1749  // Else look at yourself (not available so continue to face the same direction)
1750 }
1751 
1752 int unit::upkeep() const
1753 {
1754  // Leaders do not incur upkeep.
1755  if(can_recruit()) {
1756  return 0;
1757  }
1758 
1759  return utils::visit(upkeep_value_visitor{*this}, upkeep_);
1760 }
1761 
1762 bool unit::loyal() const
1763 {
1764  return utils::holds_alternative<upkeep_loyal>(upkeep_);
1765 }
1766 
1768 {
1769  int def = movement_type_.defense_modifier(terrain);
1770 #if 0
1771  // A [defense] ability is too costly and doesn't take into account target locations.
1772  // Left as a comment in case someone ever wonders why it isn't a good idea.
1773  unit_ability_list defense_abilities = get_abilities("defense");
1774  if(!defense_abilities.empty()) {
1775  unit_abilities::effect defense_effect(defense_abilities, def);
1776  def = defense_effect.get_composite_value();
1777  }
1778 #endif
1779  return def;
1780 }
1781 
1782 bool unit::resistance_filter_matches(const config& cfg, const std::string& damage_name, int res) const
1783 {
1784  const std::string& apply_to = cfg["apply_to"];
1785  if(!apply_to.empty()) {
1786  if(damage_name != apply_to) {
1787  if(apply_to.find(',') != std::string::npos &&
1788  apply_to.find(damage_name) != std::string::npos) {
1789  const std::vector<std::string>& vals = utils::split(apply_to);
1790  if(std::find(vals.begin(),vals.end(),damage_name) == vals.end()) {
1791  return false;
1792  }
1793  } else {
1794  return false;
1795  }
1796  }
1797  }
1798 
1799  if(!unit_abilities::filter_base_matches(cfg, res)) {
1800  return false;
1801  }
1802 
1803  return true;
1804 }
1805 
1806 int unit::resistance_value(unit_ability_list resistance_list, const std::string& damage_name) const
1807 {
1808  int res = movement_type_.resistance_against(damage_name);
1809  utils::erase_if(resistance_list, [&](const unit_ability& i) {
1810  return !resistance_filter_matches(*i.ability_cfg, damage_name, 100-res);
1811  });
1812 
1813  if(!resistance_list.empty()) {
1814  unit_abilities::effect resist_effect(resistance_list, 100-res, nullptr, unit_abilities::EFFECT_CLAMP_MIN_MAX);
1815 
1816  res = 100 - resist_effect.get_composite_value();
1817  }
1818 
1819  return res;
1820 }
1821 
1822 static bool resistance_filter_matches_base(const config& cfg, bool attacker)
1823 {
1824  if(!(!cfg.has_attribute("active_on") || (attacker && cfg["active_on"] == "offense") || (!attacker && cfg["active_on"] == "defense"))) {
1825  return false;
1826  }
1827 
1828  return true;
1829 }
1830 
1831 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
1832 {
1833  unit_ability_list resistance_list = get_abilities_weapons("resistance",loc, weapon, opp_weapon);
1834  utils::erase_if(resistance_list, [&](const unit_ability& i) {
1835  return !resistance_filter_matches_base(*i.ability_cfg, attacker);
1836  });
1837  if(opp_weapon){
1838  unit_ability_list damage_type_list = opp_weapon->get_specials_and_abilities("damage_type");
1839  if(damage_type_list.empty()){
1840  return resistance_value(resistance_list, damage_name);
1841  }
1842  std::string replacement_type = opp_weapon->select_damage_type(damage_type_list, "replacement_type", resistance_list);
1843  std::string type_damage = replacement_type.empty() ? damage_name : replacement_type;
1844  int max_res = resistance_value(resistance_list, type_damage);
1845  for(auto& i : damage_type_list) {
1846  if((*i.ability_cfg).has_attribute("alternative_type")){
1847  max_res = std::max(max_res , resistance_value(resistance_list, (*i.ability_cfg)["alternative_type"].str()));
1848  }
1849  }
1850  return max_res;
1851  }
1852  return resistance_value(resistance_list, damage_name);
1853 }
1854 
1855 std::map<std::string, std::string> unit::advancement_icons() const
1856 {
1857  std::map<std::string,std::string> temp;
1858  if(!can_advance()) {
1859  return temp;
1860  }
1861 
1862  if(!advances_to_.empty()) {
1863  std::ostringstream tooltip;
1864  const std::string& image = game_config::images::level;
1865 
1866  for(const std::string& s : advances_to()) {
1867  if(!s.empty()) {
1868  tooltip << s << std::endl;
1869  }
1870  }
1871 
1872  temp[image] = tooltip.str();
1873  }
1874 
1875  for(const config& adv : get_modification_advances()) {
1876  const std::string& image = adv["image"];
1877  if(image.empty()) {
1878  continue;
1879  }
1880 
1881  std::ostringstream tooltip;
1882  tooltip << temp[image];
1883 
1884  const std::string& tt = adv["description"];
1885  if(!tt.empty()) {
1886  tooltip << tt << std::endl;
1887  }
1888 
1889  temp[image] = tooltip.str();
1890  }
1891 
1892  return(temp);
1893 }
1894 
1895 std::vector<std::pair<std::string, std::string>> unit::amla_icons() const
1896 {
1897  std::vector<std::pair<std::string, std::string>> temp;
1898  std::pair<std::string, std::string> icon; // <image,tooltip>
1899 
1900  for(const config& adv : get_modification_advances()) {
1901  icon.first = adv["icon"].str();
1902  icon.second = adv["description"].str();
1903 
1904  for(unsigned j = 0, j_count = modification_count("advancement", adv["id"]); j < j_count; ++j) {
1905  temp.push_back(icon);
1906  }
1907  }
1908 
1909  return(temp);
1910 }
1911 
1912 std::vector<config> unit::get_modification_advances() const
1913 {
1914  std::vector<config> res;
1915  for(const config& adv : modification_advancements()) {
1916  if(adv["strict_amla"].to_bool() && !advances_to_.empty()) {
1917  continue;
1918  }
1919  if(auto filter = adv.optional_child("filter")) {
1920  if(!unit_filter(vconfig(*filter)).matches(*this, loc_)) {
1921  continue;
1922  }
1923  }
1924 
1925  if(modification_count("advancement", adv["id"]) >= static_cast<unsigned>(adv["max_times"].to_int(1))) {
1926  continue;
1927  }
1928 
1929  std::vector<std::string> temp_require = utils::split(adv["require_amla"]);
1930  std::vector<std::string> temp_exclude = utils::split(adv["exclude_amla"]);
1931 
1932  if(temp_require.empty() && temp_exclude.empty()) {
1933  res.push_back(adv);
1934  continue;
1935  }
1936 
1937  std::sort(temp_require.begin(), temp_require.end());
1938  std::sort(temp_exclude.begin(), temp_exclude.end());
1939 
1940  std::vector<std::string> uniq_require, uniq_exclude;
1941 
1942  std::unique_copy(temp_require.begin(), temp_require.end(), std::back_inserter(uniq_require));
1943  std::unique_copy(temp_exclude.begin(), temp_exclude.end(), std::back_inserter(uniq_exclude));
1944 
1945  bool exclusion_found = false;
1946  for(const std::string& s : uniq_exclude) {
1947  int max_num = std::count(temp_exclude.begin(), temp_exclude.end(), s);
1948  int mod_num = modification_count("advancement", s);
1949  if(mod_num >= max_num) {
1950  exclusion_found = true;
1951  break;
1952  }
1953  }
1954 
1955  if(exclusion_found) {
1956  continue;
1957  }
1958 
1959  bool requirements_done = true;
1960  for(const std::string& s : uniq_require) {
1961  int required_num = std::count(temp_require.begin(), temp_require.end(), s);
1962  int mod_num = modification_count("advancement", s);
1963  if(required_num > mod_num) {
1964  requirements_done = false;
1965  break;
1966  }
1967  }
1968 
1969  if(requirements_done) {
1970  res.push_back(adv);
1971  }
1972  }
1973 
1974  return res;
1975 }
1976 
1977 void unit::set_advancements(std::vector<config> advancements)
1978 {
1980  advancements_ = advancements;
1981 }
1982 
1983 const std::string& unit::type_id() const
1984 {
1985  return type_->id();
1986 }
1987 
1988 void unit::set_big_profile(const std::string& value)
1989 {
1991  profile_ = value;
1993 }
1994 
1995 std::size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
1996 {
1997  std::size_t res = 0;
1998  for(const config& item : modifications_.child_range(mod_type)) {
1999  if(item["id"] == id) {
2000  ++res;
2001  }
2002  }
2003 
2004  // For backwards compatibility, if asked for "advancement", also count "advance"
2005  if(mod_type == "advancement") {
2006  res += modification_count("advance", id);
2007  }
2008 
2009  return res;
2010 }
2011 
2012 const std::set<std::string> unit::builtin_effects {
2013  "alignment", "attack", "defense", "ellipse", "experience", "fearless",
2014  "halo", "healthy", "hitpoints", "image_mod", "jamming", "jamming_costs", "level",
2015  "loyal", "max_attacks", "max_experience", "movement", "movement_costs",
2016  "new_ability", "new_advancement", "new_animation", "new_attack", "overlay", "profile",
2017  "recall_cost", "remove_ability", "remove_advancement", "remove_attacks", "resistance",
2018  "status", "type", "variation", "vision", "vision_costs", "zoc"
2019 };
2020 
2021 std::string unit::describe_builtin_effect(std::string apply_to, const config& effect)
2022 {
2023  if(apply_to == "attack") {
2024  std::vector<t_string> attack_names;
2025 
2026  std::string desc;
2027  for(attack_ptr a : attacks_) {
2028  bool affected = a->describe_modification(effect, &desc);
2029  if(affected && !desc.empty()) {
2030  attack_names.emplace_back(a->name(), "wesnoth-units");
2031  }
2032  }
2033  if(!attack_names.empty()) {
2034  utils::string_map symbols;
2035  symbols["attack_list"] = utils::format_conjunct_list("", attack_names);
2036  symbols["effect_description"] = desc;
2037  return VGETTEXT("$attack_list|: $effect_description", symbols);
2038  }
2039  } else if(apply_to == "hitpoints") {
2040  const std::string& increase_total = effect["increase_total"];
2041  if(!increase_total.empty()) {
2042  return VGETTEXT(
2043  "<span color=\"$color\">$number_or_percent</span> HP",
2044  {{"number_or_percent", utils::print_modifier(increase_total)}, {"color", increase_total[0] == '-' ? "#f00" : "#0f0"}});
2045  }
2046  } else {
2047  const std::string& increase = effect["increase"];
2048  if(increase.empty()) {
2049  return "";
2050  }
2051  if(apply_to == "movement") {
2052  return VNGETTEXT(
2053  "<span color=\"$color\">$number_or_percent</span> move",
2054  "<span color=\"$color\">$number_or_percent</span> moves",
2055  std::stoi(increase),
2056  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2057  } else if(apply_to == "vision") {
2058  return VGETTEXT(
2059  "<span color=\"$color\">$number_or_percent</span> vision",
2060  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2061  } else if(apply_to == "jamming") {
2062  return VGETTEXT(
2063  "<span color=\"$color\">$number_or_percent</span> jamming",
2064  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2065  } else if(apply_to == "max_experience") {
2066  // Unlike others, decreasing experience is a *GOOD* thing
2067  return VGETTEXT(
2068  "<span color=\"$color\">$number_or_percent</span> XP to advance",
2069  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
2070  } else if(apply_to == "max_attacks") {
2071  return VNGETTEXT(
2072  "<span color=\"$color\">$number_or_percent</span> attack per turn",
2073  "<span color=\"$color\">$number_or_percent</span> attacks per turn",
2074  std::stoi(increase),
2075  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2076  } else if(apply_to == "recall_cost") {
2077  // Unlike others, decreasing recall cost is a *GOOD* thing
2078  return VGETTEXT(
2079  "<span color=\"$color\">$number_or_percent</span> cost to recall",
2080  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
2081  }
2082  }
2083  return "";
2084 }
2085 
2086 void unit::apply_builtin_effect(std::string apply_to, const config& effect)
2087 {
2088  appearance_changed_ = true;
2089  if(apply_to == "fearless") {
2091  is_fearless_ = effect["set"].to_bool(true);
2092  } else if(apply_to == "healthy") {
2094  is_healthy_ = effect["set"].to_bool(true);
2095  } else if(apply_to == "profile") {
2096  if(const config::attribute_value* v = effect.get("portrait")) {
2097  set_big_profile((*v).str());
2098  }
2099 
2100  if(const config::attribute_value* v = effect.get("small_portrait")) {
2101  set_small_profile((*v).str());
2102  }
2103 
2104  if(const config::attribute_value* v = effect.get("description")) {
2105  description_ = *v;
2106  }
2107 
2108  if(config::const_child_itors cfg_range = effect.child_range("special_note")) {
2109  for(const config& c : cfg_range) {
2110  if(!c["remove"].to_bool()) {
2111  special_notes_.emplace_back(c["note"].t_str());
2112  } else {
2113  auto iter = std::find(special_notes_.begin(), special_notes_.end(), c["note"].t_str());
2114  if(iter != special_notes_.end()) {
2115  special_notes_.erase(iter);
2116  }
2117  }
2118  }
2119  }
2120  } else if(apply_to == "new_attack") {
2122  attacks_.emplace_back(new attack_type(effect));
2123  } else if(apply_to == "remove_attacks") {
2125  auto iter = std::remove_if(attacks_.begin(), attacks_.end(), [&effect](attack_ptr a) {
2126  return a->matches_filter(effect);
2127  });
2128 
2129  attacks_.erase(iter, attacks_.end());
2130  } else if(apply_to == "attack") {
2132  for(attack_ptr a : attacks_) {
2133  a->apply_modification(effect);
2134  }
2135  } else if(apply_to == "hitpoints") {
2136  LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_;
2137  const std::string& increase_hp = effect["increase"];
2138  const std::string& increase_total = effect["increase_total"];
2139  const std::string& set_hp = effect["set"];
2140  const std::string& set_total = effect["set_total"];
2141 
2142  // If the hitpoints are allowed to end up greater than max hitpoints
2143  const bool violate_max = effect["violate_maximum"].to_bool();
2144 
2145  if(!set_hp.empty()) {
2146  if(set_hp.back() == '%') {
2147  hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
2148  } else {
2149  hit_points_ = lexical_cast_default<int>(set_hp);
2150  }
2151  }
2152 
2153  if(!set_total.empty()) {
2154  if(set_total.back() == '%') {
2155  set_max_hitpoints(lexical_cast_default<int>(set_total)*max_hit_points_/100);
2156  } else {
2157  set_max_hitpoints(lexical_cast_default<int>(set_total));
2158  }
2159  }
2160 
2161  if(!increase_total.empty()) {
2162  // A percentage on the end means increase by that many percent
2164  }
2165 
2166  if(max_hit_points_ < 1)
2167  set_max_hitpoints(1);
2168 
2169  if(effect["heal_full"].to_bool()) {
2170  heal_fully();
2171  }
2172 
2173  if(!increase_hp.empty()) {
2175  }
2176 
2177  LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_;
2178  if(hit_points_ > max_hit_points_ && !violate_max) {
2179  LOG_UT << "resetting hp to max";
2181  }
2182 
2183  if(hit_points_ < 1) {
2184  hit_points_ = 1;
2185  }
2186  } else if(apply_to == "movement") {
2187  const bool apply_to_vision = effect["apply_to_vision"].to_bool(true);
2188 
2189  // Unlink vision from movement, regardless of whether we'll increment both or not
2190  if(vision_ < 0) {
2192  }
2193 
2194  const int old_max = max_movement_;
2195 
2196  const std::string& increase = effect["increase"];
2197  if(!increase.empty()) {
2199  }
2200 
2201  set_total_movement(effect["set"].to_int(max_movement_));
2202 
2203  if(movement_ > max_movement_) {
2205  }
2206 
2207  if(apply_to_vision) {
2208  vision_ = std::max(0, vision_ + max_movement_ - old_max);
2209  }
2210  } else if(apply_to == "vision") {
2211  // Unlink vision from movement, regardless of which one we're about to change.
2212  if(vision_ < 0) {
2214  }
2215 
2216  const std::string& increase = effect["increase"];
2217  if(!increase.empty()) {
2218  vision_ = utils::apply_modifier(vision_, increase, 1);
2219  }
2220 
2221  vision_ = effect["set"].to_int(vision_);
2222  } else if(apply_to == "jamming") {
2223  const std::string& increase = effect["increase"];
2224 
2225  if(!increase.empty()) {
2226  jamming_ = utils::apply_modifier(jamming_, increase, 1);
2227  }
2228 
2229  jamming_ = effect["set"].to_int(jamming_);
2230  } else if(apply_to == "experience") {
2231  const std::string& increase = effect["increase"];
2232  const std::string& set = effect["set"];
2233 
2234  if(!set.empty()) {
2235  if(set.back() == '%') {
2236  experience_ = lexical_cast_default<int>(set)*max_experience_/100;
2237  } else {
2238  experience_ = lexical_cast_default<int>(set);
2239  }
2240  }
2241 
2242  if(increase.empty() == false) {
2244  }
2245  } else if(apply_to == "max_experience") {
2246  const std::string& increase = effect["increase"];
2247  const std::string& set = effect["set"];
2248 
2249  if(set.empty() == false) {
2250  if(set.back() == '%') {
2251  set_max_experience(lexical_cast_default<int>(set)*max_experience_/100);
2252  } else {
2253  set_max_experience(lexical_cast_default<int>(set));
2254  }
2255  }
2256 
2257  if(increase.empty() == false) {
2259  }
2260  } else if(apply_to == upkeep_loyal::type()) {
2261  upkeep_ = upkeep_loyal{};
2262  } else if(apply_to == "status") {
2263  const std::string& add = effect["add"];
2264  const std::string& remove = effect["remove"];
2265 
2266  for(const std::string& to_add : utils::split(add))
2267  {
2268  set_state(to_add, true);
2269  }
2270 
2271  for(const std::string& to_remove : utils::split(remove))
2272  {
2273  set_state(to_remove, false);
2274  }
2275  } else if(std::find(movetype::effects.cbegin(), movetype::effects.cend(), apply_to) != movetype::effects.cend()) {
2276  // "movement_costs", "vision_costs", "jamming_costs", "defense", "resistance"
2277  if(auto ap = effect.optional_child(apply_to)) {
2279  movement_type_.merge(*ap, apply_to, effect["replace"].to_bool());
2280  }
2281  } else if(apply_to == "zoc") {
2282  if(const config::attribute_value* v = effect.get("value")) {
2284  emit_zoc_ = v->to_bool();
2285  }
2286  } else if(apply_to == "new_ability") {
2287  if(auto ab_effect = effect.optional_child("abilities")) {
2289  config to_append;
2290  for(const config::any_child ab : ab_effect->all_children_range()) {
2291  if(!has_ability_by_id(ab.cfg["id"])) {
2292  to_append.add_child(ab.key, ab.cfg);
2293  }
2294  }
2295  abilities_.append(to_append);
2296  }
2297  } else if(apply_to == "remove_ability") {
2298  if(auto ab_effect = effect.optional_child("abilities")) {
2299  for(const config::any_child ab : ab_effect->all_children_range()) {
2300  remove_ability_by_id(ab.cfg["id"]);
2301  }
2302  }
2303  if(auto fab_effect = effect.optional_child("experimental_filter_ability")) {
2304  remove_ability_by_attribute(*fab_effect);
2305  }
2306  } else if(apply_to == "image_mod") {
2307  LOG_UT << "applying image_mod";
2308  std::string mod = effect["replace"];
2309  if(!mod.empty()){
2310  image_mods_ = mod;
2311  }
2312  LOG_UT << "applying image_mod";
2313  mod = effect["add"].str();
2314  if(!mod.empty()){
2315  if(!image_mods_.empty()) {
2316  image_mods_ += '~';
2317  }
2318 
2319  image_mods_ += mod;
2320  }
2321 
2323  LOG_UT << "applying image_mod";
2324  } else if(apply_to == "new_animation") {
2325  anim_comp_->apply_new_animation_effect(effect);
2326  } else if(apply_to == "ellipse") {
2327  set_image_ellipse(effect["ellipse"]);
2328  } else if(apply_to == "halo") {
2329  set_image_halo(effect["halo"]);
2330  } else if(apply_to == "overlay") {
2331  const std::string& add = effect["add"];
2332  const std::string& replace = effect["replace"];
2333  const std::string& remove = effect["remove"];
2334 
2335  if(!add.empty()) {
2336  for(const auto& to_add : utils::parenthetical_split(add, ',')) {
2337  overlays_.push_back(to_add);
2338  }
2339  }
2340  if(!remove.empty()) {
2341  for(const auto& to_remove : utils::parenthetical_split(remove, ',')) {
2342  overlays_.erase(std::remove(overlays_.begin(), overlays_.end(), to_remove), overlays_.end());
2343  }
2344  }
2345  if(add.empty() && remove.empty() && !replace.empty()) {
2346  overlays_ = utils::parenthetical_split(replace, ',');
2347  }
2348  } else if(apply_to == "new_advancement") {
2349  const std::string& types = effect["types"];
2350  const bool replace = effect["replace"].to_bool(false);
2352 
2353  if(!types.empty()) {
2354  if(replace) {
2356  } else {
2357  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2358  std::copy(temp_advances.begin(), temp_advances.end(), std::back_inserter(advances_to_));
2359  }
2360  }
2361 
2362  if(effect.has_child("advancement")) {
2363  if(replace) {
2364  advancements_.clear();
2365  }
2366 
2367  for(const config& adv : effect.child_range("advancement")) {
2368  advancements_.push_back(adv);
2369  }
2370  }
2371  } else if(apply_to == "remove_advancement") {
2372  const std::string& types = effect["types"];
2373  const std::string& amlas = effect["amlas"];
2375 
2376  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2378  for(const std::string& unit : temp_advances) {
2379  iter = std::find(advances_to_.begin(), advances_to_.end(), unit);
2380  if(iter != advances_to_.end()) {
2381  advances_to_.erase(iter);
2382  }
2383  }
2384 
2385  temp_advances = utils::parenthetical_split(amlas, ',');
2386 
2387  for(int i = advancements_.size() - 1; i >= 0; i--) {
2388  if(std::find(temp_advances.begin(), temp_advances.end(), advancements_[i]["id"]) != temp_advances.end()) {
2389  advancements_.erase(advancements_.begin() + i);
2390  }
2391  }
2392  } else if(apply_to == "alignment") {
2393  auto new_align = unit_alignments::get_enum(effect["set"].str());
2394  if(new_align) {
2395  set_alignment(*new_align);
2396  }
2397  } else if(apply_to == "max_attacks") {
2398  const std::string& increase = effect["increase"];
2399 
2400  if(!increase.empty()) {
2402  }
2403  } else if(apply_to == "recall_cost") {
2404  const std::string& increase = effect["increase"];
2405  const std::string& set = effect["set"];
2406  const int team_recall_cost = resources::gameboard ? resources::gameboard->get_team(side_).recall_cost() : 20;
2407  const int recall_cost = recall_cost_ < 0 ? team_recall_cost : recall_cost_;
2408 
2409  if(!set.empty()) {
2410  if(set.back() == '%') {
2411  recall_cost_ = lexical_cast_default<int>(set)*recall_cost/100;
2412  } else {
2413  recall_cost_ = lexical_cast_default<int>(set);
2414  }
2415  }
2416 
2417  if(!increase.empty()) {
2419  }
2420  } else if(effect["apply_to"] == "variation") {
2421  const unit_type* base_type = unit_types.find(type().parent_id());
2422  assert(base_type != nullptr);
2423  const std::string& variation_id = effect["name"];
2424  if(variation_id.empty() || base_type->get_gender_unit_type(gender_).has_variation(variation_id)) {
2425  variation_ = variation_id;
2426  advance_to(*base_type);
2427  if(effect["heal_full"].to_bool(false)) {
2428  heal_fully();
2429  }
2430  } else {
2431  WRN_UT << "unknown variation '" << variation_id << "' (name=) in [effect]apply_to=variation, ignoring";
2432  }
2433  } else if(effect["apply_to"] == "type") {
2434  std::string prev_type = effect["prev_type"];
2435  if(prev_type.empty()) {
2436  prev_type = type().parent_id();
2437  }
2438  const std::string& new_type_id = effect["name"];
2439  const unit_type* new_type = unit_types.find(new_type_id);
2440  if(new_type) {
2441  advance_to(*new_type);
2442  preferences::encountered_units().insert(new_type_id);
2443  if(effect["heal_full"].to_bool(false)) {
2444  heal_fully();
2445  }
2446  } else {
2447  WRN_UT << "unknown type '" << new_type_id << "' (name=) in [effect]apply_to=type, ignoring";
2448  }
2449  } else if(effect["apply_to"] == "level") {
2450  const std::string& increase = effect["increase"];
2451  const std::string& set = effect["set"];
2452 
2454 
2455  // no support for percentages, since levels are usually small numbers
2456 
2457  if(!set.empty()) {
2458  level_ = lexical_cast_default<int>(set);
2459  }
2460 
2461  if(!increase.empty()) {
2462  level_ += lexical_cast_default<int>(increase);
2463  }
2464  }
2465 }
2466 
2467 void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
2468 {
2469  bool generate_description = mod["generate_description"].to_bool(true);
2470 
2471  config* target = nullptr;
2472 
2473  if(no_add == false) {
2474  target = &modifications_.add_child(mod_type, mod);
2475  target->remove_children("effect");
2476  }
2477 
2478  std::vector<t_string> effects_description;
2479  for(const config& effect : mod.child_range("effect")) {
2480  if(target) {
2481  //Store effects only after they are added to avoid double applying effects on advance with apply_to=variation.
2482  target->add_child("effect", effect);
2483  }
2484  // Apply SUF.
2485  if(auto afilter = effect.optional_child("filter")) {
2486  assert(resources::filter_con);
2487  if(!unit_filter(vconfig(*afilter)).matches(*this, loc_)) {
2488  continue;
2489  }
2490  }
2491  const std::string& apply_to = effect["apply_to"];
2492  int times = effect["times"].to_int(1);
2493  t_string description;
2494 
2495  if(no_add && (apply_to == "type" || apply_to == "variation")) {
2496  continue;
2497  }
2498 
2499  if(effect["times"] == "per level") {
2500  if(effect["apply_to"] == "level") {
2501  WRN_UT << "[effect] times=per level is not allowed with apply_to=level, using default value of 1";
2502  times = 1;
2503  }
2504  else {
2505  times = level_;
2506  }
2507  }
2508 
2509  if(times) {
2510  while (times > 0) {
2511  times --;
2512  std::string description_component;
2513  if(resources::lua_kernel) {
2514  description_component = resources::lua_kernel->apply_effect(apply_to, *this, effect, true);
2515  } else if(builtin_effects.count(apply_to)) {
2516  // Normally, the built-in effects are dispatched through Lua so that a user
2517  // can override them if desired. However, since they're built-in, we can still
2518  // apply them if the lua kernel is unavailable.
2519  apply_builtin_effect(apply_to, effect);
2520  description_component = describe_builtin_effect(apply_to, effect);
2521  }
2522  if(!times) {
2523  description += description_component;
2524  }
2525  } // end while
2526  } else { // for times = per level & level = 0 we still need to rebuild the descriptions
2527  if(resources::lua_kernel) {
2528  description += resources::lua_kernel->apply_effect(apply_to, *this, effect, false);
2529  } else if(builtin_effects.count(apply_to)) {
2530  description += describe_builtin_effect(apply_to, effect);
2531  }
2532  }
2533 
2534  if(effect["times"] == "per level" && !times) {
2535  description = VGETTEXT("$effect_description per level", {{"effect_description", description}});
2536  }
2537 
2538  if(!description.empty()) {
2539  effects_description.push_back(description);
2540  }
2541  }
2542 
2543  t_string description;
2544 
2545  const t_string& mod_description = mod["description"];
2546  if(!mod_description.empty()) {
2547  description = mod_description;
2548  }
2549 
2550  // Punctuation should be translatable: not all languages use Latin punctuation.
2551  // (However, there maybe is a better way to do it)
2552  if(generate_description && !effects_description.empty()) {
2553  if(!mod_description.empty()) {
2554  description += "\n";
2555  }
2556 
2557  for(const auto& desc_line : effects_description) {
2558  description += desc_line + "\n";
2559  }
2560  }
2561 
2562  // store trait info
2563  if(mod_type == "trait") {
2564  add_trait_description(mod, description);
2565  }
2566 
2567  //NOTE: if not a trait, description is currently not used
2568 }
2569 
2570 void unit::add_trait_description(const config& trait, const t_string& description)
2571 {
2572  const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
2573  const auto& gender_specific_name = trait[gender_string];
2574 
2575  const t_string name = gender_specific_name.empty() ? trait["name"] : gender_specific_name;
2576 
2577  if(!name.empty()) {
2578  trait_names_.push_back(name);
2579  trait_descriptions_.push_back(description);
2580  trait_nonhidden_ids_.push_back(trait["id"]);
2581  }
2582 }
2583 
2584 std::string unit::absolute_image() const
2585 {
2586  return type().icon().empty() ? type().image() : type().icon();
2587 }
2588 
2589 std::string unit::default_anim_image() const
2590 {
2591  return type().image().empty() ? type().icon() : type().image();
2592 }
2593 
2595 {
2596  log_scope("apply mods");
2597 
2598  variables_.clear_children("mods");
2599  if(modifications_.has_child("advance")) {
2600  deprecated_message("[advance]", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [advancement] instead.");
2601  }
2603  add_modification(mod.key, mod.cfg, true);
2604  }
2605 }
2606 
2607 bool unit::invisible(const map_location& loc, bool see_all) const
2608 {
2609  if(loc != get_location()) {
2610  DBG_UT << "unit::invisible called: id = " << id() << " loc = " << loc << " get_loc = " << get_location();
2611  }
2612 
2613  // This is a quick condition to check, and it does not depend on the
2614  // location (so might as well bypass the location-based cache).
2615  if(get_state(STATE_UNCOVERED)) {
2616  return false;
2617  }
2618 
2619  // Fetch from cache
2620  /**
2621  * @todo FIXME: We use the cache only when using the default see_all=true
2622  * Maybe add a second cache if the see_all=false become more frequent.
2623  */
2624  if(see_all) {
2625  const auto itor = invisibility_cache_.find(loc);
2626  if(itor != invisibility_cache_.end()) {
2627  return itor->second;
2628  }
2629  }
2630 
2631  // Test hidden status
2632  static const std::string hides("hides");
2633  bool is_inv = get_ability_bool(hides, loc);
2634  if(is_inv){
2635  is_inv = (resources::gameboard ? !resources::gameboard->would_be_discovered(loc, side_,see_all) : true);
2636  }
2637 
2638  if(see_all) {
2639  // Add to caches
2640  if(invisibility_cache_.empty()) {
2641  units_with_cache.push_back(this);
2642  }
2643 
2644  invisibility_cache_[loc] = is_inv;
2645  }
2646 
2647  return is_inv;
2648 }
2649 
2650 bool unit::is_visible_to_team(const team& team, bool const see_all) const
2651 {
2652  const map_location& loc = get_location();
2653  return is_visible_to_team(loc, team, see_all);
2654 }
2655 
2656 bool unit::is_visible_to_team(const map_location& loc, const team& team, bool const see_all) const
2657 {
2658  if(!display::get_singleton()->get_map().on_board(loc)) {
2659  return false;
2660  }
2661 
2662  if(see_all) {
2663  return true;
2664  }
2665 
2666  if(team.is_enemy(side()) && invisible(loc)) {
2667  return false;
2668  }
2669 
2670  // allied planned moves are also visible under fog. (we assume that fake units on the map are always whiteboard markers)
2671  if(!team.is_enemy(side()) && underlying_id_.is_fake()) {
2672  return true;
2673  }
2674 
2675  // when the whiteboard planned unit map is applied, it uses moved the _real_ unit so
2676  // underlying_id_.is_fake() will be false and the check above will not apply.
2677  // TODO: improve this check so that is also works for allied planned units but without
2678  // breaking sp campaigns with allies under fog. We probably need an explicit flag
2679  // is_planned_ in unit that is set by the whiteboard.
2680  if(team.side() == side()) {
2681  return true;
2682  }
2683 
2684  if(team.fogged(loc)) {
2685  return false;
2686  }
2687 
2688  return true;
2689 }
2690 
2692 {
2693  if(underlying_id_.value == 0) {
2695  underlying_id_ = id_manager.next_id();
2696  } else {
2697  underlying_id_ = id_manager.next_fake_id();
2698  }
2699  }
2700 
2701  if(id_.empty() /*&& !underlying_id_.is_fake()*/) {
2702  std::stringstream ss;
2703  ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_.value;
2704  id_ = ss.str();
2705  }
2706 }
2707 
2708 unit& unit::mark_clone(bool is_temporary)
2709 {
2711  if(is_temporary) {
2712  underlying_id_ = ids.next_fake_id();
2713  } else {
2715  underlying_id_ = ids.next_id();
2716  }
2717  else {
2718  underlying_id_ = ids.next_fake_id();
2719  }
2720  std::string::size_type pos = id_.find_last_of('-');
2721  if(pos != std::string::npos && pos+1 < id_.size()
2722  && id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
2723  // this appears to be a duplicate of a generic unit, so give it a new id
2724  WRN_UT << "assigning new id to clone of generic unit " << id_;
2725  id_.clear();
2726  set_underlying_id(ids);
2727  }
2728  }
2729  return *this;
2730 }
2731 
2732 
2734  : u_(const_cast<unit&>(u))
2735  , moves_(u.movement_left(true))
2736 {
2737  if(operate) {
2739  }
2740 }
2741 
2743 {
2744  assert(resources::gameboard);
2745  try {
2746  if(!resources::gameboard->units().has_unit(&u_)) {
2747  /*
2748  * It might be valid that the unit is not in the unit map.
2749  * It might also mean a no longer valid unit will be assigned to.
2750  */
2751  DBG_UT << "The unit to be removed is not in the unit map.";
2752  }
2753 
2755  } catch(...) {
2756  DBG_UT << "Caught exception when destroying unit_movement_resetter: " << utils::get_unknown_exception_type();
2757  }
2758 }
2759 
2760 std::string unit::TC_image_mods() const
2761 {
2762  return formatter() << "~RC(" << flag_rgb() << ">" << team::get_side_color_id(side()) << ")";
2763 }
2764 
2765 std::string unit::image_mods() const
2766 {
2767  if(!image_mods_.empty()) {
2768  return formatter() << "~" << image_mods_ << TC_image_mods();
2769  }
2770 
2771  return TC_image_mods();
2772 }
2773 
2774 // Called by the Lua API after resetting an attack pointer.
2776 {
2778  auto iter = std::find(attacks_.begin(), attacks_.end(), atk);
2779  if(iter == attacks_.end()) {
2780  return false;
2781  }
2782  attacks_.erase(iter);
2783  return true;
2784 }
2785 
2787 {
2788  if(attacks_left_ == max_attacks_) {
2789  //TODO: add state_not_attacked
2790  }
2791 
2792  set_attacks(0);
2793 }
2794 
2796 {
2797  if(movement_left() == total_movement()) {
2798  set_state(STATE_NOT_MOVED,true);
2799  }
2800 
2801  set_movement(0, true);
2802 }
2803 
2804 void unit::set_hidden(bool state) const
2805 {
2806 // appearance_changed_ = true;
2807  hidden_ = state;
2808  if(!state) {
2809  return;
2810  }
2811 
2812  // TODO: this should really hide the halo, not destroy it
2813  // We need to get rid of haloes immediately to avoid display glitches
2814  anim_comp_->clear_haloes();
2815 }
2816 
2817 void unit::set_image_halo(const std::string& halo)
2818 {
2819  appearance_changed_ = true;
2820  anim_comp_->clear_haloes();
2821  halo_ = halo;
2822 }
2823 
2825 {
2826  if(upkeep.empty()) {
2827  return;
2828  }
2829 
2830  try {
2831  upkeep_ = upkeep.apply_visitor(upkeep_parser_visitor{});
2832  } catch(std::invalid_argument& e) {
2833  WRN_UT << "Found invalid upkeep=\"" << e.what() << "\" in a unit";
2834  upkeep_ = upkeep_full{};
2835  }
2836 }
2837 
2839 {
2840  upkeep = utils::visit(upkeep_type_visitor{}, upkeep_);
2841 }
2842 
2844 {
2845  changed_attributes_.reset();
2846  for(const auto& a_ptr : attacks_) {
2847  a_ptr->set_changed(false);
2848  }
2849 }
2850 
2851 std::vector<t_string> unit::unit_special_notes() const {
2853 }
2854 
2855 // Filters unimportant stats from the unit config and returns a checksum of
2856 // the remaining config.
2858 {
2859  config unit_config;
2860  config wcfg;
2861  u.write(unit_config);
2862 
2863  static const std::set<std::string_view> main_keys {
2864  "advances_to",
2865  "alignment",
2866  "cost",
2867  "experience",
2868  "gender",
2869  "hitpoints",
2870  "ignore_race_traits",
2871  "ignore_global_traits",
2872  "level",
2873  "recall_cost",
2874  "max_attacks",
2875  "max_experience",
2876  "max_hitpoints",
2877  "max_moves",
2878  "movement",
2879  "movement_type",
2880  "race",
2881  "random_traits",
2882  "resting",
2883  "undead_variation",
2884  "upkeep",
2885  "zoc"
2886  };
2887 
2888  for(const std::string_view& main_key : main_keys) {
2889  wcfg[main_key] = unit_config[main_key];
2890  }
2891 
2892  static const std::set<std::string_view> attack_keys {
2893  "name",
2894  "type",
2895  "range",
2896  "damage",
2897  "number"
2898  };
2899 
2900  for(const config& att : unit_config.child_range("attack")) {
2901  config& child = wcfg.add_child("attack");
2902 
2903  for(const std::string_view& attack_key : attack_keys) {
2904  child[attack_key] = att[attack_key];
2905  }
2906 
2907  for(const config& spec : att.child_range("specials")) {
2908  config& child_spec = child.add_child("specials", spec);
2909 
2910  child_spec.recursive_clear_value("description");
2912  child_spec.recursive_clear_value("description_inactive");
2913  child_spec.recursive_clear_value("name");
2914  child_spec.recursive_clear_value("name_inactive");
2915  }
2916  }
2917  }
2918 
2919  for(const config& abi : unit_config.child_range("abilities")) {
2920  config& child = wcfg.add_child("abilities", abi);
2921 
2922  child.recursive_clear_value("description");
2923  child.recursive_clear_value("description_inactive");
2924  child.recursive_clear_value("name");
2925  child.recursive_clear_value("name_inactive");
2926  }
2927 
2928  for(const config& trait : unit_config.child_range("trait")) {
2929  config& child = wcfg.add_child("trait", trait);
2930 
2931  child.recursive_clear_value("description");
2932  child.recursive_clear_value("male_name");
2933  child.recursive_clear_value("female_name");
2934  child.recursive_clear_value("name");
2935  }
2936 
2937  static const std::set<std::string_view> child_keys {
2938  "advance_from",
2939  "defense",
2940  "movement_costs",
2941  "vision_costs",
2942  "jamming_costs",
2943  "resistance"
2944  };
2945 
2946  for(const std::string_view& child_key : child_keys) {
2947  for(const config& c : unit_config.child_range(child_key)) {
2948  wcfg.add_child(child_key, c);
2949  }
2950  }
2951 
2952  DBG_UT << wcfg;
2953 
2954  return wcfg.hash();
2955 }
const map_location goto_
Definition: move.cpp:317
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:1233
Visitor helper class to fetch the appropriate upkeep value.
Definition: unit.hpp:1183
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:2843
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:1076
bool get_attacks_changed() const
Definition: unit.cpp:1568
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:1959
int movement_
Definition: unit.hpp:1982
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:2020
int unit_value_
Definition: unit.hpp:2024
int attacks_left_
Definition: unit.hpp:1993
bool generate_name_
Definition: unit.hpp:2052
movetype movement_type_
Definition: unit.hpp:1987
config variables_
Definition: unit.hpp:2003
bool unrenamable_
Definition: unit.hpp:1974
int experience_
Definition: unit.hpp:1961
int vision_
Definition: unit.hpp:1984
void remove_ability_by_attribute(const config &filter)
Removes a unit's abilities with a specific ID or other attribute.
Definition: unit.cpp:1555
std::string undead_variation_
Definition: unit.hpp:1956
t_string type_name_
The displayed name of this unit type.
Definition: unit.hpp:1947
std::optional< std::string > ellipse_
Definition: unit.hpp:2049
map_location::DIRECTION facing_
Definition: unit.hpp:2017
unit_movement_resetter(const unit_movement_resetter &)=delete
bool random_traits_
Definition: unit.hpp:2051
void write(config &cfg, bool write_all=true) const
Serializes the current unit metadata values.
Definition: unit.cpp:1578
std::bitset< UA_COUNT > changed_attributes_
Definition: unit.hpp:2061
std::string small_profile_
Definition: unit.hpp:2057
void write_upkeep(config::attribute_value &upkeep) const
Definition: unit.cpp:2838
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:1952
std::vector< t_string > special_notes_
Definition: unit.hpp:2045
bool canrecruit_
Definition: unit.hpp:1967
std::string image_mods_
Definition: unit.hpp:1972
double hp_bar_scaling_
Definition: unit.hpp:2037
std::string flag_rgb_
Definition: unit.hpp:1971
static std::map< std::string, state_t > known_boolean_state_names_
Definition: unit.hpp:2001
@ 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:2004
bool hidden_
Definition: unit.hpp:2036
bool is_healthy_
Definition: unit.hpp:2027
bool is_fearless_
Definition: unit.hpp:2027
config abilities_
Definition: unit.hpp:2040
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:1944
std::bitset< num_bool_states > known_boolean_states_
Definition: unit.hpp:2000
int side_
Definition: unit.hpp:1976
const unit_race * race_
Never nullptr, but may point to the null race.
Definition: unit.hpp:1950
bool appearance_changed_
Definition: unit.hpp:2060
double xp_bar_scaling_
Definition: unit.hpp:2037
unit_alignments::type alignment_
Definition: unit.hpp:1969
bool invisible(const map_location &loc, bool see_all=true) const
Definition: unit.cpp:2607
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2650
n_unit::unit_id underlying_id_
Definition: unit.hpp:1954
std::string variation_
Definition: unit.hpp:1957
unit & mark_clone(bool is_temporary)
Mark this unit as clone so it can be inserted to unit_map.
Definition: unit.cpp:2708
config filter_recall_
Definition: unit.hpp:2005
int max_experience_
Definition: unit.hpp:1962
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:1978
t_string description_
Definition: unit.hpp:2044
int level_
Definition: unit.hpp:1964
std::string role_
Definition: unit.hpp:2011
std::map< map_location, bool > invisibility_cache_
Hold the visibility status cache for a unit, when not uncovered.
Definition: unit.hpp:2070
bool end_turn_
Definition: unit.hpp:1990
std::vector< std::string > advances_to_
Definition: unit.hpp:1941
std::unique_ptr< unit_animation_component > anim_comp_
Definition: unit.hpp:2034
int recall_cost_
Definition: unit.hpp:1966
static std::string type()
Definition: unit.hpp:1173
attack_list attacks_
Definition: unit.hpp:2012
void remove_ability_by_id(const std::string &ability)
Removes a unit's abilities with a specific ID.
Definition: unit.cpp:1432
map_location loc_
Definition: unit.hpp:1939
std::vector< std::string > overlays_
Definition: unit.hpp:2009
config modifications_
Definition: unit.hpp:2039
bool has_ability_by_id(const std::string &ability) const
Check if the unit has an ability of a specific ID.
Definition: unit.cpp:1421
bool hold_position_
Definition: unit.hpp:1989
const config & abilities() const
Definition: unit.hpp:1798
void parse_upkeep(const config::attribute_value &upkeep)
Definition: unit.cpp:2824
std::vector< std::string > recruit_list_
Definition: unit.hpp:1968
std::vector< config > advancements_
Definition: unit.hpp:2042
utils::string_map modification_descriptions_
Definition: unit.hpp:2029
unit_checksum_version
Optional parameter for get_checksum to use the algorithm of an older version of Wesnoth,...
Definition: unit.hpp:2109
std::vector< std::string > trait_nonhidden_ids_
Definition: unit.hpp:2022
upkeep_t upkeep_
Definition: unit.hpp:2054
std::set< std::string > states_
Definition: unit.hpp:1996
std::string profile_
Definition: unit.hpp:2056
int jamming_
Definition: unit.hpp:1985
std::optional< std::string > halo_
Definition: unit.hpp:2048
map_location goto_
Definition: unit.hpp:2025
t_string name_
Definition: unit.hpp:1953
int max_attacks_
Definition: unit.hpp:1994
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:1531
int max_hit_points_
Definition: unit.hpp:1960
std::unique_ptr< unit_formula_manager > formula_man_
Definition: unit.hpp:1980
int max_movement_
Definition: unit.hpp:1983
bool resting_
Definition: unit.hpp:1991
std::vector< t_string > trait_descriptions_
Definition: unit.hpp:2021
bool emit_zoc_
Definition: unit.hpp:2007
std::optional< std::string > usage_
Definition: unit.hpp:2047
@ version_1_16_or_older
Included some of the flavortext from weapon specials.
void set_big_profile(const std::string &value)
Definition: unit.cpp:1988
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:1316
void set_state(const std::string &state, bool value)
Set whether the unit is affected by a status effect.
Definition: unit.cpp:1398
void new_turn()
Refresh unit for the beginning of a turn.
Definition: unit.cpp:1277
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:1054
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:1377
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:2804
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:1349
std::string small_profile() const
An optional profile image to display in Help.
Definition: unit.cpp:1063
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:1983
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:1332
void new_scenario()
Refresh unit for the beginning of a new scenario.
Definition: unit.cpp:1301
void end_turn()
Refresh unit for the end of a turn.
Definition: unit.cpp:1287
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:2691
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:1190
std::vector< t_string > unit_special_notes() const
The unit's special notes.
Definition: unit.cpp:2851
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:1912
std::vector< std::pair< std::string, std::string > > amla_icons() const
Gets the image and description data for modification advancements.
Definition: unit.cpp:1895
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:1977
void set_advances_to(const std::vector< std::string > &advances_to)
Sets this unit's advancement options.
Definition: unit.cpp:1211
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:1855
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:1196
void advance_to(const unit_type &t, bool use_traits=false)
Advances this unit to another type.
Definition: unit.cpp:926
void remove_attacks_ai()
Set the unit to have no attacks left for this turn.
Definition: unit.cpp:2786
void set_max_attacks(int value)
Definition: unit.hpp:983
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1767
bool resistance_filter_matches(const config &cfg, const std::string &damage_name, int res) const
Definition: unit.cpp:1782
bool remove_attack(attack_ptr atk)
Remove an attack from the unit.
Definition: unit.cpp:2775
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_value(unit_ability_list resistance_list, const std::string &damage_name) const
For the provided list of resistance abilities, determine the damage resistance based on which are act...
Definition: unit.cpp:1806
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:1831
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:1178
color_t hp_color() const
Color for this unit's current hitpoints.
Definition: unit.cpp:1124
std::string TC_image_mods() const
Constructs a recolor (RC) IPF string for this unit's team color.
Definition: unit.cpp:2760
static color_t hp_color_max()
Definition: unit.cpp:1134
const std::string & flag_rgb() const
Get the source color palette to use when recoloring the unit's image.
Definition: unit.cpp:1081
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition: unit.cpp:2765
std::string default_anim_image() const
The default image to use for animation frames with no defined image.
Definition: unit.cpp:2589
const std::vector< std::string > & overlays() const
Get the unit's overlay images.
Definition: unit.hpp:1669
std::string absolute_image() const
The name of the file to game_display (used in menus).
Definition: unit.cpp:2584
void set_image_ellipse(const std::string &ellipse)
Set the unit's ellipse image.
Definition: unit.hpp:1641
void set_image_halo(const std::string &halo)
Set the unit's halo image.
Definition: unit.cpp:2817
void apply_builtin_effect(std::string type, const config &effect)
Apply a builtin effect to the unit.
Definition: unit.cpp:2086
void add_modification(const std::string &type, const config &modification, bool no_add=false)
Add a new modification to the unit.
Definition: unit.cpp:2467
static const std::set< std::string > builtin_effects
Definition: unit.hpp:1579
std::string describe_builtin_effect(std::string type, const config &effect)
Construct a string describing a built-in effect.
Definition: unit.cpp:2021
void apply_modifications()
Re-apply all saved modifications.
Definition: unit.cpp:2594
void expire_modifications(const std::string &duration)
Clears those modifications whose duration has expired.
Definition: unit.cpp:1244
std::size_t modification_count(const std::string &type, const std::string &id) const
Count modifications of a particular type.
Definition: unit.cpp:1995
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1396
void set_facing(map_location::DIRECTION dir) const
The this unit's facing.
Definition: unit.cpp:1743
const movetype & movement_type() const
Get the unit's movement type.
Definition: unit.hpp:1469
void set_movement(int moves, bool unit_action=false)
Set this unit's remaining movement to moves.
Definition: unit.cpp:1218
void set_total_movement(int value)
Definition: unit.hpp:1310
void set_emit_zoc(bool val)
Sets the raw zone-of-control flag.
Definition: unit.hpp:1389
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1321
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1305
void remove_movement_ai()
Sets the unit to have no moves left for this turn.
Definition: unit.cpp:2795
void set_interrupted_move(const map_location &interrupted_move)
Set the target location of the unit's interrupted move.
Definition: unit.hpp:1463
std::vector< std::string > get_modifications_list(const std::string &mod_type) const
Gets a list of the modification this unit currently has.
Definition: unit.cpp:891
int upkeep() const
Gets the amount of gold this unit costs a side per turn.
Definition: unit.cpp:1752
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:2570
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:1762
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:1964
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:1213
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:1445
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:1234
#define LOG_UT
Definition: unit.cpp:63
#define DBG_UT
Definition: unit.cpp:62
static bool resistance_filter_matches_base(const config &cfg, bool attacker)
Definition: unit.cpp:1822
std::string get_checksum(const unit &u, backwards_compatibility::unit_checksum_version version)
Gets a checksum for a unit.
Definition: unit.cpp:2857
static color_t hp_color_impl(int hitpoints, int max_hitpoints)
Definition: unit.cpp:1086
#define ERR_UT
Definition: unit.cpp:65
#define e
#define a