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:
parent
545800f9f8
commit
b302289402
32 changed files with 227 additions and 295 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 } }
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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] }
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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] }
|
||||
|
|
Loading…
Add table
Reference in a new issue