Micro AIs: correctly deal with hidden and petrified enemies

Previously, the Micro AI behavior was inconsistent at best when it came
to dealing with these units and could even result in AI errors when an
AI unit was ambushed or a petrified unit was in the way of a move. Now,
both types of units are properly "ignored" and the AI moves have been
made robust against unexpected events such as ambushes. Incidentally,
the latter also makes the AI more robust against WML events doing
things the AI cannot know about (such as removing units).
This commit is contained in:
mattsc 2016-10-16 19:58:28 -07:00
parent 545800f9f8
commit b302289402
32 changed files with 227 additions and 295 deletions

View file

@ -53,39 +53,43 @@ function ca_assassin_move:execution(cfg)
local units, target = get_units_target(cfg)
local unit = units[1]
local enemies = wesnoth.get_units {
local enemies = AH.get_visible_units(wesnoth.current.side, {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "not", H.get_child(cfg, "filter_second") }
}
})
-- Maximum damage the enemies can theoretically do for all hexes they can attack
-- Note: petrified enemies need to be included for the blocked hexes rating below,
-- but need to be excluded from the damage rating
local enemy_damage_map = LS.create()
for _,enemy in ipairs(enemies) do
-- Need to "move" enemy next to unit for attack calculation
-- Do this with a unit copy, so that no actual unit has to be moved
local enemy_copy = wesnoth.copy_unit(enemy)
if (not enemy.status.petrified) then
-- Need to "move" enemy next to unit for attack calculation
-- Do this with a unit copy, so that no actual unit has to be moved
local enemy_copy = wesnoth.copy_unit(enemy)
-- First get the reach of the enemy with full moves though
enemy_copy.moves = enemy_copy.max_moves
local reach = wesnoth.find_reach(enemy_copy, { ignore_units = true })
-- First get the reach of the enemy with full moves though
enemy_copy.moves = enemy_copy.max_moves
local reach = wesnoth.find_reach(enemy_copy, { ignore_units = true })
enemy_copy.x = unit.x
enemy_copy.y = unit.y + 1 -- this even works at map border
enemy_copy.x = unit.x
enemy_copy.y = unit.y + 1 -- this even works at map border
local _, _, att_weapon, _ = wesnoth.simulate_combat(enemy_copy, unit)
local max_damage = att_weapon.damage * att_weapon.num_blows
local _, _, att_weapon, _ = wesnoth.simulate_combat(enemy_copy, unit)
local max_damage = att_weapon.damage * att_weapon.num_blows
local unit_damage_map = LS.create()
for _,loc in ipairs(reach) do
unit_damage_map:insert(loc[1], loc[2], max_damage)
for xa,ya in H.adjacent_tiles(loc[1], loc[2]) do
unit_damage_map:insert(xa, ya, max_damage)
local unit_damage_map = LS.create()
for _,loc in ipairs(reach) do
unit_damage_map:insert(loc[1], loc[2], max_damage)
for xa,ya in H.adjacent_tiles(loc[1], loc[2]) do
unit_damage_map:insert(xa, ya, max_damage)
end
end
end
enemy_damage_map:union_merge(unit_damage_map, function(x, y, v1, v2)
return (v1 or 0) + v2
end)
enemy_damage_map:union_merge(unit_damage_map, function(x, y, v1, v2)
return (v1 or 0) + v2
end)
end
end
-- Penalties for damage by enemies
@ -107,12 +111,11 @@ function ca_assassin_move:execution(cfg)
enemy_rating_map:insert(enemy.x, enemy.y, (enemy_rating_map:get(enemy.x, enemy.y) or 0) + 100)
-- Hexes adjacent to enemies get max_moves penalty
-- except if AI unit is skirmisher or enemy is level 0
-- except if AI unit is skirmisher or enemy is level 0 or is petrified
local zoc_active = (not is_skirmisher)
if zoc_active then
local level = enemy.level
if (level == 0) then zoc_active = false end
if (enemy.level == 0) or enemy.status.petrified then zoc_active = false end
end
if zoc_active then
@ -149,7 +152,7 @@ function ca_assassin_move:execution(cfg)
local sub_path, sub_cost = wesnoth.find_path(unit, path[i][1], path[i][2])
if sub_cost <= unit.moves then
local unit_in_way = wesnoth.get_unit(path[i][1], path[i][2])
if not unit_in_way then
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way)) then
farthest_hex = path[i]
end
else

View file

@ -27,11 +27,13 @@ function ca_big_animals:execution(cfg)
local avoid_tag = H.get_child(cfg, "avoid_unit")
local avoid_map = LS.create()
if avoid_tag then
avoid_map = LS.of_pairs(wesnoth.get_locations { radius = 1,
{ "filter", { { "and", avoid_tag },
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
} }
})
local enemies_to_be_avoided = AH.get_attackable_enemies(avoid_tag)
for _,enemy in ipairs(enemies_to_be_avoided) do
avoid_map:insert(enemy.x, enemy.y)
for xa,ya in H.adjacent_tiles(enemy.x, enemy.y) do
avoid_map:insert(xa, ya)
end
end
end
local goal = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
@ -64,7 +66,7 @@ function ca_big_animals:execution(cfg)
local enemy_hp = 500
for xa,ya in H.adjacent_tiles(x, y) do
local enemy = wesnoth.get_unit(xa, ya)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if AH.is_attackable_enemy(enemy) then
if (enemy.hitpoints < enemy_hp) then enemy_hp = enemy.hitpoints end
end
end
@ -96,7 +98,7 @@ function ca_big_animals:execution(cfg)
local min_hp, target = 9e99
for xa,ya in H.adjacent_tiles(unit.x, unit.y) do
local enemy = wesnoth.get_unit(xa, ya)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if AH.is_attackable_enemy(enemy) then
if (enemy.hitpoints < min_hp) then
min_hp, target = enemy.hitpoints, enemy
end

View file

@ -14,10 +14,7 @@ function ca_bottleneck_attack:evaluation(cfg, data)
local max_rating, best_attacker, best_target, best_weapon = -9e99
for _,attacker in ipairs(attackers) do
local targets = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_adjacent", { id = attacker.id } }
}
local targets = AH.get_attackable_enemies { { "filter_adjacent", { id = attacker.id } } }
for _,target in ipairs(targets) do
local n_weapon = 0

View file

@ -185,7 +185,7 @@ local function bottleneck_move_out_of_way(unit_in_way, data)
local reach = wesnoth.find_reach(unit_in_way)
local all_units = wesnoth.get_units()
local all_units = AH.get_visible_units(wesnoth.current.side)
local occ_hexes = LS:create()
for _,unit in ipairs(all_units) do
occ_hexes:insert(unit.x, unit.y)
@ -309,14 +309,15 @@ function ca_bottleneck_move:evaluation(cfg, data)
if (not best_move_away) then current_rating_map:insert(unit.x, unit.y, 20000) end
end
local enemies = AH.get_live_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
local enemies = AH.get_attackable_enemies()
local attacks = {}
for _,enemy in ipairs(enemies) do
for xa,ya in H.adjacent_tiles(enemy.x, enemy.y) do
if data.BD_is_my_territory:get(xa, ya) then
local unit_in_way = wesnoth.get_unit(xa, ya)
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way)) then
unit_in_way = nil
end
local data = { x = xa, y = ya,
defender = enemy,
defender_level = enemy.level,
@ -330,10 +331,10 @@ function ca_bottleneck_move:evaluation(cfg, data)
-- Get a map of the allies, as hexes occupied by allied units count as
-- reachable, but must be excluded. This could also be done below by
-- using bottleneck_move_out_of_way(), but this is much faster
local allies = AH.get_live_units {
local allies = AH.get_visible_units(wesnoth.current.side, {
{ "filter_side", { { "allied_with", { side = wesnoth.current.side } } } },
{ "not", { side = wesnoth.current.side } }
}
})
local allies_map = LS.create()
for _,ally in ipairs(allies) do
allies_map:insert(ally.x, ally.y)
@ -434,6 +435,9 @@ function ca_bottleneck_move:evaluation(cfg, data)
local unit_in_way = wesnoth.get_units { x = best_hex[1], y = best_hex[2],
{ "not", { id = best_unit.id } }
}[1]
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way)) then
unit_in_way = nil
end
if unit_in_way then
best_hex = bottleneck_move_out_of_way(unit_in_way, data)
@ -462,15 +466,9 @@ function ca_bottleneck_move:execution(cfg, data)
AH.checked_stopunit_moves(ai, unit)
end
else
if (data.BD_unit.x ~= data.BD_hex[1]) or (data.BD_unit.y ~= data.BD_hex[2]) then
-- Don't want full move, as this might be stepping out of the way
AH.checked_move(ai, data.BD_unit, data.BD_hex[1], data.BD_hex[2])
end
if (not data.BD_unit) or (not data.BD_unit.valid) then return end
if data.BD_level_up_defender then
AH.checked_attack(ai, data.BD_unit, data.BD_level_up_defender, data.BD_level_up_weapon)
end
-- Don't want full move, as this might be stepping out of the way
local cfg = { partial_move = true, weapon = data.BD_level_up_weapon }
AH.robust_move_and_attack(ai, data.BD_unit, data.BD_hex, data.BD_level_up_defender, cfg)
end
-- Now delete almost everything

View file

@ -25,9 +25,10 @@ function ca_coward:execution(cfg)
local filter_second =
H.get_child(cfg, "filter_second")
or { { "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } } }
local enemies = wesnoth.get_units {
local enemies = AH.get_live_units {
{ "and", filter_second },
{ "filter_location", { x = coward.x, y = coward.y, radius = cfg.distance } }
{ "filter_location", { x = coward.x, y = coward.y, radius = cfg.distance } },
{ "filter_vision", { side = wesnoth.current.side, visible = 'yes' } }
}
-- If no enemies are close: keep unit from doing anything and exit
@ -38,8 +39,10 @@ function ca_coward:execution(cfg)
for i,hex in ipairs(reach) do
-- Only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x = hex[1], y = hex[2], { "not", { id = coward.id } } }[1]
if not occ_hex then
local unit_in_way = wesnoth.get_unit(hex[1], hex[2])
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way))
or (unit_in_way == coward)
then
local rating = 0
for _,enemy in ipairs(enemies) do
local dist = H.distance_between(hex[1], hex[2], enemy.x, enemy.y)

View file

@ -93,20 +93,20 @@ local function get_attack_filter_from_aspect(aspect, which, data, is_leader)
local filter = loadstring(aspect.code)(nil, H.get_child(aspect, 'args'), data)
if (type(filter[which]) == 'function') then
temporary_attacks_filter_fcn = filter[which]
local units = wesnoth.get_units(attack_filter(which, {
local units = AH.get_live_units(attack_filter(which, {
lua_function = 'temporary_attacks_filter_fcn'
}, is_leader))
temporary_attacks_filter_fcn = nil
return units
else
return wesnoth.get_units(attack_filter(which, filter[which], is_leader))
return AH.get_live_units(attack_filter(which, filter[which], is_leader))
end
else -- Standard attacks aspect (though not name=standard_aspect)
--print("Found standard aspect")
return wesnoth.get_units(attack_filter(which,
return AH.get_live_units(attack_filter(which,
H.get_child(aspect, 'filter_' .. which), is_leader))
end
return wesnoth.get_units(attack_filter(which, {}, is_leader))
return AH.get_live_units(attack_filter(which, {}, is_leader))
end
function ca_fast_attack_utils.get_attackers(data, which)
@ -147,12 +147,12 @@ function ca_fast_attack_utils.test_attacks(my_ai, times)
if (aspect.id == 'attacks') then
local facet = H.get_child(aspect, 'facet')
if facet then
wesnoth.get_units{
AH.get_live_units{
side = wesnoth.current.side,
canrecruit = false,
{ "and", H.get_child(facet, 'filter_own') }
}
wesnoth.get_units{
AH.get_live_units{
side = wesnoth.current.side,
canrecruit = false,
{ "and", H.get_child(facet, 'filter_enemy') }
@ -189,7 +189,7 @@ function ca_fast_attack_utils.gamedata_setup()
-- Only uses one leader per side right now, but only used for finding direction
-- of move -> sufficient for this.
gamedata.leaders = {}
for _,unit_proxy in ipairs(wesnoth.get_units { canrecruit = 'yes' }) do
for _,unit_proxy in ipairs(AH.get_live_units { canrecruit = 'yes' }) do
gamedata.leaders[unit_proxy.side] = { unit_proxy.x, unit_proxy.y, id = unit_proxy.id }
end

View file

@ -22,14 +22,14 @@ function ca_fast_combat:evaluation(cfg, data)
excluded_enemies = FAU.get_attackers(data, "enemy")
else
if (not data.fast_combat_units) or (not data.fast_combat_units[1]) then
data.fast_combat_units = wesnoth.get_units(
data.fast_combat_units = AH.get_live_units(
FAU.build_attack_filter("own", filter_own)
)
if (not data.fast_combat_units[1]) then return 0 end
units_sorted = false
end
if filter_enemy then
excluded_enemies = wesnoth.get_units(
excluded_enemies = AH.get_live_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
@ -55,7 +55,7 @@ function ca_fast_combat:evaluation(cfg, data)
-- Exclude hidden enemies, except if attack_hidden_enemies=yes is set in [micro_ai] tag
if (not cfg.attack_hidden_enemies) then
local hidden_enemies = wesnoth.get_units {
local hidden_enemies = AH.get_live_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_vision", { side = wesnoth.current.side, visible = 'no' } }
}
@ -126,15 +126,7 @@ function ca_fast_combat:evaluation(cfg, data)
end
function ca_fast_combat:execution(cfg, data)
local unit = data.fast_combat_units[data.fast_combat_unit_i]
AH.movefull_outofway_stopunit(ai, unit, data.fast_dst.x, data.fast_dst.y)
if (not unit) or (not unit.valid) then return end
if (not data.fast_target) or (not data.fast_target.valid) then return end
if (H.distance_between(unit.x, unit.y, data.fast_target.x, data.fast_target.y) ~= 1) then return end
AH.checked_attack(ai, unit, data.fast_target)
AH.robust_move_and_attack(ai, data.fast_combat_units[data.fast_combat_unit_i], data.fast_dst, data.fast_target)
data.fast_combat_units[data.fast_combat_unit_i] = nil
end

View file

@ -26,12 +26,12 @@ function ca_fast_combat_leader:evaluation(cfg, data)
if (not leader) then return 0 end
excluded_enemies = FAU.get_attackers(data, "enemy")
else
leader = wesnoth.get_units(
leader = AH.get_live_units(
FAU.build_attack_filter("leader", filter_own)
)[1]
if (not leader) then return 0 end
if filter_enemy then
excluded_enemies = wesnoth.get_units(
excluded_enemies = AH.get_live_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
@ -50,7 +50,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
-- Exclude hidden enemies, except if attack_hidden_enemies=yes is set in [micro_ai] tag
if (not cfg.attack_hidden_enemies) then
local hidden_enemies = wesnoth.get_units {
local hidden_enemies = AH.get_live_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_vision", { side = wesnoth.current.side, visible = 'no' } }
}
@ -70,7 +70,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
-- Enemy power and number maps
-- Currently, the power is simply the summed hitpoints of all enemies that
-- can get to a hex
local enemies = wesnoth.get_units {
local enemies = AH.get_live_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
@ -184,15 +184,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
end
function ca_fast_combat_leader:execution(cfg, data)
local leader = data.leader
AH.movefull_outofway_stopunit(ai, leader, data.fast_dst.x, data.fast_dst.y)
if (not leader) or (not leader.valid) then return end
if (not data.fast_target) or (not data.fast_target.valid) then return end
if (H.distance_between(leader.x, leader.y, data.fast_target.x, data.fast_target.y) ~= 1) then return end
AH.checked_attack(ai, leader, data.fast_target)
AH.robust_move_and_attack(ai, data.leader, data.fast_dst, data.fast_target)
data.leader = nil
end

View file

@ -100,11 +100,7 @@ function ca_fast_move:execution(cfg)
end
-- Now add enemy leaders to the goals
local enemy_leaders =
wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
canrecruit = 'yes'
}
local enemy_leaders = AH.get_attackable_enemies { canrecruit = 'yes' }
-- Sort enemy leaders by distance to AI leader
if leader then
@ -212,7 +208,9 @@ function ca_fast_move:execution(cfg)
local unit_in_way
if (rating > max_rating) then
unit_in_way = wesnoth.get_unit(loc[1], loc[2])
if (unit_in_way == unit) then unit_in_way = nil end
if (unit_in_way == unit) or (not AH.is_visible_unit(wesnoth.current.side, unit_in_way)) then
unit_in_way = nil
end
if unit_in_way and (unit_in_way.side == unit.side) then
local reach = AH.get_reachable_unocc(unit_in_way)
@ -271,7 +269,7 @@ function ca_fast_move:execution(cfg)
if best_hex then
local dx, dy = goal.x - best_hex[1], goal.y - best_hex[2]
AH.movefull_outofway_stopunit(ai, unit, best_hex[1], best_hex[2], { dx = dx, dy = dy })
AH.robust_move_and_attack(ai, unit, best_hex, nil, { dx = dx, dy = dy })
end
-- Also remove this unit from all the tables; using table.remove is fine here

View file

@ -40,7 +40,7 @@ end
function ca_forest_animals_move:execution(cfg)
-- These animals run from any enemy
local unit = get_forest_animals(cfg)[1]
local enemies = wesnoth.get_units { { "filter_side", { { "enemy_of", {side = wesnoth.current.side } } } } }
local enemies = AH.get_attackable_enemies()
-- Get the locations of all the rabbit holes
W.store_items { variable = 'holes_wml' }
@ -108,17 +108,20 @@ function ca_forest_animals_move:execution(cfg)
if (best_hex) then
local x,y = wesnoth.find_vacant_tile(best_hex[1], best_hex[2], unit)
local next_hop = AH.next_hop(unit, x, y)
if (not next_hop) then next_hop = { unit.x, unit.y } end
if (unit.x ~= next_hop[1]) or (unit.y ~= next_hop[2]) then
AH.checked_move(ai, unit, next_hop[1], next_hop[2])
end
end
end
end
-- Now we check for close enemies again, as we might just have moved within reach of some
local close_enemies = {}
if unit and unit.valid then
if (not unit) or (not unit.valid) then return end
-- We cannot return here, as the above might not have resulted in a move,
-- but we need to get the enemies again, in case a WML event or ambush changed something
enemies = AH.get_attackable_enemies()
close_enemies = {}
for _,enemy in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, enemy.x, enemy.y) <= unit.max_moves+1) then
table.insert(close_enemies, enemy)
@ -149,17 +152,16 @@ function ca_forest_animals_move:execution(cfg)
end)
AH.movefull_stopunit(ai, unit, farthest_hex)
if (not unit) or (not unit.valid) then return end
-- If this is a rabbit ending on a hole -> disappears
if unit and unit.valid
and (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2])
then
if (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2]) then
local command = "wesnoth.erase_unit(x1, y1)"
ai.synced_command(command, farthest_hex[1], farthest_hex[2])
end
end
-- Finally, take moves away, as only partial move might have been done
-- Finally, take moves away, as only partial (or no) move might have been done
-- Also take attacks away, as these units never attack
if unit and unit.valid then AH.checked_stopunit_all(ai, unit) end
end

View file

@ -25,8 +25,7 @@ function ca_forest_animals_new_rabbit:execution(cfg)
-- We also add a random number to the ones we keep, for selection of the holes later
local holes = {}
for _,item in ipairs(all_items) do
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
local enemies = AH.get_attackable_enemies {
{ "filter_location", { x = item.x, y = item.y, radius = rabbit_enemy_distance } }
}

View file

@ -10,8 +10,7 @@ local function get_tuskers(cfg)
end
local function get_adjacent_enemies(cfg)
local adjacent_enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
local adjacent_enemies = AH.get_attackable_enemies {
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
}
return adjacent_enemies
@ -60,16 +59,7 @@ function ca_forest_animals_tusker_attack:execution(cfg)
return rating
end)
AH.movefull_stopunit(ai, attacker, best_hex)
if (not attacker) or (not attacker.valid) then return end
if (not target) or (not target.valid) then return end
local dist = H.distance_between(attacker.x, attacker.y, target.x, target.y)
if (dist == 1) then
AH.checked_attack(ai, attacker, target)
else
AH.checked_stopunit_attacks(ai, attacker)
end
AH.robust_move_and_attack(ai, attacker, best_hex, target)
end
return ca_forest_animals_tusker_attack

View file

@ -93,17 +93,27 @@ function ca_goto:execution(cfg, data)
H.wml_error("Goto AI avoid_enemies= argument must be >0")
end
local enemies = wesnoth.get_units { { "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } } }
local enemies = AH.get_visible_units(wesnoth.current.side, {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
})
local live_enemies = {}
for _,enemy in ipairs(enemies) do
if (not enemy.status.petrified) then
table.insert(live_enemies, enemy)
end
end
enemy_map = LS.create()
for _,enemy in ipairs(enemies) do
enemy_map:insert(enemy.x, enemy.y, (enemy_map:get(enemy.x, enemy.y) or 0) + 1000)
end
for _,enemy in ipairs(live_enemies) do
for xa,ya in H.adjacent_tiles(enemy.x, enemy.y) do
enemy_map:insert(xa, ya, (enemy_map:get(xa, ya) or 0) + 10)
end
end
enemy_attack_map = BC.get_attack_map(enemies)
enemy_attack_map = BC.get_attack_map(live_enemies)
end
local max_rating, closest_hex, best_path, best_unit = -9e99
@ -192,7 +202,7 @@ function ca_goto:execution(cfg, data)
local sub_path, sub_cost = wesnoth.find_path(best_unit, best_path[i][1], best_path[i][2], cfg)
if sub_cost <= best_unit.moves then
local unit_in_way = wesnoth.get_unit(best_path[i][1], best_path[i][2])
if not unit_in_way then
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way)) then
closest_hex = best_path[i]
end
else

View file

@ -56,9 +56,7 @@ function ca_healer_move:evaluation(cfg, data)
end
end
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
local enemies = AH.get_attackable_enemies()
for _,healee in ipairs(healees_MP) do wesnoth.extract_unit(healee) end
local enemy_attack_map = BC.get_attack_map(enemies)
for _,healee in ipairs(healees_MP) do wesnoth.put_unit(healee) end
@ -126,7 +124,7 @@ function ca_healer_move:evaluation(cfg, data)
end
function ca_healer_move:execution(cfg)
AH.movefull_outofway_stopunit(ai, best_healer, best_hex)
AH.robust_move_and_attack(ai, best_healer, best_hex)
best_healer, best_hex = nil, nil
end

View file

@ -10,7 +10,7 @@ local function get_sheep(cfg)
end
local function get_dogs(cfg)
local dogs = AH.get_units_with_moves {
local dogs = AH.get_units_with_attacks {
side = wesnoth.current.side,
{ "and", H.get_child(cfg, "filter") }
}
@ -18,8 +18,7 @@ local function get_dogs(cfg)
end
local function get_enemies(cfg, radius)
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
local enemies = AH.get_attackable_enemies {
{ "filter_location",
{ radius = radius,
{ "filter", { side = wesnoth.current.side, { "and", H.get_child(cfg, "filter_second") } } } }
@ -74,13 +73,7 @@ function ca_herding_attack_close_enemy:execution(cfg)
-- If we found a move, we do it, and attack if possible
if best_dog then
AH.movefull_stopunit(ai, best_dog, best_hex)
if (not best_dog) or (not best_dog.valid) then return end
if (not best_enemy) or (not best_enemy.valid) then return end
if H.distance_between(best_dog.x, best_dog.y, best_enemy.x, best_enemy.y) == 1 then
AH.checked_attack(ai, best_dog, best_enemy)
end
AH.robust_move_and_attack(ai, best_dog, best_hex, best_enemy)
return
end
@ -88,6 +81,14 @@ function ca_herding_attack_close_enemy:execution(cfg)
local radius = cfg.attention_distance or 8
local enemies = get_enemies(cfg, radius)
-- We also need to remove dogs that have no moves left, since selection was done on attacks_left
for i=#dogs,1,-1 do
if (dogs[i].moves == 0) then
table.remove(dogs, i)
end
end
if (not dogs[1]) then return end
-- Find closest sheep/enemy pair first
local min_dist, closest_sheep, closest_enemy = 9e99
for _,enemy in ipairs(enemies) do

View file

@ -1,42 +1,47 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_next_sheep(cfg)
local function get_next_sheep_enemies(cfg)
local sheep = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", H.get_child(cfg, "filter_second") },
{ "filter_location",
{
{ "filter", { { "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } } }
},
radius = (cfg.attention_distance or 8)
}
}
{ "and", H.get_child(cfg, "filter_second") }
}
return sheep[1]
if (not sheep[1]) then return end
local enemies = AH.get_attackable_enemies()
if (not enemies[1]) then return end
local attention_distance = cfg.attention_distance or 8
-- Simply return the first sheep, order does not matter
for _,single_sheep in ipairs(sheep) do
local close_enemies = {}
for _,enemy in ipairs(enemies) do
if (H.distance_between(single_sheep.x, single_sheep.y, enemy.x, enemy.y) <= attention_distance) then
table.insert(close_enemies, enemy)
end
end
if close_enemies[1] then
return single_sheep, enemies
end
end
end
local ca_herding_sheep_runs_enemy = {}
function ca_herding_sheep_runs_enemy:evaluation(cfg)
-- Sheep runs from any enemy within attention_distance hexes (after the dogs have moved in)
if get_next_sheep(cfg) then return cfg.ca_score end
if get_next_sheep_enemies(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_sheep_runs_enemy:execution(cfg)
-- Simply start with the first sheep, order does not matter
local sheep = get_next_sheep(cfg)
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_location", { x = sheep.x, y = sheep.y , radius = (cfg.attention_distance or 8) } }
}
local sheep, close_enemies = get_next_sheep_enemies(cfg)
-- Maximize distance between sheep and enemies
local best_hex = AH.find_best_move(sheep, function(x, y)
local rating = 0
for _,enemy in ipairs(enemies) do
for _,enemy in ipairs(close_enemies) do
rating = rating + H.distance_between(x, y, enemy.x, enemy.y)
end
return rating

View file

@ -15,7 +15,7 @@ local function hunter_attack_weakest_adj_enemy(ai, hunter)
local min_hp, target = 9e99
for xa,ya in H.adjacent_tiles(hunter.x, hunter.y) do
local enemy = wesnoth.get_unit(xa, ya)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if AH.is_attackable_enemy(enemy) then
if (enemy.hitpoints < min_hp) then
min_hp, target = enemy.hitpoints, enemy
end
@ -83,7 +83,7 @@ function ca_hunter:execution(cfg)
local enemy_hp = 500
for xa,ya in H.adjacent_tiles(x, y) do
local enemy = wesnoth.get_unit(xa, ya)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if AH.is_attackable_enemy(enemy) then
if (enemy.hitpoints < enemy_hp) then enemy_hp = enemy.hitpoints end
end
end
@ -137,9 +137,9 @@ function ca_hunter:execution(cfg)
if (not hunter) or (not hunter.valid) then return end
-- If there's an enemy on the 'home' hex and we got right next to it, attack that enemy
if (H.distance_between(cfg.home_x, cfg.home_y, next_hop[1], next_hop[2]) == 1) then
if (H.distance_between(cfg.home_x, cfg.home_y, hunter.x, hunter.y) == 1) then
local enemy = wesnoth.get_unit(cfg.home_x, cfg.home_y)
if enemy and wesnoth.is_enemy(enemy.side, hunter.side) then
if AH.is_attackable_enemy(enemy) then
if cfg.show_messages then
W.message { speaker = hunter.id, message = 'Get out of my home!' }
end

View file

@ -20,9 +20,7 @@ end
function ca_lurkers:execution(cfg)
local lurker = get_lurker(cfg)
local targets = AH.get_live_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
local targets = AH.get_attackable_enemies()
-- Sort targets by hitpoints (lurkers choose lowest HP target)
table.sort(targets, function(a, b) return (a.hitpoints < b.hitpoints) end)
@ -38,10 +36,10 @@ function ca_lurkers:execution(cfg)
-- Need to restrict that to reachable and not occupied by an ally (except own position)
local reachable_attack_terrain = reachable_attack_terrain:filter(function(x, y, v)
local occ_hex = wesnoth.get_units {
local occ_hex = AH.get_visible_units(wesnoth.current.side, {
x = x, y = y,
{ "not", { x = lurker.x, y = lurker.y } }
}[1]
})[1]
return not occ_hex
end)
@ -58,12 +56,7 @@ function ca_lurkers:execution(cfg)
local rand = math.random(1, reachable_attack_terrrain_adj_target:size())
local dst = reachable_attack_terrrain_adj_target:to_stable_pairs()
AH.movefull_stopunit(ai, lurker, dst[rand])
if (not lurker) or (not lurker.valid) then return end
if (not target) or (not target.valid) then return end
if (H.distance_between(lurker.x, lurker.y, target.x, target.y) ~= 1) then return end
AH.checked_attack(ai, lurker, target)
AH.robust_move_and_attack(ai, lurker, dst[rand], target)
return
end
end
@ -79,10 +72,10 @@ function ca_lurkers:execution(cfg)
-- Need to restrict that to reachable and not occupied by an ally (except own position)
local reachable_wander_terrain = reachable_wander_terrain:filter(function(x, y, v)
local occ_hex = wesnoth.get_units {
local occ_hex = AH.get_visible_units(wesnoth.current.side, {
x = x, y = y,
{ "not", { x = lurker.x, y = lurker.y } }
}[1]
})[1]
return not occ_hex
end)

View file

@ -18,20 +18,16 @@ local function messenger_find_enemies_in_way(messenger, 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 } } } }
}[1]
if enemy then return enemy end
local enemy = wesnoth.get_unit(path[2][1], path[2][2])
if AH.is_attackable_enemy(enemy) then return enemy end
-- After that, go through adjacent hexes of all the other path hexes
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 return enemy end
local enemy = wesnoth.get_unit(xa, ya)
if AH.is_attackable_enemy(enemy) then return enemy end
end
else -- If we've reached the end of the path for this turn
return
@ -49,7 +45,6 @@ local function messenger_find_clearing_attack(messenger, goal_x, goal_y, cfg)
local enemy_in_way = messenger_find_enemies_in_way(messenger, goal_x, goal_y)
if (not enemy_in_way) then return end
local filter = H.get_child(cfg, "filter") or { id = cfg.id }
local units = AH.get_units_with_attacks {
side = wesnoth.current.side,
@ -113,14 +108,7 @@ function ca_messenger_attack:evaluation(cfg)
end
function ca_messenger_attack:execution(cfg)
local attacker = wesnoth.get_unit(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.get_unit(best_attack.target.x, best_attack.target.y)
AH.movefull_stopunit(ai, attacker, best_attack.dst.x, best_attack.dst.y)
if (not attacker) or (not attacker.valid) then return end
if (not defender) or (not defender.valid) then return end
AH.checked_attack(ai, attacker, defender)
AH.robust_move_and_attack(ai, best_attack.src, best_attack.dst, best_attack.target)
best_attack = nil
end

View file

@ -32,9 +32,7 @@ function ca_messenger_escort_move:execution(cfg)
local escorts = get_escorts(cfg)
local _, _, _, messengers = messenger_next_waypoint(cfg)
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
local enemies = AH.get_attackable_enemies()
local base_rating_map = LS.create()
local max_rating, best_unit, best_hex = -9e99

View file

@ -38,6 +38,9 @@ function ca_messenger_move:execution(cfg)
break
else
local unit_in_way = wesnoth.get_unit(step[1], step[2])
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way)) then
unit_in_way = nil
end
if unit_in_way and (unit_in_way.side == messenger.side) then
local reach = AH.get_reachable_unocc(unit_in_way)
@ -76,11 +79,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
local unit_in_way = wesnoth.get_unit(next_hop[1], next_hop[2])
if unit_in_way then AH.move_unit_out_of_way(ai, unit_in_way) end
if (not messenger) or (not messenger.valid) then return end
AH.checked_move(ai, messenger, next_hop[1], next_hop[2])
AH.robust_move_and_attack(ai, messenger, next_hop)
else
AH.checked_stopunit_moves(ai, messenger)
end
@ -90,10 +89,7 @@ function ca_messenger_move:execution(cfg)
if (messenger.attacks_left <= 0) then return end
if (#messenger.attacks == 0) then return end
local targets = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_adjacent", { id = messenger.id } }
}
local targets = AH.get_attackable_enemies { { "filter_adjacent", { id = messenger.id } } }
local max_rating, best_target, best_weapon = -9e99
for _,target in ipairs(targets) do
@ -129,10 +125,9 @@ function ca_messenger_move:execution(cfg)
-- 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 {
local target = AH.get_attackable_enemies {
x = tonumber(waypoint_x[#waypoint_x]),
y = tonumber(waypoint_y[#waypoint_y]),
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_adjacent", { id = messenger.id } }
}[1]

View file

@ -48,19 +48,18 @@ function ca_patrol:execution(cfg)
while patrol.moves > 0 do
-- Check whether one of the enemies to be attacked is next to the patroller
-- If so, don't move, but attack that enemy
local adjacent_enemy = wesnoth.get_units {
local adjacent_enemy = AH.get_attackable_enemies {
id = cfg.attack,
{ "filter_adjacent", { id = patrol.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
{ "filter_adjacent", { id = patrol.id } }
}[1]
if adjacent_enemy then break end
-- Also check whether we're next to any unit (enemy or ally) which is on the next waypoint
local unit_on_wp = wesnoth.get_units {
local unit_on_wp = AH.get_visible_units(wesnoth.current.side, {
x = patrol_vars.patrol_x,
y = patrol_vars.patrol_y,
{ "filter_adjacent", { id = patrol.id } }
}[1]
})[1]
for i,wp in ipairs(waypoints) do
-- If the patrol is on a waypoint or adjacent to one that is occupied by any unit
@ -121,20 +120,18 @@ function ca_patrol:execution(cfg)
-- Attack unit on the last waypoint under all circumstances if cfg.one_time_only is set
local adjacent_enemy
if cfg.one_time_only then
adjacent_enemy = wesnoth.get_units{
adjacent_enemy = AH.get_attackable_enemies {
x = waypoints[n_wp][1],
y = waypoints[n_wp][2],
{ "filter_adjacent", { id = patrol.id } },
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
{ "filter_adjacent", { id = patrol.id } }
}[1]
end
-- Otherwise attack adjacent enemy (if specified)
if (not adjacent_enemy) then
adjacent_enemy = wesnoth.get_units{
adjacent_enemy = AH.get_attackable_enemies {
id = cfg.attack,
{ "filter_adjacent", { id = patrol.id } },
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
{ "filter_adjacent", { id = patrol.id } }
}[1]
end

View file

@ -18,9 +18,7 @@ function ca_protect_unit_attack:evaluation(cfg)
local attacks = AH.get_attacks(units, { simulate_combat = true })
if (not attacks[1]) then return 0 end
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
local enemies = AH.get_attackable_enemies()
-- Counter attack calculation
local enemy_attacks = {}
@ -102,14 +100,7 @@ function ca_protect_unit_attack:evaluation(cfg)
end
function ca_protect_unit_attack:execution(cfg)
local attacker = wesnoth.get_unit(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.get_unit(best_attack.target.x, best_attack.target.y)
AH.movefull_stopunit(ai, attacker, best_attack.dst.x, best_attack.dst.y)
if (not attacker) or (not attacker.valid) then return end
if (not defender) or (not defender.valid) then return end
AH.checked_attack(ai, attacker, defender)
AH.robust_move_and_attack(ai, best_attack.src, best_attack.dst, best_attack.target)
best_attack = nil
end

View file

@ -27,9 +27,7 @@ function ca_protect_unit_move:execution(cfg, data)
for _,unit in ipairs(protected_units) do wesnoth.extract_unit(unit) end
local units = wesnoth.get_units { side = wesnoth.current.side }
local enemy_units = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
local enemy_units = AH.get_attackable_enemies()
local attack_map = BC.get_attack_map(units).units -- enemy attack map
local enemy_attack_map = BC.get_attack_map(enemy_units).units -- enemy attack map

View file

@ -17,10 +17,7 @@ function ca_simple_attack:evaluation(cfg)
local enemy_filter = H.get_child(cfg, "filter_second")
local enemy_map
if enemy_filter then
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "and", enemy_filter }
}
local enemies = AH.get_attackable_enemies(enemy_filter)
if (not enemies[1]) then return 0 end
enemy_map = LS.create()
@ -59,14 +56,7 @@ function ca_simple_attack:evaluation(cfg)
end
function ca_simple_attack:execution(cfg)
local attacker = wesnoth.get_unit(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.get_unit(best_attack.target.x, best_attack.target.y)
AH.movefull_outofway_stopunit(ai, attacker, best_attack.dst.x, best_attack.dst.y)
if (not attacker) or (not attacker.valid) then return end
if (not defender) or (not defender.valid) then return end
AH.checked_attack(ai, attacker, defender, (cfg.weapon or -1))
AH.robust_move_and_attack(ai, best_attack.src, best_attack.dst, best_attack.target, cfg)
best_attack = nil
end

View file

@ -23,8 +23,7 @@ function ca_stationed_guardian:execution(cfg)
local guardian = get_guardian(cfg)
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
local enemies = AH.get_attackable_enemies {
{ "filter_location", { x = guardian.x, y = guardian.y, radius = cfg.distance } }
}
@ -55,8 +54,10 @@ function ca_stationed_guardian:execution(cfg)
local best_defense, attack_loc = -9e99
for xa,ya in H.adjacent_tiles(target.x, target.y) do
-- Only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x = xa, y = ya, { "not", { id = guardian.id } } }[1]
if not occ_hex then
local unit_in_way = wesnoth.get_unit(xa, ya)
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way))
or (unit_in_way == guardian)
then
local defense = 100 - wesnoth.unit_defense(guardian, wesnoth.get_terrain(xa, ya))
local nh = AH.next_hop(guardian, xa, ya)
if nh then
@ -69,11 +70,7 @@ function ca_stationed_guardian:execution(cfg)
-- If a valid hex was found: move there and attack
if attack_loc then
AH.movefull_stopunit(ai, guardian, attack_loc)
if (not guardian) or (not guardian.valid) then return end
if (not target) or (not target.valid) then return end
AH.checked_attack(ai, guardian, target)
AH.robust_move_and_attack(ai, guardian, attack_loc, target)
else -- Otherwise move toward that enemy
local reach = wesnoth.find_reach(guardian)
@ -82,8 +79,10 @@ function ca_stationed_guardian:execution(cfg)
local min_dist, nh = 9e99
for _,hex in ipairs(reach) do
-- Only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x = hex[1], y = hex[2], { "not", { id = guardian.id } } }[1]
if not occ_hex then
local unit_in_way = wesnoth.get_unit(hex[1], hex[2])
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way))
or (unit_in_way == guardian)
then
local dist = H.distance_between(hex[1], hex[2], target.x, target.y)
if (dist < min_dist) then
min_dist, nh = dist, { hex[1], hex[2] }

View file

@ -27,9 +27,7 @@ function ca_swarm_move:execution(cfg)
end
end
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", {side = wesnoth.current.side } } } }
}
local enemies = AH.get_attackable_enemies()
-- Pick one unit at random, swarm does not move systematically
local unit = units[math.random(#units)]

View file

@ -3,8 +3,7 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_enemies(cfg)
local scatter_distance = cfg.scatter_distance or 3
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
local enemies = AH.get_attackable_enemies {
{ "filter_location",
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
}
@ -49,6 +48,9 @@ function ca_swarm_scatter:execution(cfg)
end)
AH.movefull_stopunit(ai, unit, best_hex)
-- Reconsider, as situation on the map might have changed
return
end
end
end

View file

@ -11,10 +11,7 @@ local function get_wolves(cfg)
end
local function get_prey(cfg)
local prey = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "and", H.get_child(cfg, "filter_second") }
}
local prey = AH.get_attackable_enemies(H.get_child(cfg, "filter_second"))
return prey
end
@ -30,9 +27,7 @@ function ca_wolves_move:execution(cfg)
local wolves = get_wolves(cfg)
local prey = get_prey(cfg)
local avoid_units = wesnoth.get_units { type = cfg.avoid_type,
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
local avoid_units = AH.get_attackable_enemies({ type = cfg.avoid_type })
local avoid_map = BC.get_attack_map(avoid_units).units
-- Find prey that is closest to the wolves
@ -69,6 +64,9 @@ function ca_wolves_move:execution(cfg)
end)
AH.movefull_stopunit(ai, wolves[1], wolf1)
for _,check_wolf in ipairs(wolves) do
if (not check_wolf) or (not check_wolf.valid) then return end
end
for i = 2,#wolves do
move = AH.find_best_move(wolves[i], function(x,y)
@ -93,6 +91,9 @@ function ca_wolves_move:execution(cfg)
end)
AH.movefull_stopunit(ai, wolves[i], move)
for _,check_wolf in ipairs(wolves) do
if (not check_wolf) or (not check_wolf.valid) then return end
end
end
end

View file

@ -119,27 +119,21 @@ function ca_wolves_multipacks_attack:execution(cfg)
end
end
if cfg.show_pack_number then
WMPF.clear_label(best_attack.src.x, best_attack.src.y)
end
local attacker = wesnoth.get_unit(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.get_unit(best_attack.target.x, best_attack.target.y)
if cfg.show_pack_number then WMPF.clear_label(attacker.x, attacker.y) end
AH.robust_move_and_attack(ai, attacker, best_attack.dst, defender)
AH.movefull_stopunit(ai, attacker, best_attack.dst.x, best_attack.dst.y)
if attacker and attacker.valid then
if cfg.show_pack_number then WMPF.put_label(attacker.x, attacker.y, pack_number) end
end
if attacker and attacker.valid and defender and defender.valid then
-- In case one of the units dies in the attack or is removed in an event, we need these:
local ax, ay, dx, dy = attacker.x, attacker.y, defender.x, defender.y
AH.checked_attack(ai, attacker, defender)
-- Remove the labels, if one of the units isgone
if cfg.show_pack_number then
if (not attacker) or (not attacker.valid) then WMPF.clear_label(ax, ay) end
if (not defender) or (not defender.valid) then WMPF.clear_label(dx, dy) end
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
@ -150,7 +144,7 @@ function ca_wolves_multipacks_attack:execution(cfg)
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
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

View file

@ -29,9 +29,7 @@ function ca_wolves_wander:execution(cfg)
reach_map:union_merge(r, function(x, y, v1, v2) return (v1 or 0) + (v2 or 0) end)
end
local avoid_units = wesnoth.get_units { type = cfg.avoid_type,
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
}
local avoid_units = AH.get_attackable_enemies({ type = cfg.avoid_type })
local avoid_map = BC.get_attack_map(avoid_units).units
local max_rating, goal_hex = -9e99
@ -55,6 +53,9 @@ function ca_wolves_wander:execution(cfg)
end)
AH.movefull_stopunit(ai, wolf, best_hex)
for _,check_wolf in ipairs(wolves) do
if (not check_wolf) or (not check_wolf.valid) then return end
end
end
end

View file

@ -24,10 +24,7 @@ function ca_zone_guardian:execution(cfg)
local zone = H.get_child(cfg, "filter_location")
local zone_enemy = H.get_child(cfg, "filter_location_enemy") or zone
local enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_location", zone_enemy }
}
local enemies = AH.get_attackable_enemies { { "filter_location", zone_enemy } }
if enemies[1] then
local min_dist, target = 9e99
for _,enemy in ipairs(enemies) do
@ -44,8 +41,10 @@ function ca_zone_guardian:execution(cfg)
local best_defense, attack_loc = -9e99
for xa,ya in H.adjacent_tiles(target.x, target.y) do
-- Only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x = xa, y = ya, { "not", { id = guardian.id } } }[1]
if not occ_hex then
local unit_in_way = wesnoth.get_unit(xa, ya)
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way))
or (unit_in_way == guardian)
then
local defense = 100 - wesnoth.unit_defense(guardian, wesnoth.get_terrain(xa, ya))
local nh = AH.next_hop(guardian, xa, ya)
if nh then
@ -58,11 +57,7 @@ function ca_zone_guardian:execution(cfg)
-- If a valid hex was found: move there and attack
if attack_loc then
AH.movefull_stopunit(ai, guardian, attack_loc)
if (not guardian) or (not guardian.valid) then return end
if (not target) or (not target.valid) then return end
AH.checked_attack(ai, guardian, target)
AH.robust_move_and_attack(ai, guardian, attack_loc, target)
else -- Otherwise move toward that enemy
local reach = wesnoth.find_reach(guardian)
@ -71,8 +66,10 @@ function ca_zone_guardian:execution(cfg)
local min_dist, nh = 9e99
for _,hex in ipairs(reach) do
-- Only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x = hex[1], y = hex[2], { "not", { id = guardian.id } } }[1]
if not occ_hex then
local unit_in_way = wesnoth.get_unit(hex[1], hex[2])
if (not AH.is_visible_unit(wesnoth.current.side, unit_in_way))
or (unit_in_way == guardian)
then
local dist = H.distance_between(hex[1], hex[2], target.x, target.y)
if (dist < min_dist) then
min_dist, nh = dist, { hex[1], hex[2] }