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