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