mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-26 04:13:18 +00:00

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.
195 lines
9.0 KiB
Lua
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
|