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