SotBE S6: move transport ship movement code to Lua AI
Only the moves are done by the AI at this time, as it is currently not possible to determine the best hexes onto which units should be disembarking in a replay-safe way from within the AI. We are working on that functionality though, so no WML code doing this is added at this time either.
This commit is contained in:
parent
ec69b0f186
commit
0505e93509
2 changed files with 214 additions and 22 deletions
184
data/campaigns/Son_Of_The_Black_Eye/ai/transport_engine.lua
Normal file
184
data/campaigns/Son_Of_The_Black_Eye/ai/transport_engine.lua
Normal file
|
@ -0,0 +1,184 @@
|
|||
return {
|
||||
init = function(ai)
|
||||
|
||||
local engine = {}
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
-- Move transport ships according to these rules:
|
||||
-- 1. If transport can get to its designated landing site this move, find
|
||||
-- close hex with the most unoccupied adjacent non-water hexes and move there
|
||||
-- 2. If landing site is out of reach, move toward destination while
|
||||
-- staying in deep water surrounded by deep water only
|
||||
|
||||
function engine:transport_eval()
|
||||
local units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0',
|
||||
}
|
||||
|
||||
for i,u in ipairs(units) do
|
||||
local vars = H.get_child(u.__cfg, "variables")
|
||||
if vars.destination_x and vars.destination_y then
|
||||
return 300000
|
||||
end
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:transport_exec(type)
|
||||
local units = wesnoth.get_units {}
|
||||
|
||||
-- Need all transport units plus maps of all units, all transport units and
|
||||
-- all other units (as those block hexes accessible to transport units)
|
||||
local transports = {}
|
||||
local unit_map, transport_map, blocked_hex_map = LS.create(), LS.create(), LS.create()
|
||||
|
||||
for i,u in ipairs(units) do
|
||||
unit_map:insert(u.x, u.y)
|
||||
|
||||
if (u.side == wesnoth.current.side) and (u.moves > 0)
|
||||
and u.variables.destination_x and u.variables.destination_x
|
||||
then
|
||||
transport_map:insert(u.x, u.y)
|
||||
table.insert(transports, u)
|
||||
--print("----> Inserting " .. u.id, u.x, u.y, u.variables.destination_x, u.variables.destination_y)
|
||||
else
|
||||
blocked_hex_map:insert(u.x, u.y)
|
||||
end
|
||||
end
|
||||
|
||||
-- First see if a transport is within landing distance
|
||||
local landing_site_map = LS.of_pairs(
|
||||
wesnoth.get_locations {
|
||||
terrain = 'W*',
|
||||
{ "filter_adjacent_location", { terrain = '!, W*' } }
|
||||
}
|
||||
)
|
||||
|
||||
local max_rating, best_unit, best_hex, best_adj_tiles = -9e99
|
||||
for i,u in ipairs(transports) do
|
||||
local dst = { u.variables.destination_x, u.variables.destination_y }
|
||||
|
||||
if (not u.variables.landed) and (H.distance_between(u.x, u.y, dst[1], dst[2]) <= u.moves) then
|
||||
local reach = wesnoth.find_reach(u)
|
||||
|
||||
for i,r in ipairs(reach) do
|
||||
if landing_site_map:get(r[1], r[2]) and (not unit_map:get(r[1], r[2]))
|
||||
then
|
||||
-- Distance from destination is minor rating
|
||||
local rating = -H.distance_between(r[1], r[2], dst[1], dst[2]) / 100.
|
||||
|
||||
-- Main rating is number of unoccupied land hexes and
|
||||
-- water hexes next to land hexes
|
||||
-- But shouldn't be too far away (arb. set to 5 hexes here)
|
||||
-- This is mostly to avoid it being across the bay in S6
|
||||
local adj_tiles = {}
|
||||
if (rating >= -0.05) then
|
||||
for x,y in H.adjacent_tiles(r[1], r[2]) do
|
||||
if (not unit_map:get(x, y)) then
|
||||
if wesnoth.match_location(x, y, { terrain = "!, W*" }) then
|
||||
rating = rating + 1
|
||||
table.insert(adj_tiles, { x, y, 1. } )
|
||||
end
|
||||
|
||||
if wesnoth.match_location(x, y,
|
||||
{
|
||||
terrain = "W*",
|
||||
{ "filter_adjacent_location", { terrain = "!, W*" } }
|
||||
}
|
||||
)
|
||||
then
|
||||
rating = rating + 0.1
|
||||
table.insert(adj_tiles, { x, y, 0.1 } )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating = rating
|
||||
best_unit = u
|
||||
best_hex = r
|
||||
best_adj_tiles = adj_tiles
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (max_rating > -9e99) then
|
||||
-- Also find the best unloading hexes
|
||||
-- TODO: this is currently not used as the AI functionality to make
|
||||
-- it replay-safe does not exist; to be added later
|
||||
--table.sort(best_adj_tiles, function(a, b) return a[3] > b[3] end)
|
||||
--for i,tile in ipairs(best_adj_tiles) do
|
||||
-- best_unit.variables['hex' .. i .. '_x'] = tile[1]
|
||||
-- best_unit.variables['hex' .. i .. '_y'] = tile[2]
|
||||
--end
|
||||
-- For remaining hexes, simply use the center hex and let the engine decide itself
|
||||
-- This also provides a safeguard against too many hexes being occupied
|
||||
--for i = #best_adj_tiles + 1, 6 do
|
||||
-- best_unit.variables['hex' .. i .. '_x'] = best_hex[1]
|
||||
-- best_unit.variables['hex' .. i .. '_y'] = best_hex[2]
|
||||
--end
|
||||
|
||||
ai.move_full(best_unit, best_hex[1], best_hex[2])
|
||||
return
|
||||
end
|
||||
|
||||
-- If we got here, no landing site was found. Do a deep-water move instead
|
||||
local deep_water_map = LS.of_pairs(
|
||||
wesnoth.get_locations {
|
||||
terrain = 'Wo',
|
||||
{ "not", { { "filter_adjacent_location", { terrain = '!, Wo' } } } }
|
||||
}
|
||||
)
|
||||
|
||||
local max_rating, best_unit, best_hex = -9e99, {}, {}
|
||||
for i,u in ipairs(transports) do
|
||||
local dst = { u.variables.destination_x, u.variables.destination_y }
|
||||
local reach = wesnoth.find_reach(u)
|
||||
|
||||
local max_rating_unit, best_hex_unit = -9e99, {}
|
||||
for i,r in ipairs(reach) do
|
||||
if deep_water_map:get(r[1], r[2]) and (not blocked_hex_map:get(r[1], r[2])) then
|
||||
local rating = -H.distance_between(r[1], r[2], dst[1], dst[2])
|
||||
-- If possible, also move in a straight line
|
||||
rating = rating - math.abs(r[1] - dst[1]) / 100.
|
||||
rating = rating - math.abs(r[2] - dst[2]) / 100.
|
||||
if (rating > max_rating_unit) then
|
||||
max_rating_unit = rating
|
||||
best_hex_unit = r
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- We give a penalty to hexes occupied by another transport that can still move away.
|
||||
-- All ratings need to be set to the same value for this to work.
|
||||
if (max_rating_unit > -9e99) then
|
||||
max_rating_unit = 0
|
||||
if transport_map:get(best_hex_unit[1], best_hex_unit[2]) then
|
||||
max_rating_unit = -1
|
||||
end
|
||||
|
||||
if (max_rating_unit > max_rating) then
|
||||
max_rating = max_rating_unit
|
||||
best_unit = u
|
||||
best_hex = best_hex_unit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if best_unit.id then
|
||||
ai.move_full(best_unit, best_hex[1], best_hex[2])
|
||||
else -- still need to make sure gamestate gets changed
|
||||
ai.stopunit_moves(transports[1])
|
||||
end
|
||||
end
|
||||
|
||||
return engine
|
||||
end
|
||||
}
|
|
@ -54,9 +54,25 @@
|
|||
user_team_name=_"Humans"
|
||||
|
||||
[ai]
|
||||
[avoid]
|
||||
x,y=1-10,1-20
|
||||
[/avoid]
|
||||
version=10710
|
||||
[engine]
|
||||
name="lua"
|
||||
code= <<
|
||||
local ai = ...
|
||||
return wesnoth.require("campaigns/Son_Of_The_Black_Eye/ai/transport_engine.lua").init(ai)
|
||||
>>
|
||||
[/engine]
|
||||
{RCA_STAGE}
|
||||
{MODIFY_AI_ADD_CANDIDATE_ACTION 3 main_loop (
|
||||
[candidate_action]
|
||||
engine=lua
|
||||
name=transport
|
||||
id=transport
|
||||
max_score=300000
|
||||
evaluation="return (...):transport_eval()"
|
||||
execution="(...):transport_exec()"
|
||||
[/candidate_action]
|
||||
)}
|
||||
[/ai]
|
||||
[/side]
|
||||
|
||||
|
@ -270,7 +286,7 @@
|
|||
terrain=Ww
|
||||
|
||||
[filter_adjacent_location]
|
||||
terrain=Ds,*^Bw*
|
||||
terrain="!, W*"
|
||||
[/filter_adjacent_location]
|
||||
|
||||
[not]
|
||||
|
@ -288,8 +304,6 @@
|
|||
x,y=1,30
|
||||
[/filter]
|
||||
|
||||
goto_x=$possible_transport_destinations[$transport_destination_index].x
|
||||
goto_y=$possible_transport_destinations[$transport_destination_index].y
|
||||
[variables]
|
||||
destination_x=$possible_transport_destinations[$transport_destination_index].x
|
||||
destination_y=$possible_transport_destinations[$transport_destination_index].y
|
||||
|
@ -323,6 +337,14 @@
|
|||
|
||||
[filter]
|
||||
type=Transport Galleon
|
||||
|
||||
[filter_location]
|
||||
terrain=Ww
|
||||
[filter_adjacent_location]
|
||||
terrain="!, W*"
|
||||
[/filter_adjacent_location]
|
||||
[/filter_location]
|
||||
|
||||
[not]
|
||||
[filter_wml]
|
||||
[variables]
|
||||
|
@ -330,21 +352,6 @@
|
|||
[/variables]
|
||||
[/filter_wml]
|
||||
[/not]
|
||||
|
||||
[and]
|
||||
[filter_wml]
|
||||
[variables]
|
||||
destination_x=$x1
|
||||
destination_y=$y1
|
||||
[/variables]
|
||||
[/filter_wml]
|
||||
|
||||
[or]
|
||||
[filter_adjacent]
|
||||
side=1,5
|
||||
[/filter_adjacent]
|
||||
[/or]
|
||||
[/and]
|
||||
[/filter]
|
||||
|
||||
[modify_unit]
|
||||
|
@ -352,9 +359,10 @@
|
|||
id=$unit.id
|
||||
[/filter]
|
||||
|
||||
goto_x,goto_y=1,30
|
||||
[variables]
|
||||
landed=yes
|
||||
destination_x=1
|
||||
destination_y=30
|
||||
[/variables]
|
||||
[/modify_unit]
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue