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