diff --git a/changelog b/changelog
index 6c89f471d0ad..0f030521387e 100644
--- a/changelog
+++ b/changelog
@@ -15,6 +15,9 @@ Version 1.13.8+dev:
* Warnings for using deprecated Lua functions now only appear in debug mode.
* wesnoth.game_config is now accessible in application and mapgen kernels,
though some of its contents are missing.
+ * New extra argument to wesnoth.match_location and wesnoth.get_locations
+ allows specifying the teleport_unit.
+ * Support the extra wesnoth.match_unit arguments also in wesnoth.get_units
* Multiplayer:
* When set to remember your password, Wesnoth now encrypts it. It is still
possible to obtain the password from preferences, but it's no longer as
@@ -32,6 +35,18 @@ Version 1.13.8+dev:
* Miscellaneous and Bug Fixes:
* Add --report/-R command line switch for printing the same report from the
Game Version dialog's clipboard function to stdout.
+ * WFL Engine
+ * Add owner key to terrain space callable, for villages
+ * Location formulas in [tunnel] now have a teleport_unit variable
+ * Fix a crash when attempting to call a non-existent function
+ * The following previously FormulaAI-exclusive functions are now also available
+ in filter formulas (SUF, SLF, SSF, SWF):
+ adjacent_locs, location_in_radius, get_unit_type, unit_at, defense_on,
+ chance_to_hit, movement_cost
+ * New builtin functions for manipulating locations (available to all formulas):
+ adjacent_locs, are_adjacent, relative_dir, direction_from, rotate_loc_around
+ * New enemy_of function checks if its second argument is an enemy of the first
+ Arguments can be side or unit objects, or integer side indices (1..n)
* WML Engine
* If ai_algorithm is used in [modify_side][ai], it now replaces the whole AI
with the contents of [modify_side][ai], instead of appending these parameters.
@@ -46,6 +61,8 @@ Version 1.13.8+dev:
* Fix some hotkeys not working (issues #1737 and #1769)
* New vision_cost and jamming_cost keys in SUF
* Integer SUF keys (eg level) now accept a list of ranges
+ * Fix $other_unit variable in SUF not being available in nested [and] [or] [not]
+ * Unit ability values can now be specified with WFL
Version 1.13.8:
* Campaigns:
diff --git a/data/campaigns/The_Hammer_of_Thursagan/utils/abilities.cfg b/data/campaigns/The_Hammer_of_Thursagan/utils/abilities.cfg
index 0822c15996d3..e00a9d97a02a 100644
--- a/data/campaigns/The_Hammer_of_Thursagan/utils/abilities.cfg
+++ b/data/campaigns/The_Hammer_of_Thursagan/utils/abilities.cfg
@@ -6,10 +6,9 @@ _" The presence of this unit inspires own units next to it to deal more damage i
#define ABILITY_INSPIRE
# Canned definition of the Inspire ability to be included in an
# [abilities] clause.
- # Note: Works only on units of level 0-5
[leadership]
id=inspire
- value=150
+ value="(25 * (level - other.level + 1))"
cumulative=no
name= _ "inspire"
female_name= _ "female^inspire"
@@ -17,62 +16,7 @@ _" The presence of this unit inspires own units next to it to deal more damage i
affect_self=no
[affect_adjacent]
[filter]
- level=$($other_unit.level - 5)
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=inspire
- value=125
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- level=$($other_unit.level - 4)
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=inspire
- value=100
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- level=$($other_unit.level - 3)
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=inspire
- value=75
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- level=$($other_unit.level - 2)
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=inspire
- value=50
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- level=$($other_unit.level - 1)
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=inspire
- value=25
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- level=$other_unit.level
+ formula="level <= other.level"
[/filter]
[/affect_adjacent]
[/leadership]
diff --git a/data/core/_main.cfg b/data/core/_main.cfg
index 0abbeba562c8..e95fbe9d68eb 100644
--- a/data/core/_main.cfg
+++ b/data/core/_main.cfg
@@ -8,7 +8,6 @@
wesnoth.dofile 'lua/backwards-compatibility.lua'
wesnoth.dofile 'lua/wml-tags.lua'
wesnoth.dofile 'lua/feeding.lua'
-wesnoth.dofile 'lua/teleport_filter.lua'
>>
[/lua]
diff --git a/data/core/macros/abilities.cfg b/data/core/macros/abilities.cfg
index 54274ada27d4..6b66697d52bc 100644
--- a/data/core/macros/abilities.cfg
+++ b/data/core/macros/abilities.cfg
@@ -99,10 +99,9 @@ This ability will not cure an affected unit of poison, however, only delay its e
#define ABILITY_LEADERSHIP
# Canned definition of the Leadership ability to be included in an
# [abilities] clause.
- # Note: Works only on units of level 1-5
[leadership]
id=leadership
- value=125
+ value="(25 * (level - other.level))"
cumulative=no
name= _ "leadership"
female_name= _ "female^leadership"
@@ -112,51 +111,7 @@ Adjacent own units of lower level will do more damage in battle. When a unit adj
affect_self=no
[affect_adjacent]
[filter]
- lua_function="leadership_receiver_filter_5"
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=leadership
- value=100
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- lua_function="leadership_receiver_filter_4"
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=leadership
- value=75
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- lua_function="leadership_receiver_filter_3"
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=leadership
- value=50
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- lua_function="leadership_receiver_filter_2"
- [/filter]
- [/affect_adjacent]
- [/leadership]
- [leadership]
- id=leadership
- value=25
- cumulative=no
- affect_self=no
- [affect_adjacent]
- [filter]
- lua_function="leadership_receiver_filter_1"
+ formula="level < other.level"
[/filter]
[/affect_adjacent]
[/leadership]
@@ -201,15 +156,18 @@ Any units adjacent to this unit will fight as if it were dusk when it is night,
description= _ "This unit may teleport between any two empty villages owned by its side using one of its moves."
[tunnel]
id=village_teleport
-
[source]
terrain=*^V*
- lua_function="teleport_source_filter"
+ formula="
+ owner = teleport_unit.side and (unit = teleport_unit or not unit)
+ where
+ unit = unit_at(loc)
+ "
[/source]
[target]
terrain=*^V*
- lua_function="teleport_target_filter"
+ formula="owner = teleport_unit.side and not unit_at(loc)"
[/target]
[filter]
@@ -349,7 +307,11 @@ Enemy units cannot see this unit while it is in deep water, except if they have
multiply=2
active_on=offense
[filter_opponent]
- lua_function="backstab_defender_filter"
+ formula="
+ enemy_of(self, flanker) and not flanker.petrified
+ where
+ flanker = unit_at(direction_from(loc, other.facing))
+ "
[/filter_opponent]
[/damage]
#enddef
diff --git a/data/lua/teleport_filter.lua b/data/lua/teleport_filter.lua
deleted file mode 100644
index 93006a44325c..000000000000
--- a/data/lua/teleport_filter.lua
+++ /dev/null
@@ -1,58 +0,0 @@
-function teleport_source_filter(x, y)
- if wesnoth.get_village_owner(x, y) ~= wesnoth.get_variable("teleport_unit.side") then
- return false
- end
- local blocking_unit = wesnoth.get_unit(x, y)
- return (not blocking_unit) or blocking_unit.id == wesnoth.get_variable("teleport_unit.id")
-end
-
-function teleport_target_filter(x, y)
- if wesnoth.get_village_owner(x, y) ~= wesnoth.get_variable("teleport_unit.side") then
- return false
- end
- local blocking_unit = wesnoth.get_unit(x, y)
- return not blocking_unit
-end
-
-function backstab_defender_filter(defender)
-
- local attacker_x = wesnoth.get_variable("other_unit.x")
- local attacker_y = wesnoth.get_variable("other_unit.y")
-
- local defender_x = defender.x
- local defender_y = defender.y
-
- local adjacent = {wesnoth.map.get_adjacent_tiles(defender_x, defender_y)}
-
- local attacker_pos_index = nil
- for i,v in ipairs(adjacent) do
- if v[1] == attacker_x and v[2] == attacker_y then
- attacker_pos_index = i
- end
- end
-
- if attacker_pos_index == nil then
- -- Attack not from adjacent location
- return false
- end
-
- local opposite_pos_index = (((attacker_pos_index + 3) - 1) % 6) + 1
- local opposite_unit = wesnoth.get_unit(adjacent[opposite_pos_index][1], adjacent[opposite_pos_index][2])
-
- if not opposite_unit then
- -- No opposite unit
- return false
- end
-
- if opposite_unit.status.petrified then
- return false
- end
-
- return wesnoth.is_enemy(opposite_unit.side, defender.side)
-end
-
-for i = 1, 5 do
- _G["leadership_receiver_filter_" .. i] = function(receiver)
- return receiver.level == wesnoth.get_variable("other_unit.level") - i
- end
-end
diff --git a/projectfiles/VC12/wesnoth.vcxproj b/projectfiles/VC12/wesnoth.vcxproj
index 946156bd9fb1..ec7dab057ac3 100644
--- a/projectfiles/VC12/wesnoth.vcxproj
+++ b/projectfiles/VC12/wesnoth.vcxproj
@@ -1088,6 +1088,12 @@
$(IntDir)Formula\
$(IntDir)Formula\
+
+ $(IntDir)Formula\
+ $(IntDir)Formula\
+ $(IntDir)Formula\
+ $(IntDir)Formula\
+
$(IntDir)Formula\
$(IntDir)Formula\
@@ -3596,6 +3602,7 @@
+
diff --git a/projectfiles/VC12/wesnoth.vcxproj.filters b/projectfiles/VC12/wesnoth.vcxproj.filters
index 9a9d41452167..f3bff16d2805 100644
--- a/projectfiles/VC12/wesnoth.vcxproj.filters
+++ b/projectfiles/VC12/wesnoth.vcxproj.filters
@@ -1538,6 +1538,9 @@
Preferences
+
+ Formula
+
@@ -2982,6 +2985,9 @@
Preferences
+
+ Formula
+
diff --git a/source_lists/wesnoth b/source_lists/wesnoth
index 87d0dbd04ceb..6ce15a3a2fa6 100644
--- a/source_lists/wesnoth
+++ b/source_lists/wesnoth
@@ -108,6 +108,7 @@ formula/debugger.cpp
formula/debugger_fwd.cpp
formula/formula.cpp
formula/function.cpp
+formula/function_gamestate.cpp
formula/string_utils.cpp
formula/tokenizer.cpp
formula/variant.cpp
diff --git a/src/ai/formula/ai.cpp b/src/ai/formula/ai.cpp
index a98f551a7a30..c155ba600bc9 100644
--- a/src/ai/formula/ai.cpp
+++ b/src/ai/formula/ai.cpp
@@ -534,7 +534,7 @@ variant formula_ai::get_value(const std::string& key) const
return get_keeps();
} else if(key == "map")
{
- return variant(std::make_shared(resources::gameboard->map()));
+ return variant(std::make_shared(*resources::gameboard));
} else if(key == "villages")
{
return villages_from_set(resources::gameboard->map().villages());
diff --git a/src/ai/formula/function_table.cpp b/src/ai/formula/function_table.cpp
index 844dfc51922f..a10caddcce51 100644
--- a/src/ai/formula/function_table.cpp
+++ b/src/ai/formula/function_table.cpp
@@ -24,6 +24,7 @@
#include "ai/default/contexts.hpp"
+#include "formula/function_gamestate.hpp"
#include "attack_prediction.hpp"
#include "filesystem.hpp"
#include "game_board.hpp"
@@ -407,67 +408,6 @@ class nearest_loc_function : public function_expression {
};
-class adjacent_locs_function : public function_expression {
-public:
- adjacent_locs_function(const args_list& args)
- : function_expression("adjacent_locs", args, 1, 1)
- {
- }
-
-private:
- variant execute(const formula_callable& variables, formula_debugger *fdb) const {
- const map_location loc = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "adjacent_locs:location")).convert_to()->loc();
- map_location adj[6];
- get_adjacent_tiles(loc, adj);
-
- std::vector v;
- for(int n = 0; n != 6; ++n) {
- if (resources::gameboard->map().on_board(adj[n]) )
- v.emplace_back(std::make_shared(adj[n]));
- }
-
- return variant(v);
- }
-};
-
-
-class locations_in_radius_function : public function_expression {
-public:
- locations_in_radius_function(const args_list& args)
- : function_expression("locations_in_radius", args, 2, 2)
- {
- }
-
-private:
- variant execute(const formula_callable& variables, formula_debugger *fdb) const {
- const map_location loc = args()[0]->evaluate(variables,fdb).convert_to()->loc();
-
- int range = args()[1]->evaluate(variables,fdb).as_int();
-
- if( range < 0 )
- return variant();
-
- if(!range)
- return variant(std::make_shared(loc));
-
- std::vector res;
-
- get_tiles_in_radius( loc, range, res);
-
- std::vector v;
- v.reserve(res.size()+1);
- v.emplace_back(std::make_shared(loc));
-
- for(size_t n = 0; n != res.size(); ++n) {
- if (resources::gameboard->map().on_board(res[n]) )
- v.emplace_back(std::make_shared(res[n]));
- }
-
- return variant(v);
- }
-};
-
-
/** FormulaAI function to run fai script from file. Usable from in-game console.
* arguments[0] - required file name, follows the usual wml convention
*/
@@ -861,24 +801,6 @@ class outcomes_function : public function_expression {
// formula_ai& ai_;
//};
-class get_unit_type_function : public function_expression {
-public:
- explicit get_unit_type_function(const args_list& args)
- : function_expression("get_unit_type", args, 1, 1)
- {}
-private:
- variant execute(const formula_callable& variables, formula_debugger *fdb) const {
- const std::string type = args()[0]->evaluate(variables,add_debug_info(fdb,0,"get_unit_type:name")).as_string();
-
- const unit_type *ut = unit_types.find(type);
- if(ut) {
- return variant(std::make_shared(*ut));
- }
-
- return variant();
- }
-};
-
class rate_action_function : public function_expression {
@@ -1273,28 +1195,6 @@ class is_unowned_village_function : public function_expression {
};
-class unit_at_function : public function_expression {
-public:
- unit_at_function(const args_list& args)
- : function_expression("unit_at", args, 1, 1)
- {}
-private:
- variant execute(const formula_callable& variables, formula_debugger *fdb) const {
- variant loc_var = args()[0]->evaluate(variables,add_debug_info(fdb,0,"unit_at:location"));
- if (loc_var.is_null()) {
- return variant();
- }
- auto loc = loc_var.convert_to();
- const unit_map::const_iterator i = resources::gameboard->units().find(loc->loc());
- if(i != resources::gameboard->units().end()) {
- return variant(std::make_shared(*i));
- } else {
- return variant();
- }
- }
-};
-
-
class unit_moves_function : public function_expression {
public:
unit_moves_function(const args_list& args, const formula_ai& ai_object)
@@ -1347,144 +1247,6 @@ class units_can_reach_function : public function_expression {
}
};
-
-class defense_on_function : public function_expression {
-public:
- defense_on_function(const args_list& args)
- : function_expression("defense_on", args, 2, 2)
- {}
-private:
- variant execute(const formula_callable& variables, formula_debugger *fdb) const {
- variant u = args()[0]->evaluate(variables,add_debug_info(fdb,0,"defense_on:unit"));
- variant loc_var = args()[1]->evaluate(variables,add_debug_info(fdb,1,"defense_on:location"));
- if(u.is_null() || loc_var.is_null()) {
- return variant();
- }
-
- auto u_call = u.try_convert();
- auto u_type = u.try_convert();
- const map_location& loc = loc_var.convert_to()->loc();
-
- if (u_call)
- {
- const unit& un = u_call->get_unit();
-
- if( un.total_movement() < un.movement_cost( (resources::gameboard->map())[loc]) )
- return variant();
-
- if(!resources::gameboard->map().on_board(loc)) {
- return variant();
- }
-
- return variant(100 - un.defense_modifier((resources::gameboard->map())[loc]));
- }
-
- if (u_type)
- {
- const unit_type& un = u_type->get_unit_type();
-
- if( un.movement() < un.movement_type().movement_cost((resources::gameboard->map())[loc]) )
- return variant();
-
- if(!resources::gameboard->map().on_board(loc)) {
- return variant();
- }
-
- return variant(100 - un.movement_type().defense_modifier((resources::gameboard->map())[loc]));
- }
-
- return variant();
- }
-};
-
-
-class chance_to_hit_function : public function_expression {
-public:
- chance_to_hit_function(const args_list& args)
- : function_expression("chance_to_hit", args, 2, 2)
- {}
-private:
- variant execute(const formula_callable& variables, formula_debugger *fdb) const {
- variant u = args()[0]->evaluate(variables,add_debug_info(fdb,0,"chance_to_hit:unit"));
- variant loc_var = args()[1]->evaluate(variables,add_debug_info(fdb,1,"chance_to_hit:location"));
- if(u.is_null() || loc_var.is_null()) {
- return variant();
- }
-
- auto u_call = u.try_convert();
- auto u_type = u.try_convert();
- const map_location& loc = loc_var.convert_to()->loc();
-
- if (u_call)
- {
- const unit& un = u_call->get_unit();
-
- if(!resources::gameboard->map().on_board(loc)) {
- return variant();
- }
-
- return variant(un.defense_modifier((resources::gameboard->map())[loc]));
- }
-
- if (u_type)
- {
- const unit_type& un = u_type->get_unit_type();
-
- if(!resources::gameboard->map().on_board(loc)) {
- return variant();
- }
-
- return variant(un.movement_type().defense_modifier((resources::gameboard->map())[loc]));
- }
-
- return variant();
- }
-};
-
-
-class movement_cost_function : public function_expression {
-public:
- movement_cost_function(const args_list& args)
- : function_expression("movement_cost", args, 2, 2)
- {}
-private:
- variant execute(const formula_callable& variables, formula_debugger *fdb) const {
- variant u = args()[0]->evaluate(variables,add_debug_info(fdb,0,"movement_cost:unit"));
- variant loc_var = args()[1]->evaluate(variables,add_debug_info(fdb,0,"movement_cost:location"));
- if(u.is_null() || loc_var.is_null()) {
- return variant();
- }
- //we can pass to this function either unit_callable or unit_type callable
- auto u_call = u.try_convert();
- auto u_type = u.try_convert();
- const map_location& loc = loc_var.convert_to()->loc();
-
- if (u_call)
- {
- const unit& un = u_call->get_unit();
-
- if(!resources::gameboard->map().on_board(loc)) {
- return variant();
- }
-
- return variant(un.movement_cost((resources::gameboard->map())[loc]));
- }
-
- if (u_type)
- {
- const unit_type& un = u_type->get_unit_type();
-
- if(!resources::gameboard->map().on_board(loc)) {
- return variant();
- }
-
- return variant(un.movement_type().movement_cost((resources::gameboard->map())[loc]));
- }
-
- return variant();
- }
-};
-
class is_avoided_location_function : public function_expression {
public:
is_avoided_location_function(const args_list& args, const formula_ai& ai_object)
@@ -1589,54 +1351,48 @@ class ai_formula_function : public formula_function {
}
-// First macro is for functions taking an additional formula_ai argument.
-// Functions using the second macro could potentially be made core.
-#define AI_FUNCTION(name) add_function(#name, formula_function_ptr( \
+// This macro is for functions taking an additional formula_ai argument.
+// Functions using the other macro could potentially be made core.
+#define DECLARE_FAI_FUNCTION(name) add_function(#name, formula_function_ptr( \
new ai_formula_function(#name, ai)))
-#define FUNCTION(name) add_function(#name, formula_function_ptr( \
- new builtin_formula_function(#name)))
-
-ai_function_symbol_table::ai_function_symbol_table(ai::formula_ai& ai) : function_symbol_table(new action_function_symbol_table) {
- FUNCTION(outcomes);
- //AI_FUNCTION(evaluate_for_position);
- FUNCTION(move);
- FUNCTION(move_partial);
- FUNCTION(attack);
- AI_FUNCTION(rate_action);
- FUNCTION(recall);
- FUNCTION(recruit);
- FUNCTION(get_unit_type);
- AI_FUNCTION(is_avoided_location);
- FUNCTION(is_village);
- AI_FUNCTION(is_unowned_village);
- FUNCTION(unit_at);
- AI_FUNCTION(unit_moves);
- FUNCTION(set_unit_var);
- FUNCTION(fallback);
- FUNCTION(units_can_reach);
- AI_FUNCTION(debug_label);
- FUNCTION(defense_on);
- FUNCTION(chance_to_hit);
- FUNCTION(movement_cost);
- FUNCTION(max_possible_damage);
- FUNCTION(max_possible_damage_with_retaliation);
- AI_FUNCTION(next_hop);
- FUNCTION(adjacent_locs);
- FUNCTION(locations_in_radius);
- FUNCTION(castle_locs);
- FUNCTION(timeofday_modifier);
- AI_FUNCTION(distance_to_nearest_unowned_village);
- AI_FUNCTION(shortest_path);
- AI_FUNCTION(simplest_path);
- AI_FUNCTION(nearest_keep);
- AI_FUNCTION(suitable_keep);
- FUNCTION(nearest_loc);
- AI_FUNCTION(find_shroud);
- AI_FUNCTION(close_enemies);
- FUNCTION(calculate_outcome);
- AI_FUNCTION(run_file);
- AI_FUNCTION(calculate_map_ownership);
+
+ai_function_symbol_table::ai_function_symbol_table(ai::formula_ai& ai)
+ : function_symbol_table(std::make_shared(std::make_shared()))
+{
+ function_symbol_table& functions_table = *this;
+ DECLARE_WFL_FUNCTION(outcomes);
+ //DECLARE_FAI_FUNCTION(evaluate_for_position);
+ DECLARE_WFL_FUNCTION(move);
+ DECLARE_WFL_FUNCTION(move_partial);
+ DECLARE_WFL_FUNCTION(attack);
+ DECLARE_FAI_FUNCTION(rate_action);
+ DECLARE_WFL_FUNCTION(recall);
+ DECLARE_WFL_FUNCTION(recruit);
+ DECLARE_FAI_FUNCTION(is_avoided_location);
+ DECLARE_WFL_FUNCTION(is_village);
+ DECLARE_FAI_FUNCTION(is_unowned_village);
+ DECLARE_FAI_FUNCTION(unit_moves);
+ DECLARE_WFL_FUNCTION(set_unit_var);
+ DECLARE_WFL_FUNCTION(fallback);
+ DECLARE_WFL_FUNCTION(units_can_reach);
+ DECLARE_FAI_FUNCTION(debug_label);
+ DECLARE_WFL_FUNCTION(max_possible_damage);
+ DECLARE_WFL_FUNCTION(max_possible_damage_with_retaliation);
+ DECLARE_FAI_FUNCTION(next_hop);
+ DECLARE_WFL_FUNCTION(castle_locs);
+ DECLARE_WFL_FUNCTION(timeofday_modifier);
+ DECLARE_FAI_FUNCTION(distance_to_nearest_unowned_village);
+ DECLARE_FAI_FUNCTION(shortest_path);
+ DECLARE_FAI_FUNCTION(simplest_path);
+ DECLARE_FAI_FUNCTION(nearest_keep);
+ DECLARE_FAI_FUNCTION(suitable_keep);
+ DECLARE_WFL_FUNCTION(nearest_loc);
+ DECLARE_FAI_FUNCTION(find_shroud);
+ DECLARE_FAI_FUNCTION(close_enemies);
+ DECLARE_WFL_FUNCTION(calculate_outcome);
+ DECLARE_FAI_FUNCTION(run_file);
+ DECLARE_FAI_FUNCTION(calculate_map_ownership);
}
-#undef FUNCTION
+#undef DECLARE_WFL_FUNCTION
}
diff --git a/src/formula/callable_objects.cpp b/src/formula/callable_objects.cpp
index fe733f93f217..25b44ab02f4d 100644
--- a/src/formula/callable_objects.cpp
+++ b/src/formula/callable_objects.cpp
@@ -17,6 +17,7 @@
#include "config.hpp"
#include "formula/function.hpp"
#include "map/map.hpp"
+#include "display_context.hpp"
#include "team.hpp"
#include "units/formula_manager.hpp"
#include "log.hpp"
@@ -484,6 +485,11 @@ int config_callable::do_compare(const formula_callable* callable) const
return cfg_.hash().compare(cfg_callable->get_config().hash());
}
+terrain_callable::terrain_callable(const display_context& dc, const map_location& loc) : loc_(loc), t_(dc.map().get_terrain_info(loc)), owner_(dc.village_owner(loc))
+{
+ type_ = TERRAIN_C;
+}
+
variant terrain_callable::get_value(const std::string& key) const
{
if(key == "x") {
@@ -512,6 +518,8 @@ variant terrain_callable::get_value(const std::string& key) const
return variant(t_.is_keep());
} else if(key == "healing") {
return variant(t_.gives_healing());
+ } else if(key == "owner") {
+ return variant(owner_);
}
return variant();
@@ -532,6 +540,7 @@ void terrain_callable::get_inputs(formula_input_vector& inputs) const
add_input(inputs, "castle");
add_input(inputs, "keep");
add_input(inputs, "healing");
+ add_input(inputs, "owner");
}
int terrain_callable::do_compare(const formula_callable* callable) const
@@ -545,6 +554,10 @@ int terrain_callable::do_compare(const formula_callable* callable) const
return loc_.do_compare(other_loc);
}
+const gamemap& gamemap_callable::get_gamemap() const {
+ return board_.map();
+}
+
void gamemap_callable::get_inputs(formula_input_vector& inputs) const
{
add_input(inputs, "gamemap");
@@ -556,22 +569,22 @@ void gamemap_callable::get_inputs(formula_input_vector& inputs) const
variant gamemap_callable::get_value(const std::string& key) const
{
if(key == "terrain") {
- int w = gamemap_.w();
- int h = gamemap_.h();
+ int w = get_gamemap().w();
+ int h = get_gamemap().h();
std::vector vars;
for(int i = 0; i < w; i++) {
for(int j = 0; j < h; j++) {
const map_location loc(i, j);
- vars.emplace_back(std::make_shared(gamemap_.get_terrain_info(loc), loc));
+ vars.emplace_back(std::make_shared(board_, loc));
}
}
return variant(vars);
} else if(key == "w") {
- return variant(gamemap_.w());
+ return variant(get_gamemap().w());
} else if(key == "h") {
- return variant(gamemap_.h());
+ return variant(get_gamemap().h());
} else {
return variant();
}
diff --git a/src/formula/callable_objects.hpp b/src/formula/callable_objects.hpp
index b3702b179b6d..5a1667b26b02 100644
--- a/src/formula/callable_objects.hpp
+++ b/src/formula/callable_objects.hpp
@@ -21,6 +21,7 @@
class team;
class terrain_type;
+class display_context;
namespace wfl
{
@@ -28,10 +29,7 @@ namespace wfl
class terrain_callable : public formula_callable
{
public:
- terrain_callable(const terrain_type& t, const map_location& loc) : loc_(loc), t_(t)
- {
- type_ = TERRAIN_C;
- }
+ terrain_callable(const display_context& m, const map_location& loc);
variant get_value(const std::string& key) const override;
void get_inputs(formula_input_vector& inputs) const override;
@@ -41,21 +39,22 @@ class terrain_callable : public formula_callable
private:
const map_location loc_;
const terrain_type& t_;
+ const int owner_;
};
class gamemap_callable : public formula_callable
{
public:
- explicit gamemap_callable(const gamemap& g) : gamemap_(g)
+ explicit gamemap_callable(const display_context& g) : board_(g)
{}
void get_inputs(formula_input_vector& inputs) const override;
variant get_value(const std::string& key) const override;
- const gamemap& get_gamemap() const { return gamemap_; }
+ const gamemap& get_gamemap() const;
private:
- const gamemap& gamemap_;
+ const display_context& board_;
};
class location_callable : public formula_callable
diff --git a/src/formula/function.cpp b/src/formula/function.cpp
index 1d75316ea2a4..8d7c52510d28 100644
--- a/src/formula/function.cpp
+++ b/src/formula/function.cpp
@@ -1463,6 +1463,80 @@ class distance_between_function : public function_expression {
}
};
+class adjacent_locs_function : public function_expression {
+public:
+ adjacent_locs_function(const args_list& args)
+ : function_expression("adjacent_locs", args, 1, 1) {}
+
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const {
+ const map_location loc = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "adjacent_locs:location")).convert_to()->loc();
+ map_location adj[6];
+ get_adjacent_tiles(loc, adj);
+
+ std::vector v;
+ for(int n = 0; n != 6; ++n) {
+ v.emplace_back(std::make_shared(adj[n]));
+ }
+
+ return variant(v);
+ }
+};
+
+class are_adjacent_function : public function_expression {
+public:
+ are_adjacent_function(const args_list& args)
+ : function_expression("are_adjacent", args, 2, 2) {}
+
+private:
+ variant execute(const formula_callable& variables, formula_debugger* fdb) const {
+ const map_location loc1 = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "are_adjacent:location_A")).convert_to()->loc();
+ const map_location loc2 = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "are_adjacent:location_B")).convert_to()->loc();
+ return variant(tiles_adjacent(loc1, loc2) ? 1 : 0);
+ }
+};
+
+class relative_dir_function : public function_expression {
+public:
+ relative_dir_function(const args_list& args)
+ : function_expression("relative_dir", args, 2, 2) {}
+
+private:
+ variant execute(const formula_callable& variables, formula_debugger* fdb) const {
+ const map_location loc1 = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "relative_dir:location_A")).convert_to()->loc();
+ const map_location loc2 = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "relative_dir:location_B")).convert_to()->loc();
+ return variant(map_location::write_direction(loc1.get_relative_dir(loc2)));
+ }
+};
+
+class direction_from_function : public function_expression {
+public:
+ direction_from_function(const args_list& args)
+ : function_expression("direction_from", args, 2, 3) {}
+
+private:
+ variant execute(const formula_callable& variables, formula_debugger* fdb) const {
+ const map_location loc = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "direction_from:location")).convert_to()->loc();
+ const std::string dir_str = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "direction_from:dir")).as_string();
+ int n = args().size() == 3 ? args()[2]->evaluate(variables, add_debug_info(fdb, 2, "direction_from:count")).as_int() : 1;
+ return variant(std::make_shared(loc.get_direction(map_location::parse_direction(dir_str), n)));
+ }
+};
+
+class rotate_loc_around_function : public function_expression {
+public:
+ rotate_loc_around_function(const args_list& args)
+ : function_expression("rotate_loc_around", args, 2, 3) {}
+
+private:
+ variant execute(const formula_callable& variables, formula_debugger* fdb) const {
+ const map_location center = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "direction_from:center")).convert_to()->loc();
+ const map_location loc = args()[0]->evaluate(variables, add_debug_info(fdb, 1, "direction_from:location")).convert_to()->loc();
+ int n = args().size() == 3 ? args()[2]->evaluate(variables, add_debug_info(fdb, 2, "direction_from:count")).as_int() : 1;
+ return variant(std::make_shared(loc.rotate_right_around_center(center, n)));
+ }
+};
+
class type_function : public function_expression {
public:
@@ -1582,11 +1656,7 @@ function_expression_ptr user_formula_function::generate_function_expression(cons
return function_expression_ptr(new formula_function_expression(name_, args, formula_, precondition_, args_));
}
-function_symbol_table::function_symbol_table(function_symbol_table* parent) : parent(parent) {}
-
-function_symbol_table::~function_symbol_table() {
- if(parent) delete parent;
-}
+function_symbol_table::function_symbol_table(std::shared_ptr parent) : parent(parent ? parent : get_builtins()) {}
void function_symbol_table::add_function(const std::string& name, formula_function_ptr fcn)
{
@@ -1600,9 +1670,11 @@ expression_ptr function_symbol_table::create_function(const std::string& fn, con
return i->second->generate_function_expression(args);
}
- expression_ptr res((parent ? parent : get_builtins())->create_function(fn, args));
- if(res) {
- return res;
+ if(parent) {
+ expression_ptr res(parent->create_function(fn, args));
+ if(res) {
+ return res;
+ }
}
throw formula_error("Unknown function: " + fn, "", "", 0);
@@ -1613,8 +1685,6 @@ std::set function_symbol_table::get_function_names() const
std::set res;
if(parent) {
res = parent->get_function_names();
- } else if(this != get_builtins()) {
- res = get_builtins()->get_function_names();
}
for(functions_map::const_iterator iter = custom_formulas_.begin(); iter != custom_formulas_.end(); ++iter ) {
res.insert((*iter).first);
@@ -1622,84 +1692,87 @@ std::set function_symbol_table::get_function_names() const
return res;
}
-#define FUNCTION(name) functions_table.add_function(#name, \
- formula_function_ptr(new builtin_formula_function(#name)))
-
-function_symbol_table* function_symbol_table::get_builtins() {
- static function_symbol_table functions_table;
+std::shared_ptr function_symbol_table::get_builtins() {
+ static function_symbol_table functions_table(builtins_tag);
if(functions_table.empty()) {
+ functions_table.parent = nullptr;
using namespace builtins;
- FUNCTION(debug);
- FUNCTION(dir);
- FUNCTION(if);
- FUNCTION(switch);
- FUNCTION(abs);
- FUNCTION(min);
- FUNCTION(max);
- FUNCTION(choose);
- FUNCTION(debug_float);
- FUNCTION(debug_print);
- FUNCTION(debug_profile);
- FUNCTION(wave);
- FUNCTION(sort);
- FUNCTION(contains_string);
- FUNCTION(find_string);
- FUNCTION(reverse);
- FUNCTION(filter);
- FUNCTION(find);
- FUNCTION(map);
- FUNCTION(zip);
- FUNCTION(take_while);
- FUNCTION(reduce);
- FUNCTION(sum);
- FUNCTION(head);
- FUNCTION(tail);
- FUNCTION(size);
- FUNCTION(null);
- FUNCTION(ceil);
- FUNCTION(floor);
- FUNCTION(trunc);
- FUNCTION(frac);
- FUNCTION(sgn);
- FUNCTION(round);
- FUNCTION(as_decimal);
- FUNCTION(pair);
- FUNCTION(loc);
- FUNCTION(distance_between);
- FUNCTION(index_of);
- FUNCTION(keys);
- FUNCTION(values);
- FUNCTION(tolist);
- FUNCTION(tomap);
- FUNCTION(substring);
- FUNCTION(replace);
- FUNCTION(length);
- FUNCTION(concatenate);
- FUNCTION(sin);
- FUNCTION(cos);
- FUNCTION(tan);
- FUNCTION(asin);
- FUNCTION(acos);
- FUNCTION(atan);
- FUNCTION(sqrt);
- FUNCTION(cbrt);
- FUNCTION(root);
- FUNCTION(log);
- FUNCTION(exp);
- FUNCTION(pi);
- FUNCTION(hypot);
- FUNCTION(type);
- }
-
- return &functions_table;
+ DECLARE_WFL_FUNCTION(debug);
+ DECLARE_WFL_FUNCTION(dir);
+ DECLARE_WFL_FUNCTION(if);
+ DECLARE_WFL_FUNCTION(switch);
+ DECLARE_WFL_FUNCTION(abs);
+ DECLARE_WFL_FUNCTION(min);
+ DECLARE_WFL_FUNCTION(max);
+ DECLARE_WFL_FUNCTION(choose);
+ DECLARE_WFL_FUNCTION(debug_float);
+ DECLARE_WFL_FUNCTION(debug_print);
+ DECLARE_WFL_FUNCTION(debug_profile);
+ DECLARE_WFL_FUNCTION(wave);
+ DECLARE_WFL_FUNCTION(sort);
+ DECLARE_WFL_FUNCTION(contains_string);
+ DECLARE_WFL_FUNCTION(find_string);
+ DECLARE_WFL_FUNCTION(reverse);
+ DECLARE_WFL_FUNCTION(filter);
+ DECLARE_WFL_FUNCTION(find);
+ DECLARE_WFL_FUNCTION(map);
+ DECLARE_WFL_FUNCTION(zip);
+ DECLARE_WFL_FUNCTION(take_while);
+ DECLARE_WFL_FUNCTION(reduce);
+ DECLARE_WFL_FUNCTION(sum);
+ DECLARE_WFL_FUNCTION(head);
+ DECLARE_WFL_FUNCTION(tail);
+ DECLARE_WFL_FUNCTION(size);
+ DECLARE_WFL_FUNCTION(null);
+ DECLARE_WFL_FUNCTION(ceil);
+ DECLARE_WFL_FUNCTION(floor);
+ DECLARE_WFL_FUNCTION(trunc);
+ DECLARE_WFL_FUNCTION(frac);
+ DECLARE_WFL_FUNCTION(sgn);
+ DECLARE_WFL_FUNCTION(round);
+ DECLARE_WFL_FUNCTION(as_decimal);
+ DECLARE_WFL_FUNCTION(pair);
+ DECLARE_WFL_FUNCTION(loc);
+ DECLARE_WFL_FUNCTION(distance_between);
+ DECLARE_WFL_FUNCTION(adjacent_locs);
+ DECLARE_WFL_FUNCTION(are_adjacent);
+ DECLARE_WFL_FUNCTION(relative_dir);
+ DECLARE_WFL_FUNCTION(direction_from);
+ DECLARE_WFL_FUNCTION(rotate_loc_around);
+ DECLARE_WFL_FUNCTION(index_of);
+ DECLARE_WFL_FUNCTION(keys);
+ DECLARE_WFL_FUNCTION(values);
+ DECLARE_WFL_FUNCTION(tolist);
+ DECLARE_WFL_FUNCTION(tomap);
+ DECLARE_WFL_FUNCTION(substring);
+ DECLARE_WFL_FUNCTION(replace);
+ DECLARE_WFL_FUNCTION(length);
+ DECLARE_WFL_FUNCTION(concatenate);
+ DECLARE_WFL_FUNCTION(sin);
+ DECLARE_WFL_FUNCTION(cos);
+ DECLARE_WFL_FUNCTION(tan);
+ DECLARE_WFL_FUNCTION(asin);
+ DECLARE_WFL_FUNCTION(acos);
+ DECLARE_WFL_FUNCTION(atan);
+ DECLARE_WFL_FUNCTION(sqrt);
+ DECLARE_WFL_FUNCTION(cbrt);
+ DECLARE_WFL_FUNCTION(root);
+ DECLARE_WFL_FUNCTION(log);
+ DECLARE_WFL_FUNCTION(exp);
+ DECLARE_WFL_FUNCTION(pi);
+ DECLARE_WFL_FUNCTION(hypot);
+ DECLARE_WFL_FUNCTION(type);
+ }
+
+ return std::shared_ptr(&functions_table, [](function_symbol_table*){});
}
-action_function_symbol_table::action_function_symbol_table() {
+action_function_symbol_table::action_function_symbol_table(std::shared_ptr parent) : function_symbol_table(parent) {
using namespace actions;
function_symbol_table& functions_table = *this;
- FUNCTION(safe_call);
- FUNCTION(set_var);
+ DECLARE_WFL_FUNCTION(safe_call);
+ DECLARE_WFL_FUNCTION(set_var);
}
}
diff --git a/src/formula/function.hpp b/src/formula/function.hpp
index af5898fa735e..e70daecb7f0a 100644
--- a/src/formula/function.hpp
+++ b/src/formula/function.hpp
@@ -139,23 +139,29 @@ typedef std::shared_ptr formula_function_ptr;
typedef std::map functions_map;
class function_symbol_table {
- function_symbol_table* parent = nullptr;
+ std::shared_ptr parent;
functions_map custom_formulas_;
+ enum builtins_tag_t {builtins_tag};
+ function_symbol_table(builtins_tag_t) {}
public:
- explicit function_symbol_table(function_symbol_table* parent = nullptr);
- ~function_symbol_table();
+ explicit function_symbol_table(std::shared_ptr parent = nullptr);
void add_function(const std::string& name, formula_function_ptr fcn);
expression_ptr create_function(const std::string& fn, const std::vector& args) const;
std::set get_function_names() const;
- bool empty() {return custom_formulas_.empty();}
- static function_symbol_table* get_builtins();
+ bool empty() {return custom_formulas_.empty() && (parent == nullptr || parent->empty());}
+ static std::shared_ptr get_builtins();
};
class action_function_symbol_table : public function_symbol_table {
public:
- action_function_symbol_table();
+ action_function_symbol_table(std::shared_ptr parent = nullptr);
};
+/// Declares a function `name` in the local function table `functions_table`.
+/// The function must be defined by a `name_function` class which is accessible in the current scope.
+#define DECLARE_WFL_FUNCTION(name) functions_table.add_function(#name, \
+ formula_function_ptr(new builtin_formula_function(#name)))
+
class wrapper_formula : public formula_expression {
public:
wrapper_formula()
diff --git a/src/formula/function_gamestate.cpp b/src/formula/function_gamestate.cpp
new file mode 100644
index 000000000000..12e644619c0f
--- /dev/null
+++ b/src/formula/function_gamestate.cpp
@@ -0,0 +1,316 @@
+
+#include "formula/function_gamestate.hpp"
+#include "formula/callable_objects.hpp"
+
+#include "resources.hpp"
+#include "game_board.hpp"
+#include "map/map.hpp"
+#include "pathutils.hpp"
+
+namespace wfl {
+
+namespace gamestate {
+
+class adjacent_locs_function : public function_expression
+{
+public:
+ adjacent_locs_function(const args_list& args)
+ : function_expression("adjacent_locs", args, 1, 1)
+ {}
+
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const
+ {
+ const map_location loc = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "adjacent_locs:location")).convert_to()->loc();
+ map_location adj[6];
+ get_adjacent_tiles(loc, adj);
+
+ std::vector v;
+ for(int n = 0; n != 6; ++n) {
+ if(resources::gameboard->map().on_board(adj[n])) {
+ v.emplace_back(std::make_shared(adj[n]));
+ }
+ }
+
+ return variant(v);
+ }
+};
+
+class locations_in_radius_function : public function_expression
+{
+public:
+ locations_in_radius_function(const args_list& args)
+ : function_expression("locations_in_radius", args, 2, 2)
+ {}
+
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const
+ {
+ const map_location loc = args()[0]->evaluate(variables, fdb).convert_to()->loc();
+
+ int range = args()[1]->evaluate(variables, fdb).as_int();
+
+ if(range < 0) {
+ return variant();
+ }
+
+ if(!range) {
+ return variant(std::make_shared(loc));
+ }
+
+ std::vector res;
+
+ get_tiles_in_radius(loc, range, res);
+
+ std::vector v;
+ v.reserve(res.size() + 1);
+ v.emplace_back(std::make_shared(loc));
+
+ for(size_t n = 0; n != res.size(); ++n) {
+ if(resources::gameboard->map().on_board(res[n])) {
+ v.emplace_back(std::make_shared(res[n]));
+ }
+ }
+
+ return variant(v);
+ }
+};
+
+class get_unit_type_function : public function_expression
+{
+public:
+ explicit get_unit_type_function(const args_list& args)
+ : function_expression("get_unit_type", args, 1, 1)
+ {}
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const
+ {
+ const std::string type = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "get_unit_type:name")).as_string();
+
+ const unit_type *ut = unit_types.find(type);
+ if(ut) {
+ return variant(std::make_shared(*ut));
+ }
+
+ return variant();
+ }
+};
+
+
+class unit_at_function : public function_expression
+{
+public:
+ unit_at_function(const args_list& args)
+ : function_expression("unit_at", args, 1, 1)
+ {}
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const
+ {
+ variant loc_var = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "unit_at:location"));
+ if(loc_var.is_null()) {
+ return variant();
+ }
+ auto loc = loc_var.convert_to();
+ const unit_map::const_iterator i = resources::gameboard->units().find(loc->loc());
+ if(i != resources::gameboard->units().end()) {
+ return variant(std::make_shared(*i));
+ } else {
+ return variant();
+ }
+ }
+};
+
+
+class defense_on_function : public function_expression
+{
+public:
+ defense_on_function(const args_list& args)
+ : function_expression("defense_on", args, 2, 2)
+ {}
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const
+ {
+ variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "defense_on:unit"));
+ variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "defense_on:location"));
+ if(u.is_null() || loc_var.is_null()) {
+ return variant();
+ }
+
+ auto u_call = u.try_convert();
+ auto u_type = u.try_convert();
+ const map_location& loc = loc_var.convert_to()->loc();
+
+ if(u_call) {
+ const unit& un = u_call->get_unit();
+
+ if(un.total_movement() < un.movement_cost((resources::gameboard->map())[loc]))
+ return variant();
+
+ if(!resources::gameboard->map().on_board(loc)) {
+ return variant();
+ }
+
+ return variant(100 - un.defense_modifier((resources::gameboard->map())[loc]));
+ }
+
+ if(u_type) {
+ const unit_type& un = u_type->get_unit_type();
+
+ if(un.movement() < un.movement_type().movement_cost((resources::gameboard->map())[loc]))
+ return variant();
+
+ if(!resources::gameboard->map().on_board(loc)) {
+ return variant();
+ }
+
+ return variant(100 - un.movement_type().defense_modifier((resources::gameboard->map())[loc]));
+ }
+
+ return variant();
+ }
+};
+
+
+class chance_to_hit_function : public function_expression
+{
+public:
+ chance_to_hit_function(const args_list& args)
+ : function_expression("chance_to_hit", args, 2, 2)
+ {}
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const
+ {
+ variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "chance_to_hit:unit"));
+ variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "chance_to_hit:location"));
+ if(u.is_null() || loc_var.is_null()) {
+ return variant();
+ }
+
+ auto u_call = u.try_convert();
+ auto u_type = u.try_convert();
+ const map_location& loc = loc_var.convert_to()->loc();
+
+ if(u_call) {
+ const unit& un = u_call->get_unit();
+
+ if(!resources::gameboard->map().on_board(loc)) {
+ return variant();
+ }
+
+ return variant(un.defense_modifier((resources::gameboard->map())[loc]));
+ }
+
+ if(u_type) {
+ const unit_type& un = u_type->get_unit_type();
+
+ if(!resources::gameboard->map().on_board(loc)) {
+ return variant();
+ }
+
+ return variant(un.movement_type().defense_modifier((resources::gameboard->map())[loc]));
+ }
+
+ return variant();
+ }
+};
+
+
+class movement_cost_function : public function_expression
+{
+public:
+ movement_cost_function(const args_list& args)
+ : function_expression("movement_cost", args, 2, 2)
+ {}
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const
+ {
+ variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "movement_cost:unit"));
+ variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 0, "movement_cost:location"));
+ if(u.is_null() || loc_var.is_null()) {
+ return variant();
+ }
+ //we can pass to this function either unit_callable or unit_type callable
+ auto u_call = u.try_convert();
+ auto u_type = u.try_convert();
+ const map_location& loc = loc_var.convert_to()->loc();
+
+ if(u_call) {
+ const unit& un = u_call->get_unit();
+
+ if(!resources::gameboard->map().on_board(loc)) {
+ return variant();
+ }
+
+ return variant(un.movement_cost((resources::gameboard->map())[loc]));
+ }
+
+ if(u_type) {
+ const unit_type& un = u_type->get_unit_type();
+
+ if(!resources::gameboard->map().on_board(loc)) {
+ return variant();
+ }
+
+ return variant(un.movement_type().movement_cost((resources::gameboard->map())[loc]));
+ }
+
+ return variant();
+ }
+};
+
+
+class enemy_of_function : public function_expression
+{
+public:
+ enemy_of_function(const args_list& args)
+ : function_expression("enemy_of", args, 2, 2)
+ {}
+private:
+ variant execute(const formula_callable& variables, formula_debugger *fdb) const
+ {
+ variant self_v = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "enemy_of:self"));
+ variant other_v = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "enemy_of:other"));
+ int self, other;
+
+ if(auto uc = self_v.try_convert()) {
+ // For some obscure, bizarre reason, the unit callable returns a 0-indexed side. :|
+ self = uc->get_value("side").as_int() + 1;
+ } else if(auto tc = self_v.try_convert()) {
+ self = tc->get_value("side").as_int();
+ } else {
+ self = self_v.as_int();
+ }
+
+ if(auto uc = other_v.try_convert()) {
+ // For some obscure, bizarre reason, the unit callable returns a 0-indexed side. :|
+ other = uc->get_value("side").as_int() + 1;
+ } else if(auto tc = other_v.try_convert()) {
+ other = tc->get_value("side").as_int();
+ } else {
+ other = other_v.as_int();
+ }
+
+ int num_teams = resources::gameboard->teams().size();
+ if(self < 1 || self > num_teams || other < 1 || other > num_teams) {
+ return variant(0);
+ }
+ return variant(resources::gameboard->get_team(self).is_enemy(other) ? 1 : 0);
+ }
+};
+
+} // namespace gamestate
+
+gamestate_function_symbol_table::gamestate_function_symbol_table(std::shared_ptr parent) : function_symbol_table(parent) {
+ using namespace gamestate;
+ function_symbol_table& functions_table = *this;
+ DECLARE_WFL_FUNCTION(get_unit_type);
+ DECLARE_WFL_FUNCTION(unit_at);
+ DECLARE_WFL_FUNCTION(defense_on);
+ DECLARE_WFL_FUNCTION(chance_to_hit);
+ DECLARE_WFL_FUNCTION(movement_cost);
+ DECLARE_WFL_FUNCTION(adjacent_locs); // This is deliberately duplicated here; this form excludes off-map locations, while the core form does not
+ DECLARE_WFL_FUNCTION(locations_in_radius);
+ DECLARE_WFL_FUNCTION(enemy_of);
+}
+
+}
diff --git a/src/formula/function_gamestate.hpp b/src/formula/function_gamestate.hpp
new file mode 100644
index 000000000000..04a78a900a71
--- /dev/null
+++ b/src/formula/function_gamestate.hpp
@@ -0,0 +1,11 @@
+
+#include "formula/function.hpp"
+
+namespace wfl {
+
+class gamestate_function_symbol_table : public function_symbol_table {
+public:
+ gamestate_function_symbol_table(std::shared_ptr parent = nullptr);
+};
+
+}
diff --git a/src/pathfind/teleport.cpp b/src/pathfind/teleport.cpp
index 087f8d451c1a..004447f20ee1 100644
--- a/src/pathfind/teleport.cpp
+++ b/src/pathfind/teleport.cpp
@@ -109,8 +109,6 @@ void teleport_group::get_teleport_pair(
, const unit& u
, const bool ignore_units) const
{
- const map_location &loc = u.get_location();
-
const filter_context * fc = resources::filter_con;
assert(fc);
@@ -122,15 +120,12 @@ void teleport_group::get_teleport_pair(
vconfig source(cfg_.child_or_empty("source"), true);
vconfig target(cfg_.child_or_empty("target"), true);
const unit_filter ufilt(filter, resources::filter_con); //Note: Don't use the ignore units filter context here, only for the terrain filters. (That's how it worked before the filter contexts were introduced)
- if (ufilt.matches(u, loc)) {
-
- scoped_xy_unit teleport_unit("teleport_unit", loc, resources::gameboard->units());
-
+ if (ufilt.matches(u)) {
terrain_filter source_filter(source, fc);
- source_filter.get_locations(reversed_ ? loc_pair.second : loc_pair.first);
+ source_filter.get_locations(reversed_ ? loc_pair.second : loc_pair.first, u);
terrain_filter target_filter(target, fc);
- target_filter.get_locations(reversed_ ? loc_pair.first : loc_pair.second);
+ target_filter.get_locations(reversed_ ? loc_pair.first : loc_pair.second, u);
}
if (ignore_units) {
diff --git a/src/scripting/game_lua_kernel.cpp b/src/scripting/game_lua_kernel.cpp
index b95279a74637..26709015a0c8 100644
--- a/src/scripting/game_lua_kernel.cpp
+++ b/src/scripting/game_lua_kernel.cpp
@@ -547,6 +547,8 @@ int game_lua_kernel::intf_get_displayed_unit(lua_State *L)
/**
* Gets all the units matching a given filter.
* - Arg 1: optional table containing a filter
+ * - Arg 2: optional location (to find all units that would match on that location)
+ OR unit (to find all units that would match adjacent to that unit)
* - Ret 1: table containing full userdata with __index pointing to
* impl_unit_get and __newindex pointing to impl_unit_set.
*/
@@ -554,15 +556,34 @@ int game_lua_kernel::intf_get_units(lua_State *L)
{
vconfig filter = luaW_checkvconfig(L, 1, true);
+ // note that if filter is null, this yields a null filter matching everything (and doing no work)
+ filter_context & fc = game_state_;
+ unit_filter filt(filter, &fc);
+ std::vector units;
+
+ if(unit* u_adj = luaW_tounit(L, 2)) {
+ if(!u_adj) {
+ return luaL_argerror(L, 2, "unit not found");
+ }
+ units = filt.all_matches_with_unit(*u_adj);
+ } else if(!lua_isnoneornil(L, 2)) {
+ map_location loc;
+ luaW_tolocation(L, 2, loc);
+ if(!loc.valid()) {
+ return luaL_argerror(L, 2, "invalid location");
+ }
+ units = filt.all_matches_at(loc);
+ } else {
+ units = filt.all_matches_on_map();
+ }
+
// Go through all the units while keeping the following stack:
// 1: return table, 2: userdata
lua_settop(L, 0);
lua_newtable(L);
int i = 1;
- // note that if filter is null, this yields a null filter matching everything (and doing no work)
- filter_context & fc = game_state_;
- for (const unit * ui : unit_filter(filter, &fc).all_matches_on_map()) {
+ for (const unit * ui : units) {
luaW_pushunit(L, ui->underlying_id());
lua_rawseti(L, 1, i);
++i;
@@ -2808,6 +2829,7 @@ static int intf_do_unsynced(lua_State *L)
/**
* Gets all the locations matching a given filter.
* - Arg 1: WML table.
+ * - Arg 2: Optional reference unit (teleport_unit)
* - Ret 1: array of integer pairs.
*/
int game_lua_kernel::intf_get_locations(lua_State *L)
@@ -2817,7 +2839,11 @@ int game_lua_kernel::intf_get_locations(lua_State *L)
std::set res;
filter_context & fc = game_state_;
const terrain_filter t_filter(filter, &fc);
- t_filter.get_locations(res, true);
+ if(luaW_isunit(L, 2)) {
+ t_filter.get_locations(res, *luaW_tounit(L, 2), true);
+ } else {
+ t_filter.get_locations(res, true);
+ }
lua_createtable(L, res.size(), 0);
int i = 1;
@@ -2867,6 +2893,7 @@ int game_lua_kernel::intf_get_villages(lua_State *L)
* Matches a location against the given filter.
* - Arg 1: location.
* - Arg 2: WML table.
+ * - Arg 3: Optional reference unit (teleport_unit)
* - Ret 1: boolean.
*/
int game_lua_kernel::intf_match_location(lua_State *L)
@@ -2881,7 +2908,11 @@ int game_lua_kernel::intf_match_location(lua_State *L)
filter_context & fc = game_state_;
const terrain_filter t_filter(filter, &fc);
- lua_pushboolean(L, t_filter.match(loc));
+ if(luaW_isunit(L, 3)) {
+ lua_pushboolean(L, t_filter.match(loc, *luaW_tounit(L, 3)));
+ } else {
+ lua_pushboolean(L, t_filter.match(loc));
+ }
return 1;
}
diff --git a/src/side_filter.cpp b/src/side_filter.cpp
index 81ae5cd0877f..5955b70ef747 100644
--- a/src/side_filter.cpp
+++ b/src/side_filter.cpp
@@ -32,6 +32,7 @@
#include "variable.hpp"
#include "formula/callable_objects.hpp"
#include "formula/formula.hpp"
+#include "formula/function_gamestate.hpp"
static lg::log_domain log_engine_sf("engine/side_filter");
#define ERR_NG LOG_STREAM(err, log_engine_sf)
@@ -228,7 +229,7 @@ bool side_filter::match_internal(const team &t) const
if (cfg_.has_attribute("formula")) {
try {
const wfl::team_callable callable(t);
- const wfl::formula form(cfg_["formula"]);
+ const wfl::formula form(cfg_["formula"], new wfl::gamestate_function_symbol_table);
if(!form.evaluate(callable).as_bool()) {
return false;
}
diff --git a/src/terrain/filter.cpp b/src/terrain/filter.cpp
index 253c60472397..46397a953562 100644
--- a/src/terrain/filter.cpp
+++ b/src/terrain/filter.cpp
@@ -30,6 +30,7 @@
#include "variable.hpp"
#include "formula/callable_objects.hpp"
#include "formula/formula.hpp"
+#include "formula/function_gamestate.hpp"
#include "scripting/game_lua_kernel.hpp"
#include
@@ -97,7 +98,7 @@ namespace {
};
} //end anonymous namespace
-bool terrain_filter::match_internal(const map_location& loc, const bool ignore_xy) const
+bool terrain_filter::match_internal(const map_location& loc, const unit* ref_unit, const bool ignore_xy) const
{
if (!this->fc_->get_disp_context().map().on_board_with_border(loc)) {
return false;
@@ -326,11 +327,14 @@ bool terrain_filter::match_internal(const map_location& loc, const bool ignore_x
if(cfg_.has_attribute("formula")) {
try {
- const gamemap& map = fc_->get_disp_context().map();
- t_translation::terrain_code t = map.get_terrain(loc);
- const terrain_type& ter = map.tdata()->get_terrain_info(t);
- const wfl::terrain_callable callable(ter,loc);
- const wfl::formula form(cfg_["formula"]);
+ const wfl::terrain_callable main(fc_->get_disp_context(), loc);
+ wfl::map_formula_callable callable(main.fake_ptr());
+ if(ref_unit) {
+ std::shared_ptr ref(new wfl::unit_callable(*ref_unit));
+ callable.add("teleport_unit", wfl::variant(ref));
+ // It's not destroyed upon scope exit because the variant holds a reference
+ }
+ const wfl::formula form(cfg_["formula"], new wfl::gamestate_function_symbol_table);
if(!form.evaluate(callable).as_bool()) {
return false;
}
@@ -345,7 +349,17 @@ bool terrain_filter::match_internal(const map_location& loc, const bool ignore_x
return true;
}
-bool terrain_filter::match(const map_location& loc) const
+class filter_with_unit : public xy_pred {
+ const terrain_filter& filt_;
+ const unit& ref_;
+public:
+ filter_with_unit(const terrain_filter& filt, const unit& ref) : filt_(filt), ref_(ref) {}
+ bool operator()(const map_location& loc) const override {
+ return filt_.match(loc, ref_);
+ }
+};
+
+bool terrain_filter::match_impl(const map_location& loc, const unit* ref_unit) const
{
if(cfg_["x"] == "recall" && cfg_["y"] == "recall") {
return !fc_->get_disp_context().map().on_board(loc);
@@ -353,6 +367,15 @@ bool terrain_filter::match(const map_location& loc) const
std::set hexes;
std::vector loc_vec(1, loc);
+ std::unique_ptr ref_unit_var;
+ if(ref_unit) {
+ if(fc_->get_disp_context().map().on_board(ref_unit->get_location())) {
+ ref_unit_var.reset(new scoped_xy_unit("teleport_unit", ref_unit->get_location(), fc_->get_disp_context().units()));
+ } else {
+ // Possible TODO: Support recall list units?
+ }
+ }
+
//handle radius
size_t radius = cfg_["radius"].to_size_t(0);
if(radius > max_loop_) {
@@ -364,7 +387,11 @@ bool terrain_filter::match(const map_location& loc) const
hexes.insert(loc_vec.begin(), loc_vec.end());
else if ( cfg_.has_child("filter_radius") ) {
terrain_filter r_filter(cfg_.child("filter_radius"), *this);
- get_tiles_radius(fc_->get_disp_context().map(), loc_vec, radius, hexes, false, r_filter);
+ if(ref_unit) {
+ get_tiles_radius(fc_->get_disp_context().map(), loc_vec, radius, hexes, false, filter_with_unit(r_filter, *ref_unit));
+ } else {
+ get_tiles_radius(fc_->get_disp_context().map(), loc_vec, radius, hexes, false, r_filter);
+ }
} else {
get_tiles_radius(fc_->get_disp_context().map(), loc_vec, radius, hexes);
}
@@ -372,7 +399,7 @@ bool terrain_filter::match(const map_location& loc) const
size_t loop_count = 0;
std::set::const_iterator i;
for(i = hexes.begin(); i != hexes.end(); ++i) {
- bool matches = match_internal(*i, false);
+ bool matches = match_internal(*i, ref_unit, false);
//handle [and], [or], and [not] with in-order precedence
vconfig::all_children_iterator cond = cfg_.ordered_begin();
@@ -385,17 +412,17 @@ bool terrain_filter::match(const map_location& loc) const
//handle [and]
if(cond_name == "and")
{
- matches = matches && terrain_filter(cond_cfg, *this)(*i);
+ matches = matches && terrain_filter(cond_cfg, *this).match_impl(*i, ref_unit);
}
//handle [or]
else if(cond_name == "or")
{
- matches = matches || terrain_filter(cond_cfg, *this)(*i);
+ matches = matches || terrain_filter(cond_cfg, *this).match_impl(*i, ref_unit);
}
//handle [not]
else if(cond_name == "not")
{
- matches = matches && !terrain_filter(cond_cfg, *this)(*i);
+ matches = matches && !terrain_filter(cond_cfg, *this).match_impl(*i, ref_unit);
}
++cond;
}
@@ -476,8 +503,17 @@ struct cfg_to_loc
map_location operator()(const config& cfg) const { return map_location(cfg, nullptr); }
typedef map_location result_type;
};
-void terrain_filter::get_locations(std::set& locs, bool with_border) const
+void terrain_filter::get_locs_impl(std::set& locs, const unit* ref_unit, bool with_border) const
{
+ std::unique_ptr ref_unit_var;
+ if(ref_unit) {
+ if(fc_->get_disp_context().map().on_board(ref_unit->get_location())) {
+ ref_unit_var.reset(new scoped_xy_unit("teleport_unit", ref_unit->get_location(), fc_->get_disp_context().units()));
+ } else {
+ // Possible TODO: Support recall list units?
+ }
+ }
+
std::set match_set;
// See if the caller provided an override to with_border
@@ -543,10 +579,10 @@ void terrain_filter::get_locations(std::set& locs, bool with_borde
}
std::set::iterator loc_itor = match_set.begin();
while(loc_itor != match_set.end()) {
- if(match_internal(*loc_itor, true)) {
+ if(match_internal(*loc_itor, ref_unit, true)) {
++loc_itor;
} else {
- match_set.erase(loc_itor++);
+ loc_itor = match_set.erase(loc_itor);
}
}
diff --git a/src/terrain/filter.hpp b/src/terrain/filter.hpp
index 11c6ff8251dc..19faa17b1c93 100644
--- a/src/terrain/filter.hpp
+++ b/src/terrain/filter.hpp
@@ -39,13 +39,37 @@ class terrain_filter : public xy_pred {
terrain_filter(const terrain_filter &other);
terrain_filter& operator=(const terrain_filter &other);
- //match: returns true if and only if the given location matches this filter
- bool match(const map_location& loc) const;
+ /// @param loc The location to test
+ /// @returns true if and only if the given location matches this filter
+ bool match(const map_location& loc) const {
+ return match_impl(loc, nullptr);
+ }
+
+ /// @param loc The location to test
+ /// @param ref_unit A reference unit for the $teleport_unit auto-stored variable
+ /// @param unit_loc The reference unit's apparent location for this filter
+ /// @returns true if and only if the given location matches this filter
+ bool match(const map_location& loc, const unit& ref_unit) const {
+ return match_impl(loc, &ref_unit);
+ }
+
virtual bool operator()(const map_location& loc) const { return this->match(loc); }
- //get_locations: gets all locations on the map that match this filter
- // @param locs - out parameter containing the results
- void get_locations(std::set& locs, bool with_border=false) const;
+ /// gets all locations on the map that match this filter
+ /// @param[out] locs set to store the results in
+ /// @param[in] with_border whether to include the borders
+ void get_locations(std::set& locs, bool with_border=false) const {
+ return get_locs_impl(locs, nullptr, with_border);
+ }
+
+ /// gets all locations on the map that match this filter
+ /// @param[out] locs set to store the results in
+ /// @param[in] with_border whether to include the borders
+ /// @param[in] ref_unit A reference unit for the $teleport_unit auto-stored variable
+ /// @param[in] unit_loc The reference unit's apparent location for this filter
+ void get_locations(std::set& locs, const unit& ref_unit, bool with_border=false) const {
+ return get_locs_impl(locs, &ref_unit, with_border);
+ }
//restrict: limits the allowed radius size and also limits nesting
// The purpose to limit the time spent for WML handling
@@ -58,7 +82,9 @@ class terrain_filter : public xy_pred {
config to_config() const;
friend class terrain_filterimpl;
private:
- bool match_internal(const map_location& loc, const bool ignore_xy) const;
+ bool match_impl(const map_location& loc, const unit* ref_unit) const;
+ void get_locs_impl(std::set& locs, const unit* ref_unit, bool with_border) const;
+ bool match_internal(const map_location& loc, const unit* ref_unit, const bool ignore_xy) const;
const vconfig cfg_; //config contains WML for a Standard Location Filter
const filter_context * fc_;
diff --git a/src/units/abilities.cpp b/src/units/abilities.cpp
index 78ef049ed1f7..7c7c3a247c20 100644
--- a/src/units/abilities.cpp
+++ b/src/units/abilities.cpp
@@ -19,6 +19,7 @@
#include "display_context.hpp"
#include "game_board.hpp"
+#include "lexical_cast.hpp"
#include "log.hpp"
#include "resources.hpp"
#include "team.hpp"
@@ -28,7 +29,12 @@
#include "units/filter.hpp"
#include "units/map.hpp"
#include "filter_context.hpp"
+#include "formula/callable_objects.hpp"
+#include "formula/formula.hpp"
+#include "serialization/string_view.hpp"
+
#include
+#include
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
@@ -169,7 +175,7 @@ bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc
}
unit_ability_list unit::get_abilities(const std::string& tag_name, const map_location& loc) const
{
- unit_ability_list res;
+ unit_ability_list res(loc_);
for (const config &i : this->abilities_.child_range(tag_name)) {
if (ability_active(tag_name, i, loc) &&
@@ -402,39 +408,67 @@ bool unit::has_ability_type(const std::string& ability) const
return !abilities_.child_range(ability).empty();
}
-
-std::pair unit_ability_list::highest(const std::string& key, int def) const
+namespace {
+
+
+template
+class get_ability_value_visitor : public boost::static_visitor
{
- if ( cfgs_.empty() ) {
- return std::make_pair(def, map_location());
- }
- // The returned location is the best non-cumulative one, if any,
- // the best absolute cumulative one otherwise.
- map_location best_loc;
- bool only_cumulative = true;
- int abs_max = 0;
- int flat = 0;
- int stack = 0;
- for (unit_ability const &p : cfgs_)
+public:
+ // Constructor stores the default value.
+ get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
+
+ T operator()(boost::blank const &) const { return def_; }
+ T operator()(bool) const { return def_; }
+ T operator()(int i) const { return static_cast(i); }
+ T operator()(unsigned long long u) const { return static_cast(u); }
+ T operator()(double d) const { return static_cast(d); }
+ T operator()(t_string const &) const { return def_; }
+ T operator()(const std::string& s) const
{
- int value = (*p.first)[key].to_int(def);
- if ((*p.first)["cumulative"].to_bool()) {
- stack += value;
- if (value < 0) value = -value;
- if (only_cumulative && value >= abs_max) {
- abs_max = value;
- best_loc = p.second;
- }
- } else if (only_cumulative || value > flat) {
- only_cumulative = false;
- flat = value;
- best_loc = p.second;
+ if(s.size() >= 2 && s[0] == '(') {
+ return formula_handler_(s);
}
+ return lexical_cast_default(s, def_);
}
- return std::make_pair(flat + stack, best_loc);
+
+private:
+ const T def_;
+ const TFuncFormula& formula_handler_;
+};
+template
+get_ability_value_visitor make_get_ability_value_visitor(T def, const TFuncFormula& formula_handler)
+{
+ return get_ability_value_visitor(def, formula_handler);
+}
+template
+T get_single_ability_value(const config::attribute_value& v, T def, const map_location& sender_loc, const map_location& receiver_loc, const TFuncFormula& formula_handler)
+{
+ return v.apply_visitor(make_get_ability_value_visitor(def, [&](const std::string& s) {
+
+ try {
+ assert(resources::units);
+ auto u_itor = resources::units->find(sender_loc);
+
+ if(u_itor == resources::units->end()) {
+ return def;
+ }
+ wfl::map_formula_callable callable(std::make_shared(*u_itor));
+ u_itor = resources::units->find(receiver_loc);
+ if(u_itor != resources::units->end()) {
+ callable.add("other", wfl::variant(std::make_shared(*u_itor)));
+ }
+ return formula_handler(wfl::formula(s), callable);
+ } catch(wfl::formula_error& e) {
+ lg::wml_error() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
+ return def;
+ }
+ }));
+}
}
-std::pair unit_ability_list::lowest(const std::string& key, int def) const
+template
+std::pair unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
{
if ( cfgs_.empty() ) {
return std::make_pair(def, map_location());
@@ -448,15 +482,18 @@ std::pair unit_ability_list::lowest(const std::string& key, in
int stack = 0;
for (unit_ability const &p : cfgs_)
{
- int value = (*p.first)[key].to_int(def);
+ int value = get_single_ability_value((*p.first)[key], def, p.second, loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
+ return formula.evaluate(callable).as_int();
+ });
+
if ((*p.first)["cumulative"].to_bool()) {
stack += value;
if (value < 0) value = -value;
- if (only_cumulative && value <= abs_max) {
+ if (only_cumulative && !comp(value, abs_max)) {
abs_max = value;
best_loc = p.second;
}
- } else if (only_cumulative || value < flat) {
+ } else if (only_cumulative || comp(flat, value)) {
only_cumulative = false;
flat = value;
best_loc = p.second;
@@ -465,6 +502,9 @@ std::pair unit_ability_list::lowest(const std::string& key, in
return std::make_pair(flat + stack, best_loc);
}
+template std::pair unit_ability_list::get_extremum>(const std::string& key, int def, const std::less& comp) const;
+template std::pair unit_ability_list::get_extremum>(const std::string& key, int def, const std::greater& comp) const;
+
/*
*
* [special]
@@ -567,7 +607,7 @@ bool attack_type::get_special_bool(const std::string& special, bool simple_check
unit_ability_list attack_type::get_specials(const std::string& special) const
{
//log_scope("get_specials");
- unit_ability_list res;
+ unit_ability_list res(self_loc_);
for (const config &i : specials_.child_range(special)) {
if ( special_active(i, AFFECT_SELF) )
res.push_back(unit_ability(&i, self_loc_));
@@ -1016,7 +1056,11 @@ effect::effect(const unit_ability_list& list, int def, bool backstab) :
continue;
if (const config::attribute_value *v = cfg.get("value")) {
- int value = *v;
+ int value = get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
+ callable.add("base_value", wfl::variant(def));
+ return formula.evaluate(callable).as_int();
+ });
+
bool cumulative = cfg["cumulative"].to_bool();
if (!value_is_set && !cumulative) {
value_set = value;
@@ -1032,32 +1076,45 @@ effect::effect(const unit_ability_list& list, int def, bool backstab) :
}
if (const config::attribute_value *v = cfg.get("add")) {
- int add = *v;
+ int add = get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
+ callable.add("base_value", wfl::variant(def));
+ return formula.evaluate(callable).as_int();
+ });
std::map::iterator add_effect = values_add.find(effect_id);
if(add_effect == values_add.end() || add > add_effect->second.value) {
values_add[effect_id].set(ADD, add, ability.first, ability.second);
}
}
if (const config::attribute_value *v = cfg.get("sub")) {
- int sub = - *v;
+ int sub = - get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
+ callable.add("base_value", wfl::variant(def));
+ return formula.evaluate(callable).as_int();
+ });
std::map::iterator sub_effect = values_add.find(effect_id);
if(sub_effect == values_add.end() || sub > sub_effect->second.value) {
values_add[effect_id].set(ADD, sub, ability.first, ability.second);
}
}
if (const config::attribute_value *v = cfg.get("multiply")) {
- int multiply = int(v->to_double() * 100);
+ int multiply = static_cast(get_single_ability_value(*v, static_cast(def), ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
+ callable.add("base_value", wfl::variant(def));
+ return formula.evaluate(callable).as_decimal() / 1000.0 ;
+ }) * 100);
std::map::iterator mul_effect = values_mul.find(effect_id);
if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
values_mul[effect_id].set(MUL, multiply, ability.first, ability.second);
}
}
if (const config::attribute_value *v = cfg.get("divide")) {
- if (*v == 0) {
+ int divide = static_cast(get_single_ability_value(*v, static_cast(def), ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
+ callable.add("base_value", wfl::variant(def));
+ return formula.evaluate(callable).as_decimal() / 1000.0 ;
+ }) * 100);
+
+ if (divide == 0) {
ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id << std::endl;
}
else {
- int divide = int(v->to_double() * 100);
std::map::iterator div_effect = values_div.find(effect_id);
if(div_effect == values_div.end() || divide > div_effect->second.value) {
values_div[effect_id].set(DIV, divide, ability.first, ability.second);
diff --git a/src/units/attack_type.cpp b/src/units/attack_type.cpp
index 74b61762cbaa..1e6e47a386a4 100644
--- a/src/units/attack_type.cpp
+++ b/src/units/attack_type.cpp
@@ -21,6 +21,7 @@
#include "formula/callable_objects.hpp"
#include "formula/formula.hpp"
#include "formula/string_utils.hpp"
+#include "formula/function_gamestate.hpp"
#include "lexical_cast.hpp"
#include "log.hpp"
@@ -138,7 +139,7 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil
if (!filter_formula.empty()) {
try {
const wfl::attack_type_callable callable(attack);
- const wfl::formula form(filter_formula);
+ const wfl::formula form(filter_formula, new wfl::gamestate_function_symbol_table);
if(!form.evaluate(callable).as_bool()) {
return false;
}
diff --git a/src/units/filter.cpp b/src/units/filter.cpp
index ee7cf096b728..51dd651f0aff 100644
--- a/src/units/filter.cpp
+++ b/src/units/filter.cpp
@@ -36,6 +36,7 @@
#include "wml_exception.hpp" // needed for FAIL
#include "formula/callable_objects.hpp"
#include "formula/formula.hpp"
+#include "formula/function_gamestate.hpp"
#include
@@ -79,7 +80,7 @@ class null_unit_filter_impl : public unit_filter_abstract_impl {
virtual bool matches(const unit & /*u*/, const map_location & /*loc*/, const unit *) const {
return true;
}
- virtual std::vector all_matches_on_map(unsigned max_matches) const {
+ virtual std::vector all_matches_on_map(unsigned max_matches, const map_location* = nullptr, const unit* = nullptr) const {
std::vector ret;
for(const unit & u : fc_.get_disp_context().units()) {
--max_matches;
@@ -167,7 +168,7 @@ class basic_unit_filter_impl : public unit_filter_abstract_impl {
}
virtual bool matches(const unit & u, const map_location & loc, const unit * u2) const;
- virtual std::vector all_matches_on_map(unsigned max_matches) const;
+ virtual std::vector all_matches_on_map(unsigned max_matches, const map_location* loc = nullptr, const unit* u2 = nullptr) const;
virtual unit_const_ptr first_match_on_map() const;
config to_config() const {
return vcfg.get_config();
@@ -242,13 +243,25 @@ bool basic_unit_filter_impl::matches(const unit & u, const map_location& loc, co
for (size_t i = 0; i < cond_children_.size(); i++) {
switch (cond_child_types_[i].v) {
case conditional::TYPE::AND:
- matches = matches && cond_children_[i].matches(u,loc);
+ if(u2) {
+ matches = matches && cond_children_[i].matches(u,loc,*u2);
+ } else {
+ matches = matches && cond_children_[i].matches(u,loc);
+ }
break;
case conditional::TYPE::OR:
- matches = matches || cond_children_[i].matches(u,loc);
+ if(u2) {
+ matches = matches || cond_children_[i].matches(u,loc,*u2);
+ } else {
+ matches = matches || cond_children_[i].matches(u,loc);
+ }
break;
case conditional::TYPE::NOT:
- matches = matches && !cond_children_[i].matches(u,loc);
+ if(u2) {
+ matches = matches && !cond_children_[i].matches(u,loc,*u2);
+ } else {
+ matches = matches && !cond_children_[i].matches(u,loc);
+ }
}
}
return matches;
@@ -687,7 +700,7 @@ bool basic_unit_filter_impl::internal_matches_filter(const unit & u, const map_l
callable.add("other", wfl::variant(secondary));
// It's not destroyed upon scope exit because the variant holds a reference
}
- const wfl::formula form(vcfg["formula"]);
+ const wfl::formula form(vcfg["formula"], new wfl::gamestate_function_symbol_table);
if(!form.evaluate(callable).as_bool()) {
return false;
}
@@ -709,10 +722,10 @@ bool basic_unit_filter_impl::internal_matches_filter(const unit & u, const map_l
return true;
}
-std::vector basic_unit_filter_impl::all_matches_on_map(unsigned max_matches) const {
+std::vector basic_unit_filter_impl::all_matches_on_map(unsigned max_matches, const map_location* loc, const unit* u2) const {
std::vector ret;
for (const unit & u : fc_.get_disp_context().units()) {
- if (matches(u, u.get_location(), nullptr)) {
+ if (matches(u, loc ? *loc : u.get_location(), u2)) {
if(max_matches == 0) {
return ret;
}
diff --git a/src/units/filter.hpp b/src/units/filter.hpp
index 06c3246af363..63644090e222 100644
--- a/src/units/filter.hpp
+++ b/src/units/filter.hpp
@@ -39,7 +39,7 @@ struct map_location;
class unit_filter_abstract_impl {
public:
virtual bool matches(const unit & u, const map_location & loc, const unit * u2 = nullptr) const = 0;
- virtual std::vector all_matches_on_map(unsigned max_matches) const = 0;
+ virtual std::vector all_matches_on_map(unsigned max_matches, const map_location* loc = nullptr, const unit* u2 = nullptr) const = 0;
virtual unit_const_ptr first_match_on_map() const = 0;
virtual config to_config() const = 0;
virtual bool empty() const {return false;}
@@ -95,6 +95,18 @@ class unit_filter {
return impl_->all_matches_on_map(max_matches_);
}
+ std::vector all_matches_at(const map_location& loc) const {
+ return impl_->all_matches_on_map(max_matches_, &loc);
+ }
+
+ std::vector all_matches_with_unit(const unit& u) const {
+ return impl_->all_matches_on_map(max_matches_, nullptr, &u);
+ }
+
+ std::vector all_matches_with_unit_at(const unit& u, const map_location& loc) const {
+ return impl_->all_matches_on_map(max_matches_, &loc, &u);
+ }
+
unit_const_ptr first_match_on_map() const {
return impl_->first_match_on_map();
}
diff --git a/src/units/unit.hpp b/src/units/unit.hpp
index 59f7e3a5f8b7..7b7b698742cb 100644
--- a/src/units/unit.hpp
+++ b/src/units/unit.hpp
@@ -54,11 +54,20 @@ using unit_ability = std::pair;
class unit_ability_list
{
public:
- unit_ability_list() : cfgs_() {}
+ unit_ability_list(const map_location& loc = map_location()) : cfgs_() , loc_(loc) {}
// Implemented in unit_abilities.cpp
- std::pair highest(const std::string& key, int def=0) const;
- std::pair lowest(const std::string& key, int def=0) const;
+ std::pair highest(const std::string& key, int def=0) const
+ {
+ return get_extremum(key, def, std::less());
+ }
+ std::pair lowest(const std::string& key, int def=0) const
+ {
+ return get_extremum(key, def, std::greater());
+ }
+
+ template
+ std::pair get_extremum(const std::string& key, int def, const TComp& comp) const;
// The following make this class usable with standard library algorithms and such
typedef std::vector::iterator iterator;
@@ -79,9 +88,11 @@ class unit_ability_list
iterator erase(const iterator& erase_it) { return cfgs_.erase(erase_it); }
void push_back(const unit_ability& ability) { cfgs_.push_back(ability); }
+ const map_location& loc() const { return loc_; }
private:
// Data
std::vector cfgs_;
+ map_location loc_;
};
/**