Messenger MAI: allow several messengers to be controlled by AI
For two of the CAs this mostly just means reading the [filter] tag and picking one of the messengers. However, for the CA moving the escort units, it means a complete rewrite, as the AI now needs to figure out which escort unit should be moved toward which messenger.
This commit is contained in:
parent
2d458b642a
commit
62f709aaa3
5 changed files with 115 additions and 65 deletions
|
@ -68,10 +68,11 @@ local function messenger_find_clearing_attack(unit, goal_x, goal_y, cfg)
|
|||
--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 my_units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.attacks_left > 0',
|
||||
{ "not", { id = unit.id } },
|
||||
{ "not", filter },
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
|
||||
|
@ -134,15 +135,12 @@ local function messenger_find_clearing_attack(unit, goal_x, goal_y, cfg)
|
|||
end
|
||||
|
||||
function ca_messenger_attack:evaluation(ai, cfg, self)
|
||||
-- Attack units in the path of the messenger
|
||||
-- id: id of the messenger unit
|
||||
-- Attack units in the path of the messengers
|
||||
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
|
||||
|
||||
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
|
||||
local messenger, x, y = messenger_next_waypoint(cfg)
|
||||
if (not messenger) then return 0 end
|
||||
|
||||
local x, y = messenger_next_waypoint(messenger, cfg, self)
|
||||
|
||||
-- See if there's an enemy in the way that should be attacked
|
||||
local attack = messenger_find_clearing_attack(messenger, x, y, cfg)
|
||||
|
||||
|
|
|
@ -1,69 +1,92 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
local ca_messenger_escort_move = {}
|
||||
|
||||
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
|
||||
|
||||
function ca_messenger_escort_move:evaluation(ai, cfg)
|
||||
-- Move escort units close to messenger, and in between messenger and enemies
|
||||
-- The messenger has moved at this time, so we don't need to exclude him
|
||||
-- But we check that he exist (not for this scenario, but for others)
|
||||
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
|
||||
if (not messenger) then return 0 end
|
||||
-- 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
|
||||
|
||||
local my_units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0',
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
if (not my_units[1]) then return 0 end
|
||||
|
||||
if my_units[1] then
|
||||
return cfg.ca_score
|
||||
end
|
||||
return 0
|
||||
local _, _, _, messengers = messenger_next_waypoint(cfg)
|
||||
if (not messengers) or (not messengers[1]) then return 0 end
|
||||
|
||||
return cfg.ca_score
|
||||
end
|
||||
|
||||
function ca_messenger_escort_move:execution(ai, cfg)
|
||||
local messenger = wesnoth.get_units{ id = cfg.id }[1]
|
||||
local my_units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0',
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
|
||||
-- Simply move units one at a time
|
||||
local next_unit = my_units[1]
|
||||
local reach = LS.of_pairs(wesnoth.find_reach(next_unit))
|
||||
|
||||
-- Distance from messenger for each hex the unit can reach
|
||||
local dist_messenger = AH.distance_map({messenger}, reach)
|
||||
local _, _, _, messengers = messenger_next_waypoint(cfg)
|
||||
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
|
||||
}
|
||||
|
||||
-- Rating (in the end, we pick the _minimum _rating):
|
||||
-- 1. Minimize distance from enemies
|
||||
local rating = AH.distance_map(enemies, reach)
|
||||
-- 2. This one favors hexes in between messenger and enemies
|
||||
rating:union_merge(dist_messenger, function(x, y, v1, v2)
|
||||
return v1 + v2*#enemies
|
||||
end)
|
||||
-- 3. Strongly prefer hexes close to the messenger
|
||||
rating:union_merge(dist_messenger, function(x, y, v1, v2)
|
||||
return v1 + v2^2
|
||||
end)
|
||||
--AH.put_labels(rating)
|
||||
local base_rating_map = LS.create()
|
||||
local max_rating, best_unit, best_hex = -9e99
|
||||
for _,unit in ipairs(my_units) do
|
||||
-- Only considering hexes unoccupied by other units is good enough for this
|
||||
local reach_map = AH.get_reachable_unocc(unit)
|
||||
|
||||
-- Now find hex with minimum value that is unoccupied
|
||||
min_rating, best_hex = 9e99, {}
|
||||
rating:iter(function(x, y, r)
|
||||
local unit_in_way = wesnoth.get_units{ x = x, y = y, { "not", { id = next_unit.id } } }[1]
|
||||
if (not unit_in_way) and (r < min_rating) then
|
||||
min_rating, best_hex = r, { x, y }
|
||||
end
|
||||
end)
|
||||
-- and move the unit there
|
||||
AH.movefull_stopunit(ai, next_unit, best_hex)
|
||||
-- Minor rating for the fastest and strongest unit to go first
|
||||
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
|
||||
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.)
|
||||
messenger_rating = messenger_rating * 10. * (1. + m.variables.wp_rating * 2.)
|
||||
|
||||
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. / (H.distance_between(x, y, e.x, e.y) + 2.)
|
||||
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
|
||||
--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)
|
||||
end
|
||||
|
||||
return ca_messenger_escort_move
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
return function(messenger, cfg, self)
|
||||
-- Variable to store which waypoint to go to next (persistent)
|
||||
if (not self.data.next_waypoint) then self.data.next_waypoint = 1 end
|
||||
return function(cfg)
|
||||
-- Calculate next waypoint and rating for all messengers
|
||||
-- Return next messenger to move and waypoint toward which it should move
|
||||
-- Also return the array of all messengers (for escort move evaluation,
|
||||
-- so that it only needs to be done in one place, in case the syntax is changed some more)
|
||||
-- Returns nil for first 3 arguments if no messenger has moves left
|
||||
-- Returns nil for all arguments if there are no messengers on the map
|
||||
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local messengers = wesnoth.get_units { side = wesnoth.current.side, { "and", filter } }
|
||||
if (not messengers[1]) then return end
|
||||
|
||||
local waypoint_x = AH.split(cfg.waypoint_x, ",")
|
||||
local waypoint_y = AH.split(cfg.waypoint_y, ",")
|
||||
|
@ -12,14 +20,34 @@ return function(messenger, cfg, self)
|
|||
waypoint_y[i] = tonumber(waypoint_y[i])
|
||||
end
|
||||
|
||||
-- If we're 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(messenger.x, messenger.y,
|
||||
waypoint_x[self.data.next_waypoint], waypoint_y[self.data.next_waypoint]
|
||||
)
|
||||
if (dist_wp <= 3) and (self.data.next_waypoint < #waypoint_x) then
|
||||
self.data.next_waypoint = self.data.next_waypoint + 1
|
||||
-- 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
|
||||
-- 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 = m.variables.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)
|
||||
if (dist_wp <= 3) and (wp_i < #waypoint_x) then wp_i = wp_i + 1 end
|
||||
|
||||
m.variables.wp_i, m.variables.wp_x, m.variables.wp_y = wp_i, wp_x, wp_y
|
||||
|
||||
-- Also store the rating for each messenger
|
||||
-- For now, this is simply a "forward rating"
|
||||
local rating = wp_i - dist_wp / 1000.
|
||||
|
||||
m.variables.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
|
||||
x, y = wp_x, wp_y
|
||||
end
|
||||
end
|
||||
|
||||
return waypoint_x[self.data.next_waypoint], waypoint_y[self.data.next_waypoint]
|
||||
return best_messenger, x, y, messengers
|
||||
end
|
||||
|
|
|
@ -6,10 +6,10 @@ local ca_messenger_move = {}
|
|||
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
|
||||
|
||||
function ca_messenger_move:evaluation(ai, cfg)
|
||||
-- Move the messenger (unit with passed id) toward goal, attack adjacent unit if possible
|
||||
-- Move the messenger toward goal, attack adjacent unit if possible
|
||||
-- without retaliation or little expected damage with high chance of killing the enemy
|
||||
|
||||
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
|
||||
local messenger = messenger_next_waypoint(cfg)
|
||||
|
||||
if messenger then
|
||||
return cfg.ca_score
|
||||
|
@ -17,10 +17,8 @@ function ca_messenger_move:evaluation(ai, cfg)
|
|||
return 0
|
||||
end
|
||||
|
||||
function ca_messenger_move:execution(ai, cfg, self)
|
||||
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
|
||||
|
||||
local x, y = messenger_next_waypoint(messenger, cfg, self)
|
||||
function ca_messenger_move:execution(ai, cfg)
|
||||
local messenger, x, y = messenger_next_waypoint(cfg)
|
||||
|
||||
if (messenger.x ~= x) or (messenger.y ~= y) then
|
||||
local wp = AH.get_closest_location(
|
||||
|
@ -100,7 +98,7 @@ function ca_messenger_move:execution(ai, cfg, self)
|
|||
|
||||
local targets = wesnoth.get_units {
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
|
||||
{ "filter_adjacent", { id = cfg.id } }
|
||||
{ "filter_adjacent", { id = messenger.id } }
|
||||
}
|
||||
|
||||
local max_rating, best_tar, best_weapon = -9e99, {}, -1
|
||||
|
@ -141,7 +139,7 @@ function ca_messenger_move:execution(ai, cfg, self)
|
|||
x = tonumber(waypoint_x[#waypoint_x]),
|
||||
y = tonumber(waypoint_y[#waypoint_y]),
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
|
||||
{ "filter_adjacent", { id = cfg.id } }
|
||||
{ "filter_adjacent", { id = messenger.id } }
|
||||
}[1]
|
||||
|
||||
if target then
|
||||
|
|
|
@ -86,8 +86,11 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
|
||||
--------- Messenger Escort Micro AI ------------------------------------
|
||||
elseif (cfg.ai_type == 'messenger_escort') then
|
||||
required_keys = { "id", "waypoint_x", "waypoint_y" }
|
||||
optional_keys = { "enemy_death_chance", "filter_second", "messenger_death_chance" }
|
||||
if (not cfg.id) and (not H.get_child(cfg, "filter")) then
|
||||
H.wml_error("Messenger [micro_ai] tag requires either id= key or [filter] tag")
|
||||
end
|
||||
required_keys = { "waypoint_x", "waypoint_y" }
|
||||
optional_keys = { "id", "enemy_death_chance", "filter", "filter_second", "messenger_death_chance" }
|
||||
local score = cfg.ca_score or 300000
|
||||
CA_parms = {
|
||||
{ ca_id = 'mai_messenger_attack', location = CA_path .. 'ca_messenger_attack.lua', score = score },
|
||||
|
|
Loading…
Add table
Reference in a new issue