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