The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
abilities.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2006 - 2025
3 by Dominic Bolin <dominic.bolin@exong.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 * Manage unit-abilities, like heal, cure, and weapon_specials.
19 */
20
21#include "deprecation.hpp"
22#include "display.hpp"
23#include "display_context.hpp"
24#include "filter_context.hpp"
27#include "formula/formula.hpp"
30#include "game_board.hpp"
31#include "game_version.hpp" // for version_info
32#include "gettext.hpp"
33#include "language.hpp"
34#include "lexical_cast.hpp"
35#include "log.hpp"
36#include "map/map.hpp"
37#include "preferences/preferences.hpp" // for prefs::get_show_deprecation
38#include "resources.hpp"
41#include "team.hpp"
42#include "terrain/filter.hpp"
43#include "units/types.hpp"
44#include "units/abilities.hpp"
46#include "units/filter.hpp"
47#include "units/map.hpp"
49#include "units/unit.hpp"
50#include "utils/general.hpp"
51
52#include <utility>
53
54static lg::log_domain log_engine("engine");
55#define ERR_NG LOG_STREAM(err, log_engine)
56
57static lg::log_domain log_wml("wml");
58#define ERR_WML LOG_STREAM(err, log_wml)
59#define DEBUG_WML LOG_STREAM(debug, log_wml)
60
61namespace
62{
63 using namespace std::string_literals;
64 const std::array numeric_keys{
65 "value"s, "add"s, "sub"s, "multiply"s, "divide"s, "max_value"s, "min_value"s
66 };
67}
68
69
70/*
71 *
72 * [abilities]
73 * ...
74 *
75 * [heals]
76 * value=4
77 * max_value=8
78 * cumulative=no
79 * affect_allies=yes
80 * name= _ "heals"
81 * female_name= _ "female^heals"
82 * name_inactive=null
83 * female_name_inactive=null
84 * description= _ "Heals:
85Allows the unit to heal adjacent friendly units at the beginning of each turn.
86
87A unit cared for by a healer may heal up to 4 HP per turn.
88A poisoned unit cannot be cured of its poison by a healer, and must seek the care of a village or a unit that can cure."
89 * description_inactive=null
90 *
91 * affect_self=yes
92 * [filter] // SUF
93 * ...
94 * [/filter]
95 * [filter_self] // SUF
96 * ...
97 * [/filter_self]
98 * [filter_adjacent] // SUF
99 * adjacent=n,ne,nw
100 * ...
101 * [/filter_adjacent]
102 * [filter_adjacent_location]
103 * adjacent=n,ne,nw
104 * ...
105 * [/filter_adjacent]
106 * [affect_adjacent]
107 * adjacent=n,ne,nw
108 * [filter] // SUF
109 * ...
110 * [/filter]
111 * [/affect_adjacent]
112 * [affect_adjacent]
113 * adjacent=s,se,sw
114 * [filter] // SUF
115 * ...
116 * [/filter]
117 * [/affect_adjacent]
118 *
119 * [/heals]
120 *
121 * ...
122 * [/abilities]
123 *
124 */
125
126unit_ability_t::unit_ability_t(std::string tag, config cfg, bool inside_attack)
127 : tag_(std::move(tag))
128 , id_(cfg["id"].str())
129 , in_specials_tag_(inside_attack)
130 , active_on_(active_on_t::both)
131 , apply_to_(apply_to_t::self)
132 , affects_allies_(affects_allies_t::same_side_only)
133 , affects_self_(true)
134 , affects_enemies_(false)
135 , priority_(cfg["priority"].to_double(0.00))
136 , suppress_special_priority_(-100000.00)
137 , suppress_ability_priority_(-100000.00)
138 , cfg_(std::move(cfg))
139 , currently_checked_(false)
140{
141 do_compat_fixes(cfg_, tag_, inside_attack);
142
143 if (tag_ != "resistance" && tag_ != "leadership") {
144 std::string apply_to = cfg_["apply_to"].str();
145 apply_to_ = apply_to == "attacker" ? apply_to_t::attacker :
146 apply_to == "defender" ? apply_to_t::defender :
147 apply_to == "self" ? apply_to_t::self :
148 apply_to == "opponent" ? apply_to_t::opponent :
149 apply_to == "both" ? apply_to_t::both :
151
152 }
153 if (tag_ != "leadership") {
154 std::string active_on = cfg_["active_on"].str();
155 active_on_ = active_on == "defense" ? active_on_t::defense :
156 active_on == "offense" ? active_on_t::offense :
158 }
159 if (!cfg_.has_child("affect_adjacent")) {
160 //optimisation
162 }
163 if (cfg_["affect_allies"].to_bool(false)) {
165 }
166 if (!cfg_["affect_allies"].to_bool(true)) {
168 }
169 affects_self_ = cfg_["affect_self"].to_bool(true);
170 affects_enemies_ = cfg_["affect_enemies"].to_bool(false);
171
172 if(auto overwrite_specials = cfg_.optional_child("overwrite_specials")) {
173 suppress_special_priority_ = overwrite_specials["priority"].to_double(0.00);
174 }
175 else if(cfg_["overwrite_specials"] == "one_side" || cfg_["overwrite_specials"] == "both_sides") {
176 if(auto overwrite = cfg_.optional_child("overwrite")) {
177 suppress_special_priority_ = overwrite["priority"].to_double(0.00);
178 } else {
180 }
181 }
182 if(auto overwrite_abilities = cfg_.optional_child("overwrite_abilities")) {
183 suppress_ability_priority_ = overwrite_abilities["priority"].to_double(0.00);
184 }
185}
186
187void unit_ability_t::do_compat_fixes(config& cfg, const std::string& tag, bool inside_attack)
188{
189 // Some behavior changed without deprecation when moving to using the common implementations of
190 // SUF and SLF instead of ability-specific reimplementations. For filters that, in 1.18 and
191 // earlier, could never match anything, it seems reasonable to treat those edge cases as bugs in
192 // the WML and warn about them, rather than to add bug-for-bug compatibility.
193
194 // The warnings about changed behavior are similar to deprecation warnings, so show them in the
195 // chat log if and only if the player has enabled deprecation messages. Most of the message
196 // isn't translated, but the hint about which preference controls it can use an existing string.
197 auto note_bugfix = [](const std::string& message) {
198 DEBUG_WML << message;
200 lg::log_to_chat() << message << " (“" << _("Show deprecation messages in-game") << "” is on)\n";
201 }
202 };
203
204 // replace deprecated backstab with formula
205 if (!cfg["backstab"].blank()) {
206 deprecated_message("backstab= in weapon specials", DEP_LEVEL::INDEFINITE, "", "Use [filter_opponent] with a formula instead; the code can be found in data/core/macros/ in the WEAPON_SPECIAL_BACKSTAB macro.");
207 }
208 if (cfg["backstab"].to_bool()) {
209 const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
210 config& filter_opponent = cfg.child_or_add("filter_opponent");
211 config& filter_opponent2 = filter_opponent.empty() ? filter_opponent : filter_opponent.add_child("and");
212 filter_opponent2["formula"] = backstab_formula;
213 }
214 cfg.remove_attribute("backstab");
215
216 // replace deprecated filter_adjacent/filter_adjacent_location with formula
217 std::string filter_teacher = inside_attack ? "filter_self" : "filter";
218 if (cfg.has_child("filter_adjacent")) {
219 if (inside_attack) {
220 deprecated_message("[filter_adjacent] in weapon specials in [specials] tags", DEP_LEVEL::FOR_REMOVAL, version_info("1.23"), "Use [filter_self][filter_adjacent] instead.");
221 }
222 else {
223 deprecated_message("[filter_adjacent] in abilities", DEP_LEVEL::FOR_REMOVAL, version_info("1.23"), "Use [filter][filter_adjacent] instead or other unit filter.");
224 }
225 }
226 if (cfg.has_child("filter_adjacent_location")) {
227 if (inside_attack) {
228 deprecated_message("[filter_adjacent_location] in weapon specials in [specials] tags", DEP_LEVEL::FOR_REMOVAL, version_info("1.23"), "Use [filter_self][filter_location][filter_adjacent_location] instead.");
229 }
230 else {
231 deprecated_message("[filter_adjacent_location] in abilities", DEP_LEVEL::FOR_REMOVAL, version_info("1.23"), "Use [filter][filter_location][filter_adjacent_location] instead.");
232 }
233 }
234
235 //These tags are were never supported inside [specials] according to the wiki.
236 for (config& filter_adjacent : cfg.child_range("filter_adjacent")) {
237 // For SUF, omitting adjacent= or count= triggers defaults. Contrary to SLF's behavior, an
238 // empty attribute is treated the same as an omitted one, so this could just use
239 // config::operator[] without checking that the attribute already exists, but one of SUF or
240 // SLF is likely to change behavior to match the other.
241 const auto has_dirs = filter_adjacent.has_attribute("adjacent");
242 const auto has_count = filter_adjacent.has_attribute("count");
243 if (!has_dirs) {
244 std::ostringstream ss;
245 ss << "Filter [" << tag << "][filter_adjacent] changed behavior in 1.19.26. Previously this would never match because adjacent= is omitted.";
246 note_bugfix(ss.str());
247 }
248 if (!has_count) {
249 // It was documented on the Wiki that omitting count meant that every given direction
250 // must match, equivalent to a count that matches the number of directions given.
251 //
252 // However, a bug introduced in 1.13.2 meant filters without count= would never match.
253 //
254 // For 1.20 this simply passes the data through to the common SUF implementation.
255 std::ostringstream ss;
256 ss << "Filter [" << tag << "][filter_adjacent] changed behavior in 1.19.26. Previously this would never match because count= is omitted.";
257 note_bugfix(ss.str());
258 }
259 if (has_dirs && has_count && !in_ranges<int>(map_location::parse_directions(filter_adjacent["adjacent"]).size(), utils::parse_ranges_unsigned(filter_adjacent["count"].str()))) {
260 // In 1.18, all directions had to match, for example count=1 with adjacent=n,s would never match.
261 // This was a bug, it returned false on the first non-match instead of counting.
262 std::ostringstream ss;
263 ss << "Filter [" << tag << "][filter_adjacent] changed behavior in 1.19.26. In 1.18 this combination of adjacent= and count= would never match.";
264 note_bugfix(ss.str());
265 }
266 cfg.child_or_add(filter_teacher).add_child("filter_adjacent", filter_adjacent);
267 }
268 cfg.remove_children("filter_adjacent");
269 for (config& filter_adjacent : cfg.child_range("filter_adjacent_location")) {
270 // For the common implementation of SLF, omitting adjacent or count triggers defaults, but
271 // creating an empty attribute prevents that behavior. Therefore we can't use
272 // config::operator[] without first checking that the attribute already exists.
273 //
274 // The warnings refer to the behavior of [filter_adjacent_location] in 1.18, which means the
275 // now-removed ability-specific implementation rather than the common implementation.
276 const auto has_dirs = filter_adjacent.has_attribute("adjacent");
277 const auto has_count = filter_adjacent.has_attribute("count");
278 if (!has_dirs) {
279 std::ostringstream ss;
280 ss << "Filter [" << tag << "][filter_adjacent_location] changed behavior in 1.19.26. Previously this would never match because adjacent= is omitted.";
281 note_bugfix(ss.str());
282 }
283 if (!has_count) {
284 std::ostringstream ss;
285 ss << "Filter [" << tag << "][filter_adjacent_location] changed behavior in 1.19.26. Previously this would never match because count= is omitted.";
286 note_bugfix(ss.str());
287 }
288 if (has_dirs && has_count && !in_ranges<int>(map_location::parse_directions(filter_adjacent["adjacent"]).size(), utils::parse_ranges_unsigned(filter_adjacent["count"].str()))) {
289 // In 1.18, all directions had to match, for example count=1 with adjacent=n,s would never match
290 std::ostringstream ss;
291 ss << "Filter [" << tag << "][filter_adjacent_location] changed behavior in 1.19.26. In 1.18 this combination of adjacent= and count= would never match.";
292 note_bugfix(ss.str());
293 }
294 cfg.child_or_add(filter_teacher).add_child("filter_location").add_child("filter_adjacent_location", filter_adjacent);
295 }
296 cfg.remove_children("filter_adjacent_location");
297
298 if (tag == "resistance" || tag == "leadership") {
299 if (auto child = cfg.optional_child("filter_second_weapon")) {
300 cfg.add_child("filter_opponent").add_child("filter_weapon", *child);
301 }
302 if (auto child = cfg.optional_child("filter_weapon")) {
303 cfg.add_child("filter_student").add_child("filter_weapon", *child);
304 }
305 cfg.remove_children("filter_second_weapon");
306 cfg.remove_children("filter_weapon");
307 }
308
309 if (tag == "drains" && cfg["value"].empty()) {
310 deprecated_message("the default value of 50 of [drains]value=", DEP_LEVEL::FOR_REMOVAL, version_info("1.21"), "Specify value=50 directly.");
311 cfg["value"] = 50;
312 }
313
314 if(!cfg["overwrite_specials"].blank() || cfg.optional_child("overwrite")) {
315 deprecated_message("overwrite_specials= or [overwrite] in weapon specials", DEP_LEVEL::INDEFINITE, "", "Use [overwrite_specials] instead.");
316 }
317}
318
319
321{
322 return cfg["unique_id"].str(cfg["id"].str());
323}
324
326{
327 return get_help_topic_id(cfg());
328}
329
330
331void unit_ability_t::parse_vector(const config& abilities_cfg, ability_vector& res, bool inside_attack)
332{
333 for (auto item : abilities_cfg.all_children_range()) {
334 res.push_back(unit_ability_t::create(item.key, item.cfg, inside_attack));
335 }
336}
337
338ability_vector unit_ability_t::cfg_to_vector(const config& abilities_cfg, bool inside_attack)
339{
340 ability_vector res;
341 parse_vector(abilities_cfg, res, inside_attack);
342 return res;
343}
344
346{
347 ability_vector res;
348 for (const ability_ptr& p_ab : abs) {
349 if (p_ab->tag() == tag) {
350 res.push_back(p_ab);
351 }
352 }
353 return res;
354}
355
357{
358 ability_vector res;
359 for (const ability_ptr& p_ab : abs) {
360 res.push_back(std::make_shared<unit_ability_t>(*p_ab));
361 }
362 return res;
363}
364
366{
367 config abilities_cfg;
368 for (const auto& item : abilities) {
369 item->write(abilities_cfg);
370 }
371 return abilities_cfg;
372}
373
374
375void unit_ability_t::write(config& abilities_cfg)
376{
377 abilities_cfg.add_child(tag(), cfg());
378}
379
380std::string unit_ability_t::substitute_variables(const std::string& str) const {
381 // TODO add more [specials] keys
382
383 utils::string_map symbols;
384
385 // [plague]type= -> $type
386 if(tag() == "plague") {
387 // Substitute [plague]type= as $type
388 const auto iter = unit_types.types().find(cfg()["type"]);
389
390 // TODO: warn if an invalid type is specified?
391 if (iter == unit_types.types().end()) {
392 return str;
393 }
394
395 const unit_type& type = iter->second;
396 symbols.emplace("type", type.type_name());
397 }
398
399 // weapon specials with value keys, like value, add, sub etc.
400 // i.e., [heals]value= -> $value, [regenerates]add= -> $add etc.
401 for(const auto& vkey : numeric_keys) {
402 if(cfg().has_attribute(vkey)) {
403 if(vkey == "multiply" || vkey == "divide") {
404 const std::string lang_locale = get_language().localename;
405 std::stringstream formatter_str;
406 try {
407 formatter_str.imbue(std::locale{lang_locale});
408 } catch(const std::runtime_error&) {}
409 formatter_str << cfg()[vkey].to_double();
410 symbols.emplace(vkey, formatter_str.str());
411 } else {
412 symbols.emplace(vkey, std::to_string(cfg()[vkey].to_int()));
413 }
414 }
415 }
416
417 return symbols.empty() ? str : utils::interpolate_variables_into_string(str, &symbols);
418}
419
420
421namespace {
422 const config_attribute_value& get_attr_four_fallback(const config& cfg, bool b1, bool b2, std::string_view s_yes_yes, std::string_view s_yes_no, std::string_view s_no_yes, std::string_view s_no_no)
423 {
424 if (b1 && b2) {
425 if (auto* attr = cfg.get(s_yes_yes)) { return *attr; }
426 }
427 if (b1) {
428 if (auto* attr = cfg.get(s_yes_no)) { return *attr; }
429 }
430 if (b2) {
431 if (auto* attr = cfg.get(s_no_yes)) { return *attr; }
432 }
433 return cfg[s_no_no];
434 }
435}
436
437std::string unit_ability_t::get_name(bool is_inactive, unit_race::GENDER gender) const
438{
439 bool is_female = gender == unit_race::FEMALE;
440 std::string res = get_attr_four_fallback(cfg_, is_inactive, is_female, "female_name_inactive", "name_inactive", "female_name", "name").str();
441 return substitute_variables(res);
442}
443
444std::string unit_ability_t::get_description(bool is_inactive, unit_race::GENDER gender) const
445{
446 bool is_female = gender == unit_race::FEMALE;
447 std::string res = get_attr_four_fallback(cfg_, is_inactive, is_female, "female_description_inactive", "description_inactive", "female_description", "description").str();
448 return substitute_variables(res);
449}
450
451bool unit_ability_t::active_on_matches(bool student_is_attacker) const
452{
454 return true;
455 }
456 if (active_on() == unit_ability_t::active_on_t::offense && student_is_attacker) {
457 return true;
458 }
459 if (active_on() == unit_ability_t::active_on_t::defense && !student_is_attacker) {
460 return true;
461 }
462 return false;
463}
464
465
467 : parent()
468{
469 if (!p.currently_checked_) {
470 p.currently_checked_ = true;
471 parent = &p;
472 }
473}
474
476{
477 if (parent) {
478 parent->currently_checked_ = false;
479 }
480}
481
482unit_ability_t::recursion_guard::operator bool() const {
483 return bool(parent);
484}
485
487{
488 if (currently_checked_) {
489 static std::vector<std::tuple<std::string, std::string>> already_shown;
490
491 auto identifier = std::tuple<std::string, std::string>{ u.id(), cfg().debug()};
492 if (!utils::contains(already_shown, identifier)) {
493
494 std::string_view filter_text_view = std::get<1>(identifier);
495 utils::trim(filter_text_view);
496 ERR_NG << "Looped recursion error for unit '" << u.id()
497 << "' while checking ability '" << filter_text_view << "'";
498
499 // Arbitrary limit, just ensuring that having a huge number of specials causing recursion
500 // warnings can't lead to unbounded memory consumption here.
501 if (already_shown.size() > 100) {
502 already_shown.clear();
503 }
504 already_shown.push_back(std::move(identifier));
505 }
506 }
507 return recursion_guard(*this);
508}
509
510
511
512
513namespace {
514
515const unit_map& get_unit_map()
516{
517 // Used if we're in the game, including during the construction of the display_context
519 return resources::gameboard->units();
520 }
521
522 // If we get here, we're in the scenario editor
523 assert(display::get_singleton());
525}
526
527const team& get_team(std::size_t side)
528{
529 // Used if we're in the game, including during the construction of the display_context
531 return resources::gameboard->get_team(side);
532 }
533
534 // If we get here, we're in the scenario editor
535 assert(display::get_singleton());
536 return display::get_singleton()->context().get_team(side);
537}
538
539/**
540 * Common code for the question "some other unit has an ability, can that ability affect this
541 * unit" - it's not the full answer to that question, just a part of it.
542 *
543 * Although this is called while checking which units' "hides" abilities are active, that's only
544 * for the question "is this unit next to an ally that has a 'camoflages adjacent allies' ability";
545 * not the question "is this unit next to an enemy, therefore visible".
546 */
547bool affects_side(const unit_ability_t& ab, std::size_t side, std::size_t other_side)
548{
549 const team& side_team = get_team(side);
550
551 if(side == other_side)
553 if(side_team.is_enemy(other_side))
554 return ab.affects_enemies();
555 else
557}
558
559/**
560 * This function defines in which direction loc is relative to from_loc
561 * either by pointing to the hexagon loc is on or by pointing to the hexagon adjacent to from_loc closest to loc.
562 */
563int find_direction(const map_location& loc, const map_location& from_loc, std::size_t distance)
564{
565 const auto adjacent = get_adjacent_tiles(from_loc);
566 for(std::size_t j = 0; j < adjacent.size(); ++j) {
567 bool adj_or_dist = distance != 1 ? distance_between(adjacent[j], loc) == (distance - 1) : adjacent[j] == loc;
568 if(adj_or_dist) {
569 return j;
570 }
571 }
572 return 0;
573}
574
575/// Helper function, to turn void retuning function into false retuning functions
576/// Calls @a f with arguments @args, but if @f returns void this function returns false.
577template<typename TFunc, typename... TArgs>
578bool default_false(const TFunc& f, const TArgs&... args) {
579 if constexpr (std::is_same_v<decltype(f(args...)), void>) {
580 f(args...);
581 return false;
582 }
583 else {
584 return f(args...);
585 }
586}
587
588struct tag_check
589{
590 const std::string& tag;
591
592 bool operator()(const ability_ptr& p_ab) const { return p_ab->tag() == tag; }
593 size_t get_unit_radius(const unit& u) const { return u.max_ability_radius_type(tag); }
594};
595
596template <typename T>
597auto radius_helper(const unit&u, T const& check, int) -> decltype(check.get_unit_radius(u))
598{
599 return check.get_unit_radius(u);
600}
601
602template <typename T>
603size_t radius_helper(const unit& u, T const&, long)
604{
605 return u.max_ability_radius();
606}
607
608
609template<typename TCheck, typename THandler>
610bool foreach_distant_active_ability(const unit& un, const map_location& loc, TCheck&& quick_check, THandler&& handler)
611{
612 // If the unit does not have abilities that match the criteria, check if adjacent units or elsewhere on the map have active abilities
613 // with the [affect_adjacent] subtag that could affect the unit.
614 const unit_map& units = get_unit_map();
615
616 // Check for each unit present on the map that it corresponds to the criteria
617 // (possession of an ability with [affect_adjacent] via a boolean variable, not incapacitated,
618 // different from the central unit, that the ability is of the right type, detailed verification of each ability),
619 // if so return true.
620 for (const unit& u : units) {
621 if (!u.max_ability_radius() || u.incapacitated() || u.underlying_id() == un.underlying_id()) {
622 continue;
623 }
624 size_t u_ability_radius = radius_helper(u, quick_check, 0);
625 if (!u_ability_radius) {
626 continue;
627 }
628 const map_location& from_loc = u.get_location();
629 std::size_t distance = distance_between(from_loc, loc);
630 if (distance > u_ability_radius) {
631 continue;
632 }
633 int dir = find_direction(loc, from_loc, distance);
634 for (const auto& p_ab : u.abilities()) {
635 if (!quick_check(p_ab)) {
636 continue;
637 }
638 if (un.get_adj_ability_bool(*p_ab, distance, dir, loc, u, from_loc)) {
639 if (default_false(handler, p_ab, u)) {
640 return true;
641 }
642 }
643 }
644 }
645 return false;
646}
647
648template<typename TCheck, typename THandler>
649bool foreach_self_active_ability(const unit& un, map_location loc, const TCheck& quick_check, const THandler& handler)
650{
651 for (const auto& p_ab : un.abilities()) {
652 if (!quick_check(p_ab)) {
653 continue;
654 }
655 if (un.get_self_ability_bool(*p_ab, loc)) {
656 if (default_false(handler, p_ab, un)) {
657 return true;
658 }
659 }
660 }
661 return false;
662}
663
664// enum class loop_type_t { self_only, distant_only, both };
665
666/*
667 * execeutes a given function for each active ability of @a unit, including
668 * abilitied thought by other units
669 * @param un the unit receiving the abilities
670 * @param loc the location we assume the unit to be at.
671 * @param quick_check a quick check that is exceuted before the ability tested
672 * @param handler the function that is called for each acive ability.
673 * if this is a boolean function and returns true the execeution
674 * is aborted, used for "have any active ability"-like checks.
675 * @returns true iff any of the handlers returned true.
676 */
677template<typename TCheck, typename THandler>
678bool foreach_active_ability(const unit& un, map_location loc, const TCheck& quick_check, const THandler& handler, bool skip_adjacent = false)
679{
680 // Check that the unit has an ability of tag_name type which meets the conditions to be active.
681 // If so, return true.
682 if (foreach_self_active_ability(un, loc, quick_check, handler)) {
683 return true;
684 }
685 if (!skip_adjacent && foreach_distant_active_ability(un, loc, quick_check, handler)) {
686 return true;
687 }
688 return false;
689}
690
691auto return_true = [](const auto&...) { return true; };
692
693/*
694 * executes the given handler for each active special/ability affecting an attack during combat.
695 * a simple_check parameter can be as a predicate to filter out the abilities/specials that we
696 * are not interested in, simple_check is executed before checking whether a given ability is active.
697 * the @a skip_adjacent parameter can be set to true if we are not intereted in abilities from
698 * adjacent units, used by the [filter_special] code as an optimisation.
699 * @param context the abilities context we are in, describing attacker defender etc.
700 * @param self the combatant whose weapons are affected by the specials
701 * @param quick_check a predicate describing in whiich abilities we are interested in. (usually checking for tag name)
702 * @handler handler a callback that is executed for each active speical that affects @self in the current context, it takes 3 parameters:
703 * - a const ability_t& describing the ability
704 * - a specials_combatant& student
705 * - a 'source', this is either am attack_type& or a unit&. so handler operator() has to be able to handle both cases
706 * handler can return a bool, if it returns true, the seacrch is aborted and this functions returns true withaout checking mroe abilities.
707 * @skip_adjacent whether we should skip looking into adjacent units effecting the weapon via leadership-like abilities. (used by the [filter_special] as a optimisation)
708 */
709template<typename TCheck, typename THandler>
710bool foreach_active_special(
711 const specials_context_t& context,
713 const TCheck& quick_check,
714 const THandler& handler,
715 bool skip_adjacent = false)
716{
717 auto& other = context.other(self);
718 // "const auto&..." because foreach_active_ability calls this with a unit& argument.
719 auto handler_self = [&](const ability_ptr& p_ab, const auto& source) {
720 return context.is_special_active(self, *p_ab, unit_ability_t::affects_t::SELF) && default_false(handler, p_ab, self, source);
721 };
722 auto handler_other = [&](const ability_ptr& p_ab, const auto& source) {
723 return context.is_special_active(other, *p_ab, unit_ability_t::affects_t::OTHER) && default_false(handler, p_ab, other, source);
724 };
725
726 //search in the attacks [specials]
727 if (self.at) {
728 for (const ability_ptr& p_ab : self.at->specials()) {
729 if (quick_check(p_ab) && handler_self(p_ab, *self.at)) {
730 return true;
731 }
732 }
733 }
734 //search in the opponents attacks [specials]
735 if (other.at) {
736 for (const ability_ptr& p_ab : other.at->specials()) {
737 if (quick_check(p_ab) && handler_other(p_ab, *other.at)) {
738 return true;
739 }
740 }
741 }
742 //search in unit [abilities] including abilities tought via loadship like abilities.
743 if (self.un) {
744 if (foreach_active_ability(*self.un, self.loc, quick_check, handler_self, skip_adjacent)) {
745 return true;
746 }
747 }
748 //search in the opponents [abilities] including abilities tought via loadship like abilities.
749 if (other.un) {
750 if (foreach_active_ability(*other.un, other.loc, quick_check, handler_other, skip_adjacent)) {
751 return true;
752 }
753 }
754 return false;
755}
756
757
758}
759
760bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc) const
761{
762 return foreach_active_ability(*this, loc, tag_check{ tag_name },
763 [&](const ability_ptr&, const unit&) {
764 return true;
765 });
766}
767
768active_ability_list unit::get_abilities(const std::string& tag_name, const map_location& loc) const
769{
770 active_ability_list res(loc_);
771 foreach_active_ability(*this, loc, tag_check{ tag_name },
772 [&](const ability_ptr& p_ab, const unit& u2) {
773 res.emplace_back(p_ab, loc, u2.get_location());
774 });
775 return res;
776}
777
778
779std::vector<std::string> unit::get_ability_id_list() const
780{
781 std::vector<std::string> res;
782
783 for(const auto& p_ab : this->abilities()) {
784 std::string id = p_ab->id();
785 if (!id.empty())
786 res.push_back(std::move(id));
787 }
788 return res;
789}
790
791
792namespace {
793 /**
794 * Adds a quadruple consisting of (in order) id, base name,
795 * male or female name as appropriate for the unit, and description.
796 *
797 * @returns Whether name was resolved and quadruple added.
798 */
799 bool add_ability_tooltip(const unit_ability_t& ab, unit_race::GENDER gender, std::vector<unit_ability_t::tooltip_info>& res, bool active)
800 {
801 auto name = ab.get_name(!active, gender);
802 auto desc = ab.get_description(!active, gender);
803
804 if (name.empty()) {
805 return false;
806 }
807
808 res.AGGREGATE_EMPLACE(
809 name,
810 desc,
812 );
813 return true;
814 }
815}
816
817std::vector<unit_ability_t::tooltip_info> unit::ability_tooltips() const
818{
819 std::vector<unit_ability_t::tooltip_info> res;
820
821 for(const auto& p_ab : abilities())
822 {
823 add_ability_tooltip(*p_ab, gender_, res, true);
824 }
825
826 return res;
827}
828
829std::vector<unit_ability_t::tooltip_info> unit::ability_tooltips(boost::dynamic_bitset<>& active_list, const map_location& loc) const
830{
831 std::vector<unit_ability_t::tooltip_info> res;
832 active_list.clear();
833
834 for(const auto& p_ab : abilities())
835 {
836 bool active = ability_active(*p_ab, loc);
837 if(add_ability_tooltip(*p_ab, gender_, res, active))
838 {
839 active_list.push_back(active);
840 }
841 }
842 return res;
843}
844
845
847{
848 auto filter_lock = ab.guard_against_recursion(*this);
849 if(!filter_lock) {
850 return false;
851 }
852 return ability_active_impl(ab, loc);
853}
854
856{
857 bool illuminates = ab.tag() == "illuminates";
858
859 if(auto afilter = ab.cfg().optional_child("filter")) {
860 if(!unit_filter(vconfig(*afilter)).set_use_flat_tod(illuminates).matches(*this, loc)) {
861 return false;
862 }
863 }
864
865 return true;
866}
867
868bool unit::ability_affects_adjacent(const unit_ability_t& ab, std::size_t dist, int dir, const map_location& loc, const unit& from) const
869{
870 if(!ab.cfg().has_child("affect_adjacent")) {
871 return false;
872 }
873 bool illuminates = ab.tag() == "illuminates";
874
875 assert(dir >=0 && dir <= 5);
876 map_location::direction direction{ dir };
877
878 for (const config &i : ab.cfg().child_range("affect_adjacent"))
879 {
880 if(i["radius"] != "all_map") {
881 int radius = i["radius"].to_int(1);
882 if(radius <= 0) {
883 continue;
884 }
885 if(dist > size_t(radius)) {
886 continue;
887 }
888 }
889 if (i.has_attribute("adjacent")) { //key adjacent defined
890 if(!utils::contains(map_location::parse_directions(i["adjacent"]), direction)) {
891 continue;
892 }
893 }
894 auto filter = i.optional_child("filter");
895 if (!filter || //filter tag given
896 unit_filter(vconfig(*filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
897 return true;
898 }
899 }
900 return false;
901}
902
904{
905 auto filter = ab.cfg().optional_child("filter_self");
906 bool affect_self = ab.affects_self();
907 if (!filter || !affect_self) return affect_self;
908 return unit_filter(vconfig(*filter)).set_use_flat_tod(ab.tag() == "illuminates").matches(*this, loc);
909}
910
911bool unit::has_ability_type(const std::string& ability) const
912{
913 return !abilities(ability).empty();
914}
915
916
917std::vector<std::string> unit::halo_or_icon_abilities(const std::string& image_type) const
918{
919 std::string attr_image = image_type + "_image";
920
921 // i didn't know this syntax existed.
922 struct {
923 bool operator()(const ability_ptr& p_ab) const { return !p_ab->cfg()[attr_image_].empty(); }
924 size_t get_unit_radius(const unit& u) const { return u.max_ability_radius_image(); }
925 std::string& attr_image_;
926 } quick_check { attr_image };
927
928
929 //todoc++23: use std::flat_set
930 std::set<std::string> image_list;
931 for(const auto& p_ab : abilities()){
932 bool is_active = ability_active(*p_ab, loc_);
933 //Add halo/overlay to owner of ability if active and affect_self is true.
934 if( !p_ab->cfg()[attr_image].str().empty() && is_active && ability_affects_self(*p_ab, loc_)){
935 image_list.insert(p_ab->cfg()[attr_image].str());
936 }
937 //Add halo/overlay to owner of ability who affect adjacent only if active.
938 if(!p_ab->cfg()[image_type + "_image_self"].str().empty() && is_active){
939 image_list.insert(p_ab->cfg()[image_type + "_image_self"].str());
940 }
941 }
942
943 foreach_distant_active_ability(*this, loc_, quick_check,
944 [&](const ability_ptr& p_ab, const unit&) {
945 image_list.insert(p_ab->cfg()[attr_image].str());
946 });
947
948 return std::vector(image_list.begin(), image_list.end());
949}
950
952{
953 //TODO: remove this function and make the caller use specials_context_t::add_formula_context directly.
954 if (context_) {
955 context_->add_formula_context(callable);
956 }
957}
958
960{
961 if(const unit_const_ptr & att = attacker.un) {
962 callable.add("attacker", wfl::make_callable<wfl::unit_callable>(*att));
963 }
964 if(const unit_const_ptr & def = defender.un) {
965 callable.add("defender", wfl::make_callable<wfl::unit_callable>(*def));
966 }
967}
968
969namespace {
970
971
972template<typename T, typename TFuncFormula>
973class get_ability_value_visitor
974#ifdef USING_BOOST_VARIANT
975 : public boost::static_visitor<T>
976#endif
977{
978public:
979 // Constructor stores the default value.
980 get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
981
982 T operator()(const utils::monostate&) const { return def_; }
983 T operator()(bool) const { return def_; }
984 T operator()(int i) const { return static_cast<T>(i); }
985 T operator()(unsigned long long u) const { return static_cast<T>(u); }
986 T operator()(double d) const { return static_cast<T>(d); }
987 T operator()(const t_string&) const { return def_; }
988 T operator()(const std::string& s) const
989 {
990 if(s.size() >= 2 && s[0] == '(') {
991 return formula_handler_(s);
992 }
993 return lexical_cast_default<T>(s, def_);
994 }
995
996private:
997 const T def_;
998 const TFuncFormula& formula_handler_;
999};
1000
1001template<typename T, typename TFuncFormula>
1002T get_single_ability_value(const config::attribute_value& v, T def, const active_ability& ability_info, const map_location& receiver_loc, const specials_context_t* ctx, const TFuncFormula& formula_handler)
1003{
1004 return v.apply_visitor(get_ability_value_visitor(def, [&](const std::string& s) {
1005
1006 try {
1007 const unit_map& units = get_unit_map();
1008
1009 auto u_itor = units.find(ability_info.teacher_loc);
1010
1011 if(u_itor == units.end()) {
1012 return def;
1013 }
1014 wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
1015 if(ctx) {
1016 ctx->add_formula_context(callable);
1017 }
1018 if (auto uptr = units.find_unit_ptr(ability_info.student_loc)) {
1019 callable.add("student", wfl::make_callable<wfl::unit_callable>(*uptr));
1020 }
1021 if (auto uptr = units.find_unit_ptr(receiver_loc)) {
1022 callable.add("other", wfl::make_callable<wfl::unit_callable>(*uptr));
1023 }
1024 return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table, true), callable);
1025 } catch(const wfl::formula_error& e) {
1026 lg::log_to_chat() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
1027 ERR_WML << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")";
1028 return def;
1029 }
1030 }));
1031}
1032}
1033
1034template<typename TComp>
1035std::pair<int,map_location> active_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
1036{
1037 if ( cfgs_.empty() ) {
1038 return std::pair(def, map_location());
1039 }
1040 // The returned location is the best non-cumulative one, if any,
1041 // the best absolute cumulative one otherwise.
1042 map_location best_loc;
1043 bool only_cumulative = true;
1044 int abs_max = 0;
1045 int flat = 0;
1046 int stack = 0;
1047 for (const active_ability& p : cfgs_)
1048 {
1049 int value = std::round(get_single_ability_value(p.ability_cfg()[key], static_cast<double>(def), p, loc(), nullptr, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1050 return std::round(formula.evaluate(callable).as_int());
1051 }));
1052
1053 if (p.ability_cfg()["cumulative"].to_bool()) {
1054 stack += value;
1055 if (value < 0) value = -value;
1056 if (only_cumulative && !comp(value, abs_max)) {
1057 abs_max = value;
1058 best_loc = p.teacher_loc;
1059 }
1060 } else if (only_cumulative || comp(flat, value)) {
1061 only_cumulative = false;
1062 flat = value;
1063 best_loc = p.teacher_loc;
1064 }
1065 }
1066 return std::pair(flat + stack, best_loc);
1067}
1068
1069template std::pair<int, map_location> active_ability_list::get_extremum<std::less<int>>(const std::string& key, int def, const std::less<int>& comp) const;
1070template std::pair<int, map_location> active_ability_list::get_extremum<std::greater<int>>(const std::string& key, int def, const std::greater<int>& comp) const;
1071
1072/*
1073 *
1074 * [special]
1075 * [swarm]
1076 * name= _ "swarm"
1077 * name_inactive= _ ""
1078 * description= _ ""
1079 * description_inactive= _ ""
1080 * cumulative=no
1081 * apply_to=self #self,opponent,defender,attacker,both
1082 * #active_on=defense # or offense; omitting this means "both"
1083 *
1084 * swarm_attacks_max=4
1085 * swarm_attacks_min=2
1086 *
1087 * [filter_self] // SUF
1088 * ...
1089 * [/filter_self]
1090 * [filter_opponent] // SUF
1091 * [filter_attacker] // SUF
1092 * [filter_defender] // SUF
1093 * [filter_adjacent] // SAUF
1094 * [filter_adjacent_location] // SAUF + locs
1095 * [/swarm]
1096 * [/special]
1097 *
1098 */
1099
1101 : attacker(std::move(att))
1102 , defender(std::move(def))
1103 , is_for_listing(false)
1104{
1105 if (attacker.at) {
1106 attacker.at->context_ = this;
1107 }
1108 if (defender.at) {
1109 defender.at->context_ = this;
1110 }
1111}
1112
1114{
1115 if (attacker.at) {
1116 attacker.at->context_ = nullptr;
1117 }
1118 if (defender.at) {
1119 defender.at->context_ = nullptr;
1120 }
1121}
1122
1123
1124static bool is_enemy(std::size_t side, std::size_t other_side)
1125{
1126 const team& side_team = get_team(side);
1127 return side_team.is_enemy(other_side);
1128}
1129
1130/**
1131 * Returns a comma-separated string of active names for the specials of *this.
1132 * Empty names are skipped.
1133 *
1134 * Whether or not a special is active depends
1135 * on the current context (see set_specials_context)
1136 */
1138{
1139 auto s_a_o = self_and_other(at);
1140 const auto& [self, other] = s_a_o;
1141
1142 std::vector<std::string> special_names;
1143 std::set<std::string> ability_names;
1144
1145 for(const auto& p_ab : at.specials()) {
1146 const bool active = is_special_active(self, *p_ab, unit_ability_t::affects_t::EITHER);
1147 std::string name = p_ab->get_name(!active);
1148 if(!name.empty()) {
1149 special_names.push_back(active ? std::move(name) : markup::span_color(font::INACTIVE_COLOR, name));
1150 }
1151 }
1152
1153 // FIXME: clean this up...
1154
1155 if (self.un) {
1156 const std::set<std::string>& checking_tags = abilities_list::all_weapon_tags();
1157 auto quick_check = [&](const ability_ptr& p_ab) {
1158 return checking_tags.count(p_ab->tag()) != 0;
1159 };
1160
1161 foreach_active_ability(*self.un, self.loc, quick_check,
1162 [&](const ability_ptr& p_ab, const unit& source) {
1163 if (is_enemy(source.side(), s_a_o.self.un->side())) {
1164 return;
1165 }
1166 if (!is_special_active(s_a_o.self, *p_ab, unit_ability_t::affects_t::SELF)) {
1167 return;
1168 }
1169 const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1170 ability_names.insert(p_ab->substitute_variables(name_affected));
1171 });
1172 }
1173
1174 if(!ability_names.empty()) {
1175 special_names.push_back("\n" + utils::join(ability_names, ", "));
1176 }
1177
1178 return utils::join(special_names, ", ");
1179}
1180
1181std::string specials_context_t::describe_weapon_specials_value(const attack_type& at, const std::set<std::string>& checking_tags) const
1182{
1183 auto s_a_o = self_and_other(at);
1184 const auto& [self, other] = s_a_o;
1185
1186 std::string res;
1187
1188 std::set<std::string> wespon_specials;
1189 std::set<std::string> abilities_self;
1190 std::set<std::string> abilities_allies;
1191 std::set<std::string> abilities_enemies;
1192 std::set<std::string> opponents_abilities;
1193
1194 auto quick_check = [&](const ability_ptr& p_ab) {
1195 return checking_tags.count(p_ab->tag()) != 0;
1196 };
1197
1198 auto add_to_list = [&](const ability_ptr& p_ab, const specials_combatant& student, const auto& source) {
1199 if (&student == &s_a_o.other) {
1200 opponents_abilities.insert(p_ab->substitute_variables(p_ab->cfg()["name"].str()));
1201 } else if constexpr (utils::decayed_is_same<decltype(source), attack_type>) {
1202 wespon_specials.insert(p_ab->substitute_variables(p_ab->cfg()["name"].str()));
1203 } else if (&source == s_a_o.self.un.get()) {
1204 const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1205 abilities_self.insert(p_ab->substitute_variables(name_affected));
1206 } else if (!is_enemy(source.side(), s_a_o.self.un->side())) {
1207 const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1208 abilities_allies.insert(p_ab->substitute_variables(name_affected));
1209 } else {
1210 const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1211 abilities_enemies.insert(p_ab->substitute_variables(name_affected));
1212 }
1213 };
1214
1215 auto add_to_res = [&](std::set<std::string>& to_add, const std::string& category_name) {
1216 to_add.erase("");
1217 if (!to_add.empty()) {
1218 //TODO: markup::span_color(font::TITLE_COLOR) ??
1219 res += (res.empty() ? "\n" : "") + category_name + utils::join(to_add, ", ");
1220 }
1221 };
1222
1223
1224 foreach_active_special(*this, self, quick_check, add_to_list);
1225
1226 add_to_res(wespon_specials, "");
1227 add_to_res(abilities_self, _("Owned: "));
1228 // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1229 add_to_res(abilities_allies, _("Taught: "));
1230 // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1231 add_to_res(abilities_enemies, _("Taught: (by an enemy): "));
1232 add_to_res(opponents_abilities, _("Used by opponent: "));
1233
1234 return res;
1235}
1236
1237
1238namespace { // Helpers for attack_type::special_active()
1239
1240 /**
1241 * Returns whether or not the given special affects the opponent of the unit
1242 * with the special.
1243 * @param ab the ability/special
1244 * @param[in] is_attacker whether or not the unit with the special is the attacker
1245 */
1246 bool special_affects_opponent(const unit_ability_t& ab, bool is_attacker)
1247 {
1248 using apply_to_t = unit_ability_t::apply_to_t;
1249 const auto apply_to = ab.apply_to();
1250 if ( apply_to == apply_to_t::both)
1251 return true;
1252 if ( apply_to == apply_to_t::opponent )
1253 return true;
1254 if ( is_attacker && apply_to == apply_to_t::defender)
1255 return true;
1256 if ( !is_attacker && apply_to == apply_to_t::attacker)
1257 return true;
1258 return false;
1259 }
1260
1261 /**
1262 * Returns whether or not the given special affects the unit with the special.
1263 * @param ab the ability/special
1264 * @param[in] is_attacker whether or not the unit with the special is the attacker
1265 */
1266 bool special_affects_self(const unit_ability_t& ab, bool is_attacker)
1267 {
1268 using apply_to_t = unit_ability_t::apply_to_t;
1269 const auto apply_to = ab.apply_to();
1270 if ( apply_to == apply_to_t::both )
1271 return true;
1272 if ( apply_to == apply_to_t::self)
1273 return true;
1274 if ( is_attacker && apply_to == apply_to_t::attacker)
1275 return true;
1276 if ( !is_attacker && apply_to == apply_to_t::defender)
1277 return true;
1278 return false;
1279 }
1280
1281 static bool buildin_is_immune(const unit_ability_t& ab, const unit_const_ptr& them, map_location their_loc)
1282 {
1283 if (ab.tag() == "drains" && them && them->get_state("undrainable")) {
1284 return true;
1285 }
1286 if (ab.tag() == "plague" && them &&
1287 (them->get_state("unplagueable") ||
1288 resources::gameboard->map().is_village(their_loc))) {
1289 return true;
1290 }
1291 if (ab.tag() == "poison" && them &&
1292 (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
1293 return true;
1294 }
1295 if (ab.tag() == "slow" && them &&
1296 (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
1297 return true;
1298 }
1299 if (ab.tag() == "petrifies" && them &&
1300 them->get_state("unpetrifiable")) {
1301 return true;
1302 }
1303 return false;
1304 }
1305 /**
1306 * Determines if a unit/weapon combination matches the specified child
1307 * (normally a [filter_*] child) of the provided filter.
1308 * @param[in] u A unit to filter.
1309 * @param[in] u2 Another unit to filter.
1310 * @param[in] loc The presumed location of @a unit.
1311 * @param[in] weapon The attack_type to filter.
1312 * @param[in] filter The filter containing the child filter to use.
1313 * @param[in] for_listing
1314 * @param[in] child_tag The tag of the child filter to use.
1315 * @param[in] applies_to_checked Parameter used for don't have infinite recusion for some filter attribute.
1316 */
1317 static bool special_unit_matches(const unit_const_ptr & u,
1318 const unit_const_ptr & u2,
1319 const map_location & loc,
1320 const const_attack_ptr& weapon,
1321 const unit_ability_t& ab,
1322 const bool for_listing,
1323 const std::string & child_tag, bool applies_to_checked)
1324 {
1325 if (for_listing && !loc.valid())
1326 // The special's context was set to ignore this unit, so assume we pass.
1327 // (This is used by reports.cpp to show active specials when the
1328 // opponent is not known. From a player's perspective, the special
1329 // is active, in that it can be used, even though the player might
1330 // need to select an appropriate opponent.)
1331 return true;
1332
1333 const config& filter = ab.cfg();
1334 const config& filter_backstab = filter;
1335
1336 auto filter_child = filter_backstab.optional_child(child_tag);
1337 if ( !filter_child )
1338 // The special does not filter on this unit, so we pass.
1339 return true;
1340
1341 // If the primary unit doesn't exist, there's nothing to match
1342 if (!u) {
1343 return false;
1344 }
1345
1346 unit_filter ufilt{vconfig(*filter_child)};
1347
1348 // If the other unit doesn't exist, try matching without it
1349
1350
1351 auto filter_lock = ab.guard_against_recursion(*u);
1352 if(!filter_lock) {
1353 return false;
1354 }
1355 // Check for a weapon match.
1356 if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1357 std::string check_if_recursion = applies_to_checked ? ab.tag() : "";
1358 if ( !weapon || !weapon->matches_filter(*filter_weapon, check_if_recursion) )
1359 return false;
1360 }
1361
1362 // Passed.
1363 // If the other unit doesn't exist, try matching without it
1364 if (!u2) {
1365 return ufilt.matches(*u, loc);
1366 }
1367 return ufilt.matches(*u, loc, *u2);
1368 }
1369
1370}//anonymous namespace
1371
1372
1373/**
1374 * Returns a vector of names and descriptions for the specials of *this.
1375 * Each std::pair in the vector has first = name and second = description.
1376 *
1377 * This uses either the active or inactive name/description for each special,
1378 * based on the current context (see set_specials_context), provided
1379 * @a active_list is not nullptr. Otherwise specials are assumed active.
1380 * If the appropriate name is empty, the special is skipped.
1381 */
1382std::vector<unit_ability_t::tooltip_info> specials_context_t::special_tooltips(const attack_type& at,
1383 boost::dynamic_bitset<>& active_list) const
1384{
1385 //log_scope("special_tooltips");
1386 auto [self, other] = self_and_other(at);
1387
1388 std::vector<unit_ability_t::tooltip_info> res;
1389 active_list.clear();
1390
1391 for (const auto& p_ab : self.at->specials()) {
1392 bool active = is_special_active(self, *p_ab, unit_ability_t::affects_t::EITHER);
1393 auto name = p_ab->get_name(!active);
1394 auto desc = p_ab->get_description(!active);
1395
1396 if (name.empty()) {
1397 continue;
1398 }
1399
1400 res.AGGREGATE_EMPLACE(
1401 name,
1402 desc,
1403 p_ab->get_help_topic_id()
1404 );
1405
1406 active_list.push_back(active);
1407 }
1408 return res;
1409}
1410
1411namespace {
1412 /**
1413 * Returns whether or not the given special is active for the specified unit disregarding other units,
1414 * based on the current context (see specials_context).
1415 * @param ab the ability/special
1416 */
1417 bool special_tooltip_active(const specials_context_t& context, const specials_context_t::specials_combatant& self, const unit_ability_t& ab)
1418 {
1419 bool is_for_listing = context.is_for_listing;
1420
1421 auto& other = context.other(self);
1422 bool is_attacker = &self == &context.attacker;
1423 //log_scope("special_tooltip_active");
1424
1425 //here 'active_on' and checking of opponent weapon shouldn't implemented
1426 //because other_attack_ don't exist in sidebar display.
1427 //'apply_to' and some filters like [filter_student] are checked for know if
1428 //special must be displayed in sidebar.
1429
1430 //only special who affect self are valid here.
1431 bool whom_is_self = special_affects_self(ab, is_attacker);
1432 if (!whom_is_self)
1433 return false;
1434
1435 //this part of checking is similar to special_active but not the same.
1436 //"filter_opponent" is not checked here, and "filter_attacker/defender" only
1437 //if attacker/defender is self_.
1438 bool applied_both = ab.apply_to() == unit_ability_t::apply_to_t::both;
1439
1440 if (!special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_student", applied_both || whom_is_self))
1441 return false;
1442 bool applied_to_attacker = applied_both || (whom_is_self && is_attacker);
1443 if (is_attacker && !special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_attacker", applied_to_attacker))
1444 return false;
1445 bool applied_to_defender = applied_both || (whom_is_self && !is_attacker);
1446 if (!is_attacker && !special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_defender", applied_to_defender))
1447 return false;
1448
1449 return true;
1450 }
1451
1452}
1453
1454
1455std::vector<unit_ability_t::tooltip_info> specials_context_t::abilities_special_tooltips(const attack_type& at,
1456 boost::dynamic_bitset<>& active_list) const
1457{
1458 auto s_a_o = self_and_other(at);
1459 const auto& [self, other] = s_a_o;
1460
1461 std::vector<unit_ability_t::tooltip_info> res;
1462 active_list.clear();
1463 std::set<std::string> checking_name;
1464 if (!self.un) {
1465 return res;
1466 }
1467 foreach_active_ability(*self.un, self.loc,
1468 [&](const ability_ptr&) {
1469 return true;
1470 },
1471 [&](const ability_ptr& p_ab, const unit&) {
1472 if (special_tooltip_active(*this, s_a_o.self, *p_ab)) {
1473 bool active = is_special_active(s_a_o.self, *p_ab, unit_ability_t::affects_t::SELF);
1474 const std::string name = p_ab->substitute_variables(p_ab->cfg()["name_affected"]);
1475 const std::string desc = p_ab->substitute_variables(p_ab->cfg()["description_affected"]);
1476
1477 if (name.empty() || checking_name.count(name) != 0) {
1478 return;
1479 }
1480 res.AGGREGATE_EMPLACE(name, desc, p_ab->get_help_topic_id());
1481 checking_name.insert(name);
1482 active_list.push_back(active);
1483 }
1484 });
1485 return res;
1486}
1487
1488
1489//The following functions are intended to allow the use in combat of capacities
1490//identical to special weapons and therefore to be able to use them on adjacent
1491//units (abilities of type 'aura') or else on all types of weapons even if the
1492//beneficiary unit does not have a corresponding weapon
1493//(defense against ranged weapons abilities for a unit that only has melee attacks)
1494namespace {
1495 bool overwrite_special_affects(const unit_ability_t& ab)
1496 {
1497 const std::string& apply_to = ab.cfg()["overwrite_specials"];
1498 return (apply_to == "one_side" || apply_to == "both_sides");
1499 }
1500}
1501
1503{
1504 auto ctx = fallback_context();
1505 auto [self, other] = context_->self_and_other(*this);
1506 const map_location& loc = overwritten.student_loc;
1507 if(overwriters.empty()) {
1508 return false;
1509 }
1510
1511 const unit_ability_t& ab = overwritten.ability();
1512 for(const auto& j : overwriters) {
1513 if(j.ability().suppress_special_priority() <= ab.suppress_special_priority()) {
1514 continue;
1515 }
1516
1517 // code for new feature [overwrite_specials].
1518 auto overwrite_specials = j.ability_cfg().optional_child("overwrite_specials");
1519 if(overwrite_specials) {
1520 // the location of the fighters is used to differentiate the specials applied to 'self' from those applied to 'opponent'
1521 // in all cases including 'apply_to=attacker/defender'.
1522 if((*overwrite_specials)["affect"].str("both") == "self" && loc != self.loc) {
1523 continue;
1524 }
1525 if(other.un && (*overwrite_specials)["affect"].str("both") == "opponent" && loc != other.loc) {
1526 continue;
1527 }
1528 // if ab don't match with [filter_special], continue check with next element of overwriters list.
1529 auto filter_abilities_specials = (*overwrite_specials).optional_child("filter_special");
1530 if(filter_abilities_specials && !ab.matches_filter(*filter_abilities_specials)) {
1531 continue;
1532 }
1533 return true;
1534 }
1535
1536 // If neither the tag nor the 'overwrite_specials' attribute are valid, we move directly to the next element in the overwriters list.
1537 if(!overwrite_special_affects(j.ability())) {
1538 continue;
1539 }
1540
1541 //code for old feature 'overwrite_specials' if special don't have new one.
1542
1543 // the location of the fighters is used to differentiate the specials applied to 'self' from those applied to 'opponent'
1544 // in all cases including 'apply_to=attacker/defender'.
1545 if(j.ability_cfg()["overwrite_specials"].str() == "one_side") {
1546 if(j.student_loc == self.loc && loc != self.loc) {
1547 continue;
1548 }
1549 if(other.un && j.student_loc == other.loc && loc != other.loc) {
1550 continue;
1551 }
1552 }
1553
1554 auto overwrite_filter = j.ability_cfg().optional_child("overwrite");
1555 if(overwrite_filter) {
1556 auto filter_abilities_specials = (*overwrite_filter).optional_child("filter_specials");
1557 if(!filter_abilities_specials) {
1558 filter_abilities_specials = (*overwrite_filter).optional_child("experimental_filter_specials");
1559 if(filter_abilities_specials) {
1560 deprecated_message("experimental_filter_specials", DEP_LEVEL::FOR_REMOVAL, {1, 21, 0}, "Use filter_specials instead.");
1561 }
1562 }
1563 if(filter_abilities_specials && !ab.matches_filter(*filter_abilities_specials)) {
1564 continue;
1565 }
1566 }
1567 return true;
1568 }
1569 return false;
1570}
1571
1573{
1574 auto filter_lock = ab.guard_against_recursion(*this);
1575 if(!filter_lock) {
1576 return false;
1577 }
1578 return (ability_active_impl(ab, loc) && ability_affects_self(ab, loc));
1579}
1580
1581bool unit::get_adj_ability_bool(const unit_ability_t& ab, std::size_t dist, int dir, const map_location& loc, const unit& from, const map_location& from_loc) const
1582{
1583 auto filter_lock = ab.guard_against_recursion(from);;
1584 if(!filter_lock) {
1585 return false;
1586 }
1587 return (affects_side(ab, side(), from.side()) && from.ability_active_impl(ab, from_loc) && ability_affects_adjacent(ab, dist, dir, loc, from));
1588}
1589
1590/**
1591 * Returns whether or not @a *this has a special ability with a tag or id equal to
1592 * @a special. the Check is for a special ability
1593 * active in the current context (see set_specials_context), including
1594 * specials obtained from the opponent's attack.
1595 */
1596bool specials_context_t::has_active_special(const attack_type & at, const std::string & tag_name) const
1597{
1598
1599 auto quick_check = [&](const ability_ptr& p_ab) {
1600 return p_ab->tag() == tag_name;
1601 };
1602
1603 auto [self, other] = self_and_other(at);
1604 return foreach_active_special(*this, self, quick_check, return_true);
1605}
1606
1607bool specials_context_t::has_active_special_id(const attack_type& at, const std::string& special_id) const
1608{
1609 //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1610 //these can be detected even in placeholder attacks generated to compensate for the lack of attack in defense against an attacker using a range attack not possessed by the defender.
1611 //It is therefore necessary to check if the range is not empty (proof that the weapon is not a placeholder) to decide if has_weapon_ability can be returned or not.
1612 if (at.range().empty()) {
1613 return false;
1614 }
1615
1616
1617 auto quick_check = [&](const ability_ptr& p_ab) {
1618 return p_ab->id() == special_id;
1619 };
1620
1621 auto [self, other] = self_and_other(at);
1622 return foreach_active_special(*this, self, quick_check, return_true);
1623}
1624
1626{
1627 auto s_a_o = self_and_other(at);
1628 const auto& [self, other] = s_a_o;
1629
1630 const map_location loc = self.un ? self.un->get_location() : self.loc;
1632 const std::set<std::string>& checking_tags = abilities_list::all_weapon_tags();
1633 auto quick_check = [&](const ability_ptr& p_ab) {
1634 return checking_tags.count(p_ab->tag()) != 0;
1635 };
1636
1637 if (self.un) {
1638 foreach_distant_active_ability(*self.un, self.loc, quick_check,
1639 [&](const ability_ptr& p_ab, const unit& u_teacher) {
1640 if (is_special_active(s_a_o.self, *p_ab, unit_ability_t::affects_t::SELF)) {
1641 res.emplace_back(p_ab, s_a_o.self.un->get_location(), u_teacher.get_location());
1642 }
1643 }
1644 );
1645 }
1646 if (other.un) {
1647 foreach_distant_active_ability(*other.un, other.loc, quick_check,
1648 [&](const ability_ptr& p_ab, const unit& u_teacher) {
1649 if (is_special_active(s_a_o.other, *p_ab, unit_ability_t::affects_t::OTHER)) {
1650 res.emplace_back(p_ab, s_a_o.other.un->get_location(), u_teacher.get_location());
1651 }
1652 }
1653 );
1654 }
1655 return res;
1656}
1657
1659{
1660 auto [self, other] = self_and_other(at);
1661 const map_location loc = self.un ? self.un->get_location() : self.loc;
1663
1664 auto quick_check = [&](const ability_ptr& p_ab) {
1665 return p_ab->tag() == tag_name;
1666 };
1667
1668 auto add_to_list = utils::overload {
1669 [&](const ability_ptr& p_ab, const specials_combatant& student, const attack_type&) {
1670 res.emplace_back(p_ab, student.loc, student.loc);
1671 },
1672 [&](const ability_ptr& p_ab, const specials_combatant& student, const unit& source) {
1673 res.emplace_back(p_ab, student.un->get_location(), source.get_location());
1674 }
1675 };
1676
1677
1678 foreach_active_special(*this, self, quick_check, add_to_list);
1679 return res;
1680}
1681
1682active_ability_list specials_context_t::get_abilities_weapons(const std::string& tag_name, const unit& un) const
1683{
1684 auto s_a_o = self_and_other(un);
1685 const auto& [self, other] = s_a_o;
1686 //TODO: fall back to un.get_location() ?
1687 active_ability_list res = un.get_abilities(tag_name, self.loc);
1688
1689 utils::erase_if(res, [&](const active_ability& i) {
1690 //If no weapon is given, assume the ability is active. this is used by ai code.
1691 return !is_special_active(s_a_o.self, i.ability(), unit_ability_t::affects_t::SELF);
1692 });
1693 return res;
1694
1695}
1696
1697
1698//end of emulate weapon special functions.
1699
1700namespace
1701{
1702 bool exclude_ability_attributes(const std::string& tag_name, const config & filter)
1703 {
1704 ///check what filter attributes used can be used in type of ability checked.
1705 bool abilities_check = abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1706 if(filter.has_attribute("active_on") && tag_name != "resistance" && abilities_check)
1707 return false;
1708 if(filter.has_attribute("apply_to") && tag_name != "resistance" && abilities_check)
1709 return false;
1710
1711 if(filter.has_attribute("overwrite_specials") && abilities_list::weapon_math_tags().count(tag_name) == 0)
1712 return false;
1713
1714 bool no_value_weapon_abilities_check = abilities_list::no_weapon_math_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1715 if(filter.has_attribute("cumulative") && no_value_weapon_abilities_check && (tag_name != "swarm" || tag_name != "berserk"))
1716 return false;
1717 if(filter.has_attribute("value") && (no_value_weapon_abilities_check && tag_name != "berserk"))
1718 return false;
1719 if(filter.has_attribute("add") && no_value_weapon_abilities_check)
1720 return false;
1721 if(filter.has_attribute("sub") && no_value_weapon_abilities_check)
1722 return false;
1723 if(filter.has_attribute("multiply") && no_value_weapon_abilities_check)
1724 return false;
1725 if(filter.has_attribute("divide") && no_value_weapon_abilities_check)
1726 return false;
1727 if(filter.has_attribute("priority") && no_value_weapon_abilities_check)
1728 return false;
1729
1730 bool all_engine = abilities_list::no_weapon_math_tags().count(tag_name) != 0 || abilities_list::weapon_math_tags().count(tag_name) != 0 || abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1731 if(filter.has_attribute("replacement_type") && tag_name != "damage_type" && all_engine)
1732 return false;
1733 if(filter.has_attribute("alternative_type") && tag_name != "damage_type" && all_engine)
1734 return false;
1735 if(filter.has_attribute("type") && tag_name != "plague" && all_engine)
1736 return false;
1737
1738 return true;
1739 }
1740
1741 bool matches_ability_filter(const config & cfg, const std::string& tag_name, const config & filter)
1742 {
1743 using namespace utils::config_filters;
1744
1745 //check if attributes have right to be in type of ability checked
1746 if(!exclude_ability_attributes(tag_name, filter))
1747 return false;
1748
1749 // tag_name and id are equivalent of ability ability_type and ability_id/type_active filters
1750 //can be extent to special_id/type_active. If tag_name or id matche if present in list.
1751 const std::vector<std::string> filter_type = utils::split(filter["tag_name"]);
1752 if(!filter_type.empty() && !utils::contains(filter_type, tag_name))
1753 return false;
1754
1755 if(!string_matches_if_present(filter, cfg, "id", ""))
1756 return false;
1757
1758 //when affect_adjacent=yes detect presence of [affect_adjacent] in abilities, if no
1759 //then matches when tag not present.
1760 if(!filter["affect_adjacent"].empty()){
1761 bool adjacent = cfg.has_child("affect_adjacent");
1762 if(filter["affect_adjacent"].to_bool() != adjacent){
1763 return false;
1764 }
1765 }
1766
1767 //these attributs below filter attribute used in all engine abilities.
1768 //matches if filter attribute have same boolean value what attribute
1769 if(!bool_matches_if_present(filter, cfg, "affect_self", true))
1770 return false;
1771
1772 //here if value of affect_allies but also his presence who is checked because
1773 //when affect_allies not specified, ability affect unit of same side what owner only.
1774 if(!bool_or_empty(filter, cfg, "affect_allies"))
1775 return false;
1776
1777 if(!bool_matches_if_present(filter, cfg, "affect_enemies", false))
1778 return false;
1779
1780
1781 //cumulative, overwrite_specials and active_on check attributes used in all abilities
1782 //who return a numerical value.
1783 if(!bool_matches_if_present(filter, cfg, "cumulative", false))
1784 return false;
1785
1786 if(!cfg["overwrite_specials"].blank() || cfg.optional_child("overwrite")) {
1787 deprecated_message("overwrite_specials= or [overwrite] in weapon specials", DEP_LEVEL::INDEFINITE, "", "Use [overwrite_specials] instead.");
1788 }
1789 if(!string_matches_if_present(filter, cfg, "overwrite_specials", "none"))
1790 return false;
1791
1792 if(!string_matches_if_present(filter, cfg, "active_on", "both"))
1793 return false;
1794
1795 if(abilities_list::weapon_math_tags().count(tag_name) != 0 || abilities_list::ability_value_tags().count(tag_name) != 0) {
1796 if(!double_matches_if_present(filter, cfg, "priority", 0.00)) {
1797 return false;
1798 }
1799 } else {
1800 if(!double_matches_if_present(filter, cfg, "priority")) {
1801 return false;
1802 }
1803 }
1804
1805 //value, add, sub multiply and divide check values of attribute used in engines abilities(default value of 'value' can be checked when not specified)
1806 //who return numericals value but can also check in non-engine abilities(in last case if 'value' not specified none value can matches)
1807 if(!filter["value"].empty()){
1808 if(tag_name == "drains"){
1809 if(!int_matches_if_present(filter, cfg, "value", 50)){
1810 return false;
1811 }
1812 } else if(tag_name == "berserk"){
1813 if(!int_matches_if_present(filter, cfg, "value", 1)){
1814 return false;
1815 }
1816 } else if(tag_name == "heal_on_hit" || tag_name == "heals" || tag_name == "regenerate" || tag_name == "leadership"){
1817 if(!int_matches_if_present(filter, cfg, "value" , 0)){
1818 return false;
1819 }
1820 } else {
1821 if(!int_matches_if_present(filter, cfg, "value")){
1822 return false;
1823 }
1824 }
1825 }
1826
1827 if(!int_matches_if_present_or_negative(filter, cfg, "add", "sub"))
1828 return false;
1829
1830 if(!int_matches_if_present_or_negative(filter, cfg, "sub", "add"))
1831 return false;
1832
1833 if(!double_matches_if_present(filter, cfg, "multiply"))
1834 return false;
1835
1836 if(!double_matches_if_present(filter, cfg, "divide"))
1837 return false;
1838
1839
1840 //apply_to is a special case, in resistance ability, it check a list of damage type used by [resistance]
1841 //but in weapon specials, check identity of unit affected by special(self, opponent tc...)
1842 if(tag_name == "resistance"){
1843 if(!set_includes_if_present(filter, cfg, "apply_to")){
1844 return false;
1845 }
1846 } else {
1847 if(!string_matches_if_present(filter, cfg, "apply_to", "self")){
1848 return false;
1849 }
1850 }
1851
1852 //the three attribute below are used for check in specifics abilitie:
1853 //replacement_type and alternative_type are present in [damage_type] only for engine abilities
1854 //and type for [plague], but if someone want use this in non-engine abilities, these attribute can be checked outside type mentioned.
1855 //
1856
1857 //for damage_type only(in engine cases)
1858 if(!string_matches_if_present(filter, cfg, "replacement_type", ""))
1859 return false;
1860
1861 if(!string_matches_if_present(filter, cfg, "alternative_type", ""))
1862 return false;
1863
1864 //for plague only(in engine cases)
1865 if(!string_matches_if_present(filter, cfg, "type", ""))
1866 return false;
1867
1868 //the wml_filter is used in cases where the attribute we are looking for is not
1869 //previously listed or to check the contents of the sub_tags ([filter_adjacent],[filter_self],[filter_opponent] etc.
1870 //If the checked set does not exactly match the content of the capability, the function returns a false response.
1871 auto fwml = filter.optional_child("filter_wml");
1872 if (fwml){
1873 if(!cfg.matches(*fwml)){
1874 return false;
1875 }
1876 }
1877
1878 // Passed all tests.
1879 return true;
1880 }
1881
1882 static bool common_matches_filter(const config & cfg, const std::string& tag_name, const config & filter)
1883 {
1884 // Handle the basic filter.
1885 bool matches = matches_ability_filter(cfg, tag_name, filter);
1886
1887 // Handle [and], [or], and [not] with in-order precedence
1888 for(const auto [key, condition_cfg] : filter.all_children_view() )
1889 {
1890 // Handle [and]
1891 if ( key == "and" )
1892 matches = matches && common_matches_filter(cfg, tag_name, condition_cfg);
1893
1894 // Handle [or]
1895 else if ( key == "or" )
1896 matches = matches || common_matches_filter(cfg, tag_name, condition_cfg);
1897
1898 // Handle [not]
1899 else if ( key == "not" )
1900 matches = matches && !common_matches_filter(cfg, tag_name, condition_cfg);
1901 }
1902
1903 return matches;
1904 }
1905}
1906
1907bool unit_ability_t::matches_filter(const config & filter) const
1908{
1909 return common_matches_filter(cfg(), tag(), filter);
1910}
1911
1913{
1914 if(at.range().empty()){
1915 return false;
1916 }
1917
1918 bool skip_adjacent = !filter["affect_adjacent"].to_bool(true);
1919
1920 auto quick_check = [&](const ability_ptr& p_ab) {
1921 return p_ab->matches_filter(filter);
1922 };
1923
1924 auto [self, other] = self_and_other(at);
1925 return foreach_active_special(*this, self, quick_check, return_true, skip_adjacent);
1926}
1927
1929{
1930 bool skip_adjacent = !filter["affect_adjacent"].to_bool(true);
1931
1932 auto quick_check = [&](const ability_ptr& p_ab) {
1933 return p_ab->matches_filter(filter);
1934 };
1935
1936 return foreach_active_ability(un, loc, quick_check, return_true, skip_adjacent);
1937}
1938
1940{
1941 auto quick_check = [&](const ability_ptr& p_ab) {
1942 return p_ab->id() == id;
1943 };
1944
1945 return foreach_active_ability(un, loc, quick_check, return_true);
1946}
1947
1948namespace {
1949 class temporary_facing
1950 {
1951 map_location::direction save_dir_;
1952 unit_const_ptr u_;
1953 public:
1954 temporary_facing(const unit_const_ptr& u, map_location::direction new_dir)
1955 : save_dir_(u ? u->facing() : map_location::direction::indeterminate)
1956 , u_(u)
1957 {
1958 if (u_) {
1959 u_->set_facing(new_dir);
1960 }
1961 }
1962 ~temporary_facing()
1963 {
1964 if (u_) {
1965 u_->set_facing(save_dir_);
1966 }
1967 }
1968 };
1969}
1970/**
1971 * Returns whether or not the given special is active for the specified unit,
1972 * based on the current context (see set_specials_context).
1973 * @param self this combatant
1974 * @param ab the ability
1975 * @param whom specifies which combatant we care about
1976 */
1978{
1979 bool is_attacker = &self == &attacker;
1980 const auto& other = this->other(self);
1981
1982 bool is_for_listing = this->is_for_listing;
1983 //log_scope("special_active");
1984
1985
1986 // Does this affect the specified unit?
1987 if ( whom == unit_ability_t::affects_t::SELF ) {
1988 if ( !special_affects_self(ab, is_attacker) )
1989 return false;
1990 }
1991 if ( whom == unit_ability_t::affects_t::OTHER ) {
1992 if ( !special_affects_opponent(ab, is_attacker) )
1993 return false;
1994 }
1995
1996 // Is this active on attack/defense?
1997 if (!ab.active_on_matches(is_attacker)) {
1998 return false;
1999 }
2000
2001 // Get the units involved.
2002 const unit_map& units = get_unit_map();
2003
2004 unit_const_ptr self_u = self.un;
2005 unit_const_ptr other_u = other.un;
2006
2007 // We also set the weapons context during (attack) wml events, in that case we identify the units via locations because wml might change
2008 // the actual unit and usually does so via replacing, in that case self_ is set to nullptr.
2009 // TODO: does this really make sense? if wml replaces the unit it also replaces the attack object, deleting the attack context properties
2010 if(self_u == nullptr) {
2011 unit_map::const_iterator it = units.find(self.loc);
2012 if(it.valid()) {
2013 self_u = it.get_shared_ptr();
2014 }
2015 }
2016 if(other_u == nullptr) {
2018 if(it.valid()) {
2019 other_u = it.get_shared_ptr();
2020 }
2021 }
2022
2023 // Make sure they're facing each other.
2024 temporary_facing self_facing(self_u, self.loc.get_relative_dir(other.loc));
2025 temporary_facing other_facing(other_u, other.loc.get_relative_dir(self.loc));
2026
2027 // Filter poison, plague, drain, slow, petrifies
2028 // True if "whom" corresponds to "self", false if "whom" is "other"
2029 bool whom_is_self = ((whom == unit_ability_t::affects_t::SELF) || ((whom == unit_ability_t::affects_t::EITHER) && special_affects_self(ab, is_attacker)));
2030 unit_const_ptr them = whom_is_self ? other_u : self_u;
2031 map_location their_loc = whom_is_self ? other.loc : self.loc;
2032
2033 if (buildin_is_immune(ab, them, their_loc)) {
2034 return false;
2035 }
2036
2037
2038 // Translate our context into terms of "attacker" and "defender".
2039 unit_const_ptr & att = is_attacker ? self_u : other_u;
2040 unit_const_ptr & def = is_attacker ? other_u : self_u;
2041
2042 // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
2043 // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
2044 if (ab.tag() == "firststrike") {
2045 bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
2046 if (whom_is_defender && attacker.at && attacker.at->has_special_or_ability("firststrike"))
2047 return false;
2048 }
2049
2050 // Filter the units involved.
2051 //If filter concerns the unit on which special is applied,
2052 //then the type of special must be entered to avoid calling
2053 //the function of this special in matches_filter()
2054 //In apply_to=both case, ab.tag() must be checked in all filter because special applied to both self and opponent.
2055 bool applied_both = ab.apply_to() == unit_ability_t::apply_to_t::both;
2056 const std::string& filter_self = ab.in_specials_tag() ? "filter_self" : "filter_student";
2057
2058 bool applied_to_self = (applied_both || whom_is_self);
2059 if (!special_unit_matches(self_u, other_u, self.loc, self.at, ab, is_for_listing, filter_self, applied_to_self))
2060 return false;
2061 bool applied_to_opp = (applied_both || !whom_is_self);
2062 if (!special_unit_matches(other_u, self_u, other.loc, other.at, ab, is_for_listing, "filter_opponent", applied_to_opp))
2063 return false;
2064 //in case of apply_to=attacker|defender, if both [filter_attacker] and [filter_defender] are used,
2065 //check what is_attacker is true(or false for (filter_defender]) in affect self case only is necessary for what unit affected by special has a tag_name check.
2066 bool applied_to_attacker = applied_both || (whom_is_self && is_attacker) || (!whom_is_self && !is_attacker);
2067 if (!special_unit_matches(att, def, attacker.loc, attacker.at, ab, is_for_listing, "filter_attacker", applied_to_attacker))
2068 return false;
2069 bool applied_to_defender = applied_both || (whom_is_self && !is_attacker) || (!whom_is_self && is_attacker);
2070 if (!special_unit_matches(def, att, defender.loc, defender.at, ab, is_for_listing, "filter_defender", applied_to_defender))
2071 return false;
2072
2073 return true;
2074}
2075
2076namespace
2077{
2078 bool priority_checking(active_ability_list& overwriters, const active_ability& overwritten)
2079 {
2080 if(overwriters.empty()){
2081 return false;
2082 }
2083 const unit_ability_t& ab = overwritten.ability();
2084
2085 for(const auto& j : overwriters) {
2086 // If this element of overwriters does not have a priority strictly higher than ab,
2087 // either it does not have [overwrite_abilities] or both have [overwrite_abilities] of the same priority;
2088 // in either case, ab cannot be suppressed by this element of the list
2089 if(j.ability().suppress_ability_priority() <= ab.suppress_ability_priority()) {
2090 continue;
2091 }
2092
2093 // [overwrite_abilities]priority= having already been checked above, it remains to check if a sub-filter [filter_ability] exists and if so,
2094 // if it corresponds to ab.
2095 auto overwrite_filter = j.ability_cfg().optional_child("overwrite_abilities");
2096 if(overwrite_filter) {
2097 auto filter_abilities = (*overwrite_filter).optional_child("filter_ability");
2098 if(filter_abilities && !common_matches_filter(ab.cfg(), ab.tag(), *filter_abilities)) {
2099 continue;
2100 }
2101 // if all checks match, ab can be suppressed and other elements of the list won't be checked.
2102 return true;
2103 }
2104 }
2105 return false;
2106 }
2107
2108 void apply_ability_suppression(active_ability_list& abil_list)
2109 {
2110 // If two or more abilities have [overwrite_abilities],
2111 // priority sorting allows the highest priority ability to suppress a lower priority ability before the lower priority ability can,
2112 // in turn, suppress an ability with an even lower priority than the first two.
2113 utils::sort_if(abil_list,[](const active_ability& i, const active_ability& j){
2114 double l = i.ability().suppress_ability_priority();
2115 double r = j.ability().suppress_ability_priority();
2116 return l > r;
2117 });
2118 utils::erase_if(abil_list, [&](const active_ability& i) {
2119 return (priority_checking(abil_list, i));
2120 });
2121 }
2122}
2123
2125{
2126
2127void individual_effect::set(value_modifier t, int val, const config& abil, const map_location &l)
2128{
2129 type = t;
2130 value = val;
2131 ability = &abil;
2132 loc = l;
2133}
2134
2135bool filter_base_matches(const config& cfg, int def)
2136{
2137 if (auto apply_filter = cfg.optional_child("filter_base_value")) {
2138 config::attribute_value cond_eq = apply_filter["equals"];
2139 config::attribute_value cond_ne = apply_filter["not_equals"];
2140 config::attribute_value cond_lt = apply_filter["less_than"];
2141 config::attribute_value cond_gt = apply_filter["greater_than"];
2142 config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
2143 config::attribute_value cond_le = apply_filter["less_than_equal_to"];
2144 return (cond_eq.empty() || def == cond_eq.to_int()) &&
2145 (cond_ne.empty() || def != cond_ne.to_int()) &&
2146 (cond_lt.empty() || def < cond_lt.to_int()) &&
2147 (cond_gt.empty() || def > cond_gt.to_int()) &&
2148 (cond_ge.empty() || def >= cond_ge.to_int()) &&
2149 (cond_le.empty() || def <= cond_le.to_int());
2150 }
2151 return true;
2152}
2153
2154static int individual_value_int(const config::attribute_value *v, int def, const active_ability & ability, const map_location& loc, const specials_context_t* ctx) {
2155 int value = std::round(get_single_ability_value(*v, static_cast<double>(def), ability, loc, ctx, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2156 callable.add("base_value", wfl::variant(def));
2157 return std::round(formula.evaluate(callable).as_int());
2158 }));
2159 return value;
2160}
2161
2162static int individual_value_double(const config::attribute_value *v, int def, const active_ability & ability, const map_location& loc, const specials_context_t* ctx) {
2163 int value = std::round(get_single_ability_value(*v, static_cast<double>(def), ability, loc, ctx, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2164 callable.add("base_value", wfl::variant(def));
2165 return formula.evaluate(callable).as_decimal() / 1000.0 ;
2166 }) * 100);
2167 return value;
2168}
2169
2171 effect_list_(),
2172 composite_value_(def),
2173 composite_double_value_(def)
2174{
2175 // If ctx is not empty, then this is a special. The [overwrite_specials] feature is handled in get_specials_and_abilities() and so nothing should be done here in that case.
2176 if(!ctx) {
2177 apply_ability_suppression(list);
2178 }
2179 std::map<double, active_ability_list> base_list;
2180 for(const active_ability& i : list) {
2181 double priority = i.ability().priority();
2182 if(base_list[priority].empty()) {
2183 base_list[priority] = active_ability_list(list.loc());
2184 }
2185 base_list[priority].emplace_back(i);
2186 }
2187 int value = def;
2188 for(auto base : base_list) {
2189 effect::effect_impl(base.second, value, ctx, wham);
2191 }
2192}
2193
2194void effect::effect_impl(const active_ability_list& list, int def, const specials_context_t* ctx, EFFECTS wham )
2195{
2196 int value_set = def;
2197 std::map<std::string,individual_effect> values_add;
2198 std::map<std::string,individual_effect> values_sub;
2199 std::map<std::string,individual_effect> values_mul;
2200 std::map<std::string,individual_effect> values_div;
2201
2202 individual_effect set_effect_max;
2203 individual_effect set_effect_min;
2204 individual_effect set_effect_cum;
2205 utils::optional<int> max_value = utils::nullopt;
2206 utils::optional<int> min_value = utils::nullopt;
2207
2208 for (const active_ability & ability : list) {
2209 const config& cfg = ability.ability_cfg();
2210 const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
2211
2212 if (!filter_base_matches(cfg, def))
2213 continue;
2214
2215 if (const config::attribute_value *v = cfg.get("value")) {
2216 int value = individual_value_int(v, def, ability, list.loc(), ctx);
2217 int value_cum = wham != EFFECT_CUMULABLE && cfg["cumulative"].to_bool() ? std::max(def, value) : value;
2218 if(set_effect_cum.type != NOT_USED && wham == EFFECT_CUMULABLE && cfg["cumulative"].to_bool()) {
2219 set_effect_cum.set(SET, set_effect_cum.value + value_cum, ability.ability_cfg(), ability.teacher_loc);
2220 } else if(wham == EFFECT_CUMULABLE && cfg["cumulative"].to_bool()) {
2221 set_effect_cum.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2222 } else {
2223 assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
2224 if(set_effect_min.type == NOT_USED) {
2225 set_effect_min.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2226 set_effect_max.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2227 }
2228 else {
2229 if(value_cum > set_effect_max.value) {
2230 set_effect_max.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2231 }
2232 if(value_cum < set_effect_min.value) {
2233 set_effect_min.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2234 }
2235 }
2236 }
2237 }
2238
2239 if(wham != EFFECT_WITHOUT_CLAMP_MIN_MAX) {
2240 if(const config::attribute_value *v = cfg.get("max_value")) {
2241 int value = individual_value_int(v, def, ability, list.loc(), ctx);
2242 max_value = max_value ? std::min(*max_value, value) : value;
2243 }
2244 if(const config::attribute_value *v = cfg.get("min_value")) {
2245 int value = individual_value_int(v, def, ability, list.loc(), ctx);
2246 min_value = min_value ? std::max(*min_value, value) : value;
2247 }
2248 }
2249
2250 if (const config::attribute_value *v = cfg.get("add")) {
2251 int add = individual_value_int(v, def, ability, list.loc(), ctx);
2252 std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
2253 if(add_effect == values_add.end() || add > add_effect->second.value) {
2254 values_add[effect_id].set(ADD, add, ability.ability_cfg(), ability.teacher_loc);
2255 }
2256 }
2257 if (const config::attribute_value *v = cfg.get("sub")) {
2258 int sub = - individual_value_int(v, def, ability, list.loc(), ctx);
2259 std::map<std::string,individual_effect>::iterator sub_effect = values_sub.find(effect_id);
2260 if(sub_effect == values_sub.end() || sub < sub_effect->second.value) {
2261 values_sub[effect_id].set(ADD, sub, ability.ability_cfg(), ability.teacher_loc);
2262 }
2263 }
2264 if (const config::attribute_value *v = cfg.get("multiply")) {
2265 int multiply = individual_value_double(v, def, ability, list.loc(), ctx);
2266 std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
2267 if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2268 values_mul[effect_id].set(MUL, multiply, ability.ability_cfg(), ability.teacher_loc);
2269 }
2270 }
2271 if (const config::attribute_value *v = cfg.get("divide")) {
2272 int divide = individual_value_double(v, def, ability, list.loc(), ctx);
2273
2274 if (divide == 0) {
2275 ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
2276 }
2277 else {
2278 std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
2279 if(div_effect == values_div.end() || divide > div_effect->second.value) {
2280 values_div[effect_id].set(DIV, divide, ability.ability_cfg(), ability.teacher_loc);
2281 }
2282 }
2283 }
2284 }
2285
2286 if(set_effect_max.type != NOT_USED) {
2287 value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
2288 if(set_effect_max.value > def) {
2289 effect_list_.push_back(set_effect_max);
2290 }
2291 if(set_effect_min.value < def) {
2292 effect_list_.push_back(set_effect_min);
2293 }
2294 }
2295
2296 /* Do multiplication with floating point values rather than integers
2297 * We want two places of precision for each multiplier
2298 * Using integers multiplied by 100 to keep precision causes overflow
2299 * after 3-4 abilities for 32-bit values and ~8 for 64-bit
2300 * Avoiding the overflow by dividing after each step introduces rounding errors
2301 * that may vary depending on the order effects are applied
2302 * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
2303 */
2304 double multiplier = 1.0;
2305 double divisor = 1.0;
2306
2307 for(const auto& val : values_mul) {
2308 multiplier *= val.second.value/100.0;
2309 effect_list_.push_back(val.second);
2310 }
2311
2312 for(const auto& val : values_div) {
2313 divisor *= val.second.value/100.0;
2314 effect_list_.push_back(val.second);
2315 }
2316
2317 int addition = 0;
2318 for(const auto& val : values_add) {
2319 addition += val.second.value;
2320 effect_list_.push_back(val.second);
2321 }
2322
2323 /* Additional and subtraction are independent since Wesnoth 1.19.4. Prior to that, they affected each other.
2324 */
2325 int substraction = 0;
2326 for(const auto& val : values_sub) {
2327 substraction += val.second.value;
2328 effect_list_.push_back(val.second);
2329 }
2330
2331 if(set_effect_cum.type != NOT_USED) {
2332 value_set += set_effect_cum.value;
2333 effect_list_.push_back(set_effect_cum);
2334 }
2335
2336 composite_double_value_ = (value_set + addition + substraction) * multiplier / divisor;
2337 //clamp what if min_value < max_value or one attribute only used.
2338 if(max_value && min_value && *min_value < *max_value) {
2339 composite_double_value_ = std::clamp(static_cast<double>(*min_value), static_cast<double>(*max_value), composite_double_value_);
2340 } else if(max_value && !min_value) {
2341 composite_double_value_ = std::min(static_cast<double>(*max_value), composite_double_value_);
2342 } else if(min_value && !max_value) {
2343 composite_double_value_ = std::max(static_cast<double>(*min_value), composite_double_value_);
2344 }
2346}
2347
2348} // end namespace unit_abilities
static lg::log_domain log_engine("engine")
static bool is_enemy(std::size_t side, std::size_t other_side)
#define DEBUG_WML
Definition abilities.cpp:59
static lg::log_domain log_wml("wml")
std::vector< ability_ptr > ability_vector
Definition abilities.hpp:33
map_location loc
Definition move.cpp:172
#define ERR_NG
double t
const map_location & loc() const
std::pair< int, map_location > get_extremum(const std::string &key, int def, const TComp &comp) const
void emplace_back(T &&... args)
bool empty() const
void add_formula_context(wfl::map_formula_callable &) const
std::unique_ptr< specials_context_t > fallback_context(const unit_ptr &self=nullptr) const
bool overwrite_special_checking(active_ability_list &overwriters, const active_ability &overwrited) const
Check whether overwrited would be overwritten by any element of overwriters.
const std::string & range() const
specials_context_t * context_
const ability_vector & specials() const
Variant for storing WML attributes.
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
void remove_attribute(std::string_view key)
Definition config.cpp:162
config & add_child(std::string_view key)
Definition config.cpp:436
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
bool matches(const config &filter) const
Definition config.cpp:1168
config & child_or_add(std::string_view key)
Returns a reference to the first child with the given key.
Definition config.cpp:401
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
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition config.cpp:858
bool has_attribute(std::string_view key) const
Definition config.cpp:157
std::string debug() const
Definition config.cpp:1214
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition config.cpp:312
bool empty() const
Definition config.cpp:823
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
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
virtual const unit_map & units() const =0
static display * get_singleton()
Returns the display object if a display object exists.
Definition display.hpp:102
const display_context & context() const
Definition display.hpp:187
virtual const gamemap & map() const override
team & get_team(int i)
virtual const unit_map & units() const override
bool is_village(const map_location &loc) const
Definition map.cpp:60
static prefs & get()
bool get_show_deprecation(bool def)
self_and_other_ref self_and_other(const attack_type &self_att) const
bool has_active_special(const attack_type &at, const std::string &tag) const
Returns whether or not *this has a special ability with a tag or id equal to special.
active_ability_list get_abilities_weapons(const std::string &tag, const unit &un) const
bool is_special_active(const specials_combatant &wep, const unit_ability_t &ab, unit_ability_t::affects_t whom) const
Returns whether or not the given special is active for the specified unit, based on the current conte...
specials_combatant defender
std::vector< unit_ability_t::tooltip_info > special_tooltips(const attack_type &at, boost::dynamic_bitset<> &active_list) const
Returns a vector of names and descriptions for the specials of *this.
static bool has_active_ability_matching_filter(const unit &un, map_location loc, const config &filter)
std::string describe_weapon_specials_value(const attack_type &at, const std::set< std::string > &checking_tags) const
const specials_combatant & other(const specials_combatant &self) const
specials_combatant attacker
std::string describe_weapon_specials(const attack_type &at) const
Returns a comma-separated string of active names for the specials of *this.
void add_formula_context(wfl::map_formula_callable &callable) const
bool has_active_special_id(const attack_type &at, const std::string &id) const
specials_context_t(specials_context_t &&)=delete
std::vector< unit_ability_t::tooltip_info > abilities_special_tooltips(const attack_type &at, boost::dynamic_bitset<> &active_list) const
bool has_active_special_matching_filter(const attack_type &at, const config &filter) const
active_ability_list get_active_specials(const attack_type &at, const std::string &tag) const
static bool has_active_ability_id(const unit &un, map_location loc, const std::string &id)
active_ability_list get_active_combat_teachers(const attack_type &at) const
This class stores all the data for a single 'side' (in game nomenclature).
Definition team.hpp:74
bool is_enemy(int n) const
Definition team.hpp:270
std::vector< individual_effect > effect_list_
effect(active_ability_list list, int def, const specials_context_t *ctx=nullptr, EFFECTS wham=EFFECT_DEFAULT)
void effect_impl(const active_ability_list &list, int def, const specials_context_t *ctx, EFFECTS wham)
Part of the constructor, calculates for a group of abilities with equal priority.
const unit_ability_t * parent
std::string substitute_variables(const std::string &str) const
Substitute gettext variables in name and description of abilities and specials.
const std::string & tag() const
Definition abilities.hpp:53
double suppress_special_priority_
static std::string get_help_topic_id(const config &cfg)
double suppress_special_priority() const
Definition abilities.hpp:62
apply_to_t apply_to() const
Definition abilities.hpp:59
static config vector_to_cfg(const ability_vector &abilities)
static void do_compat_fixes(config &cfg, const std::string &tag, bool inside_attack)
static void parse_vector(const config &abilities_cfg, ability_vector &res, bool inside_attack)
bool affects_enemies() const
Definition abilities.hpp:70
std::string get_description(bool is_inactive=false, unit_race::GENDER=unit_race::MALE) const
bool matches_filter(const config &filter) const
active_on_t active_on() const
Definition abilities.hpp:58
bool in_specials_tag() const
Definition abilities.hpp:55
active_on_t active_on_
static ability_vector filter_tag(const ability_vector &vec, const std::string &tag)
unit_ability_t(std::string tag, config cfg, bool inside_attack)
double suppress_ability_priority() const
Definition abilities.hpp:63
static ability_ptr create(std::string tag, config cfg, bool inside_attack)
Definition abilities.hpp:47
bool currently_checked_
affects_allies_t affects_allies_
std::string tag_
static ability_vector clone(const ability_vector &vec)
const config & cfg() const
Definition abilities.hpp:56
std::string get_name(bool is_inactive=false, unit_race::GENDER=unit_race::MALE) const
std::string get_help_topic_id() const
recursion_guard guard_against_recursion(const unit &u) const
Tests which might otherwise cause infinite recursion should call this, check that the returned object...
void write(config &abilities_cfg)
double suppress_ability_priority_
static ability_vector cfg_to_vector(const config &abilities_cfg, bool inside_attack)
affects_allies_t affects_allies() const
Definition abilities.hpp:66
apply_to_t apply_to_
bool active_on_matches(bool student_is_attacker) const
bool affects_self() const
Definition abilities.hpp:68
unit_filter & set_use_flat_tod(bool value)
Definition filter.hpp:113
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition filter.hpp:123
Container associating units to locations.
Definition map.hpp:98
unit_iterator end()
Definition map.hpp:428
unit_ptr find_unit_ptr(const T &val)
Definition map.hpp:387
unit_iterator find(std::size_t id)
Definition map.cpp:302
@ FEMALE
Definition race.hpp:28
const unit_type_map & types() const
Definition types.hpp:398
A single unit type that the player may recruit.
Definition types.hpp:43
This class represents a single unit of a specific type.
Definition unit.hpp:39
A variable-expanding proxy for the config class.
Definition variable.hpp:45
Represents version numbers.
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition formula.hpp:48
map_formula_callable & add(const std::string &key, const variant &value)
Definition callable.hpp:249
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition variant.cpp:336
#define ERR_WML
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
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition gettext.hpp:100
bool ability_active_impl(const unit_ability_t &ab, const map_location &loc) const
Check if an ability is active.
bool ability_affects_self(const unit_ability_t &ab, const map_location &loc) const
Check if an ability affects the owning unit.
bool ability_active(const unit_ability_t &ab, const map_location &loc) const
Check if an ability is active.
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.
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.
bool ability_affects_adjacent(const unit_ability_t &ab, std::size_t dist, int dir, const map_location &loc, const unit &from) const
Check if an ability affects distant units.
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
std::vector< std::string > get_ability_id_list() const
Get a list of all abilities by ID.
bool get_self_ability_bool(const unit_ability_t &ab, const map_location &loc) const
Checks whether this unit currently possesses a given ability, and that that ability is active.
bool get_adj_ability_bool(const unit_ability_t &ab, std::size_t dist, int dir, const map_location &loc, const unit &from, const map_location &from_loc) const
Checks whether this unit is affected by a given ability, and that that ability is active.
std::vector< unit_ability_t::tooltip_info > ability_tooltips() const
Gets the names and descriptions of this unit's abilities.
bool incapacitated() const
Check if the unit has been petrified.
Definition unit.hpp:826
const std::string & id() const
Gets this unit's id.
Definition unit.hpp:286
int side() const
The side this unit belongs to.
Definition unit.hpp:249
std::size_t underlying_id() const
This unit's unique internal ID.
Definition unit.hpp:298
@ STATE_SLOWED
Definition unit.hpp:775
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition unit.hpp:776
std::vector< std::string > halo_or_icon_abilities(const std::string &image_type) const
const map_location & get_location() const
The current map location this unit is at.
Definition unit.hpp:1327
std::size_t max_ability_radius_type(const std::string &tag_name) const
If this unit has abilities of tag_name type with [affect_adjacent] subtags, returns the radius of the...
Definition unit.hpp:1202
std::size_t max_ability_radius_image() const
If this unit has abilities with [affect_adjacent] subtags and halo_image or overlay_image attributes,...
Definition unit.hpp:1223
std::size_t max_ability_radius() const
If this unit has abilities with [affect_adjacent] subtags, returns the radius of the one with the fur...
Definition unit.hpp:1213
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
const language_def & get_language()
Definition language.cpp:317
New lexcical_cast header.
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition location.cpp:584
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition location.cpp:513
Standard logging facilities (interface).
const color_t INACTIVE_COLOR
const version_info wesnoth_version(VERSION)
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition log.cpp:550
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition markup.hpp:110
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition markup.hpp:45
game_board * gameboard
Definition resources.cpp:20
STL namespace.
static int individual_value_int(const config::attribute_value *v, int def, const active_ability &ability, const map_location &loc, const specials_context_t *ctx)
static int individual_value_double(const config::attribute_value *v, int def, const active_ability &ability, const map_location &loc, const specials_context_t *ctx)
bool filter_base_matches(const config &cfg, int def)
@ EFFECT_WITHOUT_CLAMP_MIN_MAX
Utility functions for implementing [filter], [filter_ability], [filter_weapon], etc.
bool int_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, utils::optional< int > def=utils::nullopt)
bool set_includes_if_present(const config &filter, const config &cfg, const std::string &attribute)
filter[attribute] and cfg[attribute] are assumed to be comma-separated lists.
bool double_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, utils::optional< double > def=utils::nullopt)
Checks whether the filter matches the value of cfg[attribute].
bool int_matches_if_present_or_negative(const config &filter, const config &cfg, const std::string &attribute, const std::string &opposite, utils::optional< int > def=utils::nullopt)
Supports filters using "add" and "sub" attributes, for example a filter add=1 matching a cfg containi...
bool string_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, const std::string &def)
bool bool_or_empty(const config &filter, const config &cfg, const std::string &attribute)
bool bool_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, bool def)
Checks whether the filter matches the value of cfg[attribute].
constexpr auto filter
Definition ranges.hpp:42
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
Definition general.hpp:32
void trim(std::string_view &s)
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition general.hpp:108
std::map< std::string, t_string > string_map
void sort_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::sort on a container.
Definition general.hpp:133
std::vector< std::string > split(const config_attribute_value &val)
std::shared_ptr< const unit > unit_const_ptr
Definition ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition ptr.hpp:34
std::shared_ptr< unit_ability_t > ability_ptr
Definition ptr.hpp:38
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
Data typedef for active_ability_list.
const unit_ability_t & ability() const
map_location teacher_loc
The location of the teacher, that is the unit who owns the ability tags (different from student becau...
map_location student_loc
Used by the formula in the ability.
std::string localename
Definition language.hpp:32
Encapsulates the map of the game.
Definition location.hpp:46
static std::vector< direction > parse_directions(const std::string &str)
Parse_directions takes a comma-separated list, and filters out any invalid directions.
Definition location.cpp:138
bool valid() const
Definition location.hpp:111
direction
Valid directions which can be moved in our hexagonal world.
Definition location.hpp:48
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition location.cpp:238
void set(value_modifier t, int val, const config &abil, const map_location &l)
bool valid() const
Definition map.hpp:273
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition map.hpp:217
mock_party p
static map_location::direction s
unit_type_data unit_types
Definition types.cpp:1499
#define i
#define d
#define e
#define f