Skip to content

Commit

Permalink
Several expansions to filters
Browse files Browse the repository at this point in the history
- Side, location, and weapon filters now support formulas
- Additional keys in weapon filters; now covers everything except weights
- [has_attack] in unit filters
- Formula view of sides, terrain, and weapons expanded to mostly match Lua view
  • Loading branch information
CelticMinstrel committed Mar 29, 2016
1 parent d8ee9dc commit 0f072f3
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 12 deletions.
7 changes: 7 additions & 0 deletions changelog
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
69 changes: 60 additions & 9 deletions src/formula/callable_objects.cpp
Expand Up @@ -15,6 +15,7 @@
#include "formula/callable_objects.hpp"
#include "units/unit.hpp"
#include "units/formula_manager.hpp"
#include "utils/foreach.hpp"

template <typename T, typename K>
variant convert_map( const std::map<T, K>& input_map ) {
Expand Down Expand Up @@ -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<std::pair<t_string, t_string> > 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<variant> 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);
}
Expand All @@ -106,12 +123,19 @@ variant attack_type_callable::get_value(const std::string& key) const
void attack_type_callable::get_inputs(std::vector<game_logic::formula_input>* 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
Expand Down Expand Up @@ -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();
}
Expand All @@ -442,6 +484,15 @@ void terrain_callable::get_inputs(std::vector<game_logic::formula_input>* 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
Expand Down
44 changes: 42 additions & 2 deletions src/formula/callable_objects.hpp
Expand Up @@ -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<int>(object_.start_gold())); \
if(key == "start_gold") {
return variant(lexical_cast<int>(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<std::string>& recruits = object_.recruits();
std::vector<variant> result;
for(std::set<std::string>::const_iterator it = recruits.begin(); it != recruits.end(); ++it) {
result.push_back(variant(*it));
}
return variant(&result);
} else
CALLABLE_WRAPPER_END

#endif
17 changes: 17 additions & 0 deletions src/side_filter.cpp
Expand Up @@ -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 <boost/foreach.hpp>

Expand Down Expand Up @@ -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;
}
Expand Down
20 changes: 20 additions & 0 deletions src/terrain/filter.cpp
Expand Up @@ -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 <boost/foreach.hpp>

Expand Down Expand Up @@ -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;
}
Expand Down
34 changes: 34 additions & 0 deletions src/units/attack_type.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -96,16 +98,33 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil
{
const std::vector<std::string>& 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<std::string> filter_name = utils::split(filter["name"]);
const std::vector<std::string> 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;

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;

Expand All @@ -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;
Expand Down
16 changes: 15 additions & 1 deletion src/units/filter.cpp
Expand Up @@ -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<attack_type>& attacks = u.attacks();
for(std::vector<attack_type>::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<attack_type>& attacks = u.attacks();
Expand Down

0 comments on commit 0f072f3

Please sign in to comment.