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