55 #define ERR_NG LOG_STREAM(err, log_engine)
58 #define ERR_WML LOG_STREAM(err, log_wml)
62 using namespace std::string_literals;
63 const std::array numeric_keys{
64 "value"s,
"add"s,
"sub"s,
"multiply"s,
"divide"s,
"max_value"s,
"min_value"s
126 : tag_(std::move(
tag))
127 , id_(
cfg[
"id"].str())
128 , in_specials_tag_(inside_attack)
132 , affects_self_(true)
133 , affects_enemies_(false)
134 , priority_(
cfg[
"priority"].to_double(0.00))
135 , cfg_(std::move(
cfg))
136 , currently_checked_(false)
140 if (
tag_ !=
"resistance" &&
tag_ !=
"leadership") {
150 if (
tag_ !=
"leadership") {
160 if (
cfg_[
"affect_allies"].to_bool(
false)) {
163 if (!
cfg_[
"affect_allies"].to_bool(
true)) {
174 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.");
176 if (
cfg[
"backstab"].to_bool()) {
177 const std::string& backstab_formula =
"enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
179 config& filter_opponent2 = filter_opponent.
empty() ? filter_opponent : filter_opponent.
add_child(
"and");
180 filter_opponent2[
"formula"] = backstab_formula;
185 std::string filter_teacher = inside_attack ?
"filter_self" :
"filter";
205 if (filter_adjacent[
"count"].empty()) {
214 if (filter_adjacent[
"count"].empty()) {
223 if (
tag ==
"resistance" ||
tag ==
"leadership") {
242 return cfg[
"id"].str() +
cfg[
"name"].t_str().base_str();
247 return id() +
cfg()[
"name"].t_str().base_str();
269 if (p_ab->tag() ==
tag) {
280 res.push_back(std::make_shared<unit_ability_t>(*p_ab));
288 for (
const auto& item : abilities) {
289 item->write(abilities_cfg);
291 return abilities_cfg;
306 if(
tag() ==
"plague") {
316 symbols.emplace(
"type",
type.type_name());
321 for(
const auto& vkey : numeric_keys) {
322 if(
cfg().has_attribute(vkey)) {
323 if(vkey ==
"multiply" || vkey ==
"divide") {
324 symbols.emplace(vkey, std::to_string(
cfg()[vkey].to_double()));
326 symbols.emplace(vkey, std::to_string(
cfg()[vkey].to_int()));
336 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)
339 if (
auto* attr =
cfg.
get(s_yes_yes)) {
return *attr; }
342 if (
auto* attr =
cfg.
get(s_yes_no)) {
return *attr; }
345 if (
auto* attr =
cfg.
get(s_no_yes)) {
return *attr; }
354 std::string res = get_attr_four_fallback(
cfg_, is_inactive, is_female,
"female_name_inactive",
"name_inactive",
"female_name",
"name").str();
361 std::string res = get_attr_four_fallback(
cfg_, is_inactive, is_female,
"female_description_inactive",
"description_inactive",
"female_description",
"description").str();
383 if (!
p.currently_checked_) {
384 p.currently_checked_ =
true;
392 parent->currently_checked_ =
false;
396 unit_ability_t::recursion_guard::operator bool()
const {
403 static std::vector<std::tuple<std::string, std::string>> already_shown;
405 auto identifier = std::tuple<std::string, std::string>{ u.
id(),
cfg().
debug()};
408 std::string_view filter_text_view = std::get<1>(identifier);
410 ERR_NG <<
"Looped recursion error for unit '" << u.
id()
411 <<
"' while checking ability '" << filter_text_view <<
"'";
415 if (already_shown.size() > 100) {
416 already_shown.clear();
418 already_shown.push_back(std::move(identifier));
441 const team& get_team(std::size_t side)
461 bool affects_side(
const unit_ability_t& ab, std::size_t side, std::size_t other_side)
463 const team& side_team = get_team(side);
465 if(side == other_side)
480 for(std::size_t j = 0; j < adjacent.size(); ++j) {
481 bool adj_or_dist = distance != 1 ?
distance_between(adjacent[j],
loc) == (distance - 1) : adjacent[j] ==
loc;
491 template<
typename TFunc,
typename... TArgs>
492 bool default_false(
const TFunc&
f,
const TArgs&... args) {
493 if constexpr (std::is_same_v<decltype(
f(args...)),
void>) {
502 template<
typename TCheck,
typename THandler>
503 bool foreach_distant_active_ability(
const unit& un,
const map_location&
loc, TCheck&& quick_check, THandler&& handler)
507 const unit_map& units = get_unit_map();
513 for(
const unit& u : units) {
515 if (!u.max_ability_radius() || u.incapacitated() || u.underlying_id() == un.
underlying_id()) {
518 std::size_t max_ability_radius = u.max_ability_radius();
521 if (distance > max_ability_radius) {
524 int dir = find_direction(
loc, from_loc, distance);
525 for (
const auto& p_ab : u.abilities()) {
526 if (!quick_check(p_ab)) {
530 if (default_false(handler, p_ab, u)) {
539 template<
typename TCheck,
typename THandler>
540 bool foreach_self_active_ability(
const unit& un,
map_location loc,
const TCheck& quick_check,
const THandler& handler)
542 for (
const auto& p_ab : un.
abilities()) {
543 if (!quick_check(p_ab)) {
547 if (default_false(handler, p_ab, un)) {
568 template<
typename TCheck,
typename THandler>
569 bool foreach_active_ability(
const unit& un,
map_location loc,
const TCheck& quick_check,
const THandler& handler,
bool skip_adjacent =
false)
573 if (foreach_self_active_ability(un,
loc, quick_check, handler)) {
576 if (!skip_adjacent && foreach_distant_active_ability(un,
loc, quick_check, handler)) {
582 auto return_true = [](
const auto&...) {
return true; };
591 template<
typename TCheck,
typename THandler>
592 bool foreach_active_special(
599 const TCheck& quick_check,
600 const THandler& handler,
601 bool skip_adjacent =
false)
604 auto handler_self = [&](
const ability_ptr& p_ab,
const auto&...) {
607 auto handler_other = [&](
const ability_ptr& p_ab,
const auto&...) {
613 for (
const ability_ptr& p_ab : self_attack->specials()) {
614 if (quick_check(p_ab) && handler_self(p_ab)) {
621 for (
const ability_ptr& p_ab : other_attack->specials()) {
622 if (quick_check(p_ab) && handler_other(p_ab)) {
629 if (foreach_active_ability(*
self, self_loc, quick_check, handler_self, skip_adjacent)) {
635 if (foreach_active_ability(*other, other_loc, quick_check, handler_other, skip_adjacent)) {
647 return foreach_active_ability(*
this,
loc,
649 return p_ab->tag() == tag_name;
659 foreach_active_ability(*
this,
loc,
661 return p_ab->tag() == tag_name;
681 std::vector<std::string> res;
683 for(
const auto& p_ab : this->abilities()) {
684 std::string
id = p_ab->id();
686 res.push_back(std::move(
id));
701 auto name = ab.
get_name(!active, gender);
708 res.AGGREGATE_EMPLACE(
719 std::vector<unit_ability_t::tooltip_info> res;
721 for(
const auto& p_ab : abilities())
723 add_ability_tooltip(*p_ab, gender_, res,
true);
731 std::vector<unit_ability_t::tooltip_info> res;
734 for(
const auto& p_ab : abilities())
736 bool active = ability_active(*p_ab,
loc);
737 if(add_ability_tooltip(*p_ab, gender_, res, active))
739 active_list.push_back(active);
752 return ability_active_impl(ab,
loc);
757 bool illuminates = ab.
tag() ==
"illuminates";
773 bool illuminates = ab.
tag() ==
"illuminates";
775 assert(dir >=0 && dir <= 5);
780 if(
i[
"radius"] !=
"all_map") {
781 int radius =
i[
"radius"].to_int(1);
785 if(dist >
size_t(radius)) {
789 if (
i.has_attribute(
"adjacent")) {
794 auto filter =
i.optional_child(
"filter");
807 if (!
filter || !affect_self)
return affect_self;
813 return !abilities(ability).empty();
821 image_list.push_back(
cfg[attribute_name].str());
827 std::vector<std::string> image_list;
828 for(
const auto& p_ab : abilities()){
829 bool is_active = ability_active(*p_ab, loc_);
831 if( !p_ab->cfg()[image_type +
"_image"].str().empty() &&
is_active && ability_affects_self(*p_ab, loc_)){
835 if(!p_ab->cfg()[image_type +
"_image_self"].str().empty() &&
is_active){
840 foreach_distant_active_ability(*
this, loc_,
842 return !p_ab->cfg()[image_type +
"_image"].str().empty();
849 if(image_list.size() >= 2){
850 std::sort(image_list.begin(), image_list.end());
858 callable.
add(
"attacker",
wfl::variant(std::make_shared<wfl::unit_callable>(*att)));
861 callable.
add(
"defender",
wfl::variant(std::make_shared<wfl::unit_callable>(*def)));
868 template<
typename T,
typename TFuncFormula>
869 class get_ability_value_visitor
870 #ifdef USING_BOOST_VARIANT
871 :
public boost::static_visitor<T>
876 get_ability_value_visitor(T def,
const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
878 T operator()(
const utils::monostate&)
const {
return def_; }
879 T operator()(
bool)
const {
return def_; }
880 T operator()(
int i)
const {
return static_cast<T
>(
i); }
881 T operator()(
unsigned long long u)
const {
return static_cast<T
>(u); }
882 T operator()(
double d)
const {
return static_cast<T
>(
d); }
883 T operator()(
const t_string&)
const {
return def_; }
884 T operator()(
const std::string&
s)
const
886 if(
s.size() >= 2 &&
s[0] ==
'(') {
887 return formula_handler_(
s);
889 return lexical_cast_default<T>(
s, def_);
894 const TFuncFormula& formula_handler_;
897 template<
typename T,
typename TFuncFormula>
900 return v.
apply_visitor(get_ability_value_visitor(def, [&](
const std::string&
s) {
903 const unit_map& units = get_unit_map();
907 if(u_itor == units.
end()) {
912 att->add_formula_context(callable);
915 callable.add(
"student",
wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
918 callable.add(
"other",
wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
922 lg::log_to_chat() <<
"Formula error in ability or weapon special: " <<
e.type <<
" at " <<
e.filename <<
':' <<
e.line <<
")\n";
923 ERR_WML <<
"Formula error in ability or weapon special: " <<
e.type <<
" at " <<
e.filename <<
':' <<
e.line <<
")";
930 template<
typename TComp>
933 if ( cfgs_.empty() ) {
939 bool only_cumulative =
true;
949 if (
p.ability_cfg()[
"cumulative"].to_bool()) {
951 if (value < 0) value = -value;
952 if (only_cumulative && !comp(value, abs_max)) {
954 best_loc =
p.teacher_loc;
956 }
else if (only_cumulative || comp(flat, value)) {
957 only_cumulative =
false;
959 best_loc =
p.teacher_loc;
962 return std::pair(flat + stack, best_loc);
965 template 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;
966 template 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;
1007 for(
const ability_ptr& p_ab : specials(special)) {
1008 if(special_active(*p_ab, AFFECTS::SELF)) {
1013 if(!other_attack_) {
1017 for(
const ability_ptr& p_ab : other_attack_->specials(special)) {
1035 boost::dynamic_bitset<>* active_list)
const
1038 std::vector<unit_ability_t::tooltip_info> res;
1040 active_list->clear();
1043 for(
const auto& p_ab : specials()) {
1044 bool active = !active_list || special_active(*p_ab, AFFECTS::EITHER);
1046 auto name = p_ab->get_name(!active);
1047 auto desc = p_ab->get_description(!active);
1053 res.AGGREGATE_EMPLACE(
1056 p_ab->get_help_topic_id()
1060 active_list->push_back(active);
1067 boost::dynamic_bitset<>* active_list)
const
1069 std::vector<unit_ability_t::tooltip_info> res;
1071 active_list->clear();
1073 std::set<std::string> checking_name;
1077 foreach_active_ability(*self_, self_loc_,
1082 if (special_tooltip_active(*p_ab)) {
1083 bool active = !active_list || special_active(*p_ab, AFFECTS::SELF);
1084 const std::string name = p_ab->cfg()[
"name_affected"];
1085 const std::string desc = p_ab->cfg()[
"description_affected"];
1087 if(name.empty() || checking_name.count(name) != 0) {
1090 res.AGGREGATE_EMPLACE(name, desc, p_ab->get_help_topic_id());
1091 checking_name.insert(name);
1093 active_list->push_back(active);
1108 static void add_name(std::string& temp_string,
bool active,
const std::string& name, std::set<std::string>& checking_name)
1110 if (active && !name.empty() && checking_name.count(name) == 0) {
1111 checking_name.insert(name);
1112 if (!temp_string.empty()) temp_string +=
", ";
1127 std::vector<std::string> special_names;
1129 for(
const auto& p_ab : specials()) {
1130 const bool active = special_active(*p_ab, AFFECTS::EITHER);
1132 std::string name = active
1133 ? p_ab->cfg()[
"name"].str()
1134 : p_ab->cfg().get_or(
"name_inactive",
"name").str();
1140 name = p_ab->substitute_variables(name);
1146 std::string temp_string;
1147 std::set<std::string> checking_name;
1148 const std::set<std::string>& checking_tags = abilities_list::all_weapon_tags();
1149 weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECTS::SELF, checking_name, checking_tags);
1150 weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECTS::SELF, checking_name, checking_tags,
"affect_allies");
1152 if(!temp_string.empty()) {
1153 special_names.push_back(
"\n" + std::move(temp_string));
1159 static void add_name_list(std::string& temp_string, std::string& weapon_abilities, std::set<std::string>& checking_name,
const std::string& from_str)
1161 if(!temp_string.empty()){
1162 temp_string = from_str.c_str() + temp_string;
1163 weapon_abilities += (!weapon_abilities.empty() && !temp_string.empty()) ?
"\n" :
"";
1164 weapon_abilities += temp_string;
1165 temp_string.clear();
1166 checking_name.clear();
1173 std::string temp_string, weapon_abilities;
1174 std::set<std::string> checking_name;
1175 for(
const auto& p_ab : specials()) {
1176 if(checking_tags.count(p_ab->tag()) != 0) {
1177 const bool active = special_active(*p_ab, AFFECTS::SELF);
1178 add_name(temp_string, active, p_ab->cfg()[
"name"].str(), checking_name);
1181 add_name_list(temp_string, weapon_abilities, checking_name,
"");
1183 weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECTS::SELF, checking_name, checking_tags);
1184 add_name_list(temp_string, weapon_abilities, checking_name,
_(
"Owned: "));
1186 weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECTS::SELF, checking_name, checking_tags,
"affect_allies");
1188 add_name_list(temp_string, weapon_abilities, checking_name,
_(
"Taught: "));
1190 weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECTS::SELF, checking_name, checking_tags,
"affect_enemies");
1192 add_name_list(temp_string, weapon_abilities, checking_name,
_(
"Taught: (by an enemy): "));
1196 for(
const auto& p_ab : other_attack_->specials()) {
1197 if((checking_tags.count(p_ab->tag()) != 0)){
1198 const bool active = other_attack_->special_active(*p_ab,
AFFECTS::OTHER);
1199 add_name(temp_string, active, p_ab->cfg()[
"name"].str(), checking_name);
1203 weapon_specials_impl_self(temp_string, other_, other_attack_, shared_from_this(), other_loc_,
AFFECTS::OTHER, checking_name, checking_tags);
1204 weapon_specials_impl_adj(temp_string, other_, other_attack_, shared_from_this(), other_loc_,
AFFECTS::OTHER, checking_name, checking_tags);
1205 add_name_list(temp_string, weapon_abilities, checking_name,
_(
"Used by opponent: "));
1207 return weapon_abilities;
1211 std::string& temp_string,
1217 std::set<std::string>& checking_name,
1218 const std::set<std::string>& checking_tags)
1221 foreach_self_active_ability(*
self, self_loc,
1223 return !checking_tags.empty() ? checking_tags.count(p_ab->tag()) != 0 :
true;
1226 const bool active = special_active_impl(self_attack, other_attack, *p_ab, whom);
1227 add_name(temp_string, active, p_ab->cfg().get_or(
"name_affected",
"name").str(), checking_name);
1233 std::string& temp_string,
1239 std::set<std::string>& checking_name,
1240 const std::set<std::string>& checking_tags,
1241 const std::string& affect_adjacents)
1244 foreach_distant_active_ability(*
self, self_loc,
1246 bool tag_checked = !checking_tags.empty() ? checking_tags.count(p_ab->tag()) != 0 :
true;
1247 bool default_bool = affect_adjacents ==
"affect_allies" ?
true :
false;
1248 bool affect_allies = !affect_adjacents.empty() ? p_ab->cfg()[affect_adjacents].to_bool(default_bool) :
true;
1249 return tag_checked && affect_allies;
1252 const bool active = special_active_impl(self_attack, other_attack, *p_ab, whom);
1253 add_name(temp_string, active, p_ab->cfg().get_or(
"name_affected",
"name").str(), checking_name);
1278 : parent(weapon.shared_from_this())
1280 weapon.
self_ = std::move(
self);
1281 weapon.
other_ = std::move(other);
1298 : parent(weapon.shared_from_this())
1300 weapon.
self_ = std::move(
self);
1310 : parent(weapon.shared_from_this())
1318 if(was_moved)
return;
1323 parent->is_attacker_ =
false;
1324 parent->other_attack_ =
nullptr;
1325 parent->is_for_listing_ =
false;
1329 : parent(std::move(other.parent))
1331 other.was_moved =
true;
1344 bool special_affects_opponent(
const unit_ability_t& ab,
bool is_attacker)
1347 const auto apply_to = ab.
apply_to();
1348 if ( apply_to == apply_to_t::both)
1350 if ( apply_to == apply_to_t::opponent )
1352 if ( is_attacker && apply_to == apply_to_t::defender)
1354 if ( !is_attacker && apply_to == apply_to_t::attacker)
1364 bool special_affects_self(
const unit_ability_t& ab,
bool is_attacker)
1367 const auto apply_to = ab.
apply_to();
1368 if ( apply_to == apply_to_t::both )
1370 if ( apply_to == apply_to_t::self)
1372 if ( is_attacker && apply_to == apply_to_t::attacker)
1374 if ( !is_attacker && apply_to == apply_to_t::defender)
1381 if (ab.
tag() ==
"drains" && them && them->get_state(
"undrainable")) {
1384 if (ab.
tag() ==
"plague" && them &&
1385 (them->get_state(
"unplagueable") ||
1389 if (ab.
tag() ==
"poison" && them &&
1393 if (ab.
tag() ==
"slow" && them &&
1397 if (ab.
tag() ==
"petrifies" && them &&
1398 them->get_state(
"unpetrifiable")) {
1420 const bool for_listing,
1421 const std::string & child_tag,
bool applies_to_checked)
1435 if ( !filter_child )
1454 if (
auto filter_weapon = filter_child->optional_child(
"filter_weapon") ) {
1455 std::string check_if_recursion = applies_to_checked ? ab.
tag() :
"";
1456 if ( !weapon || !weapon->matches_filter(*filter_weapon, check_if_recursion) )
1463 return ufilt.matches(*u,
loc);
1465 return ufilt.matches(*u,
loc, *u2);
1483 return special_active(i.ability(), AFFECTS::SELF);
1489 return special_active_impl(other_attack_, shared_from_this(), i.ability(), AFFECTS::OTHER);
1504 if(!abil_list.
empty() && !overwriters.
empty()){
1515 const std::string& apply_to = ab.
cfg()[
"overwrite_specials"];
1516 return (apply_to ==
"one_side" || apply_to ==
"both_sides");
1527 if(overwriters.
empty()){
1532 if(overwriters.
size() >= 2){
1535 auto oi =
i.ability_cfg().optional_child(
"overwrite");
1537 if(oi && !oi[
"priority"].empty()){
1538 l = oi[
"priority"].to_double(0);
1540 auto oj = j.
ability_cfg().optional_child(
"overwrite");
1542 if(oj && !oj[
"priority"].empty()){
1543 r = oj[
"priority"].to_double(0);
1557 if(overwriters.
empty()){
1561 for(
const auto& j : overwriters) {
1563 bool affect_side = (j.ability_cfg()[
"overwrite_specials"] ==
"one_side");
1565 auto overwrite_specials = j.ability_cfg().optional_child(
"overwrite");
1566 double priority = overwrite_specials ? overwrite_specials[
"priority"].to_double(0) : 0.00;
1571 bool prior = (priority > 0) ? (has_overwrite_specials && has_overwrite_specials[
"priority"].to_double(0) >= priority) :
true;
1575 bool one_side_overwritable =
true;
1579 if(affect_side && is_overwritable){
1581 one_side_overwritable = special_affects_self(ab,
is_attacker_);
1583 else if(special_affects_opponent(j.ability(), !
is_attacker_)){
1584 one_side_overwritable = special_affects_opponent(ab, !
is_attacker_);
1589 bool special_matches =
true;
1590 if(overwrite_specials){
1591 auto overwrite_filter = (*overwrite_specials).optional_child(
"filter_specials");
1592 if(!overwrite_filter){
1593 overwrite_filter = (*overwrite_specials).optional_child(
"experimental_filter_specials");
1594 if(overwrite_filter){
1598 if(overwrite_filter && is_overwritable && one_side_overwritable){
1606 if(is_overwritable && one_side_overwritable && special_matches){
1619 return (ability_active_impl(ab,
loc) && ability_affects_self(ab,
loc));
1628 return (affects_side(ab, side(), from.
side()) && from.
ability_active_impl(ab, from_loc) && ability_affects_adjacent(ab, dist, dir,
loc, from));
1641 if (
range().empty()) {
1646 return p_ab->tag() == special;
1649 return foreach_active_special(
1665 if (
range().empty()) {
1670 return p_ab->id() == special;
1673 return foreach_active_special(
1688 bool exclude_ability_attributes(
const std::string& tag_name,
const config &
filter)
1691 bool abilities_check = abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1692 if(
filter.has_attribute(
"active_on") && tag_name !=
"resistance" && abilities_check)
1694 if(
filter.has_attribute(
"apply_to") && tag_name !=
"resistance" && abilities_check)
1697 if(
filter.has_attribute(
"overwrite_specials") && abilities_list::weapon_math_tags().count(tag_name) == 0)
1700 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;
1701 if(
filter.has_attribute(
"cumulative") && no_value_weapon_abilities_check && (tag_name !=
"swarm" || tag_name !=
"berserk"))
1703 if(
filter.has_attribute(
"value") && (no_value_weapon_abilities_check && tag_name !=
"berserk"))
1705 if(
filter.has_attribute(
"add") && no_value_weapon_abilities_check)
1707 if(
filter.has_attribute(
"sub") && no_value_weapon_abilities_check)
1709 if(
filter.has_attribute(
"multiply") && no_value_weapon_abilities_check)
1711 if(
filter.has_attribute(
"divide") && no_value_weapon_abilities_check)
1713 if(
filter.has_attribute(
"priority") && no_value_weapon_abilities_check)
1716 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;
1717 if(
filter.has_attribute(
"replacement_type") && tag_name !=
"damage_type" && all_engine)
1719 if(
filter.has_attribute(
"alternative_type") && tag_name !=
"damage_type" && all_engine)
1721 if(
filter.has_attribute(
"type") && tag_name !=
"plague" && all_engine)
1732 if(!exclude_ability_attributes(tag_name,
filter))
1746 if(!
filter[
"affect_adjacent"].empty()){
1748 if(
filter[
"affect_adjacent"].to_bool() != adjacent){
1778 if(abilities_list::weapon_math_tags().count(tag_name) != 0 || abilities_list::ability_value_tags().count(tag_name) != 0) {
1790 if(!
filter[
"value"].empty()){
1791 if(tag_name ==
"drains"){
1795 }
else if(tag_name ==
"berserk"){
1799 }
else if(tag_name ==
"heal_on_hit" || tag_name ==
"heals" || tag_name ==
"regenerate" || tag_name ==
"leadership"){
1825 if(tag_name ==
"resistance"){
1854 auto fwml =
filter.optional_child(
"filter_wml");
1865 static bool common_matches_filter(
const config &
cfg,
const std::string& tag_name,
const config &
filter)
1868 bool matches = matches_ability_filter(
cfg, tag_name,
filter);
1871 for(
const auto [key, condition_cfg] :
filter.all_children_view() )
1875 matches = matches && common_matches_filter(
cfg, tag_name, condition_cfg);
1878 else if ( key ==
"or" )
1879 matches = matches || common_matches_filter(
cfg, tag_name, condition_cfg);
1882 else if ( key ==
"not" )
1883 matches = matches && !common_matches_filter(
cfg, tag_name, condition_cfg);
1897 if(
range().empty()){
1901 if (!
filter[
"active"].to_bool()) {
1905 bool skip_adjacent = !
filter[
"affect_adjacent"].to_bool(
true);
1908 return p_ab->matches_filter(
filter);
1911 return foreach_active_special(
1930 class temporary_facing
1936 : save_dir_(u ? u->facing() :
map_location::direction::indeterminate)
1940 u_->set_facing(new_dir);
1946 u_->set_facing(save_dir_);
1965 assert(self_attack || other_attack);
1966 bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
1967 bool is_for_listing = self_attack ? self_attack->is_for_listing_ : other_attack->is_for_listing_;
1973 if ( !special_affects_self(ab, is_attacker) )
1977 if ( !special_affects_opponent(ab, is_attacker) )
1987 const unit_map& units = get_unit_map();
1989 unit_const_ptr self = self_attack ? self_attack->self_ : other_attack->other_;
1990 unit_const_ptr other = self_attack ? self_attack->other_ : other_attack->self_;
1991 map_location self_loc = self_attack ? self_attack->self_loc_ : other_attack->other_loc_;
1992 map_location other_loc = self_attack ? self_attack->other_loc_ : other_attack->self_loc_;
1996 if(
self ==
nullptr) {
2002 if(other ==
nullptr) {
2011 temporary_facing other_facing(other, other_loc.
get_relative_dir(self_loc));
2017 map_location their_loc = whom_is_self ? other_loc : self_loc;
2019 if (buildin_is_immune(ab, them, their_loc)) {
2027 const map_location & att_loc = is_attacker ? self_loc : other_loc;
2028 const map_location & def_loc = is_attacker ? other_loc : self_loc;
2029 const const_attack_ptr& att_weapon = is_attacker ? self_attack : other_attack;
2030 const const_attack_ptr& def_weapon = is_attacker ? other_attack : self_attack;
2034 if (ab.
tag() ==
"firststrike") {
2035 bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
2036 if (whom_is_defender && att_weapon && att_weapon->has_special_or_ability(
"firststrike"))
2046 const std::string& filter_self = ab.
in_specials_tag() ?
"filter_self" :
"filter_student";
2048 bool applied_to_self = (applied_both || whom_is_self);
2049 if (!special_unit_matches(
self, other, self_loc, self_attack, ab, is_for_listing, filter_self, applied_to_self))
2051 bool applied_to_opp = (applied_both || !whom_is_self);
2052 if (!special_unit_matches(other,
self, other_loc, other_attack, ab, is_for_listing,
"filter_opponent", applied_to_opp))
2056 bool applied_to_attacker = applied_both || (whom_is_self && is_attacker) || (!whom_is_self && !is_attacker);
2057 if (!special_unit_matches(att, def, att_loc, att_weapon, ab, is_for_listing,
"filter_attacker", applied_to_attacker))
2059 bool applied_to_defender = applied_both || (whom_is_self && !is_attacker) || (!whom_is_self && is_attacker);
2060 if (!special_unit_matches(def, att, def_loc, def_weapon, ab, is_for_listing,
"filter_defender", applied_to_defender))
2081 bool whom_is_self = special_affects_self(ab,
is_attacker_);
2092 bool applied_to_attacker = applied_both || (whom_is_self &&
is_attacker_);
2095 bool applied_to_defender = applied_both || (whom_is_self && !
is_attacker_);
2124 return (cond_eq.
empty() || def == cond_eq.
to_int()) &&
2145 return formula.
evaluate(callable).as_decimal() / 1000.0 ;
2152 composite_value_(def),
2153 composite_double_value_(def)
2155 std::map<double, active_ability_list> base_list;
2157 double priority =
i.ability().priority();
2158 if(base_list[priority].empty()) {
2161 base_list[priority].emplace_back(
i);
2164 for(
auto base : base_list) {
2172 int value_set = def;
2173 std::map<std::string,individual_effect> values_add;
2174 std::map<std::string,individual_effect> values_sub;
2175 std::map<std::string,individual_effect> values_mul;
2176 std::map<std::string,individual_effect> values_div;
2181 utils::optional<int> max_value = utils::nullopt;
2182 utils::optional<int> min_value = utils::nullopt;
2185 const config&
cfg = ability.ability_cfg();
2186 const std::string& effect_id =
cfg[
cfg[
"id"].
empty() ?
"name" :
"id"];
2193 int value_cum = wham !=
EFFECT_CUMULABLE &&
cfg[
"cumulative"].to_bool() ? std::max(def, value) : value;
2195 set_effect_cum.
set(
SET, set_effect_cum.
value + value_cum, ability.ability_cfg(), ability.teacher_loc);
2197 set_effect_cum.
set(
SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2201 set_effect_min.
set(
SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2202 set_effect_max.
set(
SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2205 if(value_cum > set_effect_max.
value) {
2206 set_effect_max.
set(
SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2208 if(value_cum < set_effect_min.
value) {
2209 set_effect_min.
set(
SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2218 max_value = max_value ? std::min(*max_value, value) : value;
2222 min_value = min_value ? std::max(*min_value, value) : value;
2229 if(add_effect == values_add.end() || add > add_effect->second.value) {
2230 values_add[effect_id].set(
ADD, add, ability.ability_cfg(), ability.teacher_loc);
2236 if(sub_effect == values_sub.end() || sub < sub_effect->second.value) {
2237 values_sub[effect_id].set(
ADD, sub, ability.ability_cfg(), ability.teacher_loc);
2243 if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2244 values_mul[effect_id].set(
MUL, multiply, ability.ability_cfg(), ability.teacher_loc);
2251 ERR_NG <<
"division by zero with divide= in ability/weapon special " << effect_id;
2255 if(div_effect == values_div.end() || divide > div_effect->second.value) {
2256 values_div[effect_id].set(
DIV, divide, ability.ability_cfg(), ability.teacher_loc);
2263 value_set = std::max(set_effect_max.
value, 0) + std::min(set_effect_min.
value, 0);
2264 if(set_effect_max.
value > def) {
2267 if(set_effect_min.
value < def) {
2280 double multiplier = 1.0;
2281 double divisor = 1.0;
2283 for(
const auto& val : values_mul) {
2284 multiplier *= val.second.value/100.0;
2288 for(
const auto& val : values_div) {
2289 divisor *= val.second.value/100.0;
2294 for(
const auto& val : values_add) {
2295 addition += val.second.value;
2301 int substraction = 0;
2302 for(
const auto& val : values_sub) {
2303 substraction += val.second.value;
2308 value_set += set_effect_cum.
value;
2314 if(max_value && min_value && *min_value < *max_value) {
2316 }
else if(max_value && !min_value) {
2318 }
else if(min_value && !max_value) {
static void add_name(std::string &temp_string, bool active, const std::string &name, std::set< std::string > &checking_name)
static lg::log_domain log_engine("engine")
static void add_string_to_vector(std::vector< std::string > &image_list, const config &cfg, const std::string &attribute_name)
static bool overwrite_special_affects(const unit_ability_t &ab)
static void add_name_list(std::string &temp_string, std::string &weapon_abilities, std::set< std::string > &checking_name, const std::string &from_str)
static lg::log_domain log_wml("wml")
std::vector< ability_ptr > ability_vector
void append_if(const active_ability_list &other, const Predicate &predicate)
Appends any abilities from other for which the given condition returns true to this,...
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)
void append(const active_ability_list &other)
Appends the abilities from other to this, ignores other.loc()
specials_context_t(const attack_type &weapon, bool attacking)
Initialize weapon specials context for listing.
active_ability_list overwrite_special_overwriter(active_ability_list overwriters) const
Filter a list of abilities or weapon specials, removing any entries that don't own the overwrite_spec...
bool has_active_special_or_ability_id(const std::string &special) const
static void weapon_specials_impl_adj(std::string &temp_string, const unit_const_ptr &self, const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const map_location &self_loc, AFFECTS whom, std::set< std::string > &checking_name, const std::set< std::string > &checking_tags={}, const std::string &affect_adjacents="")
const_attack_ptr other_attack_
void add_formula_context(wfl::map_formula_callable &) const
const std::string & range() const
active_ability_list get_weapon_ability(const std::string &ability) const
Returns list for weapon like abilities for each ability type.
bool has_special_or_ability_with_filter(const config &filter) const
check if special matche
const std::string & type() const
bool has_special_or_ability(const std::string &special) const
used for abilities used like weapon and true specials
std::string describe_weapon_specials() const
Returns a comma-separated string of active names for the specials of *this.
active_ability_list get_specials_and_abilities(const std::string &special) const
std::vector< unit_ability_t::tooltip_info > special_tooltips(boost::dynamic_bitset<> *active_list=nullptr) const
Returns a vector of names and descriptions for the specials of *this.
std::string describe_weapon_specials_value(const std::set< std::string > &checking_tags) const
std::vector< unit_ability_t::tooltip_info > abilities_special_tooltips(boost::dynamic_bitset<> *active_list) const
static void weapon_specials_impl_self(std::string &temp_string, const unit_const_ptr &self, const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const map_location &self_loc, AFFECTS whom, std::set< std::string > &checking_name, const std::set< std::string > &checking_tags={})
weapon_specials_impl_self and weapon_specials_impl_adj : check if special name can be added.
bool special_active(const unit_ability_t &ab, AFFECTS whom) const
bool special_tooltip_active(const unit_ability_t &ab) const
Returns whether or not the given special is active for the specified unit disregarding other units,...
active_ability_list get_specials(const std::string &special) const
Returns the currently active specials as an ability list, given the current context (see set_specials...
bool overwrite_special_checking(active_ability_list &overwriters, const unit_ability_t &ab) const
Check whether cfg would be overwritten by any element of overwriters.
const ability_vector & specials() const
static bool special_active_impl(const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const unit_ability_t &special, AFFECTS whom)
Returns whether or not the given special is active for the specified unit, based on the current conte...
Variant for storing WML attributes.
int to_int(int def=0) const
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.
void remove_attribute(std::string_view key)
config & add_child(std::string_view key)
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.
bool matches(const config &filter) const
config & child_or_add(std::string_view key)
Returns a reference to the first child with the given key.
child_itors child_range(std::string_view key)
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.
const_all_children_itors all_children_range() const
In-order iteration over all children.
std::string debug() const
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
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.
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
const display_context & context() const
static display * get_singleton()
Returns the display object if a display object exists.
virtual const unit_map & units() const override
virtual const gamemap & map() const override
bool is_village(const map_location &loc) const
This class stores all the data for a single 'side' (in game nomenclature).
bool is_enemy(int n) const
std::vector< individual_effect > effect_list_
double composite_double_value_
void effect_impl(const active_ability_list &list, int def, const const_attack_ptr &att, 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
static std::string get_help_topic_id(const config &cfg)
apply_to_t apply_to() const
static config vector_to_cfg(const ability_vector &abilities)
static void do_compat_fixes(config &cfg, const std::string &tag, bool inside_attack)
const config & cfg() const
static void parse_vector(const config &abilities_cfg, ability_vector &res, bool inside_attack)
bool affects_enemies() const
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
bool in_specials_tag() const
static ability_vector filter_tag(const ability_vector &vec, const std::string &tag)
const std::string & id() const
unit_ability_t(std::string tag, config cfg, bool inside_attack)
static ability_ptr create(std::string tag, config cfg, bool inside_attack)
affects_allies_t affects_allies_
static ability_vector clone(const ability_vector &vec)
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)
static ability_vector cfg_to_vector(const config &abilities_cfg, bool inside_attack)
affects_allies_t affects_allies() const
bool active_on_matches(bool student_is_attacker) const
bool affects_self() const
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
unit_filter & set_use_flat_tod(bool value)
Container associating units to locations.
unit_ptr find_unit_ptr(const T &val)
unit_iterator find(std::size_t id)
const unit_type_map & types() const
A single unit type that the player may recruit.
This class represents a single unit of a specific type.
A variable-expanding proxy for the config class.
Represents version numbers.
int as_int(int fallback=0) const
Returns the variant's value as an integer.
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.
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
const ability_vector & abilities() const
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.
active_ability_list get_abilities_weapons(const std::string &tag_name, const map_location &loc, const_attack_ptr weapon=nullptr, const_attack_ptr opp_weapon=nullptr) const
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.
const std::string & id() const
Gets this unit's id.
int side() const
The side this unit belongs to.
std::size_t underlying_id() const
This unit's unique internal ID.
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
std::vector< std::string > halo_or_icon_abilities(const std::string &image_type) const
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.
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.
Standard logging facilities (interface).
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
const color_t TITLE_COLOR
const color_t INACTIVE_COLOR
static bool is_active(const widget *wgt)
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
static int individual_value_double(const config::attribute_value *v, int def, const active_ability &ability, const map_location &loc, const const_attack_ptr &att)
bool filter_base_matches(const config &cfg, int def)
@ EFFECT_WITHOUT_CLAMP_MIN_MAX
static int individual_value_int(const config::attribute_value *v, int def, const active_ability &ability, const map_location &loc, const const_attack_ptr &att)
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].
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.
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
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.
std::vector< std::string > split(const config_attribute_value &val)
std::string::const_iterator iterator
std::shared_ptr< const unit > unit_const_ptr
std::shared_ptr< const attack_type > const_attack_ptr
std::shared_ptr< unit_ability_t > ability_ptr
std::shared_ptr< unit > unit_ptr
Data typedef for active_ability_list.
const config & ability_cfg() const
map_location teacher_loc
The location of the teacher, that is the unit who owns the ability tags (different from student becau...
const unit_ability_t & ability() const
map_location student_loc
Used by the formula in the ability.
Encapsulates the map of the game.
static std::vector< direction > parse_directions(const std::string &str)
Parse_directions takes a comma-separated list, and filters out any invalid directions.
direction
Valid directions which can be moved in our hexagonal world.
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
static const map_location & null_location()
void set(value_modifier t, int val, const config &abil, const map_location &l)
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
static map_location::direction s
unit_type_data unit_types