Messenger Escort Micro AI: code cleanup
This commit is contained in:
parent
83d23fac52
commit
d3ce1051ff
4 changed files with 74 additions and 116 deletions
|
@ -3,15 +3,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 function messenger_find_enemies_in_way(unit, goal_x, goal_y)
|
||||
local function messenger_find_enemies_in_way(messenger, goal_x, goal_y)
|
||||
-- Returns the first unit on or next to the path of the messenger
|
||||
-- unit: proxy table for the messenger unit
|
||||
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
|
||||
-- @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 = wesnoth.find_path(unit, goal_x, goal_y, { ignore_units = true })
|
||||
|
||||
-- If unit cannot get there:
|
||||
local path, cost = wesnoth.find_path(messenger, goal_x, goal_y, { ignore_units = true })
|
||||
if cost >= 42424242 then return end
|
||||
|
||||
-- The second path hex is the first that is important for the following analysis
|
||||
|
@ -20,52 +18,37 @@ local function messenger_find_enemies_in_way(unit, goal_x, goal_y)
|
|||
-- Is there an enemy unit on the second path hex?
|
||||
-- This would be caught by the adjacent hex check later, but not in the right order
|
||||
local enemy = wesnoth.get_units { x = path[2][1], y = path[2][2],
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
|
||||
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
|
||||
}[1]
|
||||
if enemy then
|
||||
--print(' enemy on second path hex:',enemy.id)
|
||||
return enemy
|
||||
end
|
||||
if enemy then return enemy end
|
||||
|
||||
-- After that, go through adjacent hexes of all the other path hexes
|
||||
for i = 2, #path do
|
||||
local path_hex = path[i]
|
||||
local sub_path, sub_cost = wesnoth.find_path( unit, path_hex[1], path_hex[2], { ignore_units = true })
|
||||
if sub_cost <= unit.moves then
|
||||
-- Check for enemy units on one of the adjacent hexes (which includes 2 hexes on path)
|
||||
for x, y in H.adjacent_tiles(path_hex[1], path_hex[2]) do
|
||||
local enemy = wesnoth.get_units { x = x, y = y,
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
|
||||
for i = 2,#path do
|
||||
local sub_path, sub_cost = wesnoth.find_path(messenger, path[i][1], path[i][2], { ignore_units = true })
|
||||
if (sub_cost <= messenger.moves) then
|
||||
for xa,ya in H.adjacent_tiles(path[i][1], path[i][2]) do
|
||||
local enemy = wesnoth.get_units { x = xa, y = ya,
|
||||
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
|
||||
}[1]
|
||||
if enemy then
|
||||
--print(' enemy next to path hex:',enemy.id)
|
||||
return enemy
|
||||
end
|
||||
if enemy then return enemy end
|
||||
end
|
||||
else -- If we've reached the end of the path for this turn
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- If no unit was found, return nil
|
||||
return
|
||||
end
|
||||
|
||||
local function messenger_find_clearing_attack(unit, goal_x, goal_y, cfg)
|
||||
local function messenger_find_clearing_attack(messenger, goal_x, goal_y, cfg)
|
||||
-- Check if an enemy is in the way of the messenger
|
||||
-- If so, find attack that would "clear" that enemy out of the way
|
||||
-- unit: proxy table for the messenger unit
|
||||
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
|
||||
-- If so, find attack that would clear that enemy out of the way
|
||||
-- @messenger: proxy table for the messenger unit
|
||||
-- @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(unit, goal_x, goal_y)
|
||||
-- If none found, don't attack, just move
|
||||
if not enemy_in_way then return end
|
||||
local enemy_in_way = messenger_find_enemies_in_way(messenger, goal_x, goal_y)
|
||||
if (not enemy_in_way) then return end
|
||||
|
||||
local max_rating, best_attack = -9e99, {}
|
||||
--print('Finding attacks on',enemy_in_way.name,enemy_in_way.id)
|
||||
|
||||
-- Find all units that can attack this enemy
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local units = AH.get_units_with_attacks {
|
||||
side = wesnoth.current.side,
|
||||
|
@ -76,62 +59,49 @@ local function messenger_find_clearing_attack(unit, goal_x, goal_y, cfg)
|
|||
|
||||
local attacks = AH.get_attacks(units, { simulate_combat = true })
|
||||
|
||||
for i, att in ipairs(attacks) do
|
||||
if (att.target.x == enemy_in_way.x) and (att.target.y == enemy_in_way.y) then
|
||||
local max_rating, best_attack = -9e99
|
||||
for _,attack in ipairs(attacks) do
|
||||
if (attack.target.x == enemy_in_way.x) and (attack.target.y == enemy_in_way.y) then
|
||||
|
||||
-- Rating: expected HP of attacker and defender
|
||||
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
|
||||
--print(' rating:', rating)
|
||||
local rating = attack.att_stats.average_hp - 2 * attack.def_stats.average_hp
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating = rating
|
||||
best_attack = att
|
||||
max_rating, best_attack = rating, attack
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If attack on this enemy_in_way is possible, return it
|
||||
if (max_rating > -9e99) then return best_attack end
|
||||
if best_attack then return best_attack end
|
||||
|
||||
-- If we got here, that means there's an enemy in the way, but none of the units can reach it
|
||||
--> try to fight our way to that enemy
|
||||
--print('Find different attack to get to enemy in way')
|
||||
for i, att in ipairs(attacks) do
|
||||
|
||||
for _,attack in ipairs(attacks) do
|
||||
-- Rating: expected HP of attacker and defender
|
||||
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
|
||||
local rating = attack.att_stats.average_hp - 2 * attack.def_stats.average_hp
|
||||
|
||||
-- plus, give a huge bonus for closeness to enemy_in_way
|
||||
local tmp_defender = wesnoth.get_unit(att.target.x, att.target.y)
|
||||
-- Give a huge bonus for closeness to enemy_in_way
|
||||
local tmp_defender = wesnoth.get_unit(attack.target.x, attack.target.y)
|
||||
local dist = H.distance_between(enemy_in_way.x, enemy_in_way.y, tmp_defender.x, tmp_defender.y)
|
||||
--print(' distance:',enemy_in_way.id, tmp_defender.id, dist)
|
||||
|
||||
rating = rating + 100. / dist
|
||||
--print(' rating:', rating)
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating = rating
|
||||
best_attack = att
|
||||
max_rating, best_attack = rating, attack
|
||||
end
|
||||
end
|
||||
|
||||
if (max_rating > -9e99) then
|
||||
return best_attack
|
||||
else
|
||||
return
|
||||
end
|
||||
if best_attack then return best_attack end
|
||||
end
|
||||
|
||||
local ca_messenger_attack = {}
|
||||
|
||||
function ca_messenger_attack:evaluation(ai, cfg, self)
|
||||
-- Attack units in the path of the messengers
|
||||
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
|
||||
|
||||
local messenger, x, y = messenger_next_waypoint(cfg)
|
||||
if (not messenger) then return 0 end
|
||||
|
||||
-- See if there's an enemy in the way that should be attacked
|
||||
local attack = messenger_find_clearing_attack(messenger, x, y, cfg)
|
||||
|
||||
if attack then
|
||||
|
|
|
@ -6,7 +6,7 @@ local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
|
|||
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
|
||||
|
||||
local function get_escorts(cfg)
|
||||
local escorts = AH.get_units_with_moves {
|
||||
local escorts = AH.get_units_with_movesssss {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ function ca_messenger_escort_move:evaluation(ai, cfg)
|
|||
-- Move escort units close to messengers, and in between messengers and enemies
|
||||
-- The messengers have moved at this time, so we don't need to exclude them,
|
||||
-- but we check that there are messengers left
|
||||
|
||||
if (not get_escorts(cfg)[1]) then return 0 end
|
||||
|
||||
local _, _, _, messengers = messenger_next_waypoint(cfg)
|
||||
|
@ -45,14 +46,13 @@ function ca_messenger_escort_move:execution(ai, cfg)
|
|||
local unit_rating = unit.max_moves / 100. + unit.hitpoints / 1000.
|
||||
|
||||
reach_map:iter( function(x, y, v)
|
||||
-- base rating only needs to be calculated once for each hex
|
||||
local base_rating = base_rating_map:get(x, y)
|
||||
|
||||
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 most through the waypoints
|
||||
-- Give somewhat of a bonus for the messenger that has moved the farthest through the waypoints
|
||||
local max_messenger_rating = -9e99
|
||||
for _,m in ipairs(messengers) do
|
||||
local messenger_rating = 1. / (H.distance_between(x, y, m.x, m.y) + 2.)
|
||||
|
@ -63,6 +63,7 @@ function ca_messenger_escort_move:execution(ai, cfg)
|
|||
max_messenger_rating = messenger_rating
|
||||
end
|
||||
end
|
||||
|
||||
base_rating = base_rating + max_messenger_rating
|
||||
|
||||
-- Distance from (sum of) enemies is important too
|
||||
|
@ -82,7 +83,6 @@ function ca_messenger_escort_move:execution(ai, cfg)
|
|||
end
|
||||
end)
|
||||
end
|
||||
--AH.put_labels(base_rating_map)
|
||||
|
||||
-- This will always find at least the hex the unit is on -> no check necessary
|
||||
AH.movefull_stopunit(ai, best_unit, best_hex)
|
||||
|
|
|
@ -16,7 +16,7 @@ return function(cfg)
|
|||
|
||||
local waypoint_x = AH.split(cfg.waypoint_x, ",")
|
||||
local waypoint_y = AH.split(cfg.waypoint_y, ",")
|
||||
for i,w in ipairs(waypoint_x) do
|
||||
for i,_ in ipairs(waypoint_x) do
|
||||
waypoint_x[i] = tonumber(waypoint_x[i])
|
||||
waypoint_y[i] = tonumber(waypoint_y[i])
|
||||
end
|
||||
|
@ -24,15 +24,15 @@ return function(cfg)
|
|||
-- Set the next waypoint for all messengers
|
||||
-- Also find those with MP left and return the one to next move, together with the WP to move toward
|
||||
local max_rating, best_messenger, x, y = -9e99
|
||||
for i, m in ipairs(messengers) do
|
||||
for _,messenger in ipairs(messengers) do
|
||||
-- To avoid code duplication and ensure consistency, we store some pieces of
|
||||
-- information in the messenger units, even though it could be calculated each time it is needed
|
||||
local wp_i = MAIUV.get_mai_unit_variables(m, cfg.ai_id, "wp_i") or 1
|
||||
local wp_i = MAIUV.get_mai_unit_variables(messenger, cfg.ai_id, "wp_i") or 1
|
||||
local wp_x, wp_y = waypoint_x[wp_i], waypoint_y[wp_i]
|
||||
|
||||
-- If this messenger is within 3 hexes of the next waypoint, we go on to the one after that
|
||||
-- except if that one's the last one already
|
||||
local dist_wp = H.distance_between(m.x, m.y, wp_x, wp_y)
|
||||
-- except if it's the last one
|
||||
local dist_wp = H.distance_between(messenger.x, messenger.y, wp_x, wp_y)
|
||||
if (dist_wp <= 3) and (wp_i < #waypoint_x) then wp_i = wp_i + 1 end
|
||||
|
||||
-- Also store the rating for each messenger
|
||||
|
@ -40,17 +40,12 @@ return function(cfg)
|
|||
local rating = wp_i - dist_wp / 1000.
|
||||
|
||||
-- If invert_order= key is set, we want to move the rearmost messenger first.
|
||||
-- We still want to keep the rating value positive (mostly, this is not strict)
|
||||
-- and of the same order of magnitude.
|
||||
if cfg.invert_order then
|
||||
rating = #waypoint_x - rating
|
||||
end
|
||||
if cfg.invert_order then rating = - rating end
|
||||
|
||||
MAIUV.set_mai_unit_variables(m, cfg.ai_id, { wp_i = wp_i, wp_x = wp_x, wp_y = wp_y, wp_rating = rating })
|
||||
MAIUV.set_mai_unit_variables(messenger, cfg.ai_id, { wp_i = wp_i, wp_x = wp_x, wp_y = wp_y, wp_rating = rating })
|
||||
|
||||
-- Find the messenger with the highest rating that has MP left
|
||||
if (m.moves > 0) and (rating > max_rating) then
|
||||
best_messenger, max_rating = m, rating
|
||||
if (messenger.moves > 0) and (rating > max_rating) then
|
||||
best_messenger, max_rating = messenger, rating
|
||||
x, y = wp_x, wp_y
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,14 +6,11 @@ local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f
|
|||
local ca_messenger_move = {}
|
||||
|
||||
function ca_messenger_move:evaluation(ai, cfg)
|
||||
-- Move the messenger toward goal, attack adjacent unit if possible
|
||||
-- without retaliation or little expected damage with high chance of killing the enemy
|
||||
-- Move the messenger toward goal, potentially attack adjacent unit
|
||||
|
||||
local messenger = messenger_next_waypoint(cfg)
|
||||
|
||||
if messenger then
|
||||
return cfg.ca_score
|
||||
end
|
||||
if messenger then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
|
@ -33,14 +30,14 @@ function ca_messenger_move:execution(ai, cfg)
|
|||
if (not next_hop) then next_hop = { messenger.x, messenger.y } end
|
||||
|
||||
-- Compare this to the "ideal path"
|
||||
local path, cost = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
|
||||
local opt_hop, opt_cost = { messenger.x, messenger.y }, 0
|
||||
for i, p in ipairs(path) do
|
||||
local sub_path, sub_cost = wesnoth.find_path(messenger, p[1], p[2])
|
||||
local path = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
|
||||
local optimum_hop, optimum_cost = { messenger.x, messenger.y }, 0
|
||||
for _,step in ipairs(path) do
|
||||
local sub_path, sub_cost = wesnoth.find_path(messenger, step[1], step[2])
|
||||
if sub_cost > messenger.moves then
|
||||
break
|
||||
else
|
||||
local unit_in_way = wesnoth.get_unit(p[1], p[2])
|
||||
local unit_in_way = wesnoth.get_unit(step[1], step[2])
|
||||
|
||||
if unit_in_way and (unit_in_way.side == messenger.side) then
|
||||
local reach = AH.get_reachable_unocc(unit_in_way)
|
||||
|
@ -48,38 +45,35 @@ function ca_messenger_move:execution(ai, cfg)
|
|||
end
|
||||
|
||||
if not unit_in_way then
|
||||
opt_hop, nh_cost = p, sub_cost
|
||||
optimum_hop, nh_cost = step, sub_cost
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--print(next_hop[1], next_hop[2], opt_hop[1], opt_hop[2])
|
||||
-- Now compare how long it would take from the end of both of these options
|
||||
local x1, y1 = messenger.x, messenger.y
|
||||
local x_current, y_current = messenger.x, messenger.y
|
||||
|
||||
local unit_in_way = wesnoth.get_unit(next_hop[1], next_hop[2])
|
||||
if (unit_in_way == messenger) then unit_in_way = nil end
|
||||
if unit_in_way then wesnoth.extract_unit(unit_in_way) end
|
||||
|
||||
wesnoth.put_unit(next_hop[1], next_hop[2], messenger)
|
||||
local tmp, cost1 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
|
||||
local _, cost1 = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
|
||||
|
||||
local unit_in_way2 = wesnoth.get_unit(opt_hop[1], opt_hop[2])
|
||||
local unit_in_way2 = wesnoth.get_unit(optimum_hop[1], optimum_hop[2])
|
||||
if (unit_in_way2 == messenger) then unit_in_way2 = nil end
|
||||
if unit_in_way2 then wesnoth.extract_unit(unit_in_way2) end
|
||||
|
||||
wesnoth.put_unit(opt_hop[1], opt_hop[2], messenger)
|
||||
local tmp, cost2 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
|
||||
wesnoth.put_unit(optimum_hop[1], optimum_hop[2], messenger)
|
||||
local _, cost2 = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
|
||||
|
||||
wesnoth.put_unit(x1, y1, messenger)
|
||||
wesnoth.put_unit(x_current, y_current, messenger)
|
||||
if unit_in_way then wesnoth.put_unit(unit_in_way) end
|
||||
if unit_in_way2 then wesnoth.put_unit(unit_in_way2) end
|
||||
--print(cost1, cost2)
|
||||
|
||||
-- If cost2 is significantly less, that means that the other path might overall be faster
|
||||
-- even though it is currently blocked
|
||||
if (cost2 + messenger.max_moves/2 < cost1) then next_hop = opt_hop end
|
||||
--print(next_hop[1], next_hop[2])
|
||||
-- If cost2 is significantly less, that means that the optimum path might
|
||||
-- overall be faster even though it is currently blocked
|
||||
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
|
||||
local unit_in_way = wesnoth.get_unit(next_hop[1], next_hop[2])
|
||||
|
@ -92,22 +86,22 @@ function ca_messenger_move:execution(ai, cfg)
|
|||
end
|
||||
if (not messenger) or (not messenger.valid) then return end
|
||||
|
||||
-- We also test whether an attack without retaliation or with little damage is possible
|
||||
-- Test whether an attack without retaliation or with little damage is possible
|
||||
if (messenger.attacks_left <= 0) then return end
|
||||
if (not H.get_child(messenger.__cfg, 'attack')) then return end
|
||||
|
||||
local targets = wesnoth.get_units {
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
|
||||
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
|
||||
{ "filter_adjacent", { id = messenger.id } }
|
||||
}
|
||||
|
||||
local max_rating, best_tar, best_weapon = -9e99, {}, -1
|
||||
for i,t in ipairs(targets) do
|
||||
local max_rating, best_target, best_weapon = -9e99
|
||||
for _,target in ipairs(targets) do
|
||||
local n_weapon = 0
|
||||
for weapon in H.child_range(messenger.__cfg, "attack") do
|
||||
n_weapon = n_weapon + 1
|
||||
|
||||
local att_stats, def_stats = wesnoth.simulate_combat(messenger, n_weapon, t)
|
||||
local att_stats, def_stats = wesnoth.simulate_combat(messenger, n_weapon, target)
|
||||
|
||||
local rating = -9e99
|
||||
-- This is an acceptable attack if:
|
||||
|
@ -120,25 +114,25 @@ function ca_messenger_move:execution(ai, cfg)
|
|||
if (att_stats.hp_chance[messenger.hitpoints] == 1)
|
||||
or (def_stats.hp_chance[0] >= tonumber(enemy_death_chance)) and (att_stats.hp_chance[0] <= tonumber(messenger_death_chance))
|
||||
then
|
||||
rating = t.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
|
||||
rating = target.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
|
||||
end
|
||||
--print(messenger.id, t.id,weapon.name, rating)
|
||||
if rating > max_rating then
|
||||
max_rating, best_tar, best_weapon = rating, t, n_weapon
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating, best_target, best_weapon = rating, target, n_weapon
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if max_rating > -9e99 then
|
||||
AH.checked_attack(ai, messenger, best_tar, best_weapon)
|
||||
if best_target then
|
||||
AH.checked_attack(ai, messenger, best_target, best_weapon)
|
||||
else
|
||||
-- Otherwise, always attack enemy on last waypoint
|
||||
-- Always attack enemy on last waypoint
|
||||
local waypoint_x = AH.split(cfg.waypoint_x, ",")
|
||||
local waypoint_y = AH.split(cfg.waypoint_y, ",")
|
||||
local target = wesnoth.get_units {
|
||||
x = tonumber(waypoint_x[#waypoint_x]),
|
||||
y = tonumber(waypoint_y[#waypoint_y]),
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
|
||||
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
|
||||
{ "filter_adjacent", { id = messenger.id } }
|
||||
}[1]
|
||||
|
||||
|
@ -148,7 +142,6 @@ function ca_messenger_move:execution(ai, cfg)
|
|||
end
|
||||
if (not messenger) or (not messenger.valid) then return end
|
||||
|
||||
-- Finally, make sure unit is really done after this
|
||||
AH.checked_stopunit_attacks(ai, messenger)
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue