diff --git a/changelog b/changelog index 09caf203f520..9c5229a8c25b 100644 --- a/changelog +++ b/changelog @@ -17,6 +17,10 @@ Version 1.13.4+dev: nearest neighbor scaling. * Support delayed_variable_substitution= in [on_undo], [on_redo] * formula= in SUF can now reference $other_unit via the formula variable "other" + * formula= now supported in location, side, and weapon filters + * Weapon filters now support number, parry, accuracy, and movement_used + * New [has_attack] in standard unit filters; supercedes has_weapon= and + uses full weapon filter. * AiWML: * Simplified aspect syntax which works for all aspects, present and future: * All aspects with simple values can be specified as key=value @@ -156,6 +160,9 @@ Version 1.13.4+dev: parameter and returns true or false. * Wesnoth formula engine: * Formulas in unit filters can now access nearly all unit attributes + * Nearly All side, weapon, and terrain attributes available to Lua code + are now also exposed to WFL. The exceptions are mainly translatable + strings. * New syntax features: * String interpolation syntax. Within a formula string (enclosed in 'single quotes'), the syntax [some_formula] interpolates the result diff --git a/src/formula/callable_objects.cpp b/src/formula/callable_objects.cpp index d76f23ee8992..4eafc80e38c8 100644 --- a/src/formula/callable_objects.cpp +++ b/src/formula/callable_objects.cpp @@ -15,6 +15,7 @@ #include "formula/callable_objects.hpp" #include "units/unit.hpp" #include "units/formula_manager.hpp" +#include "utils/foreach.hpp" template variant convert_map( const std::map& input_map ) { @@ -80,22 +81,38 @@ void location_callable::serialize_to_string(std::string& str) const variant attack_type_callable::get_value(const std::string& key) const { - if(key == "id") { + if(key == "id" || key == "name") { return variant(att_.id()); + } else if(key == "description") { + return variant(att_.name()); } else if(key == "type") { return variant(att_.type()); + } else if(key == "icon") { + return variant(att_.icon()); } else if(key == "range") { return variant(att_.range()); } else if(key == "damage") { return variant(att_.damage()); - } else if(key == "number_of_attacks") { + } else if(key == "number_of_attacks" || key == "number" || key == "num_attacks" || key == "attacks") { return variant(att_.num_attacks()); - } else if(key == "special") { - std::vector > specials = att_.special_tooltips(); + } else if(key == "attack_weight") { + return variant(att_.attack_weight(), variant::DECIMAL_VARIANT); + } else if(key == "defense_weight") { + return variant(att_.defense_weight(), variant::DECIMAL_VARIANT); + } else if(key == "accuracy") { + return variant(att_.accuracy()); + } else if(key == "parry") { + return variant(att_.parry()); + } else if(key == "movement_used") { + return variant(att_.movement_used()); + } else if(key == "specials" || key == "special") { + const config specials = att_.specials(); std::vector res; - for( size_t i = 0; i != specials.size(); ++i ) { - res.push_back( variant(specials[i].first.base_str()) ); + FOREACH(const AUTO& special , specials.all_children_range()) { + if(!special.cfg["id"].empty()) { + res.push_back(variant(special.cfg["id"].str())); + } } return variant(&res); } @@ -106,12 +123,19 @@ variant attack_type_callable::get_value(const std::string& key) const void attack_type_callable::get_inputs(std::vector* inputs) const { using game_logic::FORMULA_READ_ONLY; - inputs->push_back(game_logic::formula_input("id", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("name", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("type", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("description", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("icon", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("range", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("damage", FORMULA_READ_ONLY)); - inputs->push_back(game_logic::formula_input("number_of_attacks", FORMULA_READ_ONLY)); - inputs->push_back(game_logic::formula_input("special", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("number", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("accuracy", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("parry", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("movement_used", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("attack_weight", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("defense_weight", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("specials", FORMULA_READ_ONLY)); } int attack_type_callable::do_compare(const formula_callable* callable) const @@ -431,6 +455,24 @@ variant terrain_callable::get_value(const std::string& key) const return variant(new location_callable(loc_)); } else if(key == "id") { return variant(std::string(t_.id())); + } else if(key == "name") { + return variant(t_.name()); + } else if(key == "editor_name") { + return variant(t_.editor_name()); + } else if(key == "description") { + return variant(t_.description()); + } else if(key == "icon") { + return variant(t_.icon_image()); + } else if(key == "light") { + return variant(t_.light_bonus(0)); + } else if(key == "village") { + return variant(t_.is_village()); + } else if(key == "castle") { + return variant(t_.is_castle()); + } else if(key == "keep") { + return variant(t_.is_keep()); + } else if(key == "healing") { + return variant(t_.gives_healing()); } else return variant(); } @@ -442,6 +484,15 @@ void terrain_callable::get_inputs(std::vector* inputs inputs->push_back(game_logic::formula_input("y", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("loc", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("id", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("name", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("editor_name", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("description", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("icon", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("light", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("village", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("castle", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("keep", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("healing", FORMULA_READ_ONLY)); } int terrain_callable::do_compare(const formula_callable* callable) const diff --git a/src/formula/callable_objects.hpp b/src/formula/callable_objects.hpp index 1a8aeded583f..e1a5aef82782 100644 --- a/src/formula/callable_objects.hpp +++ b/src/formula/callable_objects.hpp @@ -188,26 +188,66 @@ class unit_type_callable : public game_logic::formula_callable { CALLABLE_WRAPPER_START(team) +CALLABLE_WRAPPER_INPUT(side) +CALLABLE_WRAPPER_INPUT(id) CALLABLE_WRAPPER_INPUT(gold) CALLABLE_WRAPPER_INPUT(start_gold) CALLABLE_WRAPPER_INPUT(base_income) +CALLABLE_WRAPPER_INPUT(total_income) CALLABLE_WRAPPER_INPUT(village_gold) CALLABLE_WRAPPER_INPUT(village_support) +CALLABLE_WRAPPER_INPUT(recall_cost) CALLABLE_WRAPPER_INPUT(name) CALLABLE_WRAPPER_INPUT(is_human) CALLABLE_WRAPPER_INPUT(is_ai) CALLABLE_WRAPPER_INPUT(is_network) +CALLABLE_WRAPPER_INPUT(fog) +CALLABLE_WRAPPER_INPUT(shroud) +CALLABLE_WRAPPER_INPUT(hidden) +CALLABLE_WRAPPER_INPUT(flag) +CALLABLE_WRAPPER_INPUT(flag_icon) +CALLABLE_WRAPPER_INPUT(team_name) +CALLABLE_WRAPPER_INPUT(color) +CALLABLE_WRAPPER_INPUT(share_vision) +CALLABLE_WRAPPER_INPUT(carryover_bonus) +CALLABLE_WRAPPER_INPUT(carryover_percentage) +CALLABLE_WRAPPER_INPUT(carryover_add) +CALLABLE_WRAPPER_INPUT(recruit) CALLABLE_WRAPPER_INPUT_END +CALLABLE_WRAPPER_FN(side) +CALLABLE_WRAPPER_FN2(id, save_id) +CALLABLE_WRAPPER_FN(save_id) CALLABLE_WRAPPER_FN(gold) - if(key == "start_gold") { \ - return variant(lexical_cast(object_.start_gold())); \ + if(key == "start_gold") { + return variant(lexical_cast(object_.start_gold())); } else CALLABLE_WRAPPER_FN(base_income) +CALLABLE_WRAPPER_FN(total_income) CALLABLE_WRAPPER_FN(village_gold) CALLABLE_WRAPPER_FN(village_support) +CALLABLE_WRAPPER_FN(recall_cost) CALLABLE_WRAPPER_FN2(is_human, is_local_human) CALLABLE_WRAPPER_FN2(is_ai, is_local_ai) CALLABLE_WRAPPER_FN(is_network) +CALLABLE_WRAPPER_FN2(fog, uses_fog) +CALLABLE_WRAPPER_FN2(shroud, uses_shroud) +CALLABLE_WRAPPER_FN(hidden) +CALLABLE_WRAPPER_FN(flag) +CALLABLE_WRAPPER_FN(flag_icon) +CALLABLE_WRAPPER_FN(team_name) +CALLABLE_WRAPPER_FN(color) +CALLABLE_WRAPPER_FN2(share_vision, share_vision().to_string) +CALLABLE_WRAPPER_FN(carryover_bonus) +CALLABLE_WRAPPER_FN(carryover_percentage) +CALLABLE_WRAPPER_FN(carryover_add) + if(key == "recruit") { + const std::set& recruits = object_.recruits(); + std::vector result; + for(std::set::const_iterator it = recruits.begin(); it != recruits.end(); ++it) { + result.push_back(variant(*it)); + } + return variant(&result); + } else CALLABLE_WRAPPER_END #endif diff --git a/src/side_filter.cpp b/src/side_filter.cpp index 4de7909cf971..1cca678cd677 100644 --- a/src/side_filter.cpp +++ b/src/side_filter.cpp @@ -31,6 +31,8 @@ #include "units/filter.hpp" #include "units/map.hpp" #include "variable.hpp" +#include "formula/callable_objects.hpp" +#include "formula/formula.hpp" #include @@ -241,6 +243,21 @@ bool side_filter::match_internal(const team &t) const } } } + + if (cfg_.has_attribute("formula")) { + try { + const team_callable callable(t); + const game_logic::formula form(cfg_["formula"]); + if(!form.evaluate(callable).as_bool()) { + return false; + } + return true; + } catch(game_logic::formula_error& e) { + lg::wml_error() << "Formula error in side filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n"; + // Formulae with syntax errors match nothing + return false; + } + } return true; } diff --git a/src/terrain/filter.cpp b/src/terrain/filter.cpp index 948fa87d197a..818fe34d308f 100644 --- a/src/terrain/filter.cpp +++ b/src/terrain/filter.cpp @@ -30,6 +30,8 @@ #include "units/unit.hpp" #include "units/filter.hpp" #include "variable.hpp" +#include "formula/callable_objects.hpp" +#include "formula/formula.hpp" #include @@ -325,6 +327,24 @@ bool terrain_filter::match_internal(const map_location& loc, const bool ignore_x return false; } } + + if(cfg_.has_attribute("formula")) { + try { + const gamemap& map = fc_->get_disp_context().map(); + t_translation::t_terrain t = map.get_terrain(loc); + const terrain_type& ter = map.tdata()->get_terrain_info(t); + const terrain_callable callable(ter,loc); + const game_logic::formula form(cfg_["formula"]); + if(!form.evaluate(callable).as_bool()) { + return false; + } + return true; + } catch(game_logic::formula_error& e) { + lg::wml_error() << "Formula error in location filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n"; + // Formulae with syntax errors match nothing + return false; + } + } return true; } diff --git a/src/units/attack_type.cpp b/src/units/attack_type.cpp index 06ba029b5b6a..4508bc278610 100644 --- a/src/units/attack_type.cpp +++ b/src/units/attack_type.cpp @@ -20,6 +20,8 @@ #include "global.hpp" #include "units/attack_type.hpp" +#include "formula/callable_objects.hpp" +#include "formula/formula.hpp" #include "log.hpp" #include "serialization/string_utils.hpp" @@ -96,9 +98,14 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil { const std::vector& filter_range = utils::split(filter["range"]); const std::string& filter_damage = filter["damage"]; + const std::string& filter_attacks = filter["number"]; + const std::string& filter_accuracy = filter["accuracy"]; + const std::string& filter_parry = filter["parry"]; + const std::string& filter_movement = filter["movement_used"]; const std::vector filter_name = utils::split(filter["name"]); const std::vector filter_type = utils::split(filter["type"]); const std::string filter_special = filter["special"]; + const std::string filter_formula = filter["formula"]; if ( !filter_range.empty() && std::find(filter_range.begin(), filter_range.end(), attack.range()) == filter_range.end() ) return false; @@ -106,6 +113,18 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges(filter_damage)) ) return false; + if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges(filter_attacks))) + return false; + + if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges(filter_accuracy))) + return false; + + if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges(filter_parry))) + return false; + + if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges(filter_movement))) + return false; + if ( !filter_name.empty() && std::find(filter_name.begin(), filter_name.end(), attack.id()) == filter_name.end() ) return false; @@ -114,6 +133,21 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil if ( !filter_special.empty() && !attack.get_special_bool(filter_special, true) ) return false; + + if (!filter_formula.empty()) { + try { + const attack_type_callable callable(attack); + const game_logic::formula form(filter_formula); + if(!form.evaluate(callable).as_bool()) { + return false; + } + return true; + } catch(game_logic::formula_error& e) { + lg::wml_error() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n"; + // Formulae with syntax errors match nothing + return false; + } + } // Passed all tests. return true; diff --git a/src/units/filter.cpp b/src/units/filter.cpp index 1f78f8bb7396..2664dacbb306 100644 --- a/src/units/filter.cpp +++ b/src/units/filter.cpp @@ -399,7 +399,21 @@ bool basic_unit_filter_impl::internal_matches_filter(const unit & u, const map_l } } - if (!vcfg["has_weapon"].blank()) { + if (vcfg.has_child("has_attack")) { + const vconfig& weap_filter = vcfg.child("has_attack"); + bool has_weapon = false; + const std::vector& attacks = u.attacks(); + for(std::vector::const_iterator i = attacks.begin(); + i != attacks.end(); ++i) { + if(i->matches_filter(weap_filter.get_parsed_config())) { + has_weapon = true; + break; + } + } + if(!has_weapon) { + return false; + } + } else if (!vcfg["has_weapon"].blank()) { std::string weapon = vcfg["has_weapon"]; bool has_weapon = false; const std::vector& attacks = u.attacks();