mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-04 04:55:52 +00:00

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.
174 lines
6.3 KiB
Lua
174 lines
6.3 KiB
Lua
local helper = wesnoth.require "helper"
|
|
local utils = wesnoth.require "wml-utils"
|
|
|
|
function wesnoth.wml_actions.find_path(cfg)
|
|
local filter_unit = wml.get_child(cfg, "traveler") or helper.wml_error("[find_path] missing required [traveler] tag")
|
|
-- only the first unit matching
|
|
local unit = wesnoth.get_units(filter_unit)[1] or helper.wml_error("[find_path]'s filter didn't match any unit")
|
|
local filter_location = wml.get_child(cfg, "destination") or helper.wml_error( "[find_path] missing required [destination] tag" )
|
|
-- support for $this_unit
|
|
local this_unit = utils.start_var_scope("this_unit")
|
|
|
|
wml.variables["this_unit"] = nil -- clearing this_unit
|
|
wml.variables["this_unit"] = unit.__cfg -- cfg field needed
|
|
|
|
local variable = cfg.variable or "path"
|
|
local ignore_units = false
|
|
local ignore_teleport = false
|
|
|
|
if cfg.check_zoc == false then --if we do not want to check the ZoCs, we must ignore units
|
|
ignore_units = true
|
|
end
|
|
if cfg.check_teleport == false then --if we do not want to check teleport, we must ignore it
|
|
ignore_teleport = true
|
|
end
|
|
|
|
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.
|
|
local locations = wesnoth.get_locations(filter_location)
|
|
|
|
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, 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 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 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 #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
|
|
-- 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,
|
|
current_location[1], current_location[2],
|
|
{
|
|
max_cost = max_cost,
|
|
ignore_units = ignore_units,
|
|
ignore_teleport = ignore_teleport,
|
|
viewing_side = viewing_side
|
|
})
|
|
local turns
|
|
|
|
if cost == 0 then -- if location is the same, of course it doesn't cost any MP
|
|
turns = 0
|
|
else
|
|
turns = math.ceil( ( ( cost - unit.moves ) / unit.max_moves ) + 1 )
|
|
end
|
|
|
|
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
|
|
utils.end_var_scope("this_unit", this_unit)
|
|
return end
|
|
|
|
if not allow_multiple_turns and turns > 1 then -- location cannot be reached in one turn
|
|
wml.variables[tostring(variable)] = { hexes = 0 }
|
|
-- support for $this_unit
|
|
wml.variables["this_unit"] = nil -- clearing this_unit
|
|
utils.end_var_scope("this_unit", this_unit)
|
|
return end -- skip the cycles below
|
|
|
|
wml.variables[tostring( variable )] =
|
|
{
|
|
hexes = current_distance,
|
|
from_x = unit.x, from_y = unit.y,
|
|
to_x = current_location[1], to_y = current_location[2],
|
|
movement_cost = cost,
|
|
required_turns = turns
|
|
}
|
|
|
|
for index, path_loc in ipairs(path) do
|
|
local sub_path, sub_cost = wesnoth.find_path(
|
|
unit,
|
|
path_loc[1],
|
|
path_loc[2],
|
|
{
|
|
max_cost = max_cost,
|
|
ignore_units = ignore_units,
|
|
ignore_teleport = ignore_teleport,
|
|
viewing_side = viewing_side
|
|
} )
|
|
local sub_turns
|
|
|
|
if sub_cost == 0 then
|
|
sub_turns = 0
|
|
else
|
|
sub_turns = math.ceil( ( ( sub_cost - unit.moves ) / unit.max_moves ) + 1 )
|
|
end
|
|
|
|
wml.variables[string.format( "%s.step[%d]", variable, index - 1 )] =
|
|
{ -- this structure takes less space in the inspection window
|
|
x = path_loc[1], y = path_loc[2],
|
|
terrain = wesnoth.get_terrain( path_loc[1], path_loc[2] ),
|
|
movement_cost = sub_cost,
|
|
required_turns = sub_turns
|
|
}
|
|
end
|
|
end
|
|
|
|
-- support for $this_unit
|
|
wml.variables["this_unit"] = nil -- clearing this_unit
|
|
utils.end_var_scope("this_unit", this_unit)
|
|
end
|