Add [find_path] option "nearest_by", and simple_find_path test
Adding this is issue 2 of #4177, changing the behavior when [find_path] is given a SLF which matches multiple hexes. The map and tests here should be easy enough for manually editing them. It duplicates some of the functionality of the existing characterize_pathfinding tests, however those tests need their expected values to be calculated and can't be changed by hand. '''nearest_by''': {DevFeature1.15|2} possible values "movement_cost" (default), "steps", "hexes". If the [destination] SLF matches multiple hexes, the one that would need the least movement points to reach may not be the one that's closest as measured by '''hexes''', or closest as measured by steps, from the starting point. Behavior in 1.14 depended on which hex was checked first.
This commit is contained in:
parent
2d16a3b410
commit
06dd9a140c
6 changed files with 224 additions and 13 deletions
|
@ -26,6 +26,19 @@ function wesnoth.wml_actions.find_path(cfg)
|
|||
local allow_multiple_turns = cfg.allow_multiple_turns
|
||||
local viewing_side
|
||||
|
||||
local nearest_by_cost = true
|
||||
local nearest_by_distance = false
|
||||
local nearest_by_steps = false
|
||||
if (cfg.nearest_by or "movement_cost") == "hexes" then
|
||||
nearest_by_cost = false
|
||||
nearest_by_distance = true
|
||||
nearest_by_steps = false
|
||||
elseif (cfg.nearest_by or "movement_cost") == "steps" then
|
||||
nearest_by_cost = false
|
||||
nearest_by_distance = false
|
||||
nearest_by_steps = true
|
||||
end
|
||||
|
||||
if not cfg.check_visibility then viewing_side = 0 end -- if check_visiblity then shroud is taken in account
|
||||
|
||||
-- only the first location with the lowest distance and lowest movement cost will match.
|
||||
|
@ -33,32 +46,57 @@ function wesnoth.wml_actions.find_path(cfg)
|
|||
|
||||
local max_cost = nil
|
||||
if not allow_multiple_turns then max_cost = unit.moves end --to avoid wrong calculation on already moved units
|
||||
local current_distance, current_cost = math.huge, math.huge
|
||||
local current_distance, current_cost, current_steps = math.huge, math.huge, math.huge
|
||||
local current_location = {}
|
||||
|
||||
local width,heigth = wesnoth.get_map_size() -- data for test below
|
||||
|
||||
for index, location in ipairs(locations) do
|
||||
-- we test if location passed to pathfinder is invalid (border);
|
||||
-- if it is, do not return and continue the cycle
|
||||
-- if it is, do not use it, and continue the cycle
|
||||
if location[1] == 0 or location[1] == ( width + 1 ) or location[2] == 0 or location[2] == ( heigth + 1 ) then
|
||||
else
|
||||
local distance = wesnoth.map.distance_between ( unit.x, unit.y, location[1], location[2] )
|
||||
-- if we pass an unreachable locations an high value will be returned
|
||||
-- if we pass an unreachable location then an empty path and high value cost will be returned
|
||||
local path, cost = wesnoth.find_path( unit, location[1], location[2], { max_cost = max_cost, ignore_units = ignore_units, ignore_teleport = ignore_teleport, viewing_side = viewing_side } )
|
||||
|
||||
if distance < current_distance and cost <= current_cost
|
||||
or cost < current_cost and distance <= current_distance
|
||||
then
|
||||
-- avoid changing the hex with one less distance and more cost, or vice versa
|
||||
current_distance = distance
|
||||
current_cost = cost
|
||||
current_location = location
|
||||
if #path == 0 or cost >= 42424241 then
|
||||
-- it's not a reachable hex. 42424242 is the high value returned for unwalkable or busy terrains
|
||||
else
|
||||
local steps = #path
|
||||
|
||||
local is_better = false
|
||||
if nearest_by_cost and cost < current_cost then
|
||||
is_better = true
|
||||
elseif nearest_by_distance and distance < current_distance then
|
||||
is_better = true
|
||||
elseif nearest_by_steps and steps < current_steps then
|
||||
is_better = true
|
||||
elseif cost == current_cost and distance == current_distance and steps == current_steps then
|
||||
-- the two options are equivalent. Treating this as not-better probably creates a bias for
|
||||
-- choosing the north-west option, treating it as better probably biases to south-east.
|
||||
-- Choosing false is more likely to match the option that 1.14 would choose.
|
||||
is_better = false
|
||||
elseif cost <= current_cost and distance <= current_distance and steps <= current_steps then
|
||||
is_better = true
|
||||
end
|
||||
|
||||
if is_better then
|
||||
current_distance = distance
|
||||
current_cost = cost
|
||||
current_steps = steps
|
||||
current_location = location
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #current_location == 0 then wesnoth.message("WML warning","[find_path]'s filter didn't match any location")
|
||||
if #current_location == 0 then
|
||||
-- either no matching locations, or only inaccessible matching locations (maybe enemy units are there)
|
||||
if #locations == 0 then
|
||||
wesnoth.message("WML warning","[find_path]'s filter didn't match any location")
|
||||
end
|
||||
wml.variables[tostring(variable)] = { hexes = 0 } -- set only hexes, nil all other values
|
||||
else
|
||||
local path, cost = wesnoth.find_path(
|
||||
unit,
|
||||
|
@ -77,7 +115,7 @@ function wesnoth.wml_actions.find_path(cfg)
|
|||
turns = math.ceil( ( ( cost - unit.moves ) / unit.max_moves ) + 1 )
|
||||
end
|
||||
|
||||
if cost >= 42424242 then -- it's the high value returned for unwalkable or busy terrains
|
||||
if cost >= 42424241 then -- it's the high value returned for unwalkable or busy terrains
|
||||
wml.variables[tostring(variable)] = { hexes = 0 } -- set only length, nil all other values
|
||||
-- support for $this_unit
|
||||
wml.variables["this_unit"] = nil -- clearing this_unit
|
||||
|
|
|
@ -1149,6 +1149,7 @@
|
|||
{DEFAULT_KEY check_visibility s_bool no}
|
||||
{DEFAULT_KEY check_teleport s_bool yes}
|
||||
{DEFAULT_KEY check_zoc s_bool yes}
|
||||
{DEFAULT_KEY nearest_by find_path_nearest_by movement_cost}
|
||||
{FILTER_TAG "traveler" unit (
|
||||
min=1
|
||||
{INSERT_TAG}
|
||||
|
|
|
@ -311,6 +311,10 @@
|
|||
name="reachable_moves"
|
||||
value="current|max"
|
||||
[/type]
|
||||
[type]
|
||||
name="find_path_nearest_by"
|
||||
value="movement_cost|hexes|steps"
|
||||
[/type]
|
||||
[type]
|
||||
name="search_recall_list"
|
||||
[union]
|
||||
|
|
11
data/test/maps/simple_find_path.map
Normal file
11
data/test/maps/simple_find_path.map
Normal file
|
@ -0,0 +1,11 @@
|
|||
Xv, Xv, Xv, Xv, Xv, Xv, Xv, Xv, Xv
|
||||
Xv, Xv, Wwt, Wwt, Wwt, Xv, Xv, Xv, Xv
|
||||
Xv, Xv, Wwt, lake Gs^Vc, Wwt, Xv, in_void Gs^Vc, Xv, Xv
|
||||
Xv, Xv, Wwt, Wwt, Wwt, Xv, Xv, Xv, Xv
|
||||
Xv, Xv, Xv, Wwt, Xv, Xv, Xv, Xv, Xv
|
||||
Xv, 2 Gs^Vc, Gg, 1 Gg, Xv, u_turn Gs^Vc, Xv, Xv, Xv
|
||||
Xv, Xv, Xv, Gg, Xv, Gg, Xv, Xv, Xv
|
||||
Xv, wet_turn Gs^Vc, Xv, Gg, Xv, Gg, Xv, Xv, Xv
|
||||
Xv, Wwt, Wwt, Gg, Gg, Gg, Xv, Xv, Xv
|
||||
Xv, Xv, Xv, Xv, spur Gs^Vc, Xv, Xv, Xv, Xv
|
||||
Xv, Xv, Xv, Xv, Xv, Xv, Xv, Xv, Xv
|
157
data/test/scenarios/simple_find_path.cfg
Normal file
157
data/test/scenarios/simple_find_path.cfg
Normal file
|
@ -0,0 +1,157 @@
|
|||
# This test is called "simple" find_path because the expected values are hand-coded by
|
||||
# the developer. This is in contrast to the characterize_pathfinding_* tests, which
|
||||
# need the expected answers to be generated automatically.
|
||||
|
||||
#define FIND_ALICES_PATH DESTINATION
|
||||
[find_path]
|
||||
[traveler]
|
||||
id=alice
|
||||
[/traveler]
|
||||
[destination]
|
||||
{DESTINATION}
|
||||
[/destination]
|
||||
allow_multiple_turns=no
|
||||
variable=path
|
||||
[/find_path]
|
||||
#enddef
|
||||
|
||||
#define FIND_ALICES_PATH_2 DESTINATION NEAREST_BY
|
||||
[find_path]
|
||||
[traveler]
|
||||
id=alice
|
||||
[/traveler]
|
||||
[destination]
|
||||
{DESTINATION}
|
||||
[/destination]
|
||||
allow_multiple_turns=no
|
||||
variable=path
|
||||
nearest_by={NEAREST_BY}
|
||||
[/find_path]
|
||||
#enddef
|
||||
|
||||
# A conditional for ASSERT checks
|
||||
#define PATH_GOES_TO DESTINATION
|
||||
[have_location]
|
||||
{DESTINATION}
|
||||
[and]
|
||||
x,y=$path.to_x, $path.to_y
|
||||
[/and]
|
||||
[/have_location]
|
||||
#enddef
|
||||
|
||||
[test]
|
||||
name = "Unit Test simple_find_path"
|
||||
map_data = "{test/maps/simple_find_path.map}"
|
||||
turns = 1
|
||||
id = simple_find_path
|
||||
random_start_time = no
|
||||
is_unit_test = yes
|
||||
|
||||
{DAWN}
|
||||
|
||||
[side]
|
||||
side=1
|
||||
controller=human
|
||||
name = "Alice"
|
||||
type = Elvish Archer
|
||||
id=alice
|
||||
fog=no
|
||||
shroud=no
|
||||
share_view=no
|
||||
[/side]
|
||||
[side]
|
||||
side=2
|
||||
controller=human
|
||||
name = "Bob"
|
||||
type = Orcish Grunt
|
||||
id=bob
|
||||
fog=no
|
||||
shroud=no
|
||||
share_view=no
|
||||
[/side]
|
||||
|
||||
[event]
|
||||
name = side 1 turn 1
|
||||
|
||||
# If a path needs multiple turns then [find_path] will include the
|
||||
# cost of movement points that were left unused at the end of all turns
|
||||
# except the last. To avoid that, give Alice enough MP to move anywhere.
|
||||
[modify_unit]
|
||||
[filter]
|
||||
id=alice
|
||||
[/filter]
|
||||
moves="$({UNREACHABLE} - 1)"
|
||||
max_moves="$({UNREACHABLE} - 1)"
|
||||
[/modify_unit]
|
||||
|
||||
# A path can go to the hex that the unit is already on
|
||||
{FIND_ALICES_PATH location_id=1}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 0}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 1}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 0}}
|
||||
|
||||
{FIND_ALICES_PATH location_id=lake}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 3}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 4}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 7}}
|
||||
|
||||
{FIND_ALICES_PATH location_id=spur}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 5}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 6}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 5}}
|
||||
|
||||
{FIND_ALICES_PATH location_id=u_turn}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 2}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 9}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 8}}
|
||||
|
||||
{FIND_ALICES_PATH location_id=wet_turn}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 3}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 7}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 10}}
|
||||
|
||||
# There's no route to the in_void village
|
||||
{FIND_ALICES_PATH location_id=in_void}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 0}}
|
||||
|
||||
{FIND_ALICES_PATH_2 terrain=*^V* movement_cost}
|
||||
{ASSERT {PATH_GOES_TO location_id=spur}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 5}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 6}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 5}}
|
||||
|
||||
{FIND_ALICES_PATH_2 terrain=*^V* steps}
|
||||
{ASSERT {PATH_GOES_TO location_id=lake}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 3}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 4}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 7}}
|
||||
|
||||
{FIND_ALICES_PATH_2 terrain=*^V* hexes}
|
||||
{ASSERT {PATH_GOES_TO location_id=u_turn}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 2}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 9}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 8}}
|
||||
|
||||
# Without ignoring units, we can't move to Bob's starting hex
|
||||
{FIND_ALICES_PATH location_id=2}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 0}}
|
||||
|
||||
# If we ignore other units, we can move to Bob's starting hex
|
||||
[find_path]
|
||||
[traveler]
|
||||
id=alice
|
||||
[/traveler]
|
||||
[destination]
|
||||
location_id=2
|
||||
[/destination]
|
||||
allow_multiple_turns=no
|
||||
variable=path
|
||||
check_zoc=false
|
||||
[/find_path]
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.hexes equals 2}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.step.length equals 3}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL path.movement_cost equals 2}}
|
||||
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[/test]
|
|
@ -111,8 +111,8 @@
|
|||
#
|
||||
0 store_locations_one
|
||||
0 store_locations_range
|
||||
0 simple_find_path
|
||||
# This test occasionally takes too long... (FIXME): 0 characterize_pathfinding_one
|
||||
# The following tests segfault becasue of http://gna.org/bugs/?23188
|
||||
0 characterize_pathfinding_reach_1
|
||||
0 characterize_pathfinding_reach_2
|
||||
0 characterize_pathfinding_reach_3
|
||||
|
|
Loading…
Add table
Reference in a new issue