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