The Battle for Wesnoth  1.19.2+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 
1596 void unit::write(config& cfg, bool write_all) const
1597 {
1598  config back;
1599  auto write_subtag = [&](const std::string& key, const config& child)
1600  {
1601  cfg.clear_children(key);
1602 
1603  if(!child.empty()) {
1604  cfg.add_child(key, child);
1605  } else {
1606  back.add_child(key, child);
1607  }
1608  };
1609 
1610  if(write_all || get_attr_changed(UA_MOVEMENT_TYPE)) {
1611  movement_type_.write(cfg, false);
1612  }
1613  if(write_all || get_attr_changed(UA_SMALL_PROFILE)) {
1614  cfg["small_profile"] = small_profile_;
1615  }
1616  if(write_all || get_attr_changed(UA_PROFILE)) {
1617  cfg["profile"] = profile_;
1618  }
1619  if(description_ != type().unit_description()) {
1620  cfg["description"] = description_;
1621  }
1622  if(write_all || get_attr_changed(UA_NOTES)) {
1623  for(const t_string& note : special_notes_) {
1624  cfg.add_child("special_note")["note"] = note;
1625  }
1626  }
1627 
1628  if(halo_) {
1629  cfg["halo"] = *halo_;
1630  }
1631 
1632  if(ellipse_) {
1633  cfg["ellipse"] = *ellipse_;
1634  }
1635 
1636  if(usage_) {
1637  cfg["usage"] = *usage_;
1638  }
1639 
1640  write_upkeep(cfg["upkeep"]);
1641 
1642  cfg["hitpoints"] = hit_points_;
1643  if(write_all || get_attr_changed(UA_MAX_HP)) {
1644  cfg["max_hitpoints"] = max_hit_points_;
1645  }
1646  cfg["image_icon"] = type().icon();
1647  cfg["image"] = type().image();
1648  cfg["random_traits"] = random_traits_;
1649  cfg["generate_name"] = generate_name_;
1650  cfg["experience"] = experience_;
1651  if(write_all || get_attr_changed(UA_MAX_XP)) {
1652  cfg["max_experience"] = max_experience_;
1653  }
1654  cfg["recall_cost"] = recall_cost_;
1655 
1656  cfg["side"] = side_;
1657 
1658  cfg["type"] = type_id();
1659 
1660  if(type_id() != type().parent_id()) {
1661  cfg["parent_type"] = type().parent_id();
1662  }
1663 
1664  // Support for unit formulas in [ai] and unit-specific variables in [ai] [vars]
1665  formula_man_->write(cfg);
1666 
1667  cfg["gender"] = gender_string(gender_);
1668  cfg["variation"] = variation_;
1669  cfg["role"] = role_;
1670 
1671  config status_flags;
1672  for(const std::string& state : get_states()) {
1673  status_flags[state] = true;
1674  }
1675 
1676  write_subtag("variables", variables_);
1677  write_subtag("filter_recall", filter_recall_);
1678  write_subtag("status", status_flags);
1679 
1680  cfg.clear_children("events");
1681  cfg.append(events_);
1682 
1683  // Overlays are exported as the modifications that add them, not as an overlays= value,
1684  // however removing the key breaks the Gui Debug Tools.
1685  // \todo does anything depend on the key's value, other than the re-import code in unit::init?
1686  cfg["overlays"] = "";
1687 
1688  cfg["name"] = name_;
1689  cfg["id"] = id_;
1690  cfg["underlying_id"] = underlying_id_.value;
1691 
1692  if(can_recruit()) {
1693  cfg["canrecruit"] = true;
1694  }
1695 
1696  cfg["extra_recruit"] = utils::join(recruit_list_);
1697 
1698  cfg["facing"] = map_location::write_direction(facing_);
1699 
1700  cfg["goto_x"] = goto_.wml_x();
1701  cfg["goto_y"] = goto_.wml_y();
1702 
1703  cfg["moves"] = movement_;
1704  if(write_all || get_attr_changed(UA_MAX_MP)) {
1705  cfg["max_moves"] = max_movement_;
1706  }
1707  cfg["vision"] = vision_;
1708  cfg["jamming"] = jamming_;
1709 
1710  cfg["resting"] = resting_;
1711 
1712  if(write_all || get_attr_changed(UA_ADVANCE_TO)) {
1713  cfg["advances_to"] = utils::join(advances_to_);
1714  }
1715 
1716  cfg["race"] = race_->id();
1717  cfg["language_name"] = type_name_;
1718  cfg["undead_variation"] = undead_variation_;
1719  if(write_all || get_attr_changed(UA_LEVEL)) {
1720  cfg["level"] = level_;
1721  }
1722  if(write_all || get_attr_changed(UA_ALIGNMENT)) {
1723  cfg["alignment"] = unit_alignments::get_string(alignment_);
1724  }
1725  cfg["flag_rgb"] = flag_rgb_;
1726  cfg["unrenamable"] = unrenamable_;
1727 
1728  cfg["attacks_left"] = attacks_left_;
1729  if(write_all || get_attr_changed(UA_MAX_AP)) {
1730  cfg["max_attacks"] = max_attacks_;
1731  }
1732  if(write_all || get_attr_changed(UA_ZOC)) {
1733  cfg["zoc"] = emit_zoc_;
1734  }
1735  cfg["hidden"] = hidden_;
1736 
1737  if(write_all || get_attr_changed(UA_ATTACKS) || get_attacks_changed()) {
1738  cfg.clear_children("attack");
1739  for(attack_ptr i : attacks_) {
1740  i->write(cfg.add_child("attack"));
1741  }
1742  }
1743 
1744  cfg["cost"] = unit_value_;
1745 
1746  write_subtag("modifications", modifications_);
1747  if(write_all || get_attr_changed(UA_ABILITIES)) {
1748  write_subtag("abilities", abilities_);
1749  }
1750  if(write_all || get_attr_changed(UA_ADVANCEMENTS)) {
1751  cfg.clear_children("advancement");
1752  for(const config& advancement : advancements_) {
1753  if(!advancement.empty()) {
1754  cfg.add_child("advancement", advancement);
1755  }
1756  }
1757  }
1758  cfg.append(back);
1759 }
1760 
1762 {
1763  if(dir != map_location::NDIRECTIONS && dir != facing_) {
1764  appearance_changed_ = true;
1765  facing_ = dir;
1766  }
1767  // Else look at yourself (not available so continue to face the same direction)
1768 }
1769 
1770 int unit::upkeep() const
1771 {
1772  // Leaders do not incur upkeep.
1773  if(can_recruit()) {
1774  return 0;
1775  }
1776 
1777  return utils::visit(upkeep_value_visitor{*this}, upkeep_);
1778 }
1779 
1780 bool unit::loyal() const
1781 {
1782  return utils::holds_alternative<upkeep_loyal>(upkeep_);
1783 }
1784 
1785 void unit::set_loyal(bool loyal)
1786 {
1787  if (loyal) {
1788  upkeep_ = upkeep_loyal{};
1789  overlays_.push_back("misc/loyal-icon.png");
1790  } else {
1791  upkeep_ = upkeep_full{};
1792  overlays_.erase(std::remove(overlays_.begin(), overlays_.end(), "misc/loyal-icon.png"), overlays_.end());
1793  }
1794 }
1795 
1797 {
1798  int def = movement_type_.defense_modifier(terrain);
1799 #if 0
1800  // A [defense] ability is too costly and doesn't take into account target locations.
1801  // Left as a comment in case someone ever wonders why it isn't a good idea.
1802  unit_ability_list defense_abilities = get_abilities("defense");
1803  if(!defense_abilities.empty()) {
1804  unit_abilities::effect defense_effect(defense_abilities, def);
1805  def = defense_effect.get_composite_value();
1806  }
1807 #endif
1808  return def;
1809 }
1810 
1811 bool unit::resistance_filter_matches(const config& cfg, const std::string& damage_name, int res) const
1812 {
1813  const std::string& apply_to = cfg["apply_to"];
1814  if(!apply_to.empty()) {
1815  if(damage_name != apply_to) {
1816  if(apply_to.find(',') != std::string::npos &&
1817  apply_to.find(damage_name) != std::string::npos) {
1818  const std::vector<std::string>& vals = utils::split(apply_to);
1819  if(std::find(vals.begin(),vals.end(),damage_name) == vals.end()) {
1820  return false;
1821  }
1822  } else {
1823  return false;
1824  }
1825  }
1826  }
1827 
1828  if(!unit_abilities::filter_base_matches(cfg, res)) {
1829  return false;
1830  }
1831 
1832  return true;
1833 }
1834 
1835 int unit::resistance_value(unit_ability_list resistance_list, const std::string& damage_name) const
1836 {
1837  int res = movement_type_.resistance_against(damage_name);
1838  utils::erase_if(resistance_list, [&](const unit_ability& i) {
1839  return !resistance_filter_matches(*i.ability_cfg, damage_name, 100-res);
1840  });
1841 
1842  if(!resistance_list.empty()) {
1843  unit_abilities::effect resist_effect(resistance_list, 100-res);
1844 
1845  res = 100 - resist_effect.get_composite_value();
1846  }
1847 
1848  return res;
1849 }
1850 
1851 static bool resistance_filter_matches_base(const config& cfg, bool attacker)
1852 {
1853  if(!(!cfg.has_attribute("active_on") || (attacker && cfg["active_on"] == "offense") || (!attacker && cfg["active_on"] == "defense"))) {
1854  return false;
1855  }
1856 
1857  return true;
1858 }
1859 
1860 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
1861 {
1862  unit_ability_list resistance_list = get_abilities_weapons("resistance",loc, weapon, opp_weapon);
1863  utils::erase_if(resistance_list, [&](const unit_ability& i) {
1864  return !resistance_filter_matches_base(*i.ability_cfg, attacker);
1865  });
1866  if(opp_weapon){
1867  unit_ability_list damage_type_list = opp_weapon->get_specials_and_abilities("damage_type");
1868  if(damage_type_list.empty()){
1869  return resistance_value(resistance_list, damage_name);
1870  }
1871  std::string replacement_type = opp_weapon->select_damage_type(damage_type_list, "replacement_type", resistance_list);
1872  std::string type_damage = replacement_type.empty() ? damage_name : replacement_type;
1873  int max_res = resistance_value(resistance_list, type_damage);
1874  for(auto& i : damage_type_list) {
1875  if((*i.ability_cfg).has_attribute("alternative_type")){
1876  max_res = std::max(max_res , resistance_value(resistance_list, (*i.ability_cfg)["alternative_type"].str()));
1877  }
1878  }
1879  return max_res;
1880  }
1881  return resistance_value(resistance_list, damage_name);
1882 }
1883 
1884 std::map<std::string, std::string> unit::advancement_icons() const
1885 {
1886  std::map<std::string,std::string> temp;
1887  if(!can_advance()) {
1888  return temp;
1889  }
1890 
1891  if(!advances_to_.empty()) {
1892  std::ostringstream tooltip;
1893  const std::string& image = game_config::images::level;
1894 
1895  for(const std::string& s : advances_to()) {
1896  if(!s.empty()) {
1897  tooltip << s << std::endl;
1898  }
1899  }
1900 
1901  temp[image] = tooltip.str();
1902  }
1903 
1904  for(const config& adv : get_modification_advances()) {
1905  const std::string& image = adv["image"];
1906  if(image.empty()) {
1907  continue;
1908  }
1909 
1910  std::ostringstream tooltip;
1911  tooltip << temp[image];
1912 
1913  const std::string& tt = adv["description"];
1914  if(!tt.empty()) {
1915  tooltip << tt << std::endl;
1916  }
1917 
1918  temp[image] = tooltip.str();
1919  }
1920 
1921  return(temp);
1922 }
1923 
1924 std::vector<std::pair<std::string, std::string>> unit::amla_icons() const
1925 {
1926  std::vector<std::pair<std::string, std::string>> temp;
1927  std::pair<std::string, std::string> icon; // <image,tooltip>
1928 
1929  for(const config& adv : get_modification_advances()) {
1930  icon.first = adv["icon"].str();
1931  icon.second = adv["description"].str();
1932 
1933  for(unsigned j = 0, j_count = modification_count("advancement", adv["id"]); j < j_count; ++j) {
1934  temp.push_back(icon);
1935  }
1936  }
1937 
1938  return(temp);
1939 }
1940 
1941 std::vector<config> unit::get_modification_advances() const
1942 {
1943  std::vector<config> res;
1944  for(const config& adv : modification_advancements()) {
1945  if(adv["strict_amla"].to_bool() && !advances_to_.empty()) {
1946  continue;
1947  }
1948  if(auto filter = adv.optional_child("filter")) {
1949  if(!unit_filter(vconfig(*filter)).matches(*this, loc_)) {
1950  continue;
1951  }
1952  }
1953 
1954  if(modification_count("advancement", adv["id"]) >= static_cast<unsigned>(adv["max_times"].to_int(1))) {
1955  continue;
1956  }
1957 
1958  std::vector<std::string> temp_require = utils::split(adv["require_amla"]);
1959  std::vector<std::string> temp_exclude = utils::split(adv["exclude_amla"]);
1960 
1961  if(temp_require.empty() && temp_exclude.empty()) {
1962  res.push_back(adv);
1963  continue;
1964  }
1965 
1966  std::sort(temp_require.begin(), temp_require.end());
1967  std::sort(temp_exclude.begin(), temp_exclude.end());
1968 
1969  std::vector<std::string> uniq_require, uniq_exclude;
1970 
1971  std::unique_copy(temp_require.begin(), temp_require.end(), std::back_inserter(uniq_require));
1972  std::unique_copy(temp_exclude.begin(), temp_exclude.end(), std::back_inserter(uniq_exclude));
1973 
1974  bool exclusion_found = false;
1975  for(const std::string& s : uniq_exclude) {
1976  int max_num = std::count(temp_exclude.begin(), temp_exclude.end(), s);
1977  int mod_num = modification_count("advancement", s);
1978  if(mod_num >= max_num) {
1979  exclusion_found = true;
1980  break;
1981  }
1982  }
1983 
1984  if(exclusion_found) {
1985  continue;
1986  }
1987 
1988  bool requirements_done = true;
1989  for(const std::string& s : uniq_require) {
1990  int required_num = std::count(temp_require.begin(), temp_require.end(), s);
1991  int mod_num = modification_count("advancement", s);
1992  if(required_num > mod_num) {
1993  requirements_done = false;
1994  break;
1995  }
1996  }
1997 
1998  if(requirements_done) {
1999  res.push_back(adv);
2000  }
2001  }
2002 
2003  return res;
2004 }
2005 
2006 void unit::set_advancements(std::vector<config> advancements)
2007 {
2009  advancements_ = advancements;
2010 }
2011 
2012 const std::string& unit::type_id() const
2013 {
2014  return type_->id();
2015 }
2016 
2017 void unit::set_big_profile(const std::string& value)
2018 {
2020  profile_ = value;
2022 }
2023 
2024 std::size_t unit::modification_count(const std::string& mod_type, const std::string& id) const
2025 {
2026  std::size_t res = 0;
2027  for(const config& item : modifications_.child_range(mod_type)) {
2028  if(item["id"] == id) {
2029  ++res;
2030  }
2031  }
2032 
2033  // For backwards compatibility, if asked for "advancement", also count "advance"
2034  if(mod_type == "advancement") {
2035  res += modification_count("advance", id);
2036  }
2037 
2038  return res;
2039 }
2040 
2041 const std::set<std::string> unit::builtin_effects {
2042  "alignment", "attack", "defense", "ellipse", "experience", "fearless",
2043  "halo", "healthy", "hitpoints", "image_mod", "jamming", "jamming_costs", "level",
2044  "loyal", "max_attacks", "max_experience", "movement", "movement_costs",
2045  "new_ability", "new_advancement", "new_animation", "new_attack", "overlay", "profile",
2046  "recall_cost", "remove_ability", "remove_advancement", "remove_attacks", "resistance",
2047  "status", "type", "variation", "vision", "vision_costs", "zoc"
2048 };
2049 
2050 std::string unit::describe_builtin_effect(std::string apply_to, const config& effect)
2051 {
2052  if(apply_to == "attack") {
2053  std::vector<t_string> attack_names;
2054 
2055  std::string desc;
2056  for(attack_ptr a : attacks_) {
2057  bool affected = a->describe_modification(effect, &desc);
2058  if(affected && !desc.empty()) {
2059  attack_names.emplace_back(a->name(), "wesnoth-units");
2060  }
2061  }
2062  if(!attack_names.empty()) {
2063  utils::string_map symbols;
2064  symbols["attack_list"] = utils::format_conjunct_list("", attack_names);
2065  symbols["effect_description"] = desc;
2066  return VGETTEXT("$attack_list|: $effect_description", symbols);
2067  }
2068  } else if(apply_to == "hitpoints") {
2069  const std::string& increase_total = effect["increase_total"];
2070  if(!increase_total.empty()) {
2071  return VGETTEXT(
2072  "<span color=\"$color\">$number_or_percent</span> HP",
2073  {{"number_or_percent", utils::print_modifier(increase_total)}, {"color", increase_total[0] == '-' ? "#f00" : "#0f0"}});
2074  }
2075  } else {
2076  const std::string& increase = effect["increase"];
2077  if(increase.empty()) {
2078  return "";
2079  }
2080  if(apply_to == "movement") {
2081  return VNGETTEXT(
2082  "<span color=\"$color\">$number_or_percent</span> move",
2083  "<span color=\"$color\">$number_or_percent</span> moves",
2084  std::stoi(increase),
2085  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2086  } else if(apply_to == "vision") {
2087  return VGETTEXT(
2088  "<span color=\"$color\">$number_or_percent</span> vision",
2089  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2090  } else if(apply_to == "jamming") {
2091  return VGETTEXT(
2092  "<span color=\"$color\">$number_or_percent</span> jamming",
2093  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2094  } else if(apply_to == "max_experience") {
2095  // Unlike others, decreasing experience is a *GOOD* thing
2096  return VGETTEXT(
2097  "<span color=\"$color\">$number_or_percent</span> XP to advance",
2098  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
2099  } else if(apply_to == "max_attacks") {
2100  return VNGETTEXT(
2101  "<span color=\"$color\">$number_or_percent</span> attack per turn",
2102  "<span color=\"$color\">$number_or_percent</span> attacks per turn",
2103  std::stoi(increase),
2104  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#f00" : "#0f0"}});
2105  } else if(apply_to == "recall_cost") {
2106  // Unlike others, decreasing recall cost is a *GOOD* thing
2107  return VGETTEXT(
2108  "<span color=\"$color\">$number_or_percent</span> cost to recall",
2109  {{"number_or_percent", utils::print_modifier(increase)}, {"color", increase[0] == '-' ? "#0f0" : "#f00"}});
2110  }
2111  }
2112  return "";
2113 }
2114 
2115 void unit::apply_builtin_effect(std::string apply_to, const config& effect)
2116 {
2117  appearance_changed_ = true;
2118  if(apply_to == "fearless") {
2120  is_fearless_ = effect["set"].to_bool(true);
2121  } else if(apply_to == "healthy") {
2123  is_healthy_ = effect["set"].to_bool(true);
2124  } else if(apply_to == "profile") {
2125  if(const config::attribute_value* v = effect.get("portrait")) {
2126  set_big_profile((*v).str());
2127  }
2128 
2129  if(const config::attribute_value* v = effect.get("small_portrait")) {
2130  set_small_profile((*v).str());
2131  }
2132 
2133  if(const config::attribute_value* v = effect.get("description")) {
2134  description_ = *v;
2135  }
2136 
2137  if(config::const_child_itors cfg_range = effect.child_range("special_note")) {
2138  for(const config& c : cfg_range) {
2139  if(!c["remove"].to_bool()) {
2140  special_notes_.emplace_back(c["note"].t_str());
2141  } else {
2142  auto iter = std::find(special_notes_.begin(), special_notes_.end(), c["note"].t_str());
2143  if(iter != special_notes_.end()) {
2144  special_notes_.erase(iter);
2145  }
2146  }
2147  }
2148  }
2149  } else if(apply_to == "new_attack") {
2151  attacks_.emplace_back(new attack_type(effect));
2152  } else if(apply_to == "remove_attacks") {
2154  auto iter = std::remove_if(attacks_.begin(), attacks_.end(), [&effect](attack_ptr a) {
2155  return a->matches_filter(effect);
2156  });
2157 
2158  attacks_.erase(iter, attacks_.end());
2159  } else if(apply_to == "attack") {
2161  for(attack_ptr a : attacks_) {
2162  a->apply_modification(effect);
2163  }
2164  } else if(apply_to == "hitpoints") {
2165  LOG_UT << "applying hitpoint mod..." << hit_points_ << "/" << max_hit_points_;
2166  const std::string& increase_hp = effect["increase"];
2167  const std::string& increase_total = effect["increase_total"];
2168  const std::string& set_hp = effect["set"];
2169  const std::string& set_total = effect["set_total"];
2170 
2171  // If the hitpoints are allowed to end up greater than max hitpoints
2172  const bool violate_max = effect["violate_maximum"].to_bool();
2173 
2174  if(!set_hp.empty()) {
2175  if(set_hp.back() == '%') {
2176  hit_points_ = lexical_cast_default<int>(set_hp)*max_hit_points_/100;
2177  } else {
2178  hit_points_ = lexical_cast_default<int>(set_hp);
2179  }
2180  }
2181 
2182  if(!set_total.empty()) {
2183  if(set_total.back() == '%') {
2184  set_max_hitpoints(lexical_cast_default<int>(set_total)*max_hit_points_/100);
2185  } else {
2186  set_max_hitpoints(lexical_cast_default<int>(set_total));
2187  }
2188  }
2189 
2190  if(!increase_total.empty()) {
2191  // A percentage on the end means increase by that many percent
2193  }
2194 
2195  if(max_hit_points_ < 1)
2196  set_max_hitpoints(1);
2197 
2198  if(effect["heal_full"].to_bool()) {
2199  heal_fully();
2200  }
2201 
2202  if(!increase_hp.empty()) {
2204  }
2205 
2206  LOG_UT << "modded to " << hit_points_ << "/" << max_hit_points_;
2207  if(hit_points_ > max_hit_points_ && !violate_max) {
2208  LOG_UT << "resetting hp to max";
2210  }
2211 
2212  if(hit_points_ < 1) {
2213  hit_points_ = 1;
2214  }
2215  } else if(apply_to == "movement") {
2216  const bool apply_to_vision = effect["apply_to_vision"].to_bool(true);
2217 
2218  // Unlink vision from movement, regardless of whether we'll increment both or not
2219  if(vision_ < 0) {
2221  }
2222 
2223  const int old_max = max_movement_;
2224 
2225  const std::string& increase = effect["increase"];
2226  if(!increase.empty()) {
2228  }
2229 
2230  set_total_movement(effect["set"].to_int(max_movement_));
2231 
2232  if(movement_ > max_movement_) {
2234  }
2235 
2236  if(apply_to_vision) {
2237  vision_ = std::max(0, vision_ + max_movement_ - old_max);
2238  }
2239  } else if(apply_to == "vision") {
2240  // Unlink vision from movement, regardless of which one we're about to change.
2241  if(vision_ < 0) {
2243  }
2244 
2245  const std::string& increase = effect["increase"];
2246  if(!increase.empty()) {
2247  vision_ = utils::apply_modifier(vision_, increase, 1);
2248  }
2249 
2250  vision_ = effect["set"].to_int(vision_);
2251  } else if(apply_to == "jamming") {
2252  const std::string& increase = effect["increase"];
2253 
2254  if(!increase.empty()) {
2255  jamming_ = utils::apply_modifier(jamming_, increase, 1);
2256  }
2257 
2258  jamming_ = effect["set"].to_int(jamming_);
2259  } else if(apply_to == "experience") {
2260  const std::string& increase = effect["increase"];
2261  const std::string& set = effect["set"];
2262 
2263  if(!set.empty()) {
2264  if(set.back() == '%') {
2265  experience_ = lexical_cast_default<int>(set)*max_experience_/100;
2266  } else {
2267  experience_ = lexical_cast_default<int>(set);
2268  }
2269  }
2270 
2271  if(increase.empty() == false) {
2273  }
2274  } else if(apply_to == "max_experience") {
2275  const std::string& increase = effect["increase"];
2276  const std::string& set = effect["set"];
2277 
2278  if(set.empty() == false) {
2279  if(set.back() == '%') {
2280  set_max_experience(lexical_cast_default<int>(set)*max_experience_/100);
2281  } else {
2282  set_max_experience(lexical_cast_default<int>(set));
2283  }
2284  }
2285 
2286  if(increase.empty() == false) {
2288  }
2289  } else if(apply_to == upkeep_loyal::type()) {
2290  upkeep_ = upkeep_loyal{};
2291  } else if(apply_to == "status") {
2292  const std::string& add = effect["add"];
2293  const std::string& remove = effect["remove"];
2294 
2295  for(const std::string& to_add : utils::split(add))
2296  {
2297  set_state(to_add, true);
2298  }
2299 
2300  for(const std::string& to_remove : utils::split(remove))
2301  {
2302  set_state(to_remove, false);
2303  }
2304  } else if(std::find(movetype::effects.cbegin(), movetype::effects.cend(), apply_to) != movetype::effects.cend()) {
2305  // "movement_costs", "vision_costs", "jamming_costs", "defense", "resistance"
2306  if(auto ap = effect.optional_child(apply_to)) {
2308  movement_type_.merge(*ap, apply_to, effect["replace"].to_bool());
2309  }
2310  } else if(apply_to == "zoc") {
2311  if(const config::attribute_value* v = effect.get("value")) {
2313  emit_zoc_ = v->to_bool();
2314  }
2315  } else if(apply_to == "new_ability") {
2316  if(auto ab_effect = effect.optional_child("abilities")) {
2318  config to_append;
2319  for(const config::any_child ab : ab_effect->all_children_range()) {
2320  if(!has_ability_by_id(ab.cfg["id"])) {
2321  to_append.add_child(ab.key, ab.cfg);
2322  }
2323  }
2324  abilities_.append(to_append);
2325  }
2326  } else if(apply_to == "remove_ability") {
2327  if(auto ab_effect = effect.optional_child("abilities")) {
2328  for(const config::any_child ab : ab_effect->all_children_range()) {
2329  remove_ability_by_id(ab.cfg["id"]);
2330  }
2331  }
2332  if(auto fab_effect = effect.optional_child("experimental_filter_ability")) {
2333  remove_ability_by_attribute(*fab_effect);
2334  }
2335  } else if(apply_to == "image_mod") {
2336  LOG_UT << "applying image_mod";
2337  std::string mod = effect["replace"];
2338  if(!mod.empty()){
2339  image_mods_ = mod;
2340  }
2341  LOG_UT << "applying image_mod";
2342  mod = effect["add"].str();
2343  if(!mod.empty()){
2344  if(!image_mods_.empty()) {
2345  image_mods_ += '~';
2346  }
2347 
2348  image_mods_ += mod;
2349  }
2350 
2352  LOG_UT << "applying image_mod";
2353  } else if(apply_to == "new_animation") {
2354  anim_comp_->apply_new_animation_effect(effect);
2355  } else if(apply_to == "ellipse") {
2356  set_image_ellipse(effect["ellipse"]);
2357  } else if(apply_to == "halo") {
2358  set_image_halo(effect["halo"]);
2359  } else if(apply_to == "overlay") {
2360  const std::string& add = effect["add"];
2361  const std::string& replace = effect["replace"];
2362  const std::string& remove = effect["remove"];
2363 
2364  if(!add.empty()) {
2365  for(const auto& to_add : utils::parenthetical_split(add, ',')) {
2366  overlays_.push_back(to_add);
2367  }
2368  }
2369  if(!remove.empty()) {
2370  for(const auto& to_remove : utils::parenthetical_split(remove, ',')) {
2371  overlays_.erase(std::remove(overlays_.begin(), overlays_.end(), to_remove), overlays_.end());
2372  }
2373  }
2374  if(add.empty() && remove.empty() && !replace.empty()) {
2375  overlays_ = utils::parenthetical_split(replace, ',');
2376  }
2377  } else if(apply_to == "new_advancement") {
2378  const std::string& types = effect["types"];
2379  const bool replace = effect["replace"].to_bool(false);
2381 
2382  if(!types.empty()) {
2383  if(replace) {
2385  } else {
2386  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2387  std::copy(temp_advances.begin(), temp_advances.end(), std::back_inserter(advances_to_));
2388  }
2389  }
2390 
2391  if(effect.has_child("advancement")) {
2392  if(replace) {
2393  advancements_.clear();
2394  }
2395 
2396  for(const config& adv : effect.child_range("advancement")) {
2397  advancements_.push_back(adv);
2398  }
2399  }
2400  } else if(apply_to == "remove_advancement") {
2401  const std::string& types = effect["types"];
2402  const std::string& amlas = effect["amlas"];
2404 
2405  std::vector<std::string> temp_advances = utils::parenthetical_split(types, ',');
2407  for(const std::string& unit : temp_advances) {
2408  iter = std::find(advances_to_.begin(), advances_to_.end(), unit);
2409  if(iter != advances_to_.end()) {
2410  advances_to_.erase(iter);
2411  }
2412  }
2413 
2414  temp_advances = utils::parenthetical_split(amlas, ',');
2415 
2416  for(int i = advancements_.size() - 1; i >= 0; i--) {
2417  if(std::find(temp_advances.begin(), temp_advances.end(), advancements_[i]["id"]) != temp_advances.end()) {
2418  advancements_.erase(advancements_.begin() + i);
2419  }
2420  }
2421  } else if(apply_to == "alignment") {
2422  auto new_align = unit_alignments::get_enum(effect["set"].str());
2423  if(new_align) {
2424  set_alignment(*new_align);
2425  }
2426  } else if(apply_to == "max_attacks") {
2427  const std::string& increase = effect["increase"];
2428 
2429  if(!increase.empty()) {
2431  }
2432  } else if(apply_to == "recall_cost") {
2433  const std::string& increase = effect["increase"];
2434  const std::string& set = effect["set"];
2435  const int team_recall_cost = resources::gameboard ? resources::gameboard->get_team(side_).recall_cost() : 20;
2436  const int recall_cost = recall_cost_ < 0 ? team_recall_cost : recall_cost_;
2437 
2438  if(!set.empty()) {
2439  if(set.back() == '%') {
2440  recall_cost_ = lexical_cast_default<int>(set)*recall_cost/100;
2441  } else {
2442  recall_cost_ = lexical_cast_default<int>(set);
2443  }
2444  }
2445 
2446  if(!increase.empty()) {
2448  }
2449  } else if(effect["apply_to"] == "variation") {
2450  const unit_type* base_type = unit_types.find(type().parent_id());
2451  assert(base_type != nullptr);
2452  const std::string& variation_id = effect["name"];
2453  if(variation_id.empty() || base_type->get_gender_unit_type(gender_).has_variation(variation_id)) {
2454  variation_ = variation_id;
2455  advance_to(*base_type);
2456  if(effect["heal_full"].to_bool(false)) {
2457  heal_fully();
2458  }
2459  } else {
2460  WRN_UT << "unknown variation '" << variation_id << "' (name=) in [effect]apply_to=variation, ignoring";
2461  }
2462  } else if(effect["apply_to"] == "type") {
2463  std::string prev_type = effect["prev_type"];
2464  if(prev_type.empty()) {
2465  prev_type = type().parent_id();
2466  }
2467  const std::string& new_type_id = effect["name"];
2468  const unit_type* new_type = unit_types.find(new_type_id);
2469  if(new_type) {
2470  advance_to(*new_type);
2471  prefs::get().encountered_units().insert(new_type_id);
2472  if(effect["heal_full"].to_bool(false)) {
2473  heal_fully();
2474  }
2475  } else {
2476  WRN_UT << "unknown type '" << new_type_id << "' (name=) in [effect]apply_to=type, ignoring";
2477  }
2478  } else if(effect["apply_to"] == "level") {
2479  const std::string& increase = effect["increase"];
2480  const std::string& set = effect["set"];
2481 
2483 
2484  // no support for percentages, since levels are usually small numbers
2485 
2486  if(!set.empty()) {
2487  level_ = lexical_cast_default<int>(set);
2488  }
2489 
2490  if(!increase.empty()) {
2491  level_ += lexical_cast_default<int>(increase);
2492  }
2493  }
2494 }
2495 
2496 void unit::add_modification(const std::string& mod_type, const config& mod, bool no_add)
2497 {
2498  bool generate_description = mod["generate_description"].to_bool(true);
2499 
2500  config* target = nullptr;
2501 
2502  if(no_add == false) {
2503  target = &modifications_.add_child(mod_type, mod);
2504  target->remove_children("effect");
2505  }
2506 
2507  std::vector<t_string> effects_description;
2508  for(const config& effect : mod.child_range("effect")) {
2509  if(target) {
2510  //Store effects only after they are added to avoid double applying effects on advance with apply_to=variation.
2511  target->add_child("effect", effect);
2512  }
2513  // Apply SUF.
2514  if(auto afilter = effect.optional_child("filter")) {
2515  assert(resources::filter_con);
2516  if(!unit_filter(vconfig(*afilter)).matches(*this, loc_)) {
2517  continue;
2518  }
2519  }
2520  const std::string& apply_to = effect["apply_to"];
2521  int times = effect["times"].to_int(1);
2522  t_string description;
2523 
2524  if(no_add && (apply_to == "type" || apply_to == "variation")) {
2525  continue;
2526  }
2527 
2528  if(effect["times"] == "per level") {
2529  if(effect["apply_to"] == "level") {
2530  WRN_UT << "[effect] times=per level is not allowed with apply_to=level, using default value of 1";
2531  times = 1;
2532  }
2533  else {
2534  times = level_;
2535  }
2536  }
2537 
2538  if(times) {
2539  while (times > 0) {
2540  times --;
2541  std::string description_component;
2542  if(resources::lua_kernel) {
2543  description_component = resources::lua_kernel->apply_effect(apply_to, *this, effect, true);
2544  } else if(builtin_effects.count(apply_to)) {
2545  // Normally, the built-in effects are dispatched through Lua so that a user
2546  // can override them if desired. However, since they're built-in, we can still
2547  // apply them if the lua kernel is unavailable.
2548  apply_builtin_effect(apply_to, effect);
2549  description_component = describe_builtin_effect(apply_to, effect);
2550  }
2551  if(!times) {
2552  description += description_component;
2553  }
2554  } // end while
2555  } else { // for times = per level & level = 0 we still need to rebuild the descriptions
2556  if(resources::lua_kernel) {
2557  description += resources::lua_kernel->apply_effect(apply_to, *this, effect, false);
2558  } else if(builtin_effects.count(apply_to)) {
2559  description += describe_builtin_effect(apply_to, effect);
2560  }
2561  }
2562 
2563  if(effect["times"] == "per level" && !times) {
2564  description = VGETTEXT("$effect_description per level", {{"effect_description", description}});
2565  }
2566 
2567  if(!description.empty()) {
2568  effects_description.push_back(description);
2569  }
2570  }
2571 
2572  t_string description;
2573 
2574  const t_string& mod_description = mod["description"];
2575  if(!mod_description.empty()) {
2576  description = mod_description;
2577  }
2578 
2579  // Punctuation should be translatable: not all languages use Latin punctuation.
2580  // (However, there maybe is a better way to do it)
2581  if(generate_description && !effects_description.empty()) {
2582  if(!mod_description.empty()) {
2583  description += "\n";
2584  }
2585 
2586  for(const auto& desc_line : effects_description) {
2587  description += desc_line + "\n";
2588  }
2589  }
2590 
2591  // store trait info
2592  if(mod_type == "trait") {
2593  add_trait_description(mod, description);
2594  }
2595 
2596  //NOTE: if not a trait, description is currently not used
2597 }
2598 
2599 void unit::add_trait_description(const config& trait, const t_string& description)
2600 {
2601  const std::string& gender_string = gender_ == unit_race::FEMALE ? "female_name" : "male_name";
2602  const auto& gender_specific_name = trait[gender_string];
2603 
2604  const t_string name = gender_specific_name.empty() ? trait["name"] : gender_specific_name;
2605 
2606  if(!name.empty()) {
2607  trait_names_.push_back(name);
2608  trait_descriptions_.push_back(description);
2609  trait_nonhidden_ids_.push_back(trait["id"]);
2610  }
2611 }
2612 
2613 std::string unit::absolute_image() const
2614 {
2615  return type().icon().empty() ? type().image() : type().icon();
2616 }
2617 
2618 std::string unit::default_anim_image() const
2619 {
2620  return type().image().empty() ? type().icon() : type().image();
2621 }
2622 
2624 {
2625  log_scope("apply mods");
2626 
2627  variables_.clear_children("mods");
2628  if(modifications_.has_child("advance")) {
2629  deprecated_message("[advance]", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [advancement] instead.");
2630  }
2632  add_modification(mod.key, mod.cfg, true);
2633  }
2634 }
2635 
2636 bool unit::invisible(const map_location& loc, bool see_all) const
2637 {
2638  if(loc != get_location()) {
2639  DBG_UT << "unit::invisible called: id = " << id() << " loc = " << loc << " get_loc = " << get_location();
2640  }
2641 
2642  // This is a quick condition to check, and it does not depend on the
2643  // location (so might as well bypass the location-based cache).
2644  if(get_state(STATE_UNCOVERED)) {
2645  return false;
2646  }
2647 
2648  // Fetch from cache
2649  /**
2650  * @todo FIXME: We use the cache only when using the default see_all=true
2651  * Maybe add a second cache if the see_all=false become more frequent.
2652  */
2653  if(see_all) {
2654  const auto itor = invisibility_cache_.find(loc);
2655  if(itor != invisibility_cache_.end()) {
2656  return itor->second;
2657  }
2658  }
2659 
2660  // Test hidden status
2661  static const std::string hides("hides");
2662  bool is_inv = get_ability_bool(hides, loc);
2663  if(is_inv){
2664  is_inv = (resources::gameboard ? !resources::gameboard->would_be_discovered(loc, side_,see_all) : true);
2665  }
2666 
2667  if(see_all) {
2668  // Add to caches
2669  if(invisibility_cache_.empty()) {
2670  units_with_cache.push_back(this);
2671  }
2672 
2673  invisibility_cache_[loc] = is_inv;
2674  }
2675 
2676  return is_inv;
2677 }
2678 
2679 bool unit::is_visible_to_team(const team& team, bool const see_all) const
2680 {
2681  const map_location& loc = get_location();
2682  return is_visible_to_team(loc, team, see_all);
2683 }
2684 
2685 bool unit::is_visible_to_team(const map_location& loc, const team& team, bool const see_all) const
2686 {
2687  if(!display::get_singleton()->get_map().on_board(loc)) {
2688  return false;
2689  }
2690 
2691  if(see_all) {
2692  return true;
2693  }
2694 
2695  if(team.is_enemy(side()) && invisible(loc)) {
2696  return false;
2697  }
2698 
2699  // allied planned moves are also visible under fog. (we assume that fake units on the map are always whiteboard markers)
2700  if(!team.is_enemy(side()) && underlying_id_.is_fake()) {
2701  return true;
2702  }
2703 
2704  // when the whiteboard planned unit map is applied, it uses moved the _real_ unit so
2705  // underlying_id_.is_fake() will be false and the check above will not apply.
2706  // TODO: improve this check so that is also works for allied planned units but without
2707  // breaking sp campaigns with allies under fog. We probably need an explicit flag
2708  // is_planned_ in unit that is set by the whiteboard.
2709  if(team.side() == side()) {
2710  return true;
2711  }
2712 
2713  if(team.fogged(loc)) {
2714  return false;
2715  }
2716 
2717  return true;
2718 }
2719 
2721 {
2722  if(underlying_id_.value == 0) {
2724  underlying_id_ = id_manager.next_id();
2725  } else {
2726  underlying_id_ = id_manager.next_fake_id();
2727  }
2728  }
2729 
2730  if(id_.empty() /*&& !underlying_id_.is_fake()*/) {
2731  std::stringstream ss;
2732  ss << (type_id().empty() ? "Unit" : type_id()) << "-" << underlying_id_.value;
2733  id_ = ss.str();
2734  }
2735 }
2736 
2737 unit& unit::mark_clone(bool is_temporary)
2738 {
2740  if(is_temporary) {
2741  underlying_id_ = ids.next_fake_id();
2742  } else {
2744  underlying_id_ = ids.next_id();
2745  }
2746  else {
2747  underlying_id_ = ids.next_fake_id();
2748  }
2749  std::string::size_type pos = id_.find_last_of('-');
2750  if(pos != std::string::npos && pos+1 < id_.size()
2751  && id_.find_first_not_of("0123456789", pos+1) == std::string::npos) {
2752  // this appears to be a duplicate of a generic unit, so give it a new id
2753  WRN_UT << "assigning new id to clone of generic unit " << id_;
2754  id_.clear();
2755  set_underlying_id(ids);
2756  }
2757  }
2758  return *this;
2759 }
2760 
2761 
2763  : u_(const_cast<unit&>(u))
2764  , moves_(u.movement_left(true))
2765 {
2766  if(operate) {
2768  }
2769 }
2770 
2772 {
2773  assert(resources::gameboard);
2774  try {
2775  if(!resources::gameboard->units().has_unit(&u_)) {
2776  /*
2777  * It might be valid that the unit is not in the unit map.
2778  * It might also mean a no longer valid unit will be assigned to.
2779  */
2780  DBG_UT << "The unit to be removed is not in the unit map.";
2781  }
2782 
2784  } catch(...) {
2785  DBG_UT << "Caught exception when destroying unit_movement_resetter: " << utils::get_unknown_exception_type();
2786  }
2787 }
2788 
2789 std::string unit::TC_image_mods() const
2790 {
2791  return formatter() << "~RC(" << flag_rgb() << ">" << team::get_side_color_id(side()) << ")";
2792 }
2793 
2794 std::string unit::image_mods() const
2795 {
2796  if(!image_mods_.empty()) {
2797  return formatter() << "~" << image_mods_ << TC_image_mods();
2798  }
2799 
2800  return TC_image_mods();
2801 }
2802 
2803 // Called by the Lua API after resetting an attack pointer.
2805 {
2807  auto iter = std::find(attacks_.begin(), attacks_.end(), atk);
2808  if(iter == attacks_.end()) {
2809  return false;
2810  }
2811  attacks_.erase(iter);
2812  return true;
2813 }
2814 
2816 {
2817  if(attacks_left_ == max_attacks_) {
2818  //TODO: add state_not_attacked
2819  }
2820 
2821  set_attacks(0);
2822 }
2823 
2825 {
2826  if(movement_left() == total_movement()) {
2827  set_state(STATE_NOT_MOVED,true);
2828  }
2829 
2830  set_movement(0, true);
2831 }
2832 
2833 void unit::set_hidden(bool state) const
2834 {
2835 // appearance_changed_ = true;
2836  hidden_ = state;
2837  if(!state) {
2838  return;
2839  }
2840 
2841  // TODO: this should really hide the halo, not destroy it
2842  // We need to get rid of haloes immediately to avoid display glitches
2843  anim_comp_->clear_haloes();
2844 }
2845 
2846 void unit::set_image_halo(const std::string& halo)
2847 {
2848  appearance_changed_ = true;
2849  anim_comp_->clear_haloes();
2850  halo_ = halo;
2851 }
2852 
2854 {
2855  if(upkeep.empty()) {
2856  return;
2857  }
2858 
2859  try {
2860  upkeep_ = upkeep.apply_visitor(upkeep_parser_visitor{});
2861  } catch(std::invalid_argument& e) {
2862  WRN_UT << "Found invalid upkeep=\"" << e.what() << "\" in a unit";
2863  upkeep_ = upkeep_full{};
2864  }
2865 }
2866 
2868 {
2869  upkeep = utils::visit(upkeep_type_visitor{}, upkeep_);
2870 }
2871 
2873 {
2874  changed_attributes_.reset();
2875  for(const auto& a_ptr : attacks_) {
2876  a_ptr->set_changed(false);
2877  }
2878 }
2879 
2880 std::vector<t_string> unit::unit_special_notes() const {
2882 }
2883 
2884 // Filters unimportant stats from the unit config and returns a checksum of
2885 // the remaining config.
2887 {
2888  config unit_config;
2889  config wcfg;
2890  u.write(unit_config);
2891 
2892  static const std::set<std::string_view> main_keys {
2893  "advances_to",
2894  "alignment",
2895  "cost",
2896  "experience",
2897  "gender",
2898  "hitpoints",
2899  "ignore_race_traits",
2900  "ignore_global_traits",
2901  "level",
2902  "recall_cost",
2903  "max_attacks",
2904  "max_experience",
2905  "max_hitpoints",
2906  "max_moves",
2907  "movement",
2908  "movement_type",
2909  "race",
2910  "random_traits",
2911  "resting",
2912  "undead_variation",
2913  "upkeep",
2914  "zoc"
2915  };
2916 
2917  for(const std::string_view& main_key : main_keys) {
2918  wcfg[main_key] = unit_config[main_key];
2919  }
2920 
2921  static const std::set<std::string_view> attack_keys {
2922  "name",
2923  "type",
2924  "range",
2925  "damage",
2926  "number"
2927  };
2928 
2929  for(const config& att : unit_config.child_range("attack")) {
2930  config& child = wcfg.add_child("attack");
2931 
2932  for(const std::string_view& attack_key : attack_keys) {
2933  child[attack_key] = att[attack_key];
2934  }
2935 
2936  for(const config& spec : att.child_range("specials")) {
2937  config& child_spec = child.add_child("specials", spec);
2938 
2939  child_spec.recursive_clear_value("description");
2941  child_spec.recursive_clear_value("description_inactive");
2942  child_spec.recursive_clear_value("name");
2943  child_spec.recursive_clear_value("name_inactive");
2944  }
2945  }
2946  }
2947 
2948  for(const config& abi : unit_config.child_range("abilities")) {
2949  config& child = wcfg.add_child("abilities", abi);
2950 
2951  child.recursive_clear_value("description");
2952  child.recursive_clear_value("description_inactive");
2953  child.recursive_clear_value("name");
2954  child.recursive_clear_value("name_inactive");
2955  }
2956 
2957  for(const config& trait : unit_config.child_range("trait")) {
2958  config& child = wcfg.add_child("trait", trait);
2959 
2960  child.recursive_clear_value("description");
2961  child.recursive_clear_value("male_name");
2962  child.recursive_clear_value("female_name");
2963  child.recursive_clear_value("name");
2964  }
2965 
2966  static const std::set<std::string_view> child_keys {
2967  "advance_from",
2968  "defense",
2969  "movement_costs",
2970  "vision_costs",
2971  "jamming_costs",
2972  "resistance"
2973  };
2974 
2975  for(const std::string_view& child_key : child_keys) {
2976  for(const config& c : unit_config.child_range(child_key)) {
2977  wcfg.add_child(child_key, c);
2978  }
2979  }
2980 
2981  DBG_UT << wcfg;
2982 
2983  return wcfg.hash();
2984 }
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:92
n_unit::id_manager & unit_id_manager()
Definition: game_board.hpp:74
static game_config_view wrap(const config &cfg)
@ INITIAL
creating intitial [unit]s, executing toplevel [lua] etc.
Definition: game_data.hpp:73
void add_events(const config::const_child_itors &cfgs, game_lua_kernel &lk, const std::string &type=std::string())
Definition: manager.cpp:153
std::string apply_effect(const std::string &name, unit &u, const config &cfg, bool need_apply)
void write(config &cfg, bool include_notes) const
Writes the movement type data to the provided config.
Definition: movetype.cpp:902
static const std::set< std::string > effects
The set of applicable effects for movement types.
Definition: movetype.hpp:340
void merge(const config &new_cfg, bool overwrite=true)
Merges the given config over the existing data, the config should have zero or more children named "m...
Definition: movetype.cpp:858
int defense_modifier(const t_translation::terrain_code &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:288
int resistance_against(const std::string &damage_type) const
Returns the vulnerability to the indicated damage type (higher means takes more damage).
Definition: movetype.hpp:292
static id_manager & global_instance()
Definition: id.hpp:61
unit_id next_fake_id()
Definition: id.cpp:35
unit_id next_id()
returns id for unit that is created
Definition: id.cpp:28
std::set< std::string > & encountered_units()
static prefs & get()
static rng & default_instance()
Definition: random.cpp:73
int get_random_int(int min, int max)
Definition: random.hpp:51
static bool is_synced()
bool empty() const
Definition: tstring.hpp: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:36
static const unit_race null_race
Dummy race used when a race is not yet known.
Definition: race.hpp:70
std::string generate_name(GENDER gender) const
Definition: race.cpp:112
@ NUM_GENDERS
Definition: race.hpp:28
@ FEMALE
Definition: race.hpp:28
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp: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:2872
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:1961
int movement_
Definition: unit.hpp:1984
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:2022
int unit_value_
Definition: unit.hpp:2026
int attacks_left_
Definition: unit.hpp:1995
bool generate_name_
Definition: unit.hpp:2054
movetype movement_type_
Definition: unit.hpp:1989
config variables_
Definition: unit.hpp:2005
bool unrenamable_
Definition: unit.hpp:1976
int experience_
Definition: unit.hpp:1963
int vision_
Definition: unit.hpp:1986
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:1958
t_string type_name_
The displayed name of this unit type.
Definition: unit.hpp:1949
map_location::DIRECTION facing_
Definition: unit.hpp:2019
unit_movement_resetter(const unit_movement_resetter &)=delete
bool random_traits_
Definition: unit.hpp:2053
void write(config &cfg, bool write_all=true) const
Serializes the current unit metadata values.
Definition: unit.cpp:1596
std::bitset< UA_COUNT > changed_attributes_
Definition: unit.hpp:2063
std::string small_profile_
Definition: unit.hpp:2059
void write_upkeep(config::attribute_value &upkeep) const
Definition: unit.cpp:2867
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:1954
std::vector< t_string > special_notes_
Definition: unit.hpp:2047
bool canrecruit_
Definition: unit.hpp:1969
std::string image_mods_
Definition: unit.hpp:1974
double hp_bar_scaling_
Definition: unit.hpp:2039
std::string flag_rgb_
Definition: unit.hpp:1973
static std::map< std::string, state_t > known_boolean_state_names_
Definition: unit.hpp:2003
@ 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:2006
bool hidden_
Definition: unit.hpp:2038
bool is_healthy_
Definition: unit.hpp:2029
bool is_fearless_
Definition: unit.hpp:2029
config abilities_
Definition: unit.hpp:2042
utils::optional< std::string > ellipse_
Definition: unit.hpp:2051
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:1946
std::bitset< num_bool_states > known_boolean_states_
Definition: unit.hpp:2002
utils::optional< std::string > halo_
Definition: unit.hpp:2050
int side_
Definition: unit.hpp:1978
const unit_race * race_
Never nullptr, but may point to the null race.
Definition: unit.hpp:1952
bool appearance_changed_
Definition: unit.hpp:2062
double xp_bar_scaling_
Definition: unit.hpp:2039
unit_alignments::type alignment_
Definition: unit.hpp:1971
bool invisible(const map_location &loc, bool see_all=true) const
Definition: unit.cpp:2636
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2679
n_unit::unit_id underlying_id_
Definition: unit.hpp:1956
std::string variation_
Definition: unit.hpp:1959
unit & mark_clone(bool is_temporary)
Mark this unit as clone so it can be inserted to unit_map.
Definition: unit.cpp:2737
config filter_recall_
Definition: unit.hpp:2007
int max_experience_
Definition: unit.hpp:1964
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:1980
t_string description_
Definition: unit.hpp:2046
int level_
Definition: unit.hpp:1966
std::string role_
Definition: unit.hpp:2013
std::map< map_location, bool > invisibility_cache_
Hold the visibility status cache for a unit, when not uncovered.
Definition: unit.hpp:2072
bool end_turn_
Definition: unit.hpp:1992
std::vector< std::string > advances_to_
Definition: unit.hpp:1943
std::unique_ptr< unit_animation_component > anim_comp_
Definition: unit.hpp:2036
int recall_cost_
Definition: unit.hpp:1968
static std::string type()
Definition: unit.hpp:1173
attack_list attacks_
Definition: unit.hpp:2014
void remove_ability_by_id(const std::string &ability)
Removes a unit's abilities with a specific ID.
Definition: unit.cpp:1432
utils::optional< std::string > usage_
Definition: unit.hpp:2049
map_location loc_
Definition: unit.hpp:1941
std::vector< std::string > overlays_
Definition: unit.hpp:2011
config modifications_
Definition: unit.hpp:2041
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:1991
const config & abilities() const
Definition: unit.hpp:1800
void parse_upkeep(const config::attribute_value &upkeep)
Definition: unit.cpp:2853
std::vector< std::string > recruit_list_
Definition: unit.hpp:1970
std::vector< config > advancements_
Definition: unit.hpp:2044
utils::string_map modification_descriptions_
Definition: unit.hpp:2031
unit_checksum_version
Optional parameter for get_checksum to use the algorithm of an older version of Wesnoth,...
Definition: unit.hpp:2111
std::vector< std::string > trait_nonhidden_ids_
Definition: unit.hpp:2024
upkeep_t upkeep_
Definition: unit.hpp:2056
std::set< std::string > states_
Definition: unit.hpp:1998
std::string profile_
Definition: unit.hpp:2058
int jamming_
Definition: unit.hpp:1987
map_location goto_
Definition: unit.hpp:2027
t_string name_
Definition: unit.hpp:1955
int max_attacks_
Definition: unit.hpp:1996
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:1962
std::unique_ptr< unit_formula_manager > formula_man_
Definition: unit.hpp:1982
int max_movement_
Definition: unit.hpp:1985
bool resting_
Definition: unit.hpp:1993
std::vector< t_string > trait_descriptions_
Definition: unit.hpp:2023
bool emit_zoc_
Definition: unit.hpp:2009
@ version_1_16_or_older
Included some of the flavortext from weapon specials.
void set_big_profile(const std::string &value)
Definition: unit.cpp:2017
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:2833
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:2012
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:2720
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:2880
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:1941
std::vector< std::pair< std::string, std::string > > amla_icons() const
Gets the image and description data for modification advancements.
Definition: unit.cpp:1924
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:2006
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:1884
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:2815
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:1796
bool resistance_filter_matches(const config &cfg, const std::string &damage_name, int res) const
Definition: unit.cpp:1811
bool remove_attack(attack_ptr atk)
Remove an attack from the unit.
Definition: unit.cpp:2804
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:1835
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:1860
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:2789
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:2794
std::string default_anim_image() const
The default image to use for animation frames with no defined image.
Definition: unit.cpp:2618
const std::vector< std::string > & overlays() const
Get the unit's overlay images.
Definition: unit.hpp:1671
std::string absolute_image() const
The name of the file to game_display (used in menus).
Definition: unit.cpp:2613
void set_image_ellipse(const std::string &ellipse)
Set the unit's ellipse image.
Definition: unit.hpp:1643
void set_image_halo(const std::string &halo)
Set the unit's halo image.
Definition: unit.cpp:2846
void apply_builtin_effect(std::string type, const config &effect)
Apply a builtin effect to the unit.
Definition: unit.cpp:2115
void add_modification(const std::string &type, const config &modification, bool no_add=false)
Add a new modification to the unit.
Definition: unit.cpp:2496
static const std::set< std::string > builtin_effects
Definition: unit.hpp:1581
std::string describe_builtin_effect(std::string type, const config &effect)
Construct a string describing a built-in effect.
Definition: unit.cpp:2050
void apply_modifications()
Re-apply all saved modifications.
Definition: unit.cpp:2623
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:2024
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1398
void set_facing(map_location::DIRECTION dir) const
The this unit's facing.
Definition: unit.cpp:1761
const movetype & movement_type() const
Get the unit's movement type.
Definition: unit.hpp:1471
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:1312
void set_emit_zoc(bool val)
Sets the raw zone-of-control flag.
Definition: unit.hpp:1391
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1323
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1307
void remove_movement_ai()
Sets the unit to have no moves left for this turn.
Definition: unit.cpp:2824
void set_interrupted_move(const map_location &interrupted_move)
Set the target location of the unit's interrupted move.
Definition: unit.hpp:1465
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:1770
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:2599
void generate_traits(bool must_have_only=false)
Applies mandatory traits (e.g.
Definition: unit.cpp:758
void set_loyal(bool loyal)
Definition: unit.cpp:1785
bool loyal() const
Gets whether this unit is loyal - ie, it costs no upkeep.
Definition: unit.cpp:1780
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:277
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:41
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:411
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 int_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, utils::optional< int > def=utils::nullopt)
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 double_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, utils::optional< double > def=utils::nullopt)
Checks whether the filter matches the value of cfg[attribute].
bool int_matches_if_present_or_negative(const config &filter, const config &cfg, const std::string &attribute, const std::string &opposite, utils::optional< int > def=utils::nullopt)
Supports filters using "add" and "sub" attributes, for example a filter add=1 matching a cfg containi...
bool string_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, const std::string &def)
bool bool_or_empty(const config &filter, const config &cfg, const std::string &attribute)
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 utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
Visitor helper struct to fetch the upkeep type flag if applicable, or the the value otherwise.
Definition: unit.hpp: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:1851
std::string get_checksum(const unit &u, backwards_compatibility::unit_checksum_version version)
Gets a checksum for a unit.
Definition: unit.cpp:2886
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