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