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