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