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