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