-
-
Notifications
You must be signed in to change notification settings - Fork 988
/
find_path.lua
164 lines (142 loc) · 5.88 KB
/
find_path.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
local utils = wesnoth.require "wml-utils"
function wesnoth.wml_actions.find_path(cfg)
local filter_unit = wml.get_child(cfg, "traveler") or wml.error("[find_path] missing required [traveler] tag")
-- only the first unit matching
local unit = wesnoth.units.find_on_map(filter_unit)[1] or wml.error("[find_path]'s filter didn't match any unit")
local filter_location = wml.get_child(cfg, "destination") or wml.error( "[find_path] missing required [destination] tag" )
-- support for $this_unit
local this_unit <close> = utils.scoped_var("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
return
end
if not allow_multiple_turns and turns > 1 then -- location cannot be reached in one turn
wml.variables[tostring(variable)] = { hexes = 0 }
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
end