The Battle for Wesnoth  1.19.1+dev
unit.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Routines to manage units.
19  */
20 
21 #include "units/unit.hpp"
22 
23 #include "ai/manager.hpp"
24 #include "color.hpp"
25 #include "deprecation.hpp"
26 #include "display.hpp"
27 #include "formatter.hpp"
28 #include "formula/string_utils.hpp" // for VGETTEXT
29 #include "game_board.hpp" // for game_board
30 #include "game_config.hpp" // for add_color_info, etc
31 #include "game_data.hpp"
32 #include "game_events/manager.hpp" // for add_events
33 #include "game_version.hpp"
34 #include "lexical_cast.hpp"
35 #include "log.hpp" // for LOG_STREAM, logger, etc
36 #include "map/map.hpp" // for gamemap
37 #include "preferences/preferences.hpp" // for encountered_units
38 #include "random.hpp" // for generator, rng
39 #include "resources.hpp" // for units, gameboard, teams, etc
40 #include "scripting/game_lua_kernel.hpp" // for game_lua_kernel
41 #include "synced_context.hpp"
42 #include "team.hpp" // for team, get_teams, etc
43 #include "units/abilities.hpp" // for effect, filter_base_matches
44 #include "units/animation_component.hpp" // for unit_animation_component
45 #include "units/filter.hpp"
46 #include "units/formula_manager.hpp" // for unit_formula_manager
47 #include "units/id.hpp"
48 #include "units/map.hpp" // for unit_map, etc
49 #include "units/types.hpp"
50 #include "utils/config_filters.hpp"
51 #include "variable.hpp" // for vconfig, etc
52 
53 #include <cassert> // for assert
54 #include <cstdlib> // for rand
55 #include <exception> // for exception
56 #include <iterator> // for back_insert_iterator, etc
57 #include <string_view>
58 
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(tag_name == "resistance"){
1476  if(!set_includes_if_present(filter, cfg, "apply_to")){
1477  return false;
1478  }
1479  } else {
1480  if(!string_matches_if_present(filter, cfg, "apply_to", "self")){
1481  return false;
1482  }
1483  }
1484 
1485  if(!string_matches_if_present(filter, cfg, "overwrite_specials", "none"))
1486  return false;
1487 
1488  if(!string_matches_if_present(filter, cfg, "active_on", "both"))
1489  return false;
1490 
1491  //for damage only
1492  if(!string_matches_if_present(filter, cfg, "replacement_type", ""))
1493  return false;
1494 
1495  if(!string_matches_if_present(filter, cfg, "alternative_type", ""))
1496  return false;
1497 
1498  //for plague only
1499  if(!string_matches_if_present(filter, cfg, "type", ""))
1500  return false;
1501 
1502  if(!filter["value"].empty()){
1503  if(tag_name == "drains"){
1504  if(!int_matches_if_present(filter, cfg, "value", 50)){
1505  return false;
1506  }
1507  } else if(tag_name == "berserk"){
1508  if(!int_matches_if_present(filter, cfg, "value", 1)){
1509  return false;
1510  }
1511  } else if(tag_name == "heal_on_hit" || tag_name == "heals" || tag_name == "regenerate" || tag_name == "leadership"){
1512  if(!int_matches_if_present(filter, cfg, "value" , 0)){
1513  return false;
1514  }
1515  } else {
1516  if(!int_matches_if_present(filter, cfg, "value")){
1517  return false;
1518  }
1519  }
1520  }
1521 
1522  if(!int_matches_if_present_or_negative(filter, cfg, "add", "sub"))
1523  return false;
1524 
1525  if(!int_matches_if_present_or_negative(filter, cfg, "sub", "add"))
1526  return false;
1527 
1528  if(!double_matches_if_present(filter, cfg, "multiply"))
1529  return false;
1530 
1531  if(!double_matches_if_present(filter, cfg, "divide"))
1532  return false;
1533 
1534  //the wml_filter is used in cases where the attribute we are looking for is not
1535  //previously listed or to check the contents of the sub_tags ([filter_adjacent],[filter_self],[filter_opponent] etc.
1536  //If the checked set does not exactly match the content of the capability, the function returns a false response.
1537  auto fwml = filter.optional_child("filter_wml");
1538  if (fwml){
1539  if(!cfg.matches(*fwml)){
1540  return false;
1541  }
1542  }
1543 
1544  // Passed all tests.
1545  return true;
1546 }
1547 
1548 bool unit::ability_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const
1549 {
1550  // Handle the basic filter.
1551  bool matches = matches_ability_filter(cfg, tag_name, filter);
1552 
1553  // Handle [and], [or], and [not] with in-order precedence
1554  for (const config::any_child condition : filter.all_children_range() )
1555  {
1556  // Handle [and]
1557  if ( condition.key == "and" )
1558  matches = matches && ability_matches_filter(cfg, tag_name, condition.cfg);
1559 
1560  // Handle [or]
1561  else if ( condition.key == "or" )
1562  matches = matches || ability_matches_filter(cfg, tag_name, condition.cfg);
1563 
1564  // Handle [not]
1565  else if ( condition.key == "not" )
1566  matches = matches && !ability_matches_filter(cfg, tag_name, condition.cfg);
1567  }
1568 
1569  return matches;
1570 }
1571 
1573 {
1576  while (i != abilities_.ordered_end()) {
1577  if(ability_matches_filter(i->cfg, i->key, filter)) {
1578  i = abilities_.erase(i);
1579  } else {
1580  ++i;
1581  }
1582  }
1583 }
1584 
1586 {
1587  for(const auto& a_ptr : attacks_) {
1588  if(a_ptr->get_changed()) {
1589  return true;
1590  }
1591 
1592  }
1593  return false;
1594 }
1595 void unit::write(config& cfg, bool write_all) const
1596 {
1597  config back;
1598  auto write_subtag = [&](const std::string& key, const config& child)
1599  {
1600  cfg.clear_children(key);
1601 
1602  if(!child.empty()) {
1603  cfg.add_child(key, child);
1604  } else {
1605  back.add_child(key, child);
1606  }
1607  };
1608 
1609  if(write_all || get_attr_changed(UA_MOVEMENT_TYPE)) {
1610  movement_type_.write(cfg, false);
1611  }
1612  if(write_all || get_attr_changed(UA_SMALL_PROFILE)) {
1613  cfg["small_profile"] = small_profile_;
1614  }
1615  if(write_all || get_attr_changed(UA_PROFILE)) {
1616  cfg["profile"] = profile_;
1617  }
1618  if(description_ != type().unit_description()) {
1619  cfg["description"] = description_;
1620  }
1621  if(write_all || get_attr_changed(UA_NOTES)) {
1622  for(const t_string& note : special_notes_) {
1623  cfg.add_child("special_note")["note"] = note;
1624  }
1625  }
1626 
1627  if(halo_) {
1628  cfg["halo"] = *halo_;
1629  }
1630 
1631  if(ellipse_) {
1632  cfg["ellipse"] = *ellipse_;
1633  }
1634 
1635  if(usage_) {
1636  cfg["usage"] = *usage_;
1637  }
1638 
1639  write_upkeep(cfg["upkeep"]);
1640 
1641  cfg["hitpoints"] = hit_points_;
1642  if(write_all || get_attr_changed(UA_MAX_HP)) {
1643  cfg["max_hitpoints"] = max_hit_points_;
1644  }
1645  cfg["image_icon"] = type().icon();
1646  cfg["image"] = type().image();
1647  cfg["random_traits"] = random_traits_;
1648  cfg["generate_name"] = generate_name_;
1649  cfg["experience"] = experience_;
1650  if(write_all || get_attr_changed(UA_MAX_XP)) {
1651  cfg["max_experience"] = max_experience_;
1652  }
1653  cfg["recall_cost"] = recall_cost_;
1654 
1655  cfg["side"] = side_;
1656 
1657  cfg["type"] = type_id();
1658 
1659  if(type_id() != type().parent_id()) {
1660  cfg["parent_type"] = type().parent_id();
1661  }
1662 
1663  // Support for unit formulas in [ai] and unit-specific variables in [ai] [vars]
1664  formula_man_->write(cfg);
1665 
1666  cfg["gender"] = gender_string(gender_);
1667  cfg["variation"] = variation_;
1668  cfg["role"] = role_;
1669 
1670  config status_flags;
1671  for(const std::string& state : get_states()) {
1672  status_flags[state] = true;
1673  }
1674 
1675  write_subtag("variables", variables_);
1676  write_subtag("filter_recall", filter_recall_);
1677  write_subtag("status", status_flags);
1678 
1679  cfg.clear_children("events");
1680  cfg.append(events_);
1681 
1682  // Overlays are exported as the modifications that add them, not as an overlays= value,
1683  // however removing the key breaks the Gui Debug Tools.
1684  // \todo does anything depend on the key's value, other than the re-import code in unit::init?
1685  cfg["overlays"] = "";
1686 
1687  cfg["name"] = name_;
1688  cfg["id"] = id_;
1689  cfg["underlying_id"] = underlying_id_.value;
1690 
1691  if(can_recruit()) {
1692  cfg["canrecruit"] = true;
1693  }
1694 
1695  cfg["extra_recruit"] = utils::join(recruit_list_);
1696 
1697  cfg["facing"] = map_location::write_direction(facing_);
1698 
1699  cfg["goto_x"] = goto_.wml_x();
1700  cfg["goto_y"] = goto_.wml_y();
1701 
1702  cfg["moves"] = movement_;
1703  if(write_all || get_attr_changed(UA_MAX_MP)) {
1704  cfg["max_moves"] = max_movement_;
1705  }
1706  cfg["vision"] = vision_;
1707  cfg["jamming"] = jamming_;
1708 
1709  cfg["resting"] = resting_;
1710 
1711  if(write_all || get_attr_changed(UA_ADVANCE_TO)) {
1712  cfg["advances_to"] = utils::join(advances_to_);
1713  }
1714 
1715  cfg["race"] = race_->id();
1716  cfg["language_name"] = type_name_;
1717  cfg["undead_variation"] = undead_variation_;
1718  if(write_all || get_attr_changed(UA_LEVEL)) {
1719  cfg["level"] = level_;
1720  }
1721  if(write_all || get_attr_changed(UA_ALIGNMENT)) {
1722  cfg["alignment"] = unit_alignments::get_string(alignment_);
1723  }
1724  cfg["flag_rgb"] = flag_rgb_;
1725  cfg["unrenamable"] = unrenamable_;
1726 
1727  cfg["attacks_left"] = attacks_left_;
1728  if(write_all || get_attr_changed(UA_MAX_AP)) {
1729  cfg["max_attacks"] = max_attacks_;
1730  }
1731  if(write_all || get_attr_changed(UA_ZOC)) {
1732  cfg["zoc"] = emit_zoc_;
1733  }
1734  cfg["hidden"] = hidden_;
1735 
1736  if(write_all || get_attr_changed(UA_ATTACKS) || get_attacks_changed()) {
1737  cfg.clear_children("attack");
1738  for(attack_ptr i : attacks_) {
1739  i->write(cfg.add_child("attack"));
1740  }
1741  }
1742 
1743  cfg["cost"] = unit_value_;
1744 
1745  write_subtag("modifications", modifications_);
1746  if(write_all || get_attr_changed(UA_ABILITIES)) {
1747  write_subtag("abilities", abilities_);
1748  }
1749  if(write_all || get_attr_changed(UA_ADVANCEMENTS)) {
1750  cfg.clear_children("advancement");
1751  for(const config& advancement : advancements_) {
1752  if(!advancement.empty()) {
1753  cfg.add_child("advancement", advancement);
1754  }
1755  }
1756  }
1757  cfg.append(back);
1758 }
1759 
1761 {
1762  if(dir != map_location::NDIRECTIONS && dir != facing_) {
1763  appearance_changed_ = true;
1764  facing_ = dir;
1765  }
1766  // Else look at yourself (not available so continue to face the same direction)
1767 }
1768 
1769 int unit::upkeep() const
1770 {
1771  // Leaders do not incur upkeep.
1772  if(can_recruit()) {
1773  return 0;
1774  }
1775 
1776  return utils::visit(upkeep_value_visitor{*this}, upkeep_);
1777 }
1778 
1779 bool unit::loyal() const
1780 {
1781  return utils::holds_alternative<upkeep_loyal>(upkeep_);
1782 }
1783 
1785 {
1786  int def = movement_type_.defense_modifier(terrain);
1787 #if 0
1788  // A [defense] ability is too costly and doesn't take into account target locations.
1789  // Left as a comment in case someone ever wonders why it isn't a good idea.
1790  unit_ability_list defense_abilities = get_abilities("defense");
1791  if(!defense_abilities.empty()) {
1792  unit_abilities::effect defense_effect(defense_abilities, def);
1793  def = defense_effect.get_composite_value();
1794  }
1795 #endif
1796  return def;
1797 }
1798 
1799 bool unit::resistance_filter_matches(const config& cfg, const std::string& damage_name, int res) const
1800 {
1801  const std::string& apply_to = cfg["apply_to"];
1802  if(!apply_to.empty()) {
1803  if(damage_name != apply_to) {
1804  if(apply_to.find(',') != std::string::npos &&
1805  apply_to.find(damage_name) != std::string::npos) {
1806  const std::vector<std::string>& vals = utils::split(apply_to);
1807  if(std::find(vals.begin(),vals.end(),damage_name) == vals.end()) {
1808  return false;
1809  }
1810  } else {
1811  return false;
1812  }
1813  }
1814  }
1815 
1816  if(!unit_abilities::filter_base_matches(cfg, res)) {
1817  return false;
1818  }
1819 
1820  return true;
1821 }
1822 
1823 int unit::resistance_value(unit_ability_list resistance_list, const std::string& damage_name) const
1824 {
1825  int res = movement_type_.resistance_against(damage_name);
1826  utils::erase_if(resistance_list, [&](const unit_ability& i) {
1827  return !resistance_filter_matches(*i.ability_cfg, damage_name, 100-res);
1828  });
1829 
1830  if(!resistance_list.empty()) {
1831  unit_abilities::effect resist_effect(resistance_list, 100-res);
1832 
1833  res = 100 - resist_effect.get_composite_value();
1834  }
1835 
1836  return res;
1837 }
1838 
1839 static bool resistance_filter_matches_base(const config& cfg, bool attacker)
1840 {
1841  if(!(!cfg.has_attribute("active_on") || (attacker && cfg["active_on"] == "offense") || (!attacker && cfg["active_on"] == "defense"))) {
1842  return false;
1843  }
1844 
1845  return true;
1846 }
1847 
1848 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
1849 {
1850  unit_ability_list resistance_list = get_abilities_weapons("resistance",loc, weapon, opp_weapon);
1851  utils::erase_if(resistance_list, [&](const unit_ability& i) {
1852  return !resistance_filter_matches_base(*i.ability_cfg, attacker);
1853  });
1854  if(opp_weapon){
1855  unit_ability_list damage_type_list = opp_weapon->get_specials_and_abilities("damage_type");
1856  if(damage_type_list.empty()){
1857  return resistance_value(resistance_list, damage_name);
1858  }
1859  std::string replacement_type = opp_weapon->select_damage_type(damage_type_list, "replacement_type", resistance_list);
1860  std::string type_damage = replacement_type.empty() ? damage_name : replacement_type;
1861  int max_res = resistance_value(resistance_list, type_damage);
1862  for(auto& i : damage_type_list) {
1863  if((*i.ability_cfg).has_attribute("alternative_type")){
1864  max_res = std::max(max_res , resistance_value(resistance_list, (*i.ability_cfg)["alternative_type"].str()));
1865  }
1866  }
1867  return max_res;
1868  }
1869  return resistance_value(resistance_list, damage_name);
1870 }
1871 
1872 std::map<std::string, std::string> unit::advancement_icons() const
1873 {
1874  std::map<std::string,std::string> temp;
1875  if(!can_advance()) {
1876  return temp;
1877  }
1878 
1879  if(!advances_to_.empty()) {
1880  std::ostringstream tooltip;
1881  const std::string& image = game_config::images::level;
1882 
1883  for(const std::string& s : advances_to()) {
1884  if(!s.empty()) {
1885  tooltip << s << std::endl;
1886  }
1887  }
1888 
1889  temp[image] = tooltip.str();
1890  }
1891 
1892  for(const config& adv : get_modification_advances()) {
1893  const std::string& image = adv["image"];
1894  if(image.empty()) {
1895  continue;
1896  }
1897 
1898  std::ostringstream tooltip;
1899  tooltip << temp[image];
1900 
1901  const std::string& tt = adv["description"];
1902  if(!tt.empty()) {
1903  tooltip << tt << std::endl;
1904  }
1905 
1906  temp[image] = tooltip.str();
1907  }
1908 
1909  return(temp);
1910 }
1911 
1912 std::vector<std::pair<std::string, std::string>> unit::amla_icons() const
1913 {
1914  std::vector<std::pair<std::string, std::string>> temp;
1915  std::pair<std::string, std::string> icon; // <image,tooltip>
1916 
1917  for(const config& adv : get_modification_advances()) {
1918  icon.first = adv["icon"].str();
1919  icon.second = adv["description"].str();
1920 
1921  for(unsigned j = 0, j_count = modification_count("advancement", adv["id"]); j < j_count; ++j) {
1922  temp.push_back(icon);
1923  }
1924  }
1925 
1926  return(temp);
1927 }
1928 
1929 std::vector<config> unit::get_modification_advances() const
1930 {
1931  std::vector<config> res;
1932  for(const config& adv : modification_advancements()) {
1933  if(adv["strict_amla"].to_bool() && !advances_to_.empty()) {
1934  continue;
1935  }
1936  if(auto filter = adv.optional_child("filter")) {
1937  if(!unit_filter(vconfig(*filter)).matches(*this, loc_)) {
1938  continue;
1939  }
1940  }
1941 
1942  if(modification_count("advancement", adv["id"]) >= static_cast<unsigned>(adv["max_times"].to_int(1))) {
1943  continue;
1944  }
1945 
1946  std::vector<std::string> temp_require = utils::split(adv["require_amla"]);
1947  std::vector<std::string> temp_exclude = utils::split(adv["exclude_amla"]);
1948 
1949  if(temp_require.empty() && temp_exclude.empty()) {
1950  res.push_back(adv);
1951  continue;
1952  }
1953 
1954  std::sort(temp_require.begin(), temp_require.end());
1955  std::sort(temp_exclude.begin(), temp_exclude.end());
1956 
1957  std::vector<std::string> uniq_require, uniq_exclude;
1958 
1959  std::unique_copy(temp_require.begin(), temp_require.end(), std::back_inserter(uniq_require));
1960  std::unique_copy(temp_exclude.begin(), temp_exclude.end(), std::back_inserter(uniq_exclude));
1961 
1962  bool exclusion_found = false;
1963  for(const std::string& s : uniq_exclude) {
1964  int max_num = std::count(temp_exclude.begin(), temp_exclude.end(), s);
1965  int mod_num = modification_count("advancement", s);
1966  if(mod_num >= max_num) {
1967  exclusion_found = true;
1968  break;
1969  }
1970  }
1971 
1972  if(exclusion_found) {
1973  continue;
1974  }
1975 
1976  bool requirements_done = true;
1977  for(const std::string& s : uniq_require) {
1978  int required_num = std::count(temp_require.begin(), temp_require.end(), s);
1979  int mod_num = modification_count("advancement", s);
1980  if(required_num > mod_num) {
1981  requirements_done = false;
1982  break;
1983  }
1984  }
1985 
1986  if(requirements_done) {
1987  res.push_back(adv);
1988  }
1989  }
1990 
1991  return res;
1992 }
1993 
1994 void unit::set_advancements(std::vector<config> advancements)
1995 {
1997  advancements_ = advancements;
1998 }
1999 
2000 const std::string& unit::type_id() const
2001 {
2002  return type_->id();
2003 }
2004 
2005 void unit::set_big_profile(const std::string& value)
2006 {
2008  profile_ = value;
2010 }
2011 
2012 std::size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
2013 {
2014  std::size_t res = 0;
2015  for(const config& item : modifications_.child_range(mod_type)) {
2016  if(item["id"] == id) {
2017  ++res;
2018  }
2019  }
2020 
2021  // For backwards compatibility, if asked for "advancement", also count "advance"
2022  if(mod_type == "advancement") {
2023  res += modification_count("advance", id);
2024  }
2025 
2026  return res;
2027 }
2028 
2029 const std::set<std::string> unit::builtin_effects {
2030  "alignment", "attack", "defense", "ellipse", "experience", "fearless",
2031  "halo", "healthy", "hitpoints", "image_mod", "jamming", "jamming_costs", "level",
2032  "loyal", "max_attacks", "max_experience", "movement", "movement_costs",
2033  "new_ability", "new_advancement", "new_animation", "new_attack", "overlay", "profile",
2034  "recall_cost", "remove_ability", "remove_advancement", "remove_attacks", "resistance",
2035  "status", "type", "variation", "vision", "vision_costs", "zoc"
2036 };
2037 
2038 std::string unit::describe_builtin_effect(std::string apply_to, const config& effect)
2039 {
2040  if(apply_to == "attack") {
2041  std::vector<t_string> attack_names;
2042 
2043  std::string desc;
2044  for(attack_ptr a : attacks_) {
2045  bool affected = a->describe_modification(effect, &desc);
2046  if(affected && !desc.empty()) {
2047  attack_names.emplace_back(a->name(), "wesnoth-units");
2048  }
2049  }
2050  if(!attack_names.empty()) {
2051  utils::string_map symbols;
2052  symbols["attack_list"] = utils::format_conjunct_list("", attack_names);
2053  symbols["effect_description"] = desc;
2054  return VGETTEXT("$attack_list|: $effect_description", symbols);
2055  }
2056  } else if(apply_to == "hitpoints") {
2057  const std::string& increase_total = effect["increase_total"];
2058  if(!increase_total.empty()) {
2059  return VGETTEXT(
2060  "<span color=\"$color\">$number_or_percent</span> HP",
2061  {{"number_or_percent", utils::print_modifier(increase_total)}, {"color", increase_total[0] == '-' ? "#f00" : "#0f0"}});
2062  }
2063  } else {
2064  const std::string& increase = effect["increase"];
2065  if(increase.empty()) {
2066  return "";
2067  }
2068  if(apply_to == "movement") {
2069  return VNGETTEXT(
2070  "<span color=\"$color\">$number_or_percent</span> move",
2071  "<span color=\"$color\">$number_or_percent</span> moves",
2072  std::stoi(increase),
2073  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2074  } else if(apply_to == "vision") {
2075  return VGETTEXT(
2076  "<span color=\"$color\">$number_or_percent</span> vision",
2077  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2078  } else if(apply_to == "jamming") {
2079  return VGETTEXT(
2080  "<span color=\"$color\">$number_or_percent</span> jamming",
2081  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2082  } else if(apply_to == "max_experience") {
2083  // Unlike others, decreasing experience is a *GOOD* thing
2084  return VGETTEXT(
2085  "<span color=\"$color\">$number_or_percent</span> XP to advance",
2086  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
2087  } else if(apply_to == "max_attacks") {
2088  return VNGETTEXT(
2089  "<span color=\"$color\">$number_or_percent</span> attack per turn",
2090  "<span color=\"$color\">$number_or_percent</span> attacks per turn",
2091  std::stoi(increase),
2092  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2093  } else if(apply_to == "recall_cost") {
2094  // Unlike others, decreasing recall cost is a *GOOD* thing
2095  return VGETTEXT(
2096  "<span color=\"$color\">$number_or_percent</span> cost to recall",
2097  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
2098  }
2099  }
2100  return "";
2101 }
2102 
2103 void unit::apply_builtin_effect(std::string apply_to, const config& effect)
2104 {
2105  appearance_changed_ = true;
2106  if(apply_to == "fearless") {
2108  is_fearless_ = effect["set"].to_bool(true);
2109  } else if(apply_to == "healthy") {
2111  is_healthy_ = effect["set"].to_bool(true);
2112  } else if(apply_to == "profile") {
2113  if(const config::attribute_value* v = effect.get("portrait")) {
2114  set_big_profile((*v).str());
2115  }
2116 
2117  if(const config::attribute_value* v = effect.get("small_portrait")) {
2118  set_small_profile((*v).str());
2119  }
2120 
2121  if(const config::attribute_value* v = effect.get("description")) {
2122  description_ = *v;
2123  }
2124 
2125  if(config::const_child_itors cfg_range = effect.child_range("special_note")) {
2126  for(const config& c : cfg_range) {
2127  if(!c["remove"].to_bool()) {
2128  special_notes_.emplace_back(c["note"].t_str());
2129  } else {
2130  auto iter = std::find(special_notes_.begin(), special_notes_.end(), c["note"].t_str());
2131  if(iter != special_notes_.end()) {
2132  special_notes_.erase(iter);
2133  }
2134  }
2135  }
2136  }
2137  } else if(apply_to == "new_attack") {
2139  attacks_.emplace_back(new attack_type(effect));
2140  } else if(apply_to == "remove_attacks") {
2142  auto iter = std::remove_if(attacks_.begin(), attacks_.end(), [&effect](attack_ptr a) {
2143  return a->matches_filter(effect);
2144  });
2145 
2146  attacks_.erase(iter, attacks_.end());
2147  } else if(apply_to == "attack") {
2149  for(attack_ptr a : attacks_) {
2150  a->apply_modification(effect);
2151  }
2152  } else if(apply_to == "hitpoints") {
2153  LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_;
2154  const std::string& increase_hp = effect["increase"];
2155  const std::string& increase_total = effect["increase_total"];
2156  const std::string& set_hp = effect["set"];
2157  const std::string& set_total = effect["set_total"];
2158 
2159  // If the hitpoints are allowed to end up greater than max hitpoints
2160  const bool violate_max = effect["violate_maximum"].to_bool();
2161 
2162  if(!set_hp.empty()) {
2163  if(set_hp.back() == '%') {
2164  hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
2165  } else {
2166  hit_points_ = lexical_cast_default<int>(set_hp);
2167  }
2168  }
2169 
2170  if(!set_total.empty()) {
2171  if(set_total.back() == '%') {
2172  set_max_hitpoints(lexical_cast_default<int>(set_total)*max_hit_points_/100);
2173  } else {
2174  set_max_hitpoints(lexical_cast_default<int>(set_total));
2175  }
2176  }
2177 
2178  if(!increase_total.empty()) {
2179  // A percentage on the end means increase by that many percent
2181  }
2182 
2183  if(max_hit_points_ < 1)
2184  set_max_hitpoints(1);
2185 
2186  if(effect["heal_full"].to_bool()) {
2187  heal_fully();
2188  }
2189 
2190  if(!increase_hp.empty()) {
2192  }
2193 
2194  LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_;
2195  if(hit_points_ > max_hit_points_ && !violate_max) {
2196  LOG_UT << "resetting hp to max";
2198  }
2199 
2200  if(hit_points_ < 1) {
2201  hit_points_ = 1;
2202  }
2203  } else if(apply_to == "movement") {
2204  const bool apply_to_vision = effect["apply_to_vision"].to_bool(true);
2205 
2206  // Unlink vision from movement, regardless of whether we'll increment both or not
2207  if(vision_ < 0) {
2209  }
2210 
2211  const int old_max = max_movement_;
2212 
2213  const std::string& increase = effect["increase"];
2214  if(!increase.empty()) {
2216  }
2217 
2218  set_total_movement(effect["set"].to_int(max_movement_));
2219 
2220  if(movement_ > max_movement_) {
2222  }
2223 
2224  if(apply_to_vision) {
2225  vision_ = std::max(0, vision_ + max_movement_ - old_max);
2226  }
2227  } else if(apply_to == "vision") {
2228  // Unlink vision from movement, regardless of which one we're about to change.
2229  if(vision_ < 0) {
2231  }
2232 
2233  const std::string& increase = effect["increase"];
2234  if(!increase.empty()) {
2235  vision_ = utils::apply_modifier(vision_, increase, 1);
2236  }
2237 
2238  vision_ = effect["set"].to_int(vision_);
2239  } else if(apply_to == "jamming") {
2240  const std::string& increase = effect["increase"];
2241 
2242  if(!increase.empty()) {
2243  jamming_ = utils::apply_modifier(jamming_, increase, 1);
2244  }
2245 
2246  jamming_ = effect["set"].to_int(jamming_);
2247  } else if(apply_to == "experience") {
2248  const std::string& increase = effect["increase"];
2249  const std::string& set = effect["set"];
2250 
2251  if(!set.empty()) {
2252  if(set.back() == '%') {
2253  experience_ = lexical_cast_default<int>(set)*max_experience_/100;
2254  } else {
2255  experience_ = lexical_cast_default<int>(set);
2256  }
2257  }
2258 
2259  if(increase.empty() == false) {
2261  }
2262  } else if(apply_to == "max_experience") {
2263  const std::string& increase = effect["increase"];
2264  const std::string& set = effect["set"];
2265 
2266  if(set.empty() == false) {
2267  if(set.back() == '%') {
2268  set_max_experience(lexical_cast_default<int>(set)*max_experience_/100);
2269  } else {
2270  set_max_experience(lexical_cast_default<int>(set));
2271  }
2272  }
2273 
2274  if(increase.empty() == false) {
2276  }
2277  } else if(apply_to == upkeep_loyal::type()) {
2278  upkeep_ = upkeep_loyal{};
2279  } else if(apply_to == "status") {
2280  const std::string& add = effect["add"];
2281  const std::string& remove = effect["remove"];
2282 
2283  for(const std::string& to_add : utils::split(add))
2284  {
2285  set_state(to_add, true);
2286  }
2287 
2288  for(const std::string& to_remove : utils::split(remove))
2289  {
2290  set_state(to_remove, false);
2291  }
2292  } else if(std::find(movetype::effects.cbegin(), movetype::effects.cend(), apply_to) != movetype::effects.cend()) {
2293  // "movement_costs", "vision_costs", "jamming_costs", "defense", "resistance"
2294  if(auto ap = effect.optional_child(apply_to)) {
2296  movement_type_.merge(*ap, apply_to, effect["replace"].to_bool());
2297  }
2298  } else if(apply_to == "zoc") {
2299  if(const config::attribute_value* v = effect.get("value")) {
2301  emit_zoc_ = v->to_bool();
2302  }
2303  } else if(apply_to == "new_ability") {
2304  if(auto ab_effect = effect.optional_child("abilities")) {
2306  config to_append;
2307  for(const config::any_child ab : ab_effect->all_children_range()) {
2308  if(!has_ability_by_id(ab.cfg["id"])) {
2309  to_append.add_child(ab.key, ab.cfg);
2310  }
2311  }
2312  abilities_.append(to_append);
2313  }
2314  } else if(apply_to == "remove_ability") {
2315  if(auto ab_effect = effect.optional_child("abilities")) {
2316  for(const config::any_child ab : ab_effect->all_children_range()) {
2317  remove_ability_by_id(ab.cfg["id"]);
2318  }
2319  }
2320  if(auto fab_effect = effect.optional_child("experimental_filter_ability")) {
2321  remove_ability_by_attribute(*fab_effect);
2322  }
2323  } else if(apply_to == "image_mod") {
2324  LOG_UT << "applying image_mod";
2325  std::string mod = effect["replace"];
2326  if(!mod.empty()){
2327  image_mods_ = mod;
2328  }
2329  LOG_UT << "applying image_mod";
2330  mod = effect["add"].str();
2331  if(!mod.empty()){
2332  if(!image_mods_.empty()) {
2333  image_mods_ += '~';
2334  }
2335 
2336  image_mods_ += mod;
2337  }
2338 
2340  LOG_UT << "applying image_mod";
2341  } else if(apply_to == "new_animation") {
2342  anim_comp_->apply_new_animation_effect(effect);
2343  } else if(apply_to == "ellipse") {
2344  set_image_ellipse(effect["ellipse"]);
2345  } else if(apply_to == "halo") {
2346  set_image_halo(effect["halo"]);
2347  } else if(apply_to == "overlay") {
2348  const std::string& add = effect["add"];
2349  const std::string& replace = effect["replace"];
2350  const std::string& remove = effect["remove"];
2351 
2352  if(!add.empty()) {
2353  for(const auto& to_add : utils::parenthetical_split(add, ',')) {
2354  overlays_.push_back(to_add);
2355  }
2356  }
2357  if(!remove.empty()) {
2358  for(const auto& to_remove : utils::parenthetical_split(remove, ',')) {
2359  overlays_.erase(std::remove(overlays_.begin(), overlays_.end(), to_remove), overlays_.end());
2360  }
2361  }
2362  if(add.empty() && remove.empty() && !replace.empty()) {
2363  overlays_ = utils::parenthetical_split(replace, ',');
2364  }
2365  } else if(apply_to == "new_advancement") {
2366  const std::string& types = effect["types"];
2367  const bool replace = effect["replace"].to_bool(false);
2369 
2370  if(!types.empty()) {
2371  if(replace) {
2373  } else {
2374  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2375  std::copy(temp_advances.begin(), temp_advances.end(), std::back_inserter(advances_to_));
2376  }
2377  }
2378 
2379  if(effect.has_child("advancement")) {
2380  if(replace) {
2381  advancements_.clear();
2382  }
2383 
2384  for(const config& adv : effect.child_range("advancement")) {
2385  advancements_.push_back(adv);
2386  }
2387  }
2388  } else if(apply_to == "remove_advancement") {
2389  const std::string& types = effect["types"];
2390  const std::string& amlas = effect["amlas"];
2392 
2393  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2395  for(const std::string& unit : temp_advances) {
2396  iter = std::find(advances_to_.begin(), advances_to_.end(), unit);
2397  if(iter != advances_to_.end()) {
2398  advances_to_.erase(iter);
2399  }
2400  }
2401 
2402  temp_advances = utils::parenthetical_split(amlas, ',');
2403 
2404  for(int i = advancements_.size() - 1; i >= 0; i--) {
2405  if(std::find(temp_advances.begin(), temp_advances.end(), advancements_[i]["id"]) != temp_advances.end()) {
2406  advancements_.erase(advancements_.begin() + i);
2407  }
2408  }
2409  } else if(apply_to == "alignment") {
2410  auto new_align = unit_alignments::get_enum(effect["set"].str());
2411  if(new_align) {
2412  set_alignment(*new_align);
2413  }
2414  } else if(apply_to == "max_attacks") {
2415  const std::string& increase = effect["increase"];
2416 
2417  if(!increase.empty()) {
2419  }
2420  } else if(apply_to == "recall_cost") {
2421  const std::string& increase = effect["increase"];
2422  const std::string& set = effect["set"];
2423  const int team_recall_cost = resources::gameboard ? resources::gameboard->get_team(side_).recall_cost() : 20;
2424  const int recall_cost = recall_cost_ < 0 ? team_recall_cost : recall_cost_;
2425 
2426  if(!set.empty()) {
2427  if(set.back() == '%') {
2428  recall_cost_ = lexical_cast_default<int>(set)*recall_cost/100;
2429  } else {
2430  recall_cost_ = lexical_cast_default<int>(set);
2431  }
2432  }
2433 
2434  if(!increase.empty()) {
2436  }
2437  } else if(effect["apply_to"] == "variation") {
2438  const unit_type* base_type = unit_types.find(type().parent_id());
2439  assert(base_type != nullptr);
2440  const std::string& variation_id = effect["name"];
2441  if(variation_id.empty() || base_type->get_gender_unit_type(gender_).has_variation(variation_id)) {
2442  variation_ = variation_id;
2443  advance_to(*base_type);
2444  if(effect["heal_full"].to_bool(false)) {
2445  heal_fully();
2446  }
2447  } else {
2448  WRN_UT << "unknown variation '" << variation_id << "' (name=) in [effect]apply_to=variation, ignoring";
2449  }
2450  } else if(effect["apply_to"] == "type") {
2451  std::string prev_type = effect["prev_type"];
2452  if(prev_type.empty()) {
2453  prev_type = type().parent_id();
2454  }
2455  const std::string& new_type_id = effect["name"];
2456  const unit_type* new_type = unit_types.find(new_type_id);
2457  if(new_type) {
2458  advance_to(*new_type);
2459  prefs::get().encountered_units().insert(new_type_id);
2460  if(effect["heal_full"].to_bool(false)) {
2461  heal_fully();
2462  }
2463  } else {
2464  WRN_UT << "unknown type '" << new_type_id << "' (name=) in [effect]apply_to=type, ignoring";
2465  }
2466  } else if(effect["apply_to"] == "level") {
2467  const std::string& increase = effect["increase"];
2468  const std::string& set = effect["set"];
2469 
2471 
2472  // no support for percentages, since levels are usually small numbers
2473 
2474  if(!set.empty()) {
2475  level_ = lexical_cast_default<int>(set);
2476  }
2477 
2478  if(!increase.empty()) {
2479  level_ += lexical_cast_default<int>(increase);
2480  }
2481  }
2482 }
2483 
2484 void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
2485 {
2486  bool generate_description = mod["generate_description"].to_bool(true);
2487 
2488  config* target = nullptr;
2489 
2490  if(no_add == false) {
2491  target = &modifications_.add_child(mod_type, mod);
2492  target->remove_children("effect");
2493  }
2494 
2495  std::vector<t_string> effects_description;
2496  for(const config& effect : mod.child_range("effect")) {
2497  if(target) {
2498  //Store effects only after they are added to avoid double applying effects on advance with apply_to=variation.
2499  target->add_child("effect", effect);
2500  }
2501  // Apply SUF.
2502  if(auto afilter = effect.optional_child("filter")) {
2503  assert(resources::filter_con);
2504  if(!unit_filter(vconfig(*afilter)).matches(*this, loc_)) {
2505  continue;
2506  }
2507  }
2508  const std::string& apply_to = effect["apply_to"];
2509  int times = effect["times"].to_int(1);
2510  t_string description;
2511 
2512  if(no_add && (apply_to == "type" || apply_to == "variation")) {
2513  continue;
2514  }
2515 
2516  if(effect["times"] == "per level") {
2517  if(effect["apply_to"] == "level") {
2518  WRN_UT << "[effect] times=per level is not allowed with apply_to=level, using default value of 1";
2519  times = 1;
2520  }
2521  else {
2522  times = level_;
2523  }
2524  }
2525 
2526  if(times) {
2527  while (times > 0) {
2528  times --;
2529  std::string description_component;
2530  if(resources::lua_kernel) {
2531  description_component = resources::lua_kernel->apply_effect(apply_to, *this, effect, true);
2532  } else if(builtin_effects.count(apply_to)) {
2533  // Normally, the built-in effects are dispatched through Lua so that a user
2534  // can override them if desired. However, since they're built-in, we can still
2535  // apply them if the lua kernel is unavailable.
2536  apply_builtin_effect(apply_to, effect);
2537  description_component = describe_builtin_effect(apply_to, effect);
2538  }
2539  if(!times) {
2540  description += description_component;
2541  }
2542  } // end while
2543  } else { // for times = per level & level = 0 we still need to rebuild the descriptions
2544  if(resources::lua_kernel) {
2545  description += resources::lua_kernel->apply_effect(apply_to, *this, effect, false);
2546  } else if(builtin_effects.count(apply_to)) {
2547  description += describe_builtin_effect(apply_to, effect);
2548  }
2549  }
2550 
2551  if(effect["times"] == "per level" && !times) {
2552  description = VGETTEXT("$effect_description per level", {{"effect_description", description}});
2553  }
2554 
2555  if(!description.empty()) {
2556  effects_description.push_back(description);
2557  }
2558  }
2559 
2560  t_string description;
2561 
2562  const t_string& mod_description = mod["description"];
2563  if(!mod_description.empty()) {
2564  description = mod_description;
2565  }
2566 
2567  // Punctuation should be translatable: not all languages use Latin punctuation.
2568  // (However, there maybe is a better way to do it)
2569  if(generate_description && !effects_description.empty()) {
2570  if(!mod_description.empty()) {
2571  description += "\n";
2572  }
2573 
2574  for(const auto& desc_line : effects_description) {
2575  description += desc_line + "\n";
2576  }
2577  }
2578 
2579  // store trait info
2580  if(mod_type == "trait") {
2581  add_trait_description(mod, description);
2582  }
2583 
2584  //NOTE: if not a trait, description is currently not used
2585 }
2586 
2587 void unit::add_trait_description(const config& trait, const t_string& description)
2588 {
2589  const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
2590  const auto& gender_specific_name = trait[gender_string];
2591 
2592  const t_string name = gender_specific_name.empty() ? trait["name"] : gender_specific_name;
2593 
2594  if(!name.empty()) {
2595  trait_names_.push_back(name);
2596  trait_descriptions_.push_back(description);
2597  trait_nonhidden_ids_.push_back(trait["id"]);
2598  }
2599 }
2600 
2601 std::string unit::absolute_image() const
2602 {
2603  return type().icon().empty() ? type().image() : type().icon();
2604 }
2605 
2606 std::string unit::default_anim_image() const
2607 {
2608  return type().image().empty() ? type().icon() : type().image();
2609 }
2610 
2612 {
2613  log_scope("apply mods");
2614 
2615  variables_.clear_children("mods");
2616  if(modifications_.has_child("advance")) {
2617  deprecated_message("[advance]", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [advancement] instead.");
2618  }
2620  add_modification(mod.key, mod.cfg, true);
2621  }
2622 }
2623 
2624 bool unit::invisible(const map_location& loc, bool see_all) const
2625 {
2626  if(loc != get_location()) {
2627  DBG_UT << "unit::invisible called: id = " << id() << " loc = " << loc << " get_loc = " << get_location();
2628  }
2629 
2630  // This is a quick condition to check, and it does not depend on the
2631  // location (so might as well bypass the location-based cache).
2632  if(get_state(STATE_UNCOVERED)) {
2633  return false;
2634  }
2635 
2636  // Fetch from cache
2637  /**
2638  * @todo FIXME: We use the cache only when using the default see_all=true
2639  * Maybe add a second cache if the see_all=false become more frequent.
2640  */
2641  if(see_all) {
2642  const auto itor = invisibility_cache_.find(loc);
2643  if(itor != invisibility_cache_.end()) {
2644  return itor->second;
2645  }
2646  }
2647 
2648  // Test hidden status
2649  static const std::string hides("hides");
2650  bool is_inv = get_ability_bool(hides, loc);
2651  if(is_inv){
2652  is_inv = (resources::gameboard ? !resources::gameboard->would_be_discovered(loc, side_,see_all) : true);
2653  }
2654 
2655  if(see_all) {
2656  // Add to caches
2657  if(invisibility_cache_.empty()) {
2658  units_with_cache.push_back(this);
2659  }
2660 
2661  invisibility_cache_[loc] = is_inv;
2662  }
2663 
2664  return is_inv;
2665 }
2666 
2667 bool unit::is_visible_to_team(const team& team, bool const see_all) const
2668 {
2669  const map_location& loc = get_location();
2670  return is_visible_to_team(loc, team, see_all);
2671 }
2672 
2673 bool unit::is_visible_to_team(const map_location& loc, const team& team, bool const see_all) const
2674 {
2675  if(!display::get_singleton()->get_map().on_board(loc)) {
2676  return false;
2677  }
2678 
2679  if(see_all) {
2680  return true;
2681  }
2682 
2683  if(team.is_enemy(side()) && invisible(loc)) {
2684  return false;
2685  }
2686 
2687  // allied planned moves are also visible under fog. (we assume that fake units on the map are always whiteboard markers)
2688  if(!team.is_enemy(side()) && underlying_id_.is_fake()) {
2689  return true;
2690  }
2691 
2692  // when the whiteboard planned unit map is applied, it uses moved the _real_ unit so
2693  // underlying_id_.is_fake() will be false and the check above will not apply.
2694  // TODO: improve this check so that is also works for allied planned units but without
2695  // breaking sp campaigns with allies under fog. We probably need an explicit flag
2696  // is_planned_ in unit that is set by the whiteboard.
2697  if(team.side() == side()) {
2698  return true;
2699  }
2700 
2701  if(team.fogged(loc)) {
2702  return false;
2703  }
2704 
2705  return true;
2706 }
2707 
2709 {
2710  if(underlying_id_.value == 0) {
2712  underlying_id_ = id_manager.next_id();
2713  } else {
2714  underlying_id_ = id_manager.next_fake_id();
2715  }
2716  }
2717 
2718  if(id_.empty() /*&& !underlying_id_.is_fake()*/) {
2719  std::stringstream ss;
2720  ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_.value;
2721  id_ = ss.str();
2722  }
2723 }
2724 
2725 unit& unit::mark_clone(bool is_temporary)
2726 {
2728  if(is_temporary) {
2729  underlying_id_ = ids.next_fake_id();
2730  } else {
2732  underlying_id_ = ids.next_id();
2733  }
2734  else {
2735  underlying_id_ = ids.next_fake_id();
2736  }
2737  std::string::size_type pos = id_.find_last_of('-');
2738  if(pos != std::string::npos && pos+1 < id_.size()
2739  && id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
2740  // this appears to be a duplicate of a generic unit, so give it a new id
2741  WRN_UT << "assigning new id to clone of generic unit " << id_;
2742  id_.clear();
2743  set_underlying_id(ids);
2744  }
2745  }
2746  return *this;
2747 }
2748 
2749 
2751  : u_(const_cast<unit&>(u))
2752  , moves_(u.movement_left(true))
2753 {
2754  if(operate) {
2756  }
2757 }
2758 
2760 {
2761  assert(resources::gameboard);
2762  try {
2763  if(!resources::gameboard->units().has_unit(&u_)) {
2764  /*
2765  * It might be valid that the unit is not in the unit map.
2766  * It might also mean a no longer valid unit will be assigned to.
2767  */
2768  DBG_UT << "The unit to be removed is not in the unit map.";
2769  }
2770 
2772  } catch(...) {
2773  DBG_UT << "Caught exception when destroying unit_movement_resetter: " << utils::get_unknown_exception_type();
2774  }
2775 }
2776 
2777 std::string unit::TC_image_mods() const
2778 {
2779  return formatter() << "~RC(" << flag_rgb() << ">" << team::get_side_color_id(side()) << ")";
2780 }
2781 
2782 std::string unit::image_mods() const
2783 {
2784  if(!image_mods_.empty()) {
2785  return formatter() << "~" << image_mods_ << TC_image_mods();
2786  }
2787 
2788  return TC_image_mods();
2789 }
2790 
2791 // Called by the Lua API after resetting an attack pointer.
2793 {
2795  auto iter = std::find(attacks_.begin(), attacks_.end(), atk);
2796  if(iter == attacks_.end()) {
2797  return false;
2798  }
2799  attacks_.erase(iter);
2800  return true;
2801 }
2802 
2804 {
2805  if(attacks_left_ == max_attacks_) {
2806  //TODO: add state_not_attacked
2807  }
2808 
2809  set_attacks(0);
2810 }
2811 
2813 {
2814  if(movement_left() == total_movement()) {
2815  set_state(STATE_NOT_MOVED,true);
2816  }
2817 
2818  set_movement(0, true);
2819 }
2820 
2821 void unit::set_hidden(bool state) const
2822 {
2823 // appearance_changed_ = true;
2824  hidden_ = state;
2825  if(!state) {
2826  return;
2827  }
2828 
2829  // TODO: this should really hide the halo, not destroy it
2830  // We need to get rid of haloes immediately to avoid display glitches
2831  anim_comp_->clear_haloes();
2832 }
2833 
2834 void unit::set_image_halo(const std::string& halo)
2835 {
2836  appearance_changed_ = true;
2837  anim_comp_->clear_haloes();
2838  halo_ = halo;
2839 }
2840 
2842 {
2843  if(upkeep.empty()) {
2844  return;
2845  }
2846 
2847  try {
2848  upkeep_ = upkeep.apply_visitor(upkeep_parser_visitor{});
2849  } catch(std::invalid_argument& e) {
2850  WRN_UT << "Found invalid upkeep=\"" << e.what() << "\" in a unit";
2851  upkeep_ = upkeep_full{};
2852  }
2853 }
2854 
2856 {
2857  upkeep = utils::visit(upkeep_type_visitor{}, upkeep_);
2858 }
2859 
2861 {
2862  changed_attributes_.reset();
2863  for(const auto& a_ptr : attacks_) {
2864  a_ptr->set_changed(false);
2865  }
2866 }
2867 
2868 std::vector<t_string> unit::unit_special_notes() const {
2870 }
2871 
2872 // Filters unimportant stats from the unit config and returns a checksum of
2873 // the remaining config.
2875 {
2876  config unit_config;
2877  config wcfg;
2878  u.write(unit_config);
2879 
2880  static const std::set<std::string_view> main_keys {
2881  "advances_to",
2882  "alignment",
2883  "cost",
2884  "experience",
2885  "gender",
2886  "hitpoints",
2887  "ignore_race_traits",
2888  "ignore_global_traits",
2889  "level",
2890  "recall_cost",
2891  "max_attacks",
2892  "max_experience",
2893  "max_hitpoints",
2894  "max_moves",
2895  "movement",
2896  "movement_type",
2897  "race",
2898  "random_traits",
2899  "resting",
2900  "undead_variation",
2901  "upkeep",
2902  "zoc"
2903  };
2904 
2905  for(const std::string_view& main_key : main_keys) {
2906  wcfg[main_key] = unit_config[main_key];
2907  }
2908 
2909  static const std::set<std::string_view> attack_keys {
2910  "name",
2911  "type",
2912  "range",
2913  "damage",
2914  "number"
2915  };
2916 
2917  for(const config& att : unit_config.child_range("attack")) {
2918  config& child = wcfg.add_child("attack");
2919 
2920  for(const std::string_view& attack_key : attack_keys) {
2921  child[attack_key] = att[attack_key];
2922  }
2923 
2924  for(const config& spec : att.child_range("specials")) {
2925  config& child_spec = child.add_child("specials", spec);
2926 
2927  child_spec.recursive_clear_value("description");
2929  child_spec.recursive_clear_value("description_inactive");
2930  child_spec.recursive_clear_value("name");
2931  child_spec.recursive_clear_value("name_inactive");
2932  }
2933  }
2934  }
2935 
2936  for(const config& abi : unit_config.child_range("abilities")) {
2937  config& child = wcfg.add_child("abilities", abi);
2938 
2939  child.recursive_clear_value("description");
2940  child.recursive_clear_value("description_inactive");
2941  child.recursive_clear_value("name");
2942  child.recursive_clear_value("name_inactive");
2943  }
2944 
2945  for(const config& trait : unit_config.child_range("trait")) {
2946  config& child = wcfg.add_child("trait", trait);
2947 
2948  child.recursive_clear_value("description");
2949  child.recursive_clear_value("male_name");
2950  child.recursive_clear_value("female_name");
2951  child.recursive_clear_value("name");
2952  }
2953 
2954  static const std::set<std::string_view> child_keys {
2955  "advance_from",
2956  "defense",
2957  "movement_costs",
2958  "vision_costs",
2959  "jamming_costs",
2960  "resistance"
2961  };
2962 
2963  for(const std::string_view& child_key : child_keys) {
2964  for(const config& c : unit_config.child_range(child_key)) {
2965  wcfg.add_child(child_key, c);
2966  }
2967  }
2968 
2969  DBG_UT << wcfg;
2970 
2971  return wcfg.hash();
2972 }
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
bool matches(const config &filter) const
Definition: config.cpp:1198
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
std::set< std::string > & encountered_units()
static prefs & get()
static rng & default_instance()
Definition: random.cpp:73
int get_random_int(int min, int max)
Definition: random.hpp:51
static bool is_synced()
bool empty() const
Definition: tstring.hpp: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:2860
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:1585
unit()=delete
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
std::vector< vconfig > child_list
Definition: variable.hpp:78
bool null() const
Definition: variable.hpp:72
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:288
const config & get_config() const
Definition: variable.hpp:75
child_list get_children(const std::string &key) const
Definition: variable.cpp:226
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
map_display and display: classes which take care of displaying the map and game-data on the screen.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp: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:1572
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:1595
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:2855
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:2624
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2667
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:2725
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:2841
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:1548
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:2005
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:2821
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:2000
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:2708
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:2868
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:1929
std::vector< std::pair< std::string, std::string > > amla_icons() const
Gets the image and description data for modification advancements.
Definition: unit.cpp:1912
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:1994
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:1872
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:2803
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:1784
bool resistance_filter_matches(const config &cfg, const std::string &damage_name, int res) const
Definition: unit.cpp:1799
bool remove_attack(attack_ptr atk)
Remove an attack from the unit.
Definition: unit.cpp:2792
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:1823
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:1848
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:2777
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:2782
std::string default_anim_image() const
The default image to use for animation frames with no defined image.
Definition: unit.cpp:2606
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:2601
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:2834
void apply_builtin_effect(std::string type, const config &effect)
Apply a builtin effect to the unit.
Definition: unit.cpp:2103
void add_modification(const std::string &type, const config &modification, bool no_add=false)
Add a new modification to the unit.
Definition: unit.cpp:2484
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:2038
void apply_modifications()
Re-apply all saved modifications.
Definition: unit.cpp:2611
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:2012
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:1760
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:2812
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:1769
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:2587
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:1779
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition: manager.cpp:209
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:95
Definition: halo.cpp:39
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
Functions to load and save images from/to disk.
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
game_board * gameboard
Definition: resources.cpp:20
game_data * gamedata
Definition: resources.cpp:22
game_events::manager * game_events
Definition: resources.cpp:24
game_lua_kernel * lua_kernel
Definition: resources.cpp:25
filter_context * filter_con
Definition: resources.cpp:23
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:1969
Utility functions for implementing [filter], [filter_ability], [filter_weapon], etc.
bool set_includes_if_present(const config &filter, const config &cfg, const std::string &attribute)
filter[attribute] and cfg[attribute] are assumed to be comma-separated lists.
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:1839
std::string get_checksum(const unit &u, backwards_compatibility::unit_checksum_version version)
Gets a checksum for a unit.
Definition: unit.cpp:2874
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