local H = wesnoth.require "lua/helper.lua" local LS = wesnoth.require "lua/location_set.lua" local ca_transport = {} -- 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 -- Also unload units onto best hexes adjacent to landing site function ca_transport:evaluation(ai) 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 ca_transport:execution(ai) 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 SotBE 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. } ) elseif 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 } ) else table.insert(adj_tiles, { x, y, 0.0 } ) 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 ai.move_full(best_unit, best_hex[1], best_hex[2]) -- Also unload units table.sort(best_adj_tiles, function(a, b) return a[3] > b[3] end) local command = "local unit = wesnoth.get_unit(x1, y1) " .. "unit.variables.landed = 'yes' " .. "unit.variables.destination_x = 1 " .. "unit.variables.destination_y = 30" ai.synced_command(command, best_unit.x, best_unit.y) -- Unload 1 level 2 unit local l2_type = H.rand('Swordsman,Javelineer,Pikeman') local command = "wesnoth.put_unit(".. best_adj_tiles[1][1] .. "," .. best_adj_tiles[1][2] .. ", { side = " .. wesnoth.current.side .. ", type = '" .. l2_type .. "', moves = 2 })" ai.synced_command(command, best_unit.x, best_unit.y) -- Unload up to 2 level 1 units for i = 2, math.min(#best_adj_tiles, 3) do local l1_type = H.rand('Fencer,Mage,Cavalryman,Bowman,Spearman') local command = "wesnoth.put_unit(".. best_adj_tiles[i][1] .. "," .. best_adj_tiles[i][2] .. ", { side = " .. wesnoth.current.side .. ", type = '" .. l1_type .. "', moves = 2 })" ai.synced_command(command, best_unit.x, best_unit.y) end 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 ca_transport