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