-
-
Notifications
You must be signed in to change notification settings - Fork 988
/
ca_goto.lua
250 lines (216 loc) · 9.48 KB
/
ca_goto.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
local H = wesnoth.require "helper"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local LS = wesnoth.require "location_set"
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local MAISD = wesnoth.require "ai/micro_ais/micro_ai_self_data.lua"
local M = wesnoth.map
local function custom_cost(x, y, unit, enemy_map, enemy_attack_map, multiplier)
local terrain = wesnoth.get_terrain(x, y)
local move_cost = unit:movement(terrain)
move_cost = move_cost + (enemy_map:get(x,y) or 0)
move_cost = move_cost + (enemy_attack_map.units:get(x,y) or 0) * multiplier
return move_cost
end
local ca_goto, GO_units, GO_locs = {}
function ca_goto:evaluation(cfg, data)
-- If cfg.release_all_units_at_goal is set, check whether the goal has
-- already been reached, in which case we do not do anything
if MAISD.get_mai_self_data(data, cfg.ai_id, "release_all") then
return 0
end
local all_units = AH.get_units_with_moves {
{ "and", { side = wesnoth.current.side } },
{ "and", wml.get_child(cfg, "filter") }
}
local units = {}
if cfg.release_unit_at_goal then
for _,unit in ipairs(all_units) do
if (not MAIUV.get_mai_unit_variables(unit, cfg.ai_id, "release")) then
table.insert(units, unit)
end
end
else
units = all_units
end
if (not units[1]) then return 0 end
-- For convenience, we check for locations here, and just pass that to the exec function
-- This is mostly to make the unique_goals option easier
local width, height = wesnoth.get_map_size()
local all_locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", wml.get_child(cfg, "filter_location") }
}
if (#all_locs == 0) then return 0 end
-- If 'unique_goals' is set, check whether there are locations left to go to.
-- This does not have to be a persistent variable
local locs = {}
if cfg.unique_goals then
-- First, some cleanup of previous turn data
local str = 'goal_taken_' .. (wesnoth.current.turn - 1)
local old_goals = MAISD.get_mai_self_data(data, cfg.ai_id)
for goal,_ in pairs(old_goals) do
if string.find(goal, str) then
old_goals[goal] = nil -- This also removes it from data
end
end
-- Now on to the current turn
for _,loc in ipairs(all_locs) do
local str = 'goal_taken_' .. wesnoth.current.turn .. '_' .. loc[1] .. '_' .. loc[2]
if (not MAISD.get_mai_self_data(data, cfg.ai_id, str)) then
table.insert(locs, loc)
end
end
else
locs = all_locs
end
if (not locs[1]) then return 0 end
-- Now store units and locs, so that we don't need to duplicate this in the exec function
GO_units, GO_locs = units, locs
return cfg.ca_score
end
function ca_goto:execution(cfg, data)
local units, locs = GO_units, GO_locs
local enemy_map, enemy_attack_map
if cfg.avoid_enemies then
if (type(cfg.avoid_enemies) ~= 'number') then
H.wml_error("Goto AI avoid_enemies= requires a number as argument")
elseif (cfg.avoid_enemies <= 0) then
H.wml_error("Goto AI avoid_enemies= argument must be >0")
end
local enemies = AH.get_visible_units(wesnoth.current.side, {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
})
local live_enemies = {}
for _,enemy in ipairs(enemies) do
if (not enemy.status.petrified) then
table.insert(live_enemies, enemy)
end
end
enemy_map = LS.create()
for _,enemy in ipairs(enemies) do
enemy_map:insert(enemy.x, enemy.y, (enemy_map:get(enemy.x, enemy.y) or 0) + 1000)
end
for _,enemy in ipairs(live_enemies) do
for xa,ya in H.adjacent_tiles(enemy.x, enemy.y) do
enemy_map:insert(xa, ya, (enemy_map:get(xa, ya) or 0) + 10)
end
end
enemy_attack_map = BC.get_attack_map(live_enemies)
end
local max_rating, closest_hex, best_path, best_unit = -9e99
for _,unit in ipairs(units) do
for _,loc in ipairs(locs) do
-- If cfg.use_straight_line is set, we simply find the closest
-- hex to the goal that the unit can get to
if cfg.use_straight_line then
local hex, _, rating = AH.find_best_move(unit, function(x, y)
local r = -M.distance_between(x, y, loc[1], loc[2])
-- Also add distance from unit as very small rating component
-- This is mostly here to keep unit in place when no better hexes are available
r = r - M.distance_between(x, y, unit.x, unit.y) / 1000.
return r
end, { no_random = true })
if (rating > max_rating) then
max_rating = rating
closest_hex, best_unit = hex, unit
end
else -- Otherwise find the best path to take
local path, cost
if cfg.avoid_enemies then
path, cost = wesnoth.find_path(unit, loc[1], loc[2],
function(x, y, current_cost)
return custom_cost(x, y, unit, enemy_map, enemy_attack_map, cfg.avoid_enemies)
end
)
else
local enemy_at_goal
if cfg.ignore_enemy_at_goal then
enemy_at_goal = wesnoth.get_unit(loc[1], loc[2])
if enemy_at_goal and wesnoth.is_enemy(wesnoth.current.side, enemy_at_goal.side) then
enemy_at_goal:extract()
else
enemy_at_goal = nil
end
end
path, cost = AH.find_path_with_shroud(unit, loc[1], loc[2], { ignore_units = cfg.ignore_units })
if enemy_at_goal then
enemy_at_goal:to_map()
--- Give massive penalty for this goal hex
cost = cost + 100
end
end
-- Make all hexes within the unit's current MP equivalent
if (cost <= unit.moves) then cost = 0 end
local rating = - cost
-- Add a small penalty for hexes occupied by an allied unit
local unit_in_way = wesnoth.get_unit(loc[1], loc[2])
if unit_in_way and (unit_in_way ~= unit) then
rating = rating - 0.01
end
if (rating > max_rating) then
max_rating = rating
closest_hex, best_unit, best_path = loc, unit, path
end
end
end
end
-- If 'unique_goals' is set, mark this location as being taken
if cfg.unique_goals then
local str = 'goal_taken_' .. wesnoth.current.turn .. '_' .. closest_hex[1] .. '_' .. closest_hex[2]
local tmp_table = {}
tmp_table[str] = true
MAISD.insert_mai_self_data(data, cfg.ai_id, tmp_table)
end
-- If any of the non-standard path finding options were used,
-- we need to pick the farthest reachable hex along that path
-- For simplicity, we do it for all types of pathfinding here,
-- rather than using ai_helper.next_hop for standard pathfinding
-- Also, straight-line does not produce a path, so we do that first
if not best_path then
best_path = AH.find_path_with_shroud(best_unit, closest_hex[1], closest_hex[2])
end
-- Now go through the hexes along that path, use normal path finding
closest_hex = best_path[1]
for i = 2,#best_path do
local sub_path, sub_cost = AH.find_path_with_shroud(best_unit, best_path[i][1], best_path[i][2], cfg)
if sub_cost <= best_unit.moves then
local unit_in_way = wesnoth.get_unit(best_path[i][1], best_path[i][2])
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way)) then
closest_hex = best_path[i]
end
else
break
end
end
if closest_hex then
AH.checked_move_full(ai, best_unit, closest_hex[1], closest_hex[2])
else
AH.checked_stopunit_moves(ai, best_unit)
end
if (not best_unit) or (not best_unit.valid) then return end
-- If release_unit_at_goal= or release_all_units_at_goal= key is set:
-- Check if the unit made it to one of the goal hexes
-- This needs to be done for the original goal hexes, not by checking the SLF again,
-- as that might have changed based on the new situation on the map
if cfg.release_unit_at_goal or cfg.release_all_units_at_goal then
local is_unit_at_goal = false
for _,loc in ipairs(locs) do
if (best_unit.x == loc[1]) and (best_unit.y == loc[2]) then
is_unit_at_goal = true
break
end
end
if is_unit_at_goal then
if cfg.release_unit_at_goal then
MAIUV.set_mai_unit_variables(best_unit, cfg.ai_id, { release = true })
end
if cfg.release_all_units_at_goal then
MAISD.insert_mai_self_data(data, cfg.ai_id, { release_all = true })
end
end
end
GO_units, GO_locs = nil, nil
end
return ca_goto