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