Merge pull request #5207 from mattsc/messenger_mai_avoid
Messenger Micro AI: add [avoid] tag functionality
This commit is contained in:
commit
d45f1d3e3d
5 changed files with 90 additions and 57 deletions
|
@ -295,6 +295,9 @@ function ai_helper.robust_move_and_attack(ai, src, dst, target_loc, cfg)
|
|||
-- parameter is true, a partial move is done instead.
|
||||
-- weapon: The number (starting at 1) of the attack weapon to be used.
|
||||
-- If omitted, the best weapon is automatically selected.
|
||||
-- avoid_map (location set): if given, the hexes in avoid_map are excluded
|
||||
-- This is only used for passing through to move_unit_out_of_way. It is assumed
|
||||
-- that @dst has been checked to lie outside areas to avoid.
|
||||
-- all optional parameters for ai_helper.move_unit_out_of_way()
|
||||
|
||||
-- Notes:
|
||||
|
@ -739,11 +742,12 @@ function ai_helper.get_locations_no_borders(location_filter)
|
|||
return locs
|
||||
end
|
||||
|
||||
function ai_helper.get_closest_location(hex, location_filter, unit)
|
||||
function ai_helper.get_closest_location(hex, location_filter, unit, avoid_map)
|
||||
-- Get the location closest to @hex (in format { x, y })
|
||||
-- that matches @location_filter (in WML table format)
|
||||
-- @unit can be passed as an optional third parameter, in which case the
|
||||
-- terrain needs to be passable for that unit
|
||||
-- @avoid_map (location set): if given, the hexes in avoid_map are excluded
|
||||
-- Returns nil if no terrain matching the filter was found
|
||||
|
||||
-- Find the maximum distance from 'hex' that's possible on the map
|
||||
|
@ -780,6 +784,14 @@ function ai_helper.get_closest_location(hex, location_filter, unit)
|
|||
|
||||
local locs = wesnoth.map.find(loc_filter)
|
||||
|
||||
if avoid_map then
|
||||
for i = #locs,1,-1 do
|
||||
if avoid_map:get(locs[i][1], locs[i][2]) then
|
||||
table.remove(locs, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if unit then
|
||||
for _,loc in ipairs(locs) do
|
||||
local movecost = unit:movement_on(wesnoth.current.map[loc])
|
||||
|
@ -1958,6 +1970,7 @@ function ai_helper.move_unit_out_of_way(ai, unit, cfg)
|
|||
-- Main rating is the moves the unit still has left after that
|
||||
-- Other, configurable, parameters are given to function in @cfg:
|
||||
-- dx, dy: the direction in which moving out of the way is preferred
|
||||
-- avoid_map (location set): if given, the hexes in avoid_map are excluded
|
||||
-- labels: if set, display labels of the rating for each hex the unit can reach
|
||||
-- viewing_side: see comments at beginning of this file. Defaults to side of @unit.
|
||||
-- ignore_visibility: see comments at beginning of this file. Defaults to nil.
|
||||
|
@ -1983,17 +1996,20 @@ function ai_helper.move_unit_out_of_way(ai, unit, cfg)
|
|||
if (not unit_in_way) -- also excludes current hex
|
||||
or ((not ignore_visibility) and (not ai_helper.is_visible_unit(viewing_side, unit_in_way)))
|
||||
then
|
||||
local rating = loc[3] -- also disfavors hexes next to visible enemy units for which loc[3] = 0
|
||||
local avoid_this_hex = cfg and cfg.avoid_map and cfg.avoid_map:get(loc[1], loc[2])
|
||||
if (not avoid_this_hex) then
|
||||
local rating = loc[3] -- also disfavors hexes next to visible enemy units for which loc[3] = 0
|
||||
|
||||
if dx then
|
||||
rating = rating + (loc[1] - unit.x) * dx * 0.01
|
||||
rating = rating + (loc[2] - unit.y) * dy * 0.01
|
||||
end
|
||||
if dx then
|
||||
rating = rating + (loc[1] - unit.x) * dx * 0.01
|
||||
rating = rating + (loc[2] - unit.y) * dy * 0.01
|
||||
end
|
||||
|
||||
if cfg.labels then reach_map:insert(loc[1], loc[2], rating) end
|
||||
if cfg.labels then reach_map:insert(loc[1], loc[2], rating) end
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating, best_hex = rating, { loc[1], loc[2] }
|
||||
if (rating > max_rating) then
|
||||
max_rating, best_hex = rating, { loc[1], loc[2] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,13 +4,13 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|||
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
|
||||
local best_attack
|
||||
|
||||
local function messenger_find_enemies_in_way(messenger, goal_x, goal_y)
|
||||
local function messenger_find_enemies_in_way(messenger, goal_x, goal_y, avoid_map)
|
||||
-- Returns the first unit on or next to the path of the messenger
|
||||
-- @messenger: proxy table for the messenger unit
|
||||
-- @goal_x,@goal_y: coordinates of the goal toward which the messenger moves
|
||||
-- Returns proxy table for the first unit found, or nil if none was found
|
||||
|
||||
local path, cost = AH.find_path_with_shroud(messenger, goal_x, goal_y, { ignore_units = true })
|
||||
local path, cost = AH.find_path_with_avoid(messenger, goal_x, goal_y, avoid_map, { ignore_enemies = true })
|
||||
if cost >= 42424242 then return end
|
||||
|
||||
-- The second path hex is the first that is important for the following analysis
|
||||
|
@ -23,7 +23,7 @@ local function messenger_find_enemies_in_way(messenger, goal_x, goal_y)
|
|||
|
||||
-- After that, go through adjacent hexes of all the other path hexes
|
||||
for i = 2,#path do
|
||||
local sub_path, sub_cost = AH.find_path_with_shroud(messenger, path[i][1], path[i][2], { ignore_units = true })
|
||||
local sub_path, sub_cost = AH.find_path_with_avoid(messenger, path[i][1], path[i][2], avoid_map, { ignore_enemies = true })
|
||||
if (sub_cost <= messenger.moves) then
|
||||
for xa,ya in H.adjacent_tiles(path[i][1], path[i][2]) do
|
||||
local enemy = wesnoth.units.get(xa, ya)
|
||||
|
@ -42,7 +42,9 @@ local function messenger_find_clearing_attack(messenger, goal_x, goal_y, cfg)
|
|||
-- @goal_x,@goal_y: coordinates of the goal toward which the messenger moves
|
||||
-- Returns proxy table containing the attack, or nil if none was found
|
||||
|
||||
local enemy_in_way = messenger_find_enemies_in_way(messenger, goal_x, goal_y)
|
||||
local avoid_map = AH.get_avoid_map(ai, wml.get_child(cfg, "avoid"), true)
|
||||
|
||||
local enemy_in_way = messenger_find_enemies_in_way(messenger, goal_x, goal_y, avoid_map)
|
||||
if (not enemy_in_way) then return end
|
||||
|
||||
local filter = wml.get_child(cfg, "filter") or { id = cfg.id }
|
||||
|
@ -57,8 +59,9 @@ local function messenger_find_clearing_attack(messenger, goal_x, goal_y, cfg)
|
|||
|
||||
local max_rating = - math.huge
|
||||
for _,attack in ipairs(attacks) do
|
||||
if (attack.target.x == enemy_in_way.x) and (attack.target.y == enemy_in_way.y) then
|
||||
|
||||
if (attack.target.x == enemy_in_way.x) and (attack.target.y == enemy_in_way.y)
|
||||
and (not avoid_map:get(attack.dst.x, attack.dst.y))
|
||||
then
|
||||
-- Rating: expected HP of attacker and defender
|
||||
local rating = attack.att_stats.average_hp - 2 * attack.def_stats.average_hp
|
||||
|
||||
|
@ -74,16 +77,18 @@ local function messenger_find_clearing_attack(messenger, goal_x, goal_y, cfg)
|
|||
--> try to fight our way to that enemy
|
||||
for _,attack in ipairs(attacks) do
|
||||
-- Rating: expected HP of attacker and defender
|
||||
local rating = attack.att_stats.average_hp - 2 * attack.def_stats.average_hp
|
||||
if (not avoid_map:get(attack.dst.x, attack.dst.y)) then
|
||||
local rating = attack.att_stats.average_hp - 2 * attack.def_stats.average_hp
|
||||
|
||||
-- Give a huge bonus for closeness to enemy_in_way
|
||||
local tmp_defender = wesnoth.units.get(attack.target.x, attack.target.y)
|
||||
local dist = wesnoth.map.distance_between(enemy_in_way.x, enemy_in_way.y, tmp_defender.x, tmp_defender.y)
|
||||
-- Give a huge bonus for closeness to enemy_in_way
|
||||
local tmp_defender = wesnoth.units.get(attack.target.x, attack.target.y)
|
||||
local dist = wesnoth.map.distance_between(enemy_in_way.x, enemy_in_way.y, tmp_defender.x, tmp_defender.y)
|
||||
|
||||
rating = rating + 100. / dist
|
||||
rating = rating + 100. / dist
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating, best_attack = rating, attack
|
||||
if (rating > max_rating) then
|
||||
max_rating, best_attack = rating, attack
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -108,7 +113,8 @@ function ca_messenger_attack:evaluation(cfg)
|
|||
end
|
||||
|
||||
function ca_messenger_attack:execution(cfg)
|
||||
AH.robust_move_and_attack(ai, best_attack.src, best_attack.dst, best_attack.target)
|
||||
local avoid_map = AH.get_avoid_map(ai, wml.get_child(cfg, "avoid"), true)
|
||||
AH.robust_move_and_attack(ai, best_attack.src, best_attack.dst, best_attack.target, { avoid_map = avoid_map })
|
||||
best_attack = nil
|
||||
end
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ function ca_messenger_escort_move:execution(cfg)
|
|||
local escorts = get_escorts(cfg)
|
||||
local _, _, _, messengers = messenger_next_waypoint(cfg)
|
||||
|
||||
local avoid_map = AH.get_avoid_map(ai, wml.get_child(cfg, "avoid"), true)
|
||||
|
||||
local enemies = AH.get_attackable_enemies()
|
||||
|
||||
local base_rating_map = LS.create()
|
||||
|
@ -44,40 +46,42 @@ function ca_messenger_escort_move:execution(cfg)
|
|||
local unit_rating = unit.max_moves / 100. + unit.hitpoints / 1000.
|
||||
|
||||
reach_map:iter( function(x, y, v)
|
||||
local base_rating = base_rating_map:get(x, y)
|
||||
if (not avoid_map:get(x, y)) or ((x == unit.x) and (y == unit.y)) then
|
||||
local base_rating = base_rating_map:get(x, y)
|
||||
|
||||
if (not base_rating) then
|
||||
base_rating = 0
|
||||
if (not base_rating) then
|
||||
base_rating = 0
|
||||
|
||||
-- Distance from messenger is most important; only closest messenger counts for this
|
||||
-- Give somewhat of a bonus for the messenger that has moved the farthest through the waypoints
|
||||
local max_messenger_rating = - math.huge
|
||||
for _,m in ipairs(messengers) do
|
||||
local messenger_rating = 1. / (M.distance_between(x, y, m.x, m.y) + 2.)
|
||||
local wp_rating = MAIUV.get_mai_unit_variables(m, cfg.ai_id, "wp_rating")
|
||||
messenger_rating = messenger_rating * 10. * (1. + wp_rating * 2.)
|
||||
-- Distance from messenger is most important; only closest messenger counts for this
|
||||
-- Give somewhat of a bonus for the messenger that has moved the farthest through the waypoints
|
||||
local max_messenger_rating = - math.huge
|
||||
for _,m in ipairs(messengers) do
|
||||
local messenger_rating = 1. / (M.distance_between(x, y, m.x, m.y) + 2.)
|
||||
local wp_rating = MAIUV.get_mai_unit_variables(m, cfg.ai_id, "wp_rating")
|
||||
messenger_rating = messenger_rating * 10. * (1. + wp_rating * 2.)
|
||||
|
||||
if (messenger_rating > max_messenger_rating) then
|
||||
max_messenger_rating = messenger_rating
|
||||
if (messenger_rating > max_messenger_rating) then
|
||||
max_messenger_rating = messenger_rating
|
||||
end
|
||||
end
|
||||
|
||||
base_rating = base_rating + max_messenger_rating
|
||||
|
||||
-- Distance from (sum of) enemies is important too
|
||||
-- This favors placing escort units between the messenger and close enemies
|
||||
for _,e in ipairs(enemies) do
|
||||
base_rating = base_rating + 1. / (M.distance_between(x, y, e.x, e.y) + 2.)
|
||||
end
|
||||
|
||||
base_rating_map:insert(x, y, base_rating)
|
||||
end
|
||||
|
||||
base_rating = base_rating + max_messenger_rating
|
||||
local rating = base_rating + unit_rating
|
||||
|
||||
-- Distance from (sum of) enemies is important too
|
||||
-- This favors placing escort units between the messenger and close enemies
|
||||
for _,e in ipairs(enemies) do
|
||||
base_rating = base_rating + 1. / (M.distance_between(x, y, e.x, e.y) + 2.)
|
||||
if (rating > max_rating) then
|
||||
max_rating = rating
|
||||
best_unit, best_hex = unit, { x, y }
|
||||
end
|
||||
|
||||
base_rating_map:insert(x, y, base_rating)
|
||||
end
|
||||
|
||||
local rating = base_rating + unit_rating
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating = rating
|
||||
best_unit, best_hex = unit, { x, y }
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -16,23 +16,30 @@ end
|
|||
function ca_messenger_move:execution(cfg)
|
||||
local messenger, x, y = messenger_next_waypoint(cfg)
|
||||
|
||||
local avoid_map = AH.get_avoid_map(ai, wml.get_child(cfg, "avoid"), true)
|
||||
|
||||
if (messenger.x ~= x) or (messenger.y ~= y) then
|
||||
local wp = AH.get_closest_location(
|
||||
{ x, y },
|
||||
{ { "not", { { "filter", { { "not", { side = wesnoth.current.side } } } } } } },
|
||||
messenger
|
||||
messenger, avoid_map
|
||||
)
|
||||
x, y = wp[1], wp[2]
|
||||
end
|
||||
|
||||
local next_hop = AH.next_hop(messenger, x, y, { ignore_own_units = true } )
|
||||
local avoid_map = AH.get_avoid_map(ai, wml.get_child(cfg, "avoid"), true)
|
||||
|
||||
local path = AH.find_path_with_avoid(messenger, x, y, avoid_map)
|
||||
if (not path) then path = { { messenger.x, messenger.y } } end
|
||||
local next_hop = AH.next_hop(messenger, x, y, { path = path, avoid_map = avoid_map } )
|
||||
if (not next_hop) then next_hop = { messenger.x, messenger.y } end
|
||||
|
||||
-- Compare this to the "ideal path"
|
||||
local path = AH.find_path_with_shroud(messenger, x, y, { ignore_units = 'yes' })
|
||||
local path = AH.find_path_with_avoid(messenger, x, y, avoid_map, { ignore_enemies = true })
|
||||
if (not path) then path = { { messenger.x, messenger.y } } end
|
||||
local optimum_hop = { messenger.x, messenger.y }
|
||||
for _,step in ipairs(path) do
|
||||
local sub_path, sub_cost = AH.find_path_with_shroud(messenger, step[1], step[2])
|
||||
local sub_path, sub_cost = AH.find_path_with_avoid(messenger, step[1], step[2], avoid_map)
|
||||
if sub_cost > messenger.moves then
|
||||
break
|
||||
else
|
||||
|
@ -60,14 +67,14 @@ function ca_messenger_move:execution(cfg)
|
|||
if unit_in_way then unit_in_way:extract() end
|
||||
|
||||
messenger.loc = { next_hop[1], next_hop[2] }
|
||||
local _, cost1 = AH.find_path_with_shroud(messenger, x, y, { ignore_units = 'yes' })
|
||||
local _, cost1 = AH.find_path_with_avoid(messenger, x, y, avoid_map, { ignore_enemies = true })
|
||||
|
||||
local unit_in_way2 = wesnoth.units.get(optimum_hop[1], optimum_hop[2])
|
||||
if (unit_in_way2 == messenger) then unit_in_way2 = nil end
|
||||
if unit_in_way2 then unit_in_way2:extract() end
|
||||
|
||||
messenger.loc = { optimum_hop[1], optimum_hop[2] }
|
||||
local _, cost2 = AH.find_path_with_shroud(messenger, x, y, { ignore_units = 'yes' })
|
||||
local _, cost2 = AH.find_path_with_avoid(messenger, x, y, avoid_map, { ignore_enemies = true })
|
||||
|
||||
messenger.loc = { x_current, y_current }
|
||||
if unit_in_way then unit_in_way:to_map() end
|
||||
|
@ -78,7 +85,7 @@ function ca_messenger_move:execution(cfg)
|
|||
if (cost2 + messenger.max_moves/2 < cost1) then next_hop = optimum_hop end
|
||||
|
||||
if next_hop and ((next_hop[1] ~= messenger.x) or (next_hop[2] ~= messenger.y)) then
|
||||
AH.robust_move_and_attack(ai, messenger, next_hop)
|
||||
AH.robust_move_and_attack(ai, messenger, next_hop, nil, { avoid_map = avoid_map })
|
||||
else
|
||||
AH.checked_stopunit_moves(ai, messenger)
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ function wesnoth.micro_ais.messenger_escort(cfg)
|
|||
AH.get_multi_named_locs_xy('waypoint', cfg, 'Messenger [micro_ai] tag')
|
||||
end
|
||||
local required_keys = {}
|
||||
local optional_keys = { "id", "enemy_death_chance", "[filter]", "[filter_second]", "invert_order", "messenger_death_chance", "waypoint_loc", "waypoint_x", "waypoint_y" }
|
||||
local optional_keys = { "[avoid]", "id", "enemy_death_chance", "[filter]", "[filter_second]", "invert_order", "messenger_death_chance", "waypoint_loc", "waypoint_x", "waypoint_y" }
|
||||
local score = cfg.ca_score or 300000
|
||||
local CA_parms = {
|
||||
ai_id = 'mai_messenger',
|
||||
|
|
Loading…
Add table
Reference in a new issue