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