wesnoth/data/ai/micro_ais/cas/ca_wolves_multipacks_attack.lua
Celtic Minstrel aaa2dc4ece Rollback possibility of wesnoth.get_units matching recall list units
Now it's renamed to wesnoth.units.find_on_map.

wesnoth.units.find implements the case of finding units on either the map or a recall list.
2019-11-15 22:39:09 -05:00

195 lines
9.0 KiB
Lua

local H = wesnoth.require "helper"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lua"
local M = wesnoth.map
local ca_wolves_multipacks_attack = {}
function ca_wolves_multipacks_attack:evaluation(cfg)
-- If wolves have attacks left, call this CA
-- It will be disabled by being black-listed, so as to avoid
-- having to do the full attack evaluation for every single move evaluation
local wolves = AH.get_units_with_attacks {
side = wesnoth.current.side,
type = cfg.type or "Wolf"
}
if wolves[1] then return cfg.ca_score end
return 0
end
function ca_wolves_multipacks_attack:execution(cfg)
local packs = WMPF.assign_packs(cfg)
local avoid_map = AH.get_avoid_map(ai, wml.get_child(cfg, "avoid"), true)
-- Attacks are dealt with on a pack by pack basis
-- and we want all wolves in a pack to move first, before going on to the next pack
for pack_number,pack in pairs(packs) do
local keep_attacking_this_pack = true
local pack_has_attacked = false
-- This repeats until all wolves in a pack have attacked, or none can attack any more
while keep_attacking_this_pack do
local wolves, attacks = {}, {}
for _,pack_wolf in ipairs(pack) do
-- Wolf might have moved in previous attack -> use id to identify it
local wolf = wesnoth.units.find_on_map { id = pack_wolf.id }[1]
if wolf and (wolf.attacks_left > 0) then table.insert(wolves, wolf) end
end
local all_attacks = {}
if wolves[1] then all_attacks = AH.get_attacks(wolves, { simulate_combat = true }) end
-- Eliminate targets that would split up the wolves by more than 3 hexes.
-- Also eliminate attacks in areas specified by [avoid] tags.
local attacks = {}
for _,attack in ipairs(all_attacks) do
if (not avoid_map:get(attack.dst.x, attack.dst.y)) then
local attack_splits_pack = false
for _,wolf in ipairs(wolves) do
local nh = AH.next_hop(wolf, attack.dst.x, attack.dst.y)
local dist = M.distance_between(nh[1], nh[2], attack.dst.x, attack.dst.y)
if (dist > 3) then
attack_splits_pack = true
break
end
end
if (not attack_splits_pack) then
table.insert(attacks, attack)
end
end
end
if attacks[1] then
-- Figure out how many different wolves can reach each target, and on how many hexes
-- The target with the largest value for the smaller of these two numbers is chosen
-- This is not an exact method, but good enough and much faster than simulating combat
local attack_map_wolves, attack_map_hexes = {}, {}
for _,attack in ipairs(attacks) do
-- Number different wolves
local att_xy = attack.src.x + attack.src.y * 1000
local def_xy = attack.target.x + attack.target.y * 1000
if (not attack_map_wolves[def_xy]) then attack_map_wolves[def_xy] = {} end
attack_map_wolves[def_xy][att_xy] = 1
-- Number different hexes
if (not attack_map_hexes[def_xy]) then attack_map_hexes[def_xy] = {} end
attack_map_hexes[def_xy][attack.dst.x + attack.dst.y * 1000] = 1
end
-- Find which target can be attacked by the most units, from the most hexes; and rate by fewest HP if equal
local max_rating, best_target = - math.huge
for attack_ind,attack in pairs(attack_map_wolves) do
local number_wolves, number_hexes = 0, 0
for _,w in pairs(attack) do number_wolves = number_wolves + 1 end
for _,h in pairs(attack_map_hexes[attack_ind]) do number_hexes = number_hexes + 1 end
local rating = math.min(number_wolves, number_hexes)
local target = wesnoth.units.get(attack_ind % 1000, math.floor(attack_ind / 1000))
rating = rating - target.hitpoints / 100.
-- Also, any target sitting next to a wolf of the same pack that has
-- no attacks left is priority targeted (in order to stick with
-- the same target for all wolves of the pack)
for xa,ya in H.adjacent_tiles(target.x, target.y) do
local adj_unit = wesnoth.units.get(xa, ya)
if adj_unit then
local unit_pack_number = MAIUV.get_mai_unit_variables(adj_unit, cfg.ai_id, "pack_number")
if (unit_pack_number == pack_number)
and (adj_unit.side == wesnoth.current.side)
and (adj_unit.attacks_left == 0)
then
rating = rating + 10
end
end
end
if rating > max_rating then
max_rating, best_target = rating, target
end
end
-- Now we know the best target and need to attack
-- This is done on a wolf-by-wolf basis, the outside while loop taking care of
-- the next wolf in the pack on subsequent iterations
local max_rating, best_attack = - math.huge
for _,attack in ipairs(attacks) do
if (attack.target.x == best_target.x) and (attack.target.y == best_target.y) then
local rating = attack.att_stats.average_hp / 2. - attack.def_stats.average_hp
if (rating > max_rating) then
max_rating, best_attack = rating, attack
end
end
end
if cfg.show_pack_number then
WMPF.clear_label(best_attack.src.x, best_attack.src.y)
end
local attacker = wesnoth.units.get(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.units.get(best_attack.target.x, best_attack.target.y)
AH.robust_move_and_attack(ai, attacker, best_attack.dst, defender)
if cfg.show_pack_number then
if attacker and attacker.valid then
if cfg.show_pack_number then WMPF.put_label(attacker.x, attacker.y, pack_number) end
end
if (not defender) or (not defender.valid) then
WMPF.clear_label(best_attack.target.x, best_attack.target.y)
end
end
pack_has_attacked = true
else
keep_attacking_this_pack = false
end
end
-- Finally, if any of the wolves in this pack did attack, move the rest of the pack in close
if pack_has_attacked then
local wolves_moves, wolves_no_moves = {}, {}
for _,pack_wolf in ipairs(pack) do
-- Wolf might have moved in previous attack -> use id to identify it
local wolf = wesnoth.units.find_on_map { id = pack_wolf.id }[1]
if wolf then
if (wolf.moves > 0) then
table.insert(wolves_moves, wolf)
else
table.insert(wolves_no_moves, wolf)
end
end
end
-- If we have both wolves that have moved and those that have not moved,
-- move the latter toward the former
if wolves_moves[1] and wolves_no_moves[1] then
for _,wolf_moves in ipairs(wolves_moves) do
local best_hex = AH.find_best_move(wolf_moves, function(x, y)
local rating = 0
for _,wolf_no_moves in ipairs(wolves_no_moves) do
rating = rating - M.distance_between(x, y, wolf_no_moves.x, wolf_no_moves.y)
end
return rating
end, { avoid_map = avoid_map })
if cfg.show_pack_number then
WMPF.clear_label(wolf_moves.x, wolf_moves.y)
end
AH.movefull_stopunit(ai, wolf_moves, best_hex or { wolf_moves.x, wolf_moves.y })
if cfg.show_pack_number and wolf_moves and wolf_moves.valid then
WMPF.put_label(wolf_moves.x, wolf_moves.y, pack_number)
end
end
end
end
end
end
return ca_wolves_multipacks_attack