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