Micro AI CA files: replace tabs by spaces

I had not realized that TextWrangler changes spaces to tabs when
shifting blocks of text to the left, which had been done for all CA
files when converting the engine files.
This commit is contained in:
mattsc 2013-11-14 10:54:14 -08:00
parent 9d6331ac19
commit 9976eac873
44 changed files with 3601 additions and 3601 deletions

View file

@ -68,7 +68,7 @@ return {
-------- Castle Switch CA --------------
local function get_reachable_enemy_leaders(unit)
local potential_enemy_leaders = AH.get_live_units { canrecruit = 'yes',
{ "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } }
{ "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } }
}
local enemy_leaders = {}
for j,e in ipairs(potential_enemy_leaders) do
@ -120,7 +120,7 @@ return {
y = '1-'..height,
{ "not", { {"filter", {}} }}, -- That have no unit
{ "not", { radius = 6, {"filter", { canrecruit = 'yes',
{ "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } }
{ "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } }
}} }}, -- That are not too close to an enemy leader
{ "not", {
x = leader.x, y = leader.y, terrain = 'K*,K*^*,*^K*',

View file

@ -5,111 +5,111 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_big_animals = {}
function ca_big_animals:evaluation(ai, cfg)
local units = wesnoth.get_units {
side = wesnoth.current.side,
{ "and" , cfg.filter },
formula = '$this_unit.moves > 0'
}
local units = wesnoth.get_units {
side = wesnoth.current.side,
{ "and" , cfg.filter },
formula = '$this_unit.moves > 0'
}
if units[1] then return cfg.ca_score end
return 0
if units[1] then return cfg.ca_score end
return 0
end
function ca_big_animals:execution(ai, cfg)
-- Big animals just move toward goal that gets set occasionally
-- Avoid the other big animals (bears, yetis, spiders) and the dogs, otherwise attack whatever is in their range
-- The only difference in behavior is the area in which the units move
-- Big animals just move toward goal that gets set occasionally
-- Avoid the other big animals (bears, yetis, spiders) and the dogs, otherwise attack whatever is in their range
-- The only difference in behavior is the area in which the units move
local units = wesnoth.get_units {
side = wesnoth.current.side,
{ "and" , cfg.filter },
formula = '$this_unit.moves > 0'
}
local avoid = LS.of_pairs(wesnoth.get_locations { radius = 1,
{ "filter", { { "and", cfg.avoid_unit },
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
} }
})
--AH.put_labels(avoid)
local units = wesnoth.get_units {
side = wesnoth.current.side,
{ "and" , cfg.filter },
formula = '$this_unit.moves > 0'
}
local avoid = LS.of_pairs(wesnoth.get_locations { radius = 1,
{ "filter", { { "and", cfg.avoid_unit },
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
} }
})
--AH.put_labels(avoid)
for i,unit in ipairs(units) do
-- Unit gets a new goal if none exist or on any move with 10% random chance
local r = AH.random(10)
if (not unit.variables.goal_x) or (r == 1) then
local locs = AH.get_passable_locations(cfg.filter_location or {})
local rand = AH.random(#locs)
--print(type, ': #locs', #locs, rand)
unit.variables.goal_x, unit.variables.goal_y = locs[rand][1], locs[rand][2]
end
--print('Big animal goto: ', type, unit.variables.goal_x, unit.variables.goal_y, r)
for i,unit in ipairs(units) do
-- Unit gets a new goal if none exist or on any move with 10% random chance
local r = AH.random(10)
if (not unit.variables.goal_x) or (r == 1) then
local locs = AH.get_passable_locations(cfg.filter_location or {})
local rand = AH.random(#locs)
--print(type, ': #locs', #locs, rand)
unit.variables.goal_x, unit.variables.goal_y = locs[rand][1], locs[rand][2]
end
--print('Big animal goto: ', type, unit.variables.goal_x, unit.variables.goal_y, r)
-- hexes the unit can reach
local reach_map = AH.get_reachable_unocc(unit)
local wander_terrain = cfg.filter_location_wander or {}
reach_map:iter( function(x, y, v)
-- Remove tiles that do not comform to the wander terrain filter
if (not wesnoth.match_location(x, y, wander_terrain) ) then
reach_map:remove(x, y)
end
end)
-- hexes the unit can reach
local reach_map = AH.get_reachable_unocc(unit)
local wander_terrain = cfg.filter_location_wander or {}
reach_map:iter( function(x, y, v)
-- Remove tiles that do not comform to the wander terrain filter
if (not wesnoth.match_location(x, y, wander_terrain) ) then
reach_map:remove(x, y)
end
end)
-- Now find the one of these hexes that is closest to the goal
local max_rating, best_hex = -9e99, {}
reach_map:iter( function(x, y, v)
-- Distance from goal is first rating
local rating = - H.distance_between(x, y, unit.variables.goal_x, unit.variables.goal_y)
-- Now find the one of these hexes that is closest to the goal
local max_rating, best_hex = -9e99, {}
reach_map:iter( function(x, y, v)
-- Distance from goal is first rating
local rating = - H.distance_between(x, y, unit.variables.goal_x, unit.variables.goal_y)
-- Proximity to an enemy unit is a plus
local enemy_hp = 500
for xa, ya in H.adjacent_tiles(x, y) do
local enemy = wesnoth.get_unit(xa, ya)
if enemy and (enemy.side ~= wesnoth.current.side) then
if (enemy.hitpoints < enemy_hp) then enemy_hp = enemy.hitpoints end
end
end
rating = rating + 500 - enemy_hp -- prefer attack on weakest enemy
-- Proximity to an enemy unit is a plus
local enemy_hp = 500
for xa, ya in H.adjacent_tiles(x, y) do
local enemy = wesnoth.get_unit(xa, ya)
if enemy and (enemy.side ~= wesnoth.current.side) then
if (enemy.hitpoints < enemy_hp) then enemy_hp = enemy.hitpoints end
end
end
rating = rating + 500 - enemy_hp -- prefer attack on weakest enemy
-- However, hexes that enemy bears, yetis and spiders can reach get a massive negative hit
-- meaning that they will only ever be chosen if there's no way around them
if avoid:get(x, y) then rating = rating - 1000 end
-- However, hexes that enemy bears, yetis and spiders can reach get a massive negative hit
-- meaning that they will only ever be chosen if there's no way around them
if avoid:get(x, y) then rating = rating - 1000 end
reach_map:insert(x, y, rating)
if (rating > max_rating) then
max_rating, best_hex = rating, { x, y }
end
end)
--print(' best_hex: ', best_hex[1], best_hex[2])
--AH.put_labels(reach_map)
reach_map:insert(x, y, rating)
if (rating > max_rating) then
max_rating, best_hex = rating, { x, y }
end
end)
--print(' best_hex: ', best_hex[1], best_hex[2])
--AH.put_labels(reach_map)
if (best_hex[1] ~= unit.x) or (best_hex[2] ~= unit.y) then
ai.move(unit, best_hex[1], best_hex[2]) -- partial move only
else -- If animal did not move, we need to stop it (also delete the goal)
ai.stopunit_moves(unit)
unit.variables.goal_x = nil
unit.variables.goal_y = nil
end
if (best_hex[1] ~= unit.x) or (best_hex[2] ~= unit.y) then
ai.move(unit, best_hex[1], best_hex[2]) -- partial move only
else -- If animal did not move, we need to stop it (also delete the goal)
ai.stopunit_moves(unit)
unit.variables.goal_x = nil
unit.variables.goal_y = nil
end
-- Or if this gets the unit to the goal, we also delete the goal
if (unit.x == unit.variables.goal_x) and (unit.y == unit.variables.goal_y) then
unit.variables.goal_x = nil
unit.variables.goal_y = nil
end
-- Or if this gets the unit to the goal, we also delete the goal
if (unit.x == unit.variables.goal_x) and (unit.y == unit.variables.goal_y) then
unit.variables.goal_x = nil
unit.variables.goal_y = nil
end
-- Finally, if the unit ended up next to enemies, attack the weakest of those
local min_hp, target = 9e99, {}
for x, y in H.adjacent_tiles(unit.x, unit.y) do
local enemy = wesnoth.get_unit(x, y)
if enemy and (enemy.side ~= wesnoth.current.side) then
if (enemy.hitpoints < min_hp) then
min_hp, target = enemy.hitpoints, enemy
end
end
end
if target.id then
ai.attack(unit, target)
end
-- Finally, if the unit ended up next to enemies, attack the weakest of those
local min_hp, target = 9e99, {}
for x, y in H.adjacent_tiles(unit.x, unit.y) do
local enemy = wesnoth.get_unit(x, y)
if enemy and (enemy.side ~= wesnoth.current.side) then
if (enemy.hitpoints < min_hp) then
min_hp, target = enemy.hitpoints, enemy
end
end
end
if target.id then
ai.attack(unit, target)
end
end
end
end
return ca_big_animals

View file

@ -3,82 +3,82 @@ local H = wesnoth.require "lua/helper.lua"
local ca_bottleneck_attack = {}
function ca_bottleneck_attack:evaluation(ai, cfg, self)
-- All units with attacks_left and enemies next to them
-- This will be much easier once the 'attacks' variable is implemented
local attackers = wesnoth.get_units {
side = wesnoth.current.side, formula = "$this_unit.attacks_left > 0",
{ "filter_adjacent", {
{ "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } }
} }
}
--print("\n\nAttackers:",#attackers)
if (not attackers[1]) then return 0 end
-- All units with attacks_left and enemies next to them
-- This will be much easier once the 'attacks' variable is implemented
local attackers = wesnoth.get_units {
side = wesnoth.current.side, formula = "$this_unit.attacks_left > 0",
{ "filter_adjacent", {
{ "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } }
} }
}
--print("\n\nAttackers:",#attackers)
if (not attackers[1]) then return 0 end
-- Now loop through the attackers, and all attacks for each
local max_rating, best_att, best_tar, best_weapon = 0, {}, {}, -1
for i,a in ipairs(attackers) do
local targets = wesnoth.get_units {
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = a.id } }
}
--print(" ",a.id,#targets)
-- Now loop through the attackers, and all attacks for each
local max_rating, best_att, best_tar, best_weapon = 0, {}, {}, -1
for i,a in ipairs(attackers) do
local targets = wesnoth.get_units {
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = a.id } }
}
--print(" ",a.id,#targets)
for j,t in ipairs(targets) do
local n_weapon = 0
for weapon in H.child_range(a.__cfg, "attack") do
n_weapon = n_weapon + 1
for j,t in ipairs(targets) do
local n_weapon = 0
for weapon in H.child_range(a.__cfg, "attack") do
n_weapon = n_weapon + 1
local att_stats, def_stats = wesnoth.simulate_combat(a, n_weapon, t)
local att_stats, def_stats = wesnoth.simulate_combat(a, n_weapon, t)
local rating = 0
-- This is an acceptable attack if:
-- 1. There is no counter attack
-- 2. Probability of death is >=67% for enemy, 0% for attacker
if (att_stats.hp_chance[a.hitpoints] == 1)
or ((def_stats.hp_chance[0] >= 0.67) and (att_stats.hp_chance[0] == 0))
then
rating = 1000 + t.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
-- if there's a chance to make the kill, unit closest to leveling up goes first, otherwise the other way around
if (def_stats.hp_chance[0] >= 0.67) then
rating = rating + (a.experience - a.max_experience) / 10.
else
rating = rating - (a.experience - a.max_experience) / 10.
end
end
--print(a.id, t.id,weapon.name, rating)
if rating > max_rating then
max_rating, best_att, best_tar, best_weapon = rating, a, t, n_weapon
end
end
end
end
--print("Best attack:",best_att.id, best_tar.id, max_rating, best_weapon)
local rating = 0
-- This is an acceptable attack if:
-- 1. There is no counter attack
-- 2. Probability of death is >=67% for enemy, 0% for attacker
if (att_stats.hp_chance[a.hitpoints] == 1)
or ((def_stats.hp_chance[0] >= 0.67) and (att_stats.hp_chance[0] == 0))
then
rating = 1000 + t.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
-- if there's a chance to make the kill, unit closest to leveling up goes first, otherwise the other way around
if (def_stats.hp_chance[0] >= 0.67) then
rating = rating + (a.experience - a.max_experience) / 10.
else
rating = rating - (a.experience - a.max_experience) / 10.
end
end
--print(a.id, t.id,weapon.name, rating)
if rating > max_rating then
max_rating, best_att, best_tar, best_weapon = rating, a, t, n_weapon
end
end
end
end
--print("Best attack:",best_att.id, best_tar.id, max_rating, best_weapon)
if max_rating == 0 then
-- In this case we take attacks away from all units
-- This is done so that the RCA AI CAs can be kept in place
self.data.bottleneck_attacks_done = true
else
self.data.bottleneck_attacks_done = false
self.data.attacker = best_att
self.data.target = best_tar
self.data.weapon = best_weapon
end
return cfg.ca_score
if max_rating == 0 then
-- In this case we take attacks away from all units
-- This is done so that the RCA AI CAs can be kept in place
self.data.bottleneck_attacks_done = true
else
self.data.bottleneck_attacks_done = false
self.data.attacker = best_att
self.data.target = best_tar
self.data.weapon = best_weapon
end
return cfg.ca_score
end
function ca_bottleneck_attack:execution(ai, cfg, self)
if self.data.bottleneck_attacks_done then
local units = wesnoth.get_units { side = wesnoth.current.side, formula = '$this_unit.attacks_left > 0' }
for i,u in ipairs(units) do
ai.stopunit_attacks(u)
end
else
ai.attack(self.data.attacker, self.data.target, self.data.weapon)
end
if self.data.bottleneck_attacks_done then
local units = wesnoth.get_units { side = wesnoth.current.side, formula = '$this_unit.attacks_left > 0' }
for i,u in ipairs(units) do
ai.stopunit_attacks(u)
end
else
ai.attack(self.data.attacker, self.data.target, self.data.weapon)
end
self.data.attacker, self.data.target, self.data.weapon = nil, nil, nil
self.data.bottleneck_attacks_done = nil
self.data.attacker, self.data.target, self.data.weapon = nil, nil, nil
self.data.bottleneck_attacks_done = nil
end
return ca_bottleneck_attack

View file

@ -6,184 +6,184 @@ local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local ca_bottleneck_move = {}
local function bottleneck_is_my_territory(map, enemy_map)
-- Create map that contains 'true' for all hexes that are
-- on the AI's side of the map
-- Create map that contains 'true' for all hexes that are
-- on the AI's side of the map
-- Get copy of leader to do pathfinding from each hex to the
-- front-line hexes, both own (stored in 'map') and enemy (enemy_map) front-line hexes
-- If there is no leader, use first unit found
local unit = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
if (not unit) then unit = wesnoth.get_units { side = wesnoth.current.side }[1] end
local dummy_unit = wesnoth.copy_unit(unit)
-- Get copy of leader to do pathfinding from each hex to the
-- front-line hexes, both own (stored in 'map') and enemy (enemy_map) front-line hexes
-- If there is no leader, use first unit found
local unit = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
if (not unit) then unit = wesnoth.get_units { side = wesnoth.current.side }[1] end
local dummy_unit = wesnoth.copy_unit(unit)
local territory_map = LS.create()
local w,h,b = wesnoth.get_map_size()
for x = 1,w do
for y = 1,h do
-- The hex might have been covered already previously
if (not territory_map:get(x,y)) then
dummy_unit.x, dummy_unit.y = x, y
local territory_map = LS.create()
local w,h,b = wesnoth.get_map_size()
for x = 1,w do
for y = 1,h do
-- The hex might have been covered already previously
if (not territory_map:get(x,y)) then
dummy_unit.x, dummy_unit.y = x, y
-- Find lowest movement cost to own front-line hexes
local min_cost, best_path = 9e99, {}
map:iter(function(xm, ym, v)
local path, cost = wesnoth.find_path(dummy_unit, xm, ym, { ignore_units = true })
if (cost < min_cost) then
min_cost, best_path = cost, path
end
end)
-- Find lowest movement cost to own front-line hexes
local min_cost, best_path = 9e99, {}
map:iter(function(xm, ym, v)
local path, cost = wesnoth.find_path(dummy_unit, xm, ym, { ignore_units = true })
if (cost < min_cost) then
min_cost, best_path = cost, path
end
end)
-- And the same to the enemy front line
local min_cost_enemy, best_path_enemy = 9e99, {}
enemy_map:iter(function(xm, ym, v)
local path, cost = wesnoth.find_path(dummy_unit, xm, ym, { ignore_units = true })
if (cost < min_cost_enemy) then
min_cost_enemy, best_path_enemy = cost, path
end
end)
-- And the same to the enemy front line
local min_cost_enemy, best_path_enemy = 9e99, {}
enemy_map:iter(function(xm, ym, v)
local path, cost = wesnoth.find_path(dummy_unit, xm, ym, { ignore_units = true })
if (cost < min_cost_enemy) then
min_cost_enemy, best_path_enemy = cost, path
end
end)
-- We can set this for the entire path
-- for efficiency reasons (this is pretty slow, esp. on large maps)
if (min_cost < min_cost_enemy) then
for i,p in ipairs(best_path) do
territory_map:insert(p[1], p[2], true)
end
else -- We do need to use 0's in this case though, false won't work
for i,p in ipairs(best_path_enemy) do
territory_map:insert(p[1], p[2], 0)
end
end
end
end
end
-- We can set this for the entire path
-- for efficiency reasons (this is pretty slow, esp. on large maps)
if (min_cost < min_cost_enemy) then
for i,p in ipairs(best_path) do
territory_map:insert(p[1], p[2], true)
end
else -- We do need to use 0's in this case though, false won't work
for i,p in ipairs(best_path_enemy) do
territory_map:insert(p[1], p[2], 0)
end
end
end
end
end
-- Now we need to go over it again and delete all the zeros
territory_map:iter(function(x, y, v)
if (territory_map:get(x, y) == 0) then territory_map:remove(x, y) end
end)
-- Now we need to go over it again and delete all the zeros
territory_map:iter(function(x, y, v)
if (territory_map:get(x, y) == 0) then territory_map:remove(x, y) end
end)
return territory_map
return territory_map
end
local function bottleneck_triple_from_keys(key_x, key_y, max_value)
-- Turn 'key_x= key_y=' comma-separated lists into a location set
local coords = {}
for x in string.gmatch(key_x, "%d+") do
table.insert(coords, { x })
end
local i = 1
for y in string.gmatch(key_y, "%d+") do
table.insert(coords[i], y)
table.insert(coords[i], max_value + 10 - i * 10) -- the rating
i = i + 1
end
-- Turn 'key_x= key_y=' comma-separated lists into a location set
local coords = {}
for x in string.gmatch(key_x, "%d+") do
table.insert(coords, { x })
end
local i = 1
for y in string.gmatch(key_y, "%d+") do
table.insert(coords[i], y)
table.insert(coords[i], max_value + 10 - i * 10) -- the rating
i = i + 1
end
return AH.LS_of_triples(coords)
return AH.LS_of_triples(coords)
end
local function bottleneck_create_positioning_map(max_value, self)
-- Create the positioning maps for the healers and leaders, if not given by WML keys
-- Max_value: the rating value for the first hex in the set
-- Create the positioning maps for the healers and leaders, if not given by WML keys
-- Max_value: the rating value for the first hex in the set
-- First, find all locations adjacent to def_map
-- This might include hexes on the line itself, but
-- only store those that are not in enemy territory
local map = LS.create()
self.data.def_map:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
if self.data.is_my_territory:get(xa, ya) then
-- This rating adds up the scores of all the adjacent def_map hexes
local rating = self.data.def_map:get(x, y) or 0
rating = rating + (map:get(xa, ya) or 0)
map:insert(xa, ya, rating)
end
end
end)
-- First, find all locations adjacent to def_map
-- This might include hexes on the line itself, but
-- only store those that are not in enemy territory
local map = LS.create()
self.data.def_map:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
if self.data.is_my_territory:get(xa, ya) then
-- This rating adds up the scores of all the adjacent def_map hexes
local rating = self.data.def_map:get(x, y) or 0
rating = rating + (map:get(xa, ya) or 0)
map:insert(xa, ya, rating)
end
end
end)
-- We need to sort the map, and assign descending values
local locs = AH.to_triples(map)
table.sort(locs, function(a, b) return a[3] > b[3] end)
for i,l in ipairs(locs) do l[3] = max_value + 10 - i * 10 end
map = AH.LS_of_triples(locs)
-- We need to sort the map, and assign descending values
local locs = AH.to_triples(map)
table.sort(locs, function(a, b) return a[3] > b[3] end)
for i,l in ipairs(locs) do l[3] = max_value + 10 - i * 10 end
map = AH.LS_of_triples(locs)
-- Finally, we merge the defense map into this, as healers/leaders (by default)
-- can take position on the front line
map:union_merge(self.data.def_map,
function(x, y, v1, v2) return v1 or v2 end
)
-- Finally, we merge the defense map into this, as healers/leaders (by default)
-- can take position on the front line
map:union_merge(self.data.def_map,
function(x, y, v1, v2) return v1 or v2 end
)
return map
return map
end
local function bottleneck_get_rating(unit, x, y, has_leadership, is_healer, self)
-- Calculate rating of a unit at the given coordinates
-- Don't want to extract is_healer and has_leadership inside this function, as it is very slow
-- thus they are provided as parameters from the calling function
-- Calculate rating of a unit at the given coordinates
-- Don't want to extract is_healer and has_leadership inside this function, as it is very slow
-- thus they are provided as parameters from the calling function
local rating = 0
-- Defense positioning rating
-- We exclude healers/leaders here, as we don't necessarily want them on the front line
if (not is_healer) and (not has_leadership) then
rating = self.data.def_map:get(x, y) or 0
end
local rating = 0
-- Defense positioning rating
-- We exclude healers/leaders here, as we don't necessarily want them on the front line
if (not is_healer) and (not has_leadership) then
rating = self.data.def_map:get(x, y) or 0
end
-- Healer positioning rating
if is_healer then
local healer_rating = self.data.healer_map:get(x, y) or 0
if (healer_rating > rating) then rating = healer_rating end
end
-- Healer positioning rating
if is_healer then
local healer_rating = self.data.healer_map:get(x, y) or 0
if (healer_rating > rating) then rating = healer_rating end
end
-- Leadership unit positioning rating
if has_leadership then
local leadership_rating = self.data.leadership_map:get(x, y) or 0
-- Leadership unit positioning rating
if has_leadership then
local leadership_rating = self.data.leadership_map:get(x, y) or 0
-- If leadership unit is injured -> prefer hexes next to healers
if (unit.hitpoints < unit.max_hitpoints) then
for xa, ya in H.adjacent_tiles(x, y) do
local adj = wesnoth.get_unit(xa, ya)
if adj and (adj.__cfg.usage == "healer") then
leadership_rating = leadership_rating + 100
break
end
end
-- If leadership unit is injured -> prefer hexes next to healers
if (unit.hitpoints < unit.max_hitpoints) then
for xa, ya in H.adjacent_tiles(x, y) do
local adj = wesnoth.get_unit(xa, ya)
if adj and (adj.__cfg.usage == "healer") then
leadership_rating = leadership_rating + 100
break
end
end
end
end
if (leadership_rating > rating) then rating = leadership_rating end
end
if (leadership_rating > rating) then rating = leadership_rating end
end
-- Injured unit positioning
if (unit.hitpoints < unit.max_hitpoints) then
local healing_rating = self.data.healing_map:get(x, y) or 0
if (healing_rating > rating) then rating = healing_rating end
end
-- Injured unit positioning
if (unit.hitpoints < unit.max_hitpoints) then
local healing_rating = self.data.healing_map:get(x, y) or 0
if (healing_rating > rating) then rating = healing_rating end
end
-- If this did not produce a positive rating, we add a
-- distance-based rating, to get units to the bottleneck in the first place
if (rating <= 0) and self.data.is_my_territory:get(x, y) then
local combined_dist = 0
self.data.def_map:iter(function(x_def, y_def, v)
combined_dist = combined_dist + H.distance_between(x, y, x_def, y_def)
end)
combined_dist = combined_dist / self.data.def_map:size()
rating = 1000 - combined_dist * 10.
end
-- If this did not produce a positive rating, we add a
-- distance-based rating, to get units to the bottleneck in the first place
if (rating <= 0) and self.data.is_my_territory:get(x, y) then
local combined_dist = 0
self.data.def_map:iter(function(x_def, y_def, v)
combined_dist = combined_dist + H.distance_between(x, y, x_def, y_def)
end)
combined_dist = combined_dist / self.data.def_map:size()
rating = 1000 - combined_dist * 10.
end
-- Now add the unit specific rating
if (rating > 0) then
rating = rating + unit.hitpoints/10. + unit.experience/100.
end
-- Now add the unit specific rating
if (rating > 0) then
rating = rating + unit.hitpoints/10. + unit.experience/100.
end
return rating
return rating
end
local function bottleneck_move_out_of_way(unit, self)
-- Find the best move out of the way for a unit
-- and choose the shortest possible move
-- Returns nil if no move was found
-- Find the best move out of the way for a unit
-- and choose the shortest possible move
-- Returns nil if no move was found
-- just a sanity check: unit to move out of the way needs to be on our side:
if (unit.side ~= wesnoth.current.side) then return nil end
-- just a sanity check: unit to move out of the way needs to be on our side:
if (unit.side ~= wesnoth.current.side) then return nil end
local reach = wesnoth.find_reach(unit)
@ -192,19 +192,19 @@ local function bottleneck_move_out_of_way(unit, self)
local all_units = wesnoth.get_units { }
local occ_hexes = LS:create()
for i,u in ipairs(all_units) do
occ_hexes:insert(u.x, u.y)
occ_hexes:insert(u.x, u.y)
end
-- find the closest unoccupied reachable hex in the east
local best_reach, best_hex = -1, {}
for i,r in ipairs(reach) do
if self.data.is_my_territory:get(r[1], r[2]) and (not occ_hexes:get(r[1], r[2])) then
-- Best hex to move out of way to:
-- (r[3] > best_reach) : move shorter than previous best move
if (r[3] > best_reach) then
best_reach, best_hex = r[3], { r[1], r[2] }
end
end
if self.data.is_my_territory:get(r[1], r[2]) and (not occ_hexes:get(r[1], r[2])) then
-- Best hex to move out of way to:
-- (r[3] > best_reach) : move shorter than previous best move
if (r[3] > best_reach) then
best_reach, best_hex = r[3], { r[1], r[2] }
end
end
end
--print("Best reach: ",unit.id, best_reach, best_hex[1], best_hex[2])
@ -212,308 +212,308 @@ local function bottleneck_move_out_of_way(unit, self)
end
function ca_bottleneck_move:evaluation(ai, cfg, self)
-- Check whether the side leader should be included or not
if cfg.active_side_leader and (not self.data.side_leader_activated) then
local can_still_recruit = false -- enough gold left for another recruit?
local recruit_list = wesnoth.sides[wesnoth.current.side].recruit
for i,recruit_type in ipairs(recruit_list) do
local cost = wesnoth.unit_types[recruit_type].cost
local current_gold = wesnoth.sides[wesnoth.current.side].gold
if (cost <= current_gold) then
can_still_recruit = true
break
end
end
if (not can_still_recruit) then self.data.side_leader_activated = true end
end
-- Check whether the side leader should be included or not
if cfg.active_side_leader and (not self.data.side_leader_activated) then
local can_still_recruit = false -- enough gold left for another recruit?
local recruit_list = wesnoth.sides[wesnoth.current.side].recruit
for i,recruit_type in ipairs(recruit_list) do
local cost = wesnoth.unit_types[recruit_type].cost
local current_gold = wesnoth.sides[wesnoth.current.side].gold
if (cost <= current_gold) then
can_still_recruit = true
break
end
end
if (not can_still_recruit) then self.data.side_leader_activated = true end
end
-- Now find all units, including the leader or not, depending on situation and settings
local units = {}
if self.data.side_leader_activated then
units = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0'
}
else
units = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'no',
formula = '$this_unit.moves > 0'
}
end
-- Now find all units, including the leader or not, depending on situation and settings
local units = {}
if self.data.side_leader_activated then
units = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0'
}
else
units = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'no',
formula = '$this_unit.moves > 0'
}
end
-- If there's no units with moves left, nothing to be done here
if (not units[1]) then return 0 end
-- If there's no units with moves left, nothing to be done here
if (not units[1]) then return 0 end
-- Set up the arrays that tell the AI where to defend the bottleneck
-- Get the x and y coordinates (this assumes that cfg.x and cfg.y have the same length)
self.data.def_map = bottleneck_triple_from_keys(cfg.x, cfg.y, 10000)
--AH.put_labels(self.data.def_map)
-- Set up the arrays that tell the AI where to defend the bottleneck
-- Get the x and y coordinates (this assumes that cfg.x and cfg.y have the same length)
self.data.def_map = bottleneck_triple_from_keys(cfg.x, cfg.y, 10000)
--AH.put_labels(self.data.def_map)
-- Get the territory map, describing which hex is on AI's side of the bottleneck
-- This one is a bit expensive, esp. on large maps -> don't delete every move and reuse
-- However, after a reload, self.data.is_my_territory is an empty string
-- -> need to recalculate in that case also (the reason is that is_my_territory is not a WML table)
if (not self.data.is_my_territory) or (type(self.data.is_my_territory) == 'string') then
local enemy_map = bottleneck_triple_from_keys(cfg.enemy_x, cfg.enemy_y, 10000)
self.data.is_my_territory = bottleneck_is_my_territory(self.data.def_map, enemy_map)
end
--AH.put_labels(self.data.is_my_territory)
-- Get the territory map, describing which hex is on AI's side of the bottleneck
-- This one is a bit expensive, esp. on large maps -> don't delete every move and reuse
-- However, after a reload, self.data.is_my_territory is an empty string
-- -> need to recalculate in that case also (the reason is that is_my_territory is not a WML table)
if (not self.data.is_my_territory) or (type(self.data.is_my_territory) == 'string') then
local enemy_map = bottleneck_triple_from_keys(cfg.enemy_x, cfg.enemy_y, 10000)
self.data.is_my_territory = bottleneck_is_my_territory(self.data.def_map, enemy_map)
end
--AH.put_labels(self.data.is_my_territory)
-- Setting up healer positioning map
if cfg.healer_x and cfg.healer_y then
-- If healer_x,healer_y are given, extract locs from there
self.data.healer_map = bottleneck_triple_from_keys(cfg.healer_x, cfg.healer_y, 5000)
else
-- Otherwise create the map here
self.data.healer_map = bottleneck_create_positioning_map(5000, self)
end
-- Use def_map values for any healer hexes that are defined in def_map as well
self.data.healer_map:inter_merge(self.data.def_map,
function(x, y, v1, v2) return v2 or v1 end
)
--AH.put_labels(self.data.healer_map)
-- Setting up healer positioning map
if cfg.healer_x and cfg.healer_y then
-- If healer_x,healer_y are given, extract locs from there
self.data.healer_map = bottleneck_triple_from_keys(cfg.healer_x, cfg.healer_y, 5000)
else
-- Otherwise create the map here
self.data.healer_map = bottleneck_create_positioning_map(5000, self)
end
-- Use def_map values for any healer hexes that are defined in def_map as well
self.data.healer_map:inter_merge(self.data.def_map,
function(x, y, v1, v2) return v2 or v1 end
)
--AH.put_labels(self.data.healer_map)
-- Setting up leadership position map
-- If leadership_x, leadership_y are not given, we create the leadership positioning array
if cfg.leadership_x and cfg.leadership_y then
-- If leadership_x,leadership_y are given, extract locs from there
self.data.leadership_map = bottleneck_triple_from_keys(cfg.leadership_x, cfg.leadership_y, 4000)
else
-- Otherwise create the map here
self.data.leadership_map = bottleneck_create_positioning_map(4000, self)
end
-- Use def_map values for any leadership hexes that are defined in def_map as well
self.data.leadership_map:inter_merge(self.data.def_map,
function(x, y, v1, v2) return v2 or v1 end
)
--AH.put_labels(self.data.leadership_map)
-- Setting up leadership position map
-- If leadership_x, leadership_y are not given, we create the leadership positioning array
if cfg.leadership_x and cfg.leadership_y then
-- If leadership_x,leadership_y are given, extract locs from there
self.data.leadership_map = bottleneck_triple_from_keys(cfg.leadership_x, cfg.leadership_y, 4000)
else
-- Otherwise create the map here
self.data.leadership_map = bottleneck_create_positioning_map(4000, self)
end
-- Use def_map values for any leadership hexes that are defined in def_map as well
self.data.leadership_map:inter_merge(self.data.def_map,
function(x, y, v1, v2) return v2 or v1 end
)
--AH.put_labels(self.data.leadership_map)
-- healing map: positions next to healers, needs to be calculated each move
-- Healers get moved with higher priority, so don't need to check their MP
local healers = wesnoth.get_units { side = wesnoth.current.side, ability = "healing" }
self.data.healing_map = LS.create()
for i,h in ipairs(healers) do
for x, y in H.adjacent_tiles(h.x, h.y) do
-- Cannot be on the line, and needs to be in own territory
if self.data.is_my_territory:get(x, y) then
local min_dist = 9e99
self.data.def_map:iter( function(xd, yd, vd)
local dist_line = H.distance_between(x, y, xd, yd)
if (dist_line < min_dist) then min_dist = dist_line end
end)
if (min_dist > 0) then
self.data.healing_map:insert(x, y, 3000 + min_dist) -- farther away from enemy is good
end
end
end
end
--AH.put_labels(self.data.healing_map)
-- healing map: positions next to healers, needs to be calculated each move
-- Healers get moved with higher priority, so don't need to check their MP
local healers = wesnoth.get_units { side = wesnoth.current.side, ability = "healing" }
self.data.healing_map = LS.create()
for i,h in ipairs(healers) do
for x, y in H.adjacent_tiles(h.x, h.y) do
-- Cannot be on the line, and needs to be in own territory
if self.data.is_my_territory:get(x, y) then
local min_dist = 9e99
self.data.def_map:iter( function(xd, yd, vd)
local dist_line = H.distance_between(x, y, xd, yd)
if (dist_line < min_dist) then min_dist = dist_line end
end)
if (min_dist > 0) then
self.data.healing_map:insert(x, y, 3000 + min_dist) -- farther away from enemy is good
end
end
end
end
--AH.put_labels(self.data.healing_map)
-- Now on to evaluating possible moves:
-- First, get the rating of all units in their current positions
-- A move is only considered if it improves the overall rating,
-- that is, its rating must be higher than:
-- 1. the rating of the unit on the target hex (if there is one)
-- 2. the rating of the currently considered unit on its current hex
local all_units = wesnoth.get_units { side = wesnoth.current.side }
local occ_hexes = LS.create()
-- Now on to evaluating possible moves:
-- First, get the rating of all units in their current positions
-- A move is only considered if it improves the overall rating,
-- that is, its rating must be higher than:
-- 1. the rating of the unit on the target hex (if there is one)
-- 2. the rating of the currently considered unit on its current hex
local all_units = wesnoth.get_units { side = wesnoth.current.side }
local occ_hexes = LS.create()
for i,u in ipairs(all_units) do
-- Is this a healer or leadership unit?
local is_healer = (u.__cfg.usage == "healer")
local has_leadership = AH.has_ability(u, "leadership")
for i,u in ipairs(all_units) do
-- Is this a healer or leadership unit?
local is_healer = (u.__cfg.usage == "healer")
local has_leadership = AH.has_ability(u, "leadership")
local rating = bottleneck_get_rating(u, u.x, u.y, has_leadership, is_healer, self)
occ_hexes:insert(u.x, u.y, rating)
local rating = bottleneck_get_rating(u, u.x, u.y, has_leadership, is_healer, self)
occ_hexes:insert(u.x, u.y, rating)
-- A unit that cannot move any more, (or at least cannot move out of the way)
-- must be considered to have a very high rating (it's in the best position
-- it can possibly achieve)
local best_move_away = bottleneck_move_out_of_way(u, self)
if (not best_move_away) then occ_hexes:insert(u.x, u.y, 20000) end
end
--AH.put_labels(occ_hexes)
-- A unit that cannot move any more, (or at least cannot move out of the way)
-- must be considered to have a very high rating (it's in the best position
-- it can possibly achieve)
local best_move_away = bottleneck_move_out_of_way(u, self)
if (not best_move_away) then occ_hexes:insert(u.x, u.y, 20000) end
end
--AH.put_labels(occ_hexes)
-- Find all attack positions next to enemies
-- This is done up here rather than in the loop, because it's too slow otherwise
local enemies = AH.get_live_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
local attacks = {}
for i,e in ipairs(enemies) do
for x,y in H.adjacent_tiles(e.x, e.y) do
if self.data.is_my_territory:get(x,y) then
local unit_in_way = wesnoth.get_unit(x, y)
local data = { x = x, y = y,
defender = e,
defender_level = wesnoth.unit_types[e.type].level,
unit_in_way = unit_in_way
}
table.insert(attacks, data)
end
end
end
-- Find all attack positions next to enemies
-- This is done up here rather than in the loop, because it's too slow otherwise
local enemies = AH.get_live_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
local attacks = {}
for i,e in ipairs(enemies) do
for x,y in H.adjacent_tiles(e.x, e.y) do
if self.data.is_my_territory:get(x,y) then
local unit_in_way = wesnoth.get_unit(x, y)
local data = { x = x, y = y,
defender = e,
defender_level = wesnoth.unit_types[e.type].level,
unit_in_way = unit_in_way
}
table.insert(attacks, data)
end
end
end
-- Go through all units with moves left
-- Variables to store best unit/move
local max_rating, best_unit, best_hex = 0, {}, {}
for i,u in ipairs(units) do
-- Go through all units with moves left
-- Variables to store best unit/move
local max_rating, best_unit, best_hex = 0, {}, {}
for i,u in ipairs(units) do
-- Is this a healer or leadership unit?
local is_healer = (u.__cfg.usage == "healer")
local has_leadership = AH.has_ability(u, "leadership")
-- Is this a healer or leadership unit?
local is_healer = (u.__cfg.usage == "healer")
local has_leadership = AH.has_ability(u, "leadership")
local current_rating = bottleneck_get_rating(u, u.x, u.y, has_leadership, is_healer, self)
--print("Finding move for:",u.id, u.x, u.y, current_rating)
local current_rating = bottleneck_get_rating(u, u.x, u.y, has_leadership, is_healer, self)
--print("Finding move for:",u.id, u.x, u.y, current_rating)
-- Find the best move for all hexes the unit can reach ...
local reach = wesnoth.find_reach(u)
for i,r in ipairs(reach) do
local rating = bottleneck_get_rating(u, r[1], r[2], has_leadership, is_healer, self)
--print(" ->",r[1],r[2],rating,occ_hexes:get(r[1], r[2]))
--reach_map:insert(r[1], r[2], rating)
-- Find the best move for all hexes the unit can reach ...
local reach = wesnoth.find_reach(u)
for i,r in ipairs(reach) do
local rating = bottleneck_get_rating(u, r[1], r[2], has_leadership, is_healer, self)
--print(" ->",r[1],r[2],rating,occ_hexes:get(r[1], r[2]))
--reach_map:insert(r[1], r[2], rating)
-- A move is only considered if it improves the overall rating,
-- that is, its rating must be higher than:
-- 1. the rating of the unit on the target hex (if there is one)
-- 2. the rating of the currently considered unit on its current hex
if occ_hexes:get(r[1], r[2]) and (occ_hexes:get(r[1], r[2]) >= rating) then rating = 0 end
if (rating <= current_rating) then rating = 0 end
--print(" ->",r[1],r[2],rating,occ_hexes:get(r[1], r[2]))
-- A move is only considered if it improves the overall rating,
-- that is, its rating must be higher than:
-- 1. the rating of the unit on the target hex (if there is one)
-- 2. the rating of the currently considered unit on its current hex
if occ_hexes:get(r[1], r[2]) and (occ_hexes:get(r[1], r[2]) >= rating) then rating = 0 end
if (rating <= current_rating) then rating = 0 end
--print(" ->",r[1],r[2],rating,occ_hexes:get(r[1], r[2]))
-- If the target hex is occupied, give it a (very) small penalty
if occ_hexes:get(r[1], r[2]) then rating = rating - 0.001 end
-- If the target hex is occupied, give it a (very) small penalty
if occ_hexes:get(r[1], r[2]) then rating = rating - 0.001 end
-- Now only valid and possible moves should have a rating > 0
if rating > max_rating then
max_rating, best_unit, best_hex = rating, u, { r[1], r[2] }
end
-- Now only valid and possible moves should have a rating > 0
if rating > max_rating then
max_rating, best_unit, best_hex = rating, u, { r[1], r[2] }
end
-- Finally, we check whether a level-up attack is possible from this hex
-- Level-up-attacks will always get a rating greater than any move
-- So if this is the case, they are done first
for j,a in ipairs(attacks) do
-- Only do calc. if there's a theoretical chance for leveling up (speeds things up a lot)
if (a.x == r[1]) and (a.y == r[2]) and (u.max_experience - u.experience <= 8 * a.defender_level) then
--print('Evaluating attack', u.id, a.x, a.y, a.defender.x, a.defender.y)
-- Finally, we check whether a level-up attack is possible from this hex
-- Level-up-attacks will always get a rating greater than any move
-- So if this is the case, they are done first
for j,a in ipairs(attacks) do
-- Only do calc. if there's a theoretical chance for leveling up (speeds things up a lot)
if (a.x == r[1]) and (a.y == r[2]) and (u.max_experience - u.experience <= 8 * a.defender_level) then
--print('Evaluating attack', u.id, a.x, a.y, a.defender.x, a.defender.y)
-- For this one, we really want to go through all weapons individually
-- as the best chosen automatically might not be the best for this specific task
local n_weapon = 0
for weapon in H.child_range(u.__cfg, "attack") do
n_weapon = n_weapon + 1
local att_stats, def_stats = BC.simulate_combat_loc(u, { a.x, a.y }, a.defender, n_weapon)
-- For this one, we really want to go through all weapons individually
-- as the best chosen automatically might not be the best for this specific task
local n_weapon = 0
for weapon in H.child_range(u.__cfg, "attack") do
n_weapon = n_weapon + 1
local att_stats, def_stats = BC.simulate_combat_loc(u, { a.x, a.y }, a.defender, n_weapon)
-- Level-up attack when:
-- 1. max_experience-experience <= target.level and chance to die = 0
-- 2. max_experience-experience <= target.level*8 and chance to die = 0
-- and chance to kill > 66% and remaining av hitpoints > 20
-- #1 is a definite level up, #2 is not, so #1 gets priority
local lu_rating = 0
if (u.max_experience - u.experience <= a.defender_level) then
if (att_stats.hp_chance[0] == 0) then
-- weakest enemy is best (favors stronger weapon)
lu_rating = 15000 - def_stats.average_hp
end
else
if (u.max_experience - u.experience <= 8 * a.defender_level) and (att_stats.hp_chance[0] == 0) and (def_stats.hp_chance[0] >= 0.66) and (att_stats.average_hp >= 20) then
-- strongest attacker and weakest enemy is best
lu_rating = 14000 + att_stats.average_hp - def_stats.average_hp/2.
end
end
-- Level-up attack when:
-- 1. max_experience-experience <= target.level and chance to die = 0
-- 2. max_experience-experience <= target.level*8 and chance to die = 0
-- and chance to kill > 66% and remaining av hitpoints > 20
-- #1 is a definite level up, #2 is not, so #1 gets priority
local lu_rating = 0
if (u.max_experience - u.experience <= a.defender_level) then
if (att_stats.hp_chance[0] == 0) then
-- weakest enemy is best (favors stronger weapon)
lu_rating = 15000 - def_stats.average_hp
end
else
if (u.max_experience - u.experience <= 8 * a.defender_level) and (att_stats.hp_chance[0] == 0) and (def_stats.hp_chance[0] >= 0.66) and (att_stats.average_hp >= 20) then
-- strongest attacker and weakest enemy is best
lu_rating = 14000 + att_stats.average_hp - def_stats.average_hp/2.
end
end
-- Very small penalty if there's a unit in the way
-- We also need to check whether this unit can actually move out of the way
if a.unit_in_way then
local moow = bottleneck_move_out_of_way(a.unit_in_way, self)
if moow then
lu_rating = lu_rating - 0.001
else
lu_rating = 0
end
end
--print("Level-up rating:",lu_rating)
-- Very small penalty if there's a unit in the way
-- We also need to check whether this unit can actually move out of the way
if a.unit_in_way then
local moow = bottleneck_move_out_of_way(a.unit_in_way, self)
if moow then
lu_rating = lu_rating - 0.001
else
lu_rating = 0
end
end
--print("Level-up rating:",lu_rating)
if (lu_rating > max_rating) then
max_rating, best_unit, best_hex = lu_rating, u, { r[1], r[2] }
-- The following are also needed in this case
-- We don't have to worry about unsetting them, as LU attacks
-- always have higher priority than any other move
self.data.lu_defender = a.defender
self.data.lu_weapon = n_weapon
end
end
end
end
end
--AH.put_labels(reach_map)
end
--print("Best move:",best_unit.id,max_rating,best_hex[1],best_hex[2])
if (lu_rating > max_rating) then
max_rating, best_unit, best_hex = lu_rating, u, { r[1], r[2] }
-- The following are also needed in this case
-- We don't have to worry about unsetting them, as LU attacks
-- always have higher priority than any other move
self.data.lu_defender = a.defender
self.data.lu_weapon = n_weapon
end
end
end
end
end
--AH.put_labels(reach_map)
end
--print("Best move:",best_unit.id,max_rating,best_hex[1],best_hex[2])
-- Finally, if there's another unit in the best location,
-- moving it out of the way becomes the best move
-- It has been checked that this is possible
local unit_in_way = wesnoth.get_units { x = best_hex[1], y = best_hex[2],
{ "not", { id = best_unit.id } }
}[1]
if unit_in_way then
best_hex = bottleneck_move_out_of_way(unit_in_way, self)
best_unit = unit_in_way
--print("Moving out of way:", best_unit.id, best_hex[1], best_hex[2])
-- Finally, if there's another unit in the best location,
-- moving it out of the way becomes the best move
-- It has been checked that this is possible
local unit_in_way = wesnoth.get_units { x = best_hex[1], y = best_hex[2],
{ "not", { id = best_unit.id } }
}[1]
if unit_in_way then
best_hex = bottleneck_move_out_of_way(unit_in_way, self)
best_unit = unit_in_way
--print("Moving out of way:", best_unit.id, best_hex[1], best_hex[2])
-- Also need to delete the level-up attack fields
-- They will be reset on the next turn
self.data.lu_defender = nil
self.data.lu_weapon = nil
end
-- Also need to delete the level-up attack fields
-- They will be reset on the next turn
self.data.lu_defender = nil
self.data.lu_weapon = nil
end
-- Set the variables for the exec() function
if max_rating == 0 then
-- In this case we take MP away from all units
-- This is done so that the RCA AI CAs can be kept in place
self.data.bottleneck_moves_done = true
else
self.data.bottleneck_moves_done = false
self.data.unit = best_unit
self.data.hex = best_hex
end
return cfg.ca_score
-- Set the variables for the exec() function
if max_rating == 0 then
-- In this case we take MP away from all units
-- This is done so that the RCA AI CAs can be kept in place
self.data.bottleneck_moves_done = true
else
self.data.bottleneck_moves_done = false
self.data.unit = best_unit
self.data.hex = best_hex
end
return cfg.ca_score
end
function ca_bottleneck_move:execution(ai, cfg, self)
if self.data.bottleneck_moves_done then
local units = {}
if self.data.side_leader_activated then
units = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0'
}
else
units = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'no',
formula = '$this_unit.moves > 0'
}
end
for i,u in ipairs(units) do
ai.stopunit_moves(u)
end
else
--print("Moving unit:",self.data.unit.id, self.data.unit.x, self.data.unit.y, " ->", best_hex[1], best_hex[2], " -- turn:", wesnoth.current.turn)
if self.data.bottleneck_moves_done then
local units = {}
if self.data.side_leader_activated then
units = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0'
}
else
units = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'no',
formula = '$this_unit.moves > 0'
}
end
for i,u in ipairs(units) do
ai.stopunit_moves(u)
end
else
--print("Moving unit:",self.data.unit.id, self.data.unit.x, self.data.unit.y, " ->", best_hex[1], best_hex[2], " -- turn:", wesnoth.current.turn)
if (self.data.unit.x ~= self.data.hex[1]) or (self.data.unit.y ~= self.data.hex[2]) then -- test needed for level-up move
ai.move(self.data.unit, self.data.hex[1], self.data.hex[2]) -- don't want full move, as this might be stepping out of the way
end
if (self.data.unit.x ~= self.data.hex[1]) or (self.data.unit.y ~= self.data.hex[2]) then -- test needed for level-up move
ai.move(self.data.unit, self.data.hex[1], self.data.hex[2]) -- don't want full move, as this might be stepping out of the way
end
-- If this is a move for a level-up attack, do the attack also
if self.data.lu_defender then
--print("Level-up attack",self.data.unit.id, self.data.lu_defender.id, self.data.lu_weapon)
-- If this is a move for a level-up attack, do the attack also
if self.data.lu_defender then
--print("Level-up attack",self.data.unit.id, self.data.lu_defender.id, self.data.lu_weapon)
ai.attack(self.data.unit, self.data.lu_defender, self.data.lu_weapon)
end
end
ai.attack(self.data.unit, self.data.lu_defender, self.data.lu_weapon)
end
end
-- Now delete almost everything
-- Keep: self.data.is_my_territory, self.data.side_leader_activated
self.data.unit, self.data.hex = nil, nil
self.data.lu_defender, self.data.lu_weapon = nil, nil
self.data.bottleneck_moves_done = nil
self.data.def_map, self.data.healer_map, self.data.leadership_map, self.data.healing_map = nil, nil, nil, nil
-- Now delete almost everything
-- Keep: self.data.is_my_territory, self.data.side_leader_activated
self.data.unit, self.data.hex = nil, nil
self.data.lu_defender, self.data.lu_weapon = nil, nil
self.data.bottleneck_moves_done = nil
self.data.def_map, self.data.healer_map, self.data.leadership_map, self.data.healing_map = nil, nil, nil, nil
end
return ca_bottleneck_move

View file

@ -4,120 +4,120 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_coward = {}
function ca_coward:evaluation(ai, cfg)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
-- Check if unit exists as sticky BCAs are not always removed successfully
if unit then return cfg.ca_score end
return 0
-- Check if unit exists as sticky BCAs are not always removed successfully
if unit then return cfg.ca_score end
return 0
end
-- cfg parameters: id, distance, seek_x, seek_y, avoid_x, avoid_y
function ca_coward:execution(ai, cfg)
--print("Coward exec " .. cfg.id)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
--print("Coward exec " .. cfg.id)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local reach = wesnoth.find_reach(unit)
-- enemy units within reach
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
}
local reach = wesnoth.find_reach(unit)
-- enemy units within reach
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
}
-- if no enemies are within reach: keep unit from doing anything and exit
if not enemies[1] then
ai.stopunit_all(unit)
return
end
-- if no enemies are within reach: keep unit from doing anything and exit
if not enemies[1] then
ai.stopunit_all(unit)
return
end
-- Go through all hexes the unit can reach
for i,r in ipairs(reach) do
-- Go through all hexes the unit can reach
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
-- Find combined distance weighting of all enemy units within distance
local value = 0
for j,e in ipairs(enemies) do
local d = H.distance_between(r[1], r[2], e.x, e.y)
value = value + 1/ d^2
end
--wesnoth.fire("label", {x=r[1], y=r[2], text = math.floor(value*1000) } )
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
-- Find combined distance weighting of all enemy units within distance
local value = 0
for j,e in ipairs(enemies) do
local d = H.distance_between(r[1], r[2], e.x, e.y)
value = value + 1/ d^2
end
--wesnoth.fire("label", {x=r[1], y=r[2], text = math.floor(value*1000) } )
-- Store this weighting in the third field of each 'reach' element
reach[i][3] = value
else
reach[i][3] = 9999
end
end
-- Store this weighting in the third field of each 'reach' element
reach[i][3] = value
else
reach[i][3] = 9999
end
end
-- Sort 'reach' by values, smallest first
table.sort(reach, function(a, b) return a[3] < b[3] end )
-- Select those within factor 2 of the minimum
local best_pos = AH.filter(reach, function(tmp) return tmp[3] < reach[1][3]*2 end)
-- Sort 'reach' by values, smallest first
table.sort(reach, function(a, b) return a[3] < b[3] end )
-- Select those within factor 2 of the minimum
local best_pos = AH.filter(reach, function(tmp) return tmp[3] < reach[1][3]*2 end)
-- Now take 'seek' and 'avoid' into account
for i,b in ipairs(best_pos) do
-- Now take 'seek' and 'avoid' into account
for i,b in ipairs(best_pos) do
-- weighting based on distance from 'seek' and 'avoid'
local ds = AH.generalized_distance(b[1], b[2], cfg.seek_x, cfg.seek_y)
local da = AH.generalized_distance(b[1], b[2], cfg.avoid_x, cfg.avoid_y)
--items.place_image(b[1], b[2], "items/ring-red.png")
local value = 1 / (ds+1) - 1 / (da+1)^2 * 0.75
-- weighting based on distance from 'seek' and 'avoid'
local ds = AH.generalized_distance(b[1], b[2], cfg.seek_x, cfg.seek_y)
local da = AH.generalized_distance(b[1], b[2], cfg.avoid_x, cfg.avoid_y)
--items.place_image(b[1], b[2], "items/ring-red.png")
local value = 1 / (ds+1) - 1 / (da+1)^2 * 0.75
--wesnoth.fire("label", {x=b[1], y=b[2], text = math.floor(value*1000) } )
best_pos[i][3] = value
end
--wesnoth.fire("label", {x=b[1], y=b[2], text = math.floor(value*1000) } )
best_pos[i][3] = value
end
-- Sort 'best_pos" by value, largest first
table.sort(best_pos, function(a, b) return a[3] > b[3] end)
-- and select all those that have the maximum score
local best_overall = AH.filter(best_pos, function(tmp) return tmp[3] == best_pos[1][3] end)
-- Sort 'best_pos" by value, largest first
table.sort(best_pos, function(a, b) return a[3] > b[3] end)
-- and select all those that have the maximum score
local best_overall = AH.filter(best_pos, function(tmp) return tmp[3] == best_pos[1][3] end)
-- As final step, if there are more than one remaining locations,
-- we take the one with the minimum score in the distance-from_enemy criterion
local min, mx, my = 9999, 0, 0
for i,b in ipairs(best_overall) do
-- As final step, if there are more than one remaining locations,
-- we take the one with the minimum score in the distance-from_enemy criterion
local min, mx, my = 9999, 0, 0
for i,b in ipairs(best_overall) do
--items.place_image(b[1], b[2], "items/ring-white.png")
local value = 0
for j,e in ipairs(enemies) do
local d = H.distance_between(b[1], b[2], e.x, e.y)
value = value + 1/d^2
end
--items.place_image(b[1], b[2], "items/ring-white.png")
local value = 0
for j,e in ipairs(enemies) do
local d = H.distance_between(b[1], b[2], e.x, e.y)
value = value + 1/d^2
end
if value < min then
min = value
mx,my = b[1], b[2]
end
end
--items.place_image(mx, my, "items/ring-gold.png")
if value < min then
min = value
mx,my = b[1], b[2]
end
end
--items.place_image(mx, my, "items/ring-gold.png")
-- (mx,my) is the position to move to
if (mx ~= unit.x or my ~= unit.y) then
AH.movefull_stopunit(ai, unit, mx, my)
end
-- (mx,my) is the position to move to
if (mx ~= unit.x or my ~= unit.y) then
AH.movefull_stopunit(ai, unit, mx, my)
end
-- Get unit again, just in case it was killed by a moveto event
local unit = wesnoth.get_units{ id = cfg.id }[1]
if unit then ai.stopunit_all(unit) end
-- Get unit again, just in case it was killed by a moveto event
local unit = wesnoth.get_units{ id = cfg.id }[1]
if unit then ai.stopunit_all(unit) end
end
return ca_coward

View file

@ -6,164 +6,164 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_forest_animals_move = {}
function ca_forest_animals_move:evaluation(ai, cfg)
local deer_type = cfg.deer_type or "no_unit_of_this_type"
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
local tusker_type = cfg.tusker_type or "no_unit_of_this_type"
local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type"
local deer_type = cfg.deer_type or "no_unit_of_this_type"
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
local tusker_type = cfg.tusker_type or "no_unit_of_this_type"
local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type"
local units = wesnoth.get_units { side = wesnoth.current.side,
type = deer_type .. ',' .. rabbit_type .. ',' .. tusker_type, formula = '$this_unit.moves > 0' }
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type, formula = '$this_unit.moves > 0' }
local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type }
local units = wesnoth.get_units { side = wesnoth.current.side,
type = deer_type .. ',' .. rabbit_type .. ',' .. tusker_type, formula = '$this_unit.moves > 0' }
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type, formula = '$this_unit.moves > 0' }
local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type }
-- If there are deer, rabbits or tuskers with moves left -> good
if units[1] then return cfg.ca_score end
-- Or, we move tusklets with this CA, if no tuskers are left (counting those without moves also)
if (not all_tuskers[1]) and tusklets[1] then return cfg.ca_score end
return 0
-- If there are deer, rabbits or tuskers with moves left -> good
if units[1] then return cfg.ca_score end
-- Or, we move tusklets with this CA, if no tuskers are left (counting those without moves also)
if (not all_tuskers[1]) and tusklets[1] then return cfg.ca_score end
return 0
end
function ca_forest_animals_move:execution(ai, cfg)
local deer_type = cfg.deer_type or "no_unit_of_this_type"
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
local tusker_type = cfg.tusker_type or "no_unit_of_this_type"
local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type"
local wander_terrain = cfg.filter_location or {}
local deer_type = cfg.deer_type or "no_unit_of_this_type"
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
local tusker_type = cfg.tusker_type or "no_unit_of_this_type"
local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type"
local wander_terrain = cfg.filter_location or {}
-- We want the deer/rabbits to move first, tuskers later
local units = wesnoth.get_units { side = wesnoth.current.side, type = deer_type .. ',' .. rabbit_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type, formula = '$this_unit.moves > 0' }
for i,t in ipairs(tuskers) do table.insert(units, t) end
-- We want the deer/rabbits to move first, tuskers later
local units = wesnoth.get_units { side = wesnoth.current.side, type = deer_type .. ',' .. rabbit_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type, formula = '$this_unit.moves > 0' }
for i,t in ipairs(tuskers) do table.insert(units, t) end
-- Also add tusklets if there are no tuskers left
local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type }
if not all_tuskers[1] then
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type, formula = '$this_unit.moves > 0' }
for i,t in ipairs(tusklets) do table.insert(units, t) end
end
-- Also add tusklets if there are no tuskers left
local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type }
if not all_tuskers[1] then
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type, formula = '$this_unit.moves > 0' }
for i,t in ipairs(tusklets) do table.insert(units, t) end
end
-- These animals run from any enemy
local enemies = wesnoth.get_units { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
--print('#units, enemies', #units, #enemies)
-- These animals run from any enemy
local enemies = wesnoth.get_units { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
--print('#units, enemies', #units, #enemies)
-- Get the locations of all the rabbit holes
W.store_items { variable = 'holes_wml' }
local holes = H.get_variable_array('holes_wml')
W.clear_variable { name = 'holes_wml' }
-- Get the locations of all the rabbit holes
W.store_items { variable = 'holes_wml' }
local holes = H.get_variable_array('holes_wml')
W.clear_variable { name = 'holes_wml' }
-- If cfg.rabbit_hole_img is set, only items with that image or halo count as holes
if cfg.rabbit_hole_img then
if (holes[i].image ~= cfg.rabbit_hole_img) and (holes[i].halo ~= cfg.rabbit_hole_img) then
table.remove(holes, i)
end
end
-- If cfg.rabbit_hole_img is set, only items with that image or halo count as holes
if cfg.rabbit_hole_img then
if (holes[i].image ~= cfg.rabbit_hole_img) and (holes[i].halo ~= cfg.rabbit_hole_img) then
table.remove(holes, i)
end
end
local hole_map = LS.create()
for i,h in ipairs(holes) do hole_map:insert(h.x, h.y, 1) end
--AH.put_labels(hole_map)
local hole_map = LS.create()
for i,h in ipairs(holes) do hole_map:insert(h.x, h.y, 1) end
--AH.put_labels(hole_map)
-- Each unit moves independently
for i,unit in ipairs(units) do
--print('Unit', i, unit.x, unit.y)
-- Behavior is different depending on whether a predator is close or not
local close_enemies = {}
for j,e in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= unit.max_moves+1) then
table.insert(close_enemies, e)
end
end
--print(' #close_enemies', #close_enemies)
-- Each unit moves independently
for i,unit in ipairs(units) do
--print('Unit', i, unit.x, unit.y)
-- Behavior is different depending on whether a predator is close or not
local close_enemies = {}
for j,e in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= unit.max_moves+1) then
table.insert(close_enemies, e)
end
end
--print(' #close_enemies', #close_enemies)
-- If no close enemies, do a random move
if (not close_enemies[1]) then
-- All hexes the unit can reach that are unoccupied
local reach = AH.get_reachable_unocc(unit)
local locs = wesnoth.get_locations(wander_terrain)
local locs_map = LS.of_pairs(locs)
--print(' #all reachable', reach:size())
-- If no close enemies, do a random move
if (not close_enemies[1]) then
-- All hexes the unit can reach that are unoccupied
local reach = AH.get_reachable_unocc(unit)
local locs = wesnoth.get_locations(wander_terrain)
local locs_map = LS.of_pairs(locs)
--print(' #all reachable', reach:size())
-- Select only those that satisfy wander_terrain
local reachable_terrain = {}
reach:iter( function(x, y, v)
local terrain = wesnoth.get_terrain(x,y)
--print(x, y, terrain)
if locs_map:get(x,y) then -- doesn't work with '^', so start search at char 2
table.insert(reachable_terrain, {x, y})
end
end)
--print(' #reachable_terrain', #reachable_terrain)
-- Select only those that satisfy wander_terrain
local reachable_terrain = {}
reach:iter( function(x, y, v)
local terrain = wesnoth.get_terrain(x,y)
--print(x, y, terrain)
if locs_map:get(x,y) then -- doesn't work with '^', so start search at char 2
table.insert(reachable_terrain, {x, y})
end
end)
--print(' #reachable_terrain', #reachable_terrain)
-- Choose one of the possible locations at random
if reachable_terrain[1] then
local rand = AH.random(#reachable_terrain)
-- This is not a full move, as running away might happen next
if (unit.x ~= reachable_terrain[rand][1]) or (unit.y ~= reachable_terrain[rand][2]) then
ai.move(unit, reachable_terrain[rand][1], reachable_terrain[rand][2])
end
else -- or if no close reachable terrain was found, move toward the closest
local locs = wesnoth.get_locations(wander_terrain)
local best_hex, min_dist = {}, 9e99
for j,l in ipairs(locs) do
local d = H.distance_between(l[1], l[2], unit.x, unit.y)
if d < min_dist then
best_hex, min_dist = l,d
end
end
if (best_hex[1]) then
local x,y = wesnoth.find_vacant_tile(best_hex[1], best_hex[2], unit)
local next_hop = AH.next_hop(unit, x, y)
--print(next_hop[1], next_hop[2])
if (unit.x ~= next_hop[1]) or (unit.y ~= next_hop[2]) then
ai.move(unit, next_hop[1], next_hop[2])
end
end
end
end
-- Choose one of the possible locations at random
if reachable_terrain[1] then
local rand = AH.random(#reachable_terrain)
-- This is not a full move, as running away might happen next
if (unit.x ~= reachable_terrain[rand][1]) or (unit.y ~= reachable_terrain[rand][2]) then
ai.move(unit, reachable_terrain[rand][1], reachable_terrain[rand][2])
end
else -- or if no close reachable terrain was found, move toward the closest
local locs = wesnoth.get_locations(wander_terrain)
local best_hex, min_dist = {}, 9e99
for j,l in ipairs(locs) do
local d = H.distance_between(l[1], l[2], unit.x, unit.y)
if d < min_dist then
best_hex, min_dist = l,d
end
end
if (best_hex[1]) then
local x,y = wesnoth.find_vacant_tile(best_hex[1], best_hex[2], unit)
local next_hop = AH.next_hop(unit, x, y)
--print(next_hop[1], next_hop[2])
if (unit.x ~= next_hop[1]) or (unit.y ~= next_hop[2]) then
ai.move(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 = {}
for j,e in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= unit.max_moves+1) then
table.insert(close_enemies, e)
end
end
--print(' #close_enemies after move', #close_enemies, #enemies, unit.id)
-- Now we check for close enemies again, as we might just have moved within reach of some
local close_enemies = {}
for j,e in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= unit.max_moves+1) then
table.insert(close_enemies, e)
end
end
--print(' #close_enemies after move', #close_enemies, #enemies, unit.id)
-- If there are close enemies, run away (and rabbits disappear into holes)
if close_enemies[1] then
-- Calculate the hex that maximizes distance of unit from enemies
-- Returns nil if the only hex that can be reached is the one the unit is on
local farthest_hex = AH.find_best_move(unit, function(x, y)
local rating = 0
for i,e in ipairs(close_enemies) do
local d = H.distance_between(e.x, e.y, x, y)
rating = rating - 1 / d^2
end
-- If this is a rabbit, try to go for holes
if (unit.type == rabbit_type) and hole_map:get(x, y) then
rating = rating + 1000
-- but if possible, go to another hole
if (x == unit.x) and (y == unit.y) then rating = rating - 10 end
end
-- If there are close enemies, run away (and rabbits disappear into holes)
if close_enemies[1] then
-- Calculate the hex that maximizes distance of unit from enemies
-- Returns nil if the only hex that can be reached is the one the unit is on
local farthest_hex = AH.find_best_move(unit, function(x, y)
local rating = 0
for i,e in ipairs(close_enemies) do
local d = H.distance_between(e.x, e.y, x, y)
rating = rating - 1 / d^2
end
-- If this is a rabbit, try to go for holes
if (unit.type == rabbit_type) and hole_map:get(x, y) then
rating = rating + 1000
-- but if possible, go to another hole
if (x == unit.x) and (y == unit.y) then rating = rating - 10 end
end
return rating
end)
--print(' farthest_hex: ', farthest_hex[1], farthest_hex[2])
return rating
end)
--print(' farthest_hex: ', farthest_hex[1], farthest_hex[2])
-- This will always find at least the hex the unit is on
-- so no check is necessary
AH.movefull_stopunit(ai, unit, farthest_hex)
-- If this is a rabbit ending on a hole -> disappears
if (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2]) then
wesnoth.put_unit(farthest_hex[1], farthest_hex[2])
end
end
-- This will always find at least the hex the unit is on
-- so no check is necessary
AH.movefull_stopunit(ai, unit, farthest_hex)
-- If this is a rabbit ending on a hole -> disappears
if (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2]) then
wesnoth.put_unit(farthest_hex[1], farthest_hex[2])
end
end
-- Finally, take moves away, as only partial move might have been done
-- Also attacks, as these units never attack
if unit and unit.valid then ai.stopunit_all(unit) end
-- Need this ^ test here because bunnies might have disappeared
end
-- Finally, take moves away, as only partial move might have been done
-- Also attacks, as these units never attack
if unit and unit.valid then ai.stopunit_all(unit) end
-- Need this ^ test here because bunnies might have disappeared
end
end
return ca_forest_animals_move

View file

@ -5,67 +5,67 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_forest_animals_new_rabbit = {}
function ca_forest_animals_new_rabbit:evaluation(ai, cfg)
-- Put new rabbits out the if there are fewer than cfg.rabbit_number
-- but only if cfg.rabbit_type is set, otherwise do nothing
-- If this gets executed, we'll let the CA black-list itself
-- Put new rabbits out the if there are fewer than cfg.rabbit_number
-- but only if cfg.rabbit_type is set, otherwise do nothing
-- If this gets executed, we'll let the CA black-list itself
if (not cfg.rabbit_type) then return 0 end
return cfg.ca_score
if (not cfg.rabbit_type) then return 0 end
return cfg.ca_score
end
function ca_forest_animals_new_rabbit:execution(ai, cfg)
local number = cfg.rabbit_number or 6
local rabbit_enemy_distance = cfg.rabbit_enemy_distance or 3
local number = cfg.rabbit_number or 6
local rabbit_enemy_distance = cfg.rabbit_enemy_distance or 3
-- Get the locations of all items on that map (which could be rabbit holes)
W.store_items { variable = 'holes_wml' }
local holes = H.get_variable_array('holes_wml')
W.clear_variable { name = 'holes_wml' }
-- Get the locations of all items on that map (which could be rabbit holes)
W.store_items { variable = 'holes_wml' }
local holes = H.get_variable_array('holes_wml')
W.clear_variable { name = 'holes_wml' }
-- Eliminate all holes that have an enemy within 'rabbit_enemy_distance' hexes
-- We also add a random number to the ones we keep, for selection of the holes later
--print('before:', #holes)
for i = #holes,1,-1 do
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", { x = holes[i].x, y = holes[i].y, radius = rabbit_enemy_distance } }
}
if enemies[1] then
table.remove(holes, i)
else
-- If cfg.rabbit_hole_img is set, only items with that image or halo count as holes
if cfg.rabbit_hole_img then
if (holes[i].image ~= cfg.rabbit_hole_img) and (holes[i].halo ~= cfg.rabbit_hole_img) then
table.remove(holes, i)
else
holes[i].random = AH.random(100)
end
else
holes[i].random = AH.random(100)
end
end
end
--print('after:', #holes)
table.sort(holes, function(a, b) return a.random > b.random end)
-- Eliminate all holes that have an enemy within 'rabbit_enemy_distance' hexes
-- We also add a random number to the ones we keep, for selection of the holes later
--print('before:', #holes)
for i = #holes,1,-1 do
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", { x = holes[i].x, y = holes[i].y, radius = rabbit_enemy_distance } }
}
if enemies[1] then
table.remove(holes, i)
else
-- If cfg.rabbit_hole_img is set, only items with that image or halo count as holes
if cfg.rabbit_hole_img then
if (holes[i].image ~= cfg.rabbit_hole_img) and (holes[i].halo ~= cfg.rabbit_hole_img) then
table.remove(holes, i)
else
holes[i].random = AH.random(100)
end
else
holes[i].random = AH.random(100)
end
end
end
--print('after:', #holes)
table.sort(holes, function(a, b) return a.random > b.random end)
local rabbits = wesnoth.get_units { side = wesnoth.current.side, type = cfg.rabbit_type }
--print('total number:', number)
number = number - #rabbits
--print('to add number:', number)
number = math.min(number, #holes)
--print('to add number possible:', number)
local rabbits = wesnoth.get_units { side = wesnoth.current.side, type = cfg.rabbit_type }
--print('total number:', number)
number = number - #rabbits
--print('to add number:', number)
number = math.min(number, #holes)
--print('to add number possible:', number)
-- Now we just can take the first 'number' (randomized) holes
local tmp_unit = wesnoth.get_units { side = wesnoth.current.side }[1]
for i = 1,number do
local x, y = -1, -1
if tmp_unit then
x, y = wesnoth.find_vacant_tile(holes[i].x, holes[i].y, tmp_unit)
else
x, y = wesnoth.find_vacant_tile(holes[i].x, holes[i].y)
end
wesnoth.put_unit(x, y, { side = wesnoth.current.side, type = cfg.rabbit_type } )
end
-- Now we just can take the first 'number' (randomized) holes
local tmp_unit = wesnoth.get_units { side = wesnoth.current.side }[1]
for i = 1,number do
local x, y = -1, -1
if tmp_unit then
x, y = wesnoth.find_vacant_tile(holes[i].x, holes[i].y, tmp_unit)
else
x, y = wesnoth.find_vacant_tile(holes[i].x, holes[i].y)
end
wesnoth.put_unit(x, y, { side = wesnoth.current.side, type = cfg.rabbit_type } )
end
end
return ca_forest_animals_new_rabbit

View file

@ -4,65 +4,65 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_forest_animals_tusker_attack = {}
function ca_forest_animals_tusker_attack:evaluation(ai, cfg)
-- Check whether there is an enemy next to a tusklet and attack it ("protective parents" AI)
-- Check whether there is an enemy next to a tusklet and attack it ("protective parents" AI)
-- Both cfg.tusker_type and cfg.tusklet_type need to be set for this to kick in
if (not cfg.tusker_type) or (not cfg.tusklet_type) then return 0 end
-- Both cfg.tusker_type and cfg.tusklet_type need to be set for this to kick in
if (not cfg.tusker_type) or (not cfg.tusklet_type) then return 0 end
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type, formula = '$this_unit.moves > 0' }
local adj_enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
}
--print('#tuskers, #adj_enemies', #tuskers, #adj_enemies)
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type, formula = '$this_unit.moves > 0' }
local adj_enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
}
--print('#tuskers, #adj_enemies', #tuskers, #adj_enemies)
if tuskers[1] and adj_enemies[1] then return cfg.ca_score end
return 0
if tuskers[1] and adj_enemies[1] then return cfg.ca_score end
return 0
end
function ca_forest_animals_tusker_attack:execution(ai, cfg)
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type, formula = '$this_unit.moves > 0' }
local adj_enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
}
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type, formula = '$this_unit.moves > 0' }
local adj_enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
}
-- Find the closest enemy to any tusker
local min_dist, attacker, target = 9e99, {}, {}
for i,t in ipairs(tuskers) do
for j,e in ipairs(adj_enemies) do
local dist = H.distance_between(t.x, t.y, e.x, e.y)
if (dist < min_dist) then
min_dist, attacker, target = dist, t, e
end
end
end
--print(attacker.id, target.id)
-- Find the closest enemy to any tusker
local min_dist, attacker, target = 9e99, {}, {}
for i,t in ipairs(tuskers) do
for j,e in ipairs(adj_enemies) do
local dist = H.distance_between(t.x, t.y, e.x, e.y)
if (dist < min_dist) then
min_dist, attacker, target = dist, t, e
end
end
end
--print(attacker.id, target.id)
-- The tusker moves as close to enemy as possible
-- Closeness to tusklets is secondary criterion
local adj_tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type,
{ "filter_adjacent", { id = target.id } }
}
-- The tusker moves as close to enemy as possible
-- Closeness to tusklets is secondary criterion
local adj_tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type,
{ "filter_adjacent", { id = target.id } }
}
local best_hex = AH.find_best_move(attacker, function(x, y)
local rating = - H.distance_between(x, y, target.x, target.y)
for i,t in ipairs(adj_tusklets) do
if (H.distance_between(x, y, t.x, t.y) == 1) then rating = rating + 0.1 end
end
local best_hex = AH.find_best_move(attacker, function(x, y)
local rating = - H.distance_between(x, y, target.x, target.y)
for i,t in ipairs(adj_tusklets) do
if (H.distance_between(x, y, t.x, t.y) == 1) then rating = rating + 0.1 end
end
return rating
end)
--print('attacker', attacker.x, attacker.y, ' -> ', best_hex[1], best_hex[2])
AH.movefull_stopunit(ai, attacker, best_hex)
return rating
end)
--print('attacker', attacker.x, attacker.y, ' -> ', best_hex[1], best_hex[2])
AH.movefull_stopunit(ai, attacker, best_hex)
-- If adjacent, attack
local dist = H.distance_between(attacker.x, attacker.y, target.x, target.y)
if (dist == 1) then
ai.attack(attacker, target)
else
ai.stopunit_attacks(attacker)
end
-- If adjacent, attack
local dist = H.distance_between(attacker.x, attacker.y, target.x, target.y)
if (dist == 1) then
ai.attack(attacker, target)
else
ai.stopunit_attacks(attacker)
end
end
return ca_forest_animals_tusker_attack

View file

@ -4,45 +4,45 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_forest_animals_tusklet_move = {}
function ca_forest_animals_tusklet_move:evaluation(ai, cfg)
-- Tusklets will simply move toward the closest tusker, without regard for anything else
-- Except if no tuskers are left, in which case the previous CA takes over and does a random move
-- Tusklets will simply move toward the closest tusker, without regard for anything else
-- Except if no tuskers are left, in which case the previous CA takes over and does a random move
-- Both cfg.tusker_type and cfg.tusklet_type need to be set for this to kick in
if (not cfg.tusker_type) or (not cfg.tusklet_type) then return 0 end
-- Both cfg.tusker_type and cfg.tusklet_type need to be set for this to kick in
if (not cfg.tusker_type) or (not cfg.tusklet_type) then return 0 end
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type }
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type }
if tusklets[1] and tuskers[1] then return cfg.ca_score end
return 0
if tusklets[1] and tuskers[1] then return cfg.ca_score end
return 0
end
function ca_forest_animals_tusklet_move:execution(ai, cfg)
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type }
--print('#tusklets, #tuskers', #tusklets, #tuskers)
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type }
--print('#tusklets, #tuskers', #tusklets, #tuskers)
for i,tusklet in ipairs(tusklets) do
-- find closest tusker
local goto_tusker, min_dist = {}, 9999
for i,t in ipairs(tuskers) do
local dist = H.distance_between(t.x, t.y, tusklet.x, tusklet.y)
if (dist < min_dist) then
min_dist, goto_tusker = dist, t
end
end
--print('closets tusker:', goto_tusker.x, goto_tusker.y, goto_tusker.id)
for i,tusklet in ipairs(tusklets) do
-- find closest tusker
local goto_tusker, min_dist = {}, 9999
for i,t in ipairs(tuskers) do
local dist = H.distance_between(t.x, t.y, tusklet.x, tusklet.y)
if (dist < min_dist) then
min_dist, goto_tusker = dist, t
end
end
--print('closets tusker:', goto_tusker.x, goto_tusker.y, goto_tusker.id)
-- Move tusklet toward that tusker
local best_hex = AH.find_best_move(tusklet, function(x, y)
return -H.distance_between(x, y, goto_tusker.x, goto_tusker.y)
end)
--print('tusklet', tusklet.x, tusklet.y, ' -> ', best_hex[1], best_hex[2])
AH.movefull_stopunit(ai, tusklet, best_hex)
-- Move tusklet toward that tusker
local best_hex = AH.find_best_move(tusklet, function(x, y)
return -H.distance_between(x, y, goto_tusker.x, goto_tusker.y)
end)
--print('tusklet', tusklet.x, tusklet.y, ' -> ', best_hex[1], best_hex[2])
AH.movefull_stopunit(ai, tusklet, best_hex)
-- Also make sure tusklets never attack
ai.stopunit_all(tusklet)
end
-- Also make sure tusklets never attack
ai.stopunit_all(tusklet)
end
end
return ca_forest_animals_tusklet_move

View file

@ -6,238 +6,238 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_goto = {}
local function custom_cost(x, y, u, enemy_map, enemy_attack_map, multiplier)
local terrain = wesnoth.get_terrain(x, y)
local move_cost = wesnoth.unit_movement_cost(u, terrain)
local terrain = wesnoth.get_terrain(x, y)
local move_cost = wesnoth.unit_movement_cost(u, terrain)
move_cost = move_cost + (enemy_map:get(x,y) or 0)
move_cost = move_cost + (enemy_attack_map.units:get(x,y) or 0) * multiplier
move_cost = move_cost + (enemy_map:get(x,y) or 0)
move_cost = move_cost + (enemy_attack_map.units:get(x,y) or 0) * multiplier
return move_cost
return move_cost
end
function ca_goto:evaluation(ai, cfg, self)
-- If cfg.release_all_units_at_goal is set, check
-- whether the goal has already been reached, in
-- which case we do not do anything
if cfg.release_all_units_at_goal then
for rel in H.child_range(self.data, "goto_release_all") do
if (rel.id == cfg.ca_id) then
return 0
end
end
end
-- If cfg.release_all_units_at_goal is set, check
-- whether the goal has already been reached, in
-- which case we do not do anything
if cfg.release_all_units_at_goal then
for rel in H.child_range(self.data, "goto_release_all") do
if (rel.id == cfg.ca_id) then
return 0
end
end
end
-- For convenience, we check for locations here, and just pass that to the exec function
-- This is mostly to make the unique_goals option easier
local width, height = wesnoth.get_map_size()
local locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", cfg.filter_location }
}
--print('#locs org', #locs)
if (#locs == 0) then return 0 end
-- For convenience, we check for locations here, and just pass that to the exec function
-- This is mostly to make the unique_goals option easier
local width, height = wesnoth.get_map_size()
local locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", cfg.filter_location }
}
--print('#locs org', #locs)
if (#locs == 0) then return 0 end
-- If 'unique_goals' is set, check whether there are locations left to go to
if cfg.unique_goals then
-- First, some cleanup of previous turn data
local str = 'goals_taken_' .. (wesnoth.current.turn - 1)
self.data[str] = nil
-- If 'unique_goals' is set, check whether there are locations left to go to
if cfg.unique_goals then
-- First, some cleanup of previous turn data
local str = 'goals_taken_' .. (wesnoth.current.turn - 1)
self.data[str] = nil
-- Now on to the current turn
local str = 'goals_taken_' .. wesnoth.current.turn
for i = #locs,1,-1 do
if self.data[str] and self.data[str]:get(locs[i][1], locs[i][2]) then
table.remove(locs, i)
end
end
end
--print('#locs mod', #locs)
if (not locs[1]) then return 0 end
-- Now on to the current turn
local str = 'goals_taken_' .. wesnoth.current.turn
for i = #locs,1,-1 do
if self.data[str] and self.data[str]:get(locs[i][1], locs[i][2]) then
table.remove(locs, i)
end
end
end
--print('#locs mod', #locs)
if (not locs[1]) then return 0 end
-- Find the goto units
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
-- Find the goto units
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
-- Exclude released units
if cfg.release_unit_at_goal then
for i_unit=#units,1,-1 do
for rel in H.child_range(self.data, "goto_release_unit") do
if (rel.id == cfg.ca_id .. '_' .. units[i_unit].id) then
table.remove(units, i_unit)
break
end
end
end
end
if (not units[1]) then return 0 end
-- Exclude released units
if cfg.release_unit_at_goal then
for i_unit=#units,1,-1 do
for rel in H.child_range(self.data, "goto_release_unit") do
if (rel.id == cfg.ca_id .. '_' .. units[i_unit].id) then
table.remove(units, i_unit)
break
end
end
end
end
if (not units[1]) then return 0 end
-- Now store units and locs in self.data, so that we don't need to duplicate this in the exec function
self.data.units, self.data.locs = units, locs
-- Now store units and locs in self.data, so that we don't need to duplicate this in the exec function
self.data.units, self.data.locs = units, locs
return cfg.ca_score
return cfg.ca_score
end
function ca_goto:execution(ai, cfg, self)
local units, locs = self.data.units, self.data.locs -- simply for convenience
local units, locs = self.data.units, self.data.locs -- simply for convenience
-- Need the enemy map and enemy attack map if avoid_enemies is set
local enemy_map, enemy_attack_map
if cfg.avoid_enemies then
if (type(cfg.avoid_enemies) ~= 'number') then
H.wml_error("Goto AI avoid_enemies= requires a number as argument")
elseif (cfg.avoid_enemies <= 0) then
H.wml_error("Goto AI avoid_enemies= argument must be >0")
end
-- Need the enemy map and enemy attack map if avoid_enemies is set
local enemy_map, enemy_attack_map
if cfg.avoid_enemies then
if (type(cfg.avoid_enemies) ~= 'number') then
H.wml_error("Goto AI avoid_enemies= requires a number as argument")
elseif (cfg.avoid_enemies <= 0) then
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 = wesnoth.get_units { { "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } } }
enemy_map = LS.create()
for i,e in ipairs(enemies) do
enemy_map:insert(e.x, e.y, (enemy_map:get(e.x, e.y) or 0) + 1000)
for x, y in H.adjacent_tiles(e.x, e.y) do
enemy_map:insert(x, y, (enemy_map:get(x, y) or 0) + 10)
end
end
enemy_map = LS.create()
for i,e in ipairs(enemies) do
enemy_map:insert(e.x, e.y, (enemy_map:get(e.x, e.y) or 0) + 1000)
for x, y in H.adjacent_tiles(e.x, e.y) do
enemy_map:insert(x, y, (enemy_map:get(x, y) or 0) + 10)
end
end
enemy_attack_map = BC.get_attack_map(enemies)
end
enemy_attack_map = BC.get_attack_map(enemies)
end
local closest_hex, best_path, best_unit, max_rating = {}, nil, {}, -9e99
for i,u in ipairs(units) do
for i,l in ipairs(locs) do
local closest_hex, best_path, best_unit, max_rating = {}, nil, {}, -9e99
for i,u in ipairs(units) do
for i,l in ipairs(locs) do
-- If use_straight_line is set, we simply find the closest
-- hex to the goal that the unit can get to
if cfg.use_straight_line then
local hex, unit, rating = AH.find_best_move(u, function(x, y)
local r = - H.distance_between(x, y, l[1], l[2])
-- Also add distance from unit as very small rating component
-- This is mostly here to keep unit in place when no better hexes are available
r = r - H.distance_between(x, y, u.x, u.y) / 1000.
return r
end, { no_random = true })
-- If use_straight_line is set, we simply find the closest
-- hex to the goal that the unit can get to
if cfg.use_straight_line then
local hex, unit, rating = AH.find_best_move(u, function(x, y)
local r = - H.distance_between(x, y, l[1], l[2])
-- Also add distance from unit as very small rating component
-- This is mostly here to keep unit in place when no better hexes are available
r = r - H.distance_between(x, y, u.x, u.y) / 1000.
return r
end, { no_random = true })
if (rating > max_rating) then
max_rating = rating
closest_hex, best_unit = hex, u
end
else -- Otherwise find the best path to take
local path, cost
if cfg.avoid_enemies then
path, cost = wesnoth.find_path(u, l[1], l[2],
function(x, y, current_cost)
return custom_cost(x, y, u, enemy_map, enemy_attack_map, cfg.avoid_enemies)
end)
else
local enemy_at_goal
if cfg.ignore_enemy_at_goal then
enemy_at_goal = wesnoth.get_unit(l[1], l[2])
if enemy_at_goal and wesnoth.is_enemy(wesnoth.current.side, enemy_at_goal.side) then
wesnoth.extract_unit(enemy_at_goal)
else
enemy_at_goal = nil
end
end
path, cost = wesnoth.find_path(u, l[1], l[2], { ignore_units = cfg.ignore_units })
if enemy_at_goal then
wesnoth.put_unit(enemy_at_goal.x, enemy_at_goal.y, enemy_at_goal)
--- Give massive penalty for this goal hex
cost = cost + 100
end
end
if (rating > max_rating) then
max_rating = rating
closest_hex, best_unit = hex, u
end
else -- Otherwise find the best path to take
local path, cost
if cfg.avoid_enemies then
path, cost = wesnoth.find_path(u, l[1], l[2],
function(x, y, current_cost)
return custom_cost(x, y, u, enemy_map, enemy_attack_map, cfg.avoid_enemies)
end)
else
local enemy_at_goal
if cfg.ignore_enemy_at_goal then
enemy_at_goal = wesnoth.get_unit(l[1], l[2])
if enemy_at_goal and wesnoth.is_enemy(wesnoth.current.side, enemy_at_goal.side) then
wesnoth.extract_unit(enemy_at_goal)
else
enemy_at_goal = nil
end
end
path, cost = wesnoth.find_path(u, l[1], l[2], { ignore_units = cfg.ignore_units })
if enemy_at_goal then
wesnoth.put_unit(enemy_at_goal.x, enemy_at_goal.y, enemy_at_goal)
--- Give massive penalty for this goal hex
cost = cost + 100
end
end
-- Make all hexes within the unit's current MP equaivalent
if (cost <= u.moves) then cost = 0 end
-- Make all hexes within the unit's current MP equaivalent
if (cost <= u.moves) then cost = 0 end
rating = - cost
rating = - cost
-- Add a small penalty for occupied hexes
-- (this mean occupied by an allied unit, as enemies make the hex unreachable)
local unit_in_way = wesnoth.get_unit(l[1], l[2])
if unit_in_way and ((unit_in_way.x ~= u.x) or (unit_in_way.y ~= u.y)) then
rating = rating - 0.01
end
-- Add a small penalty for occupied hexes
-- (this mean occupied by an allied unit, as enemies make the hex unreachable)
local unit_in_way = wesnoth.get_unit(l[1], l[2])
if unit_in_way and ((unit_in_way.x ~= u.x) or (unit_in_way.y ~= u.y)) then
rating = rating - 0.01
end
if (rating > max_rating) then
max_rating = rating
closest_hex, best_unit = l, u
best_path = path
end
end
end
end
--print(best_unit.id, best_unit.x, best_unit.y, closest_hex[1], closest_hex[2], max_rating)
if (rating > max_rating) then
max_rating = rating
closest_hex, best_unit = l, u
best_path = path
end
end
end
end
--print(best_unit.id, best_unit.x, best_unit.y, closest_hex[1], closest_hex[2], max_rating)
-- If 'unique_goals' is set, mark this location as being taken
if cfg.unique_goals then
local str = 'goals_taken_' .. wesnoth.current.turn
if (not self.data[str]) then self.data[str] = LS.create() end
self.data[str]:insert(closest_hex[1], closest_hex[2])
end
-- If 'unique_goals' is set, mark this location as being taken
if cfg.unique_goals then
local str = 'goals_taken_' .. wesnoth.current.turn
if (not self.data[str]) then self.data[str] = LS.create() end
self.data[str]:insert(closest_hex[1], closest_hex[2])
end
-- If any of the non-standard path finding options were used,
-- we need to pick the farthest reachable hex along that path
-- For simplicity, we simply do it for all kinds of pathfinding here,
-- rather than using ai_helper.next_hop for the standard
-- Also, straight-line does not produce a path, so we do that first
if not best_path then
best_path = wesnoth.find_path(best_unit, closest_hex[1], closest_hex[2])
end
-- If any of the non-standard path finding options were used,
-- we need to pick the farthest reachable hex along that path
-- For simplicity, we simply do it for all kinds of pathfinding here,
-- rather than using ai_helper.next_hop for the standard
-- Also, straight-line does not produce a path, so we do that first
if not best_path then
best_path = wesnoth.find_path(best_unit, closest_hex[1], closest_hex[2])
end
-- Now go through the hexes along that path, use normal path finding
closest_hex = best_path[1]
for i = 2,#best_path do
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
closest_hex = best_path[i]
end
else
break
end
end
-- Now go through the hexes along that path, use normal path finding
closest_hex = best_path[1]
for i = 2,#best_path do
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
closest_hex = best_path[i]
end
else
break
end
end
if closest_hex then
ai.move_full(best_unit, closest_hex[1], closest_hex[2])
else
ai.stopunit_moves(best_unit)
end
if closest_hex then
ai.move_full(best_unit, closest_hex[1], closest_hex[2])
else
ai.stopunit_moves(best_unit)
end
-- If release_unit_at_goal= or release_all_units_at_goal= key is set:
-- Check if the unit made it to one of the goal hexes
-- This needs to be done for the original goal hexes, not checking the SLF again,
-- as that might have changed based on the new situation on the map
if cfg.release_unit_at_goal or cfg.release_all_units_at_goal then
local unit_at_goal = false
for i,l in ipairs(locs) do
if (best_unit.x == l[1]) and (best_unit.y == l[2]) then
unit_at_goal = true
break
end
end
-- If release_unit_at_goal= or release_all_units_at_goal= key is set:
-- Check if the unit made it to one of the goal hexes
-- This needs to be done for the original goal hexes, not checking the SLF again,
-- as that might have changed based on the new situation on the map
if cfg.release_unit_at_goal or cfg.release_all_units_at_goal then
local unit_at_goal = false
for i,l in ipairs(locs) do
if (best_unit.x == l[1]) and (best_unit.y == l[2]) then
unit_at_goal = true
break
end
end
-- If a unit was found, mark either it or all units as released
-- Needs to be stored persistently in self.data meaning:
-- 1. Needs to be in WML table format
-- 2. Keys cannot contain certain characters -> everything potentially user-defined needs to be in values
if unit_at_goal then
if cfg.release_unit_at_goal then
table.insert(self.data, { "goto_release_unit" , { id = cfg.ca_id .. '_' .. best_unit.id } } )
end
-- If a unit was found, mark either it or all units as released
-- Needs to be stored persistently in self.data meaning:
-- 1. Needs to be in WML table format
-- 2. Keys cannot contain certain characters -> everything potentially user-defined needs to be in values
if unit_at_goal then
if cfg.release_unit_at_goal then
table.insert(self.data, { "goto_release_unit" , { id = cfg.ca_id .. '_' .. best_unit.id } } )
end
if cfg.release_all_units_at_goal then
--print("Releasing all units")
table.insert(self.data, { "goto_release_all", { id = cfg.ca_id } } )
end
end
end
if cfg.release_all_units_at_goal then
--print("Releasing all units")
table.insert(self.data, { "goto_release_all", { id = cfg.ca_id } } )
end
end
end
-- And some cleanup
self.data.units, self.data.locs = nil, nil
-- And some cleanup
self.data.units, self.data.locs = nil, nil
end
return ca_goto

View file

@ -5,117 +5,117 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_hang_out = {}
function ca_hang_out:evaluation(ai, cfg, self)
cfg = cfg or {}
cfg = cfg or {}
-- Return 0 if the mobilize condition has previously been met
for mobilze in H.child_range(self.data, "hangout_mobilize_units") do
if (mobilze.id == cfg.ca_id) then
return 0
end
end
-- Return 0 if the mobilize condition has previously been met
for mobilze in H.child_range(self.data, "hangout_mobilize_units") do
if (mobilze.id == cfg.ca_id) then
return 0
end
end
-- Otherwise check if any of the mobilize conditions are now met
if (cfg.mobilize_condition and wesnoth.eval_conditional(cfg.mobilize_condition))
or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
then
table.insert(self.data, { "hangout_mobilize_units" , { id = cfg.ca_id } } )
-- Otherwise check if any of the mobilize conditions are now met
if (cfg.mobilize_condition and wesnoth.eval_conditional(cfg.mobilize_condition))
or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
then
table.insert(self.data, { "hangout_mobilize_units" , { id = cfg.ca_id } } )
-- Need to unmark all units also
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
for i,u in ipairs(units) do
u.variables.mai_hangout_moved = nil
end
-- Need to unmark all units also
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
for i,u in ipairs(units) do
u.variables.mai_hangout_moved = nil
end
return 0
end
return 0
end
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
if units[1] then
return cfg.ca_score
end
return 0
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
if units[1] then
return cfg.ca_score
end
return 0
end
function ca_hang_out:execution(ai, cfg, self)
cfg = cfg or {}
cfg = cfg or {}
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
--print('#unit', #units)
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
--print('#unit', #units)
-- Get the locations close to which the units should hang out
-- cfg.filter_location defaults to the location of the side leader(s)
local filter_location = cfg.filter_location or {
{ "filter", { side = wesnoth.current.side, canrecruit = "yes" } }
}
local width, height = wesnoth.get_map_size()
local locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", filter_location }
}
--print('#locs', #locs)
-- Get the locations close to which the units should hang out
-- cfg.filter_location defaults to the location of the side leader(s)
local filter_location = cfg.filter_location or {
{ "filter", { side = wesnoth.current.side, canrecruit = "yes" } }
}
local width, height = wesnoth.get_map_size()
local locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", filter_location }
}
--print('#locs', #locs)
-- Get map for locations to be avoided (defaults to all castle terrain)
local avoid = cfg.avoid or { terrain = 'C*,C*^*,*^C*' }
local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid))
-- Get map for locations to be avoided (defaults to all castle terrain)
local avoid = cfg.avoid or { terrain = 'C*,C*^*,*^C*' }
local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid))
local best_hex, best_unit, max_rating = {}, {}, -9e99
for i,u in ipairs(units) do
-- Only consider units that have not been marked yet
if (not u.variables.mai_hangout_moved) then
local best_hex_unit, max_rating_unit = {}, -9e99
local best_hex, best_unit, max_rating = {}, {}, -9e99
for i,u in ipairs(units) do
-- Only consider units that have not been marked yet
if (not u.variables.mai_hangout_moved) then
local best_hex_unit, max_rating_unit = {}, -9e99
-- Check out all unoccupied hexes the unit can reach
local reach_map = AH.get_reachable_unocc(u)
reach_map:iter( function(x, y, v)
if (not avoid_map:get(x, y)) then
for k,l in ipairs(locs) do
-- Main rating is the distance from any of the goal hexes
local rating = -H.distance_between(x, y, l[1], l[2])
-- Check out all unoccupied hexes the unit can reach
local reach_map = AH.get_reachable_unocc(u)
reach_map:iter( function(x, y, v)
if (not avoid_map:get(x, y)) then
for k,l in ipairs(locs) do
-- Main rating is the distance from any of the goal hexes
local rating = -H.distance_between(x, y, l[1], l[2])
-- Fastest unit moves first
rating = rating + u.max_moves / 100.
-- Fastest unit moves first
rating = rating + u.max_moves / 100.
-- Minor penalty for distance from current position of unit
-- so that there's not too much shuffling around
local rating = rating - H.distance_between(x, y, u.x, u.y) / 1000.
-- Minor penalty for distance from current position of unit
-- so that there's not too much shuffling around
local rating = rating - H.distance_between(x, y, u.x, u.y) / 1000.
if (rating > max_rating_unit) then
max_rating_unit = rating
best_hex_unit = {x, y}
end
end
end
end)
if (rating > max_rating_unit) then
max_rating_unit = rating
best_hex_unit = {x, y}
end
end
end
end)
-- Only consider a unit if the best hex found for it is not its current location
if (best_hex_unit[1] ~= u.x) or (best_hex_unit[2] ~= u.y) then
if (max_rating_unit > max_rating) then
max_rating = max_rating_unit
best_hex, best_unit = best_hex_unit, u
end
end
end
end
--print(best_unit.id, best_unit.x, best_unit.y, best_hex[1], best_hex[2], max_rating)
-- Only consider a unit if the best hex found for it is not its current location
if (best_hex_unit[1] ~= u.x) or (best_hex_unit[2] ~= u.y) then
if (max_rating_unit > max_rating) then
max_rating = max_rating_unit
best_hex, best_unit = best_hex_unit, u
end
end
end
end
--print(best_unit.id, best_unit.x, best_unit.y, best_hex[1], best_hex[2], max_rating)
-- If no valid locations/units were found or all units are in their
-- respective best locations already, we take moves away from all units
if (max_rating == -9e99) then
for i,u in ipairs(units) do
ai.stopunit_moves(u)
-- Also remove the markers
u.variables.mai_hangout_moved = nil
end
else
-- Otherwise move unit and mark as having been used
ai.move(best_unit, best_hex[1], best_hex[2])
best_unit.variables.mai_hangout_moved = true
end
-- If no valid locations/units were found or all units are in their
-- respective best locations already, we take moves away from all units
if (max_rating == -9e99) then
for i,u in ipairs(units) do
ai.stopunit_moves(u)
-- Also remove the markers
u.variables.mai_hangout_moved = nil
end
else
-- Otherwise move unit and mark as having been used
ai.move(best_unit, best_hex[1], best_hex[2])
best_unit.variables.mai_hangout_moved = true
end
end
return ca_hang_out

View file

@ -7,38 +7,38 @@ local ca_healer_initialize = {}
-- Set variables and aspects correctly at the beginning of the turn
-- This will be blacklisted after first execution each turn
function ca_healer_initialize:evaluation(ai)
local score = 999990
return score
local score = 999990
return score
end
function ca_healer_initialize:execution(ai, cfg, self)
--print(' Initializing healer_support at beginning of Turn ' .. wesnoth.current.turn)
--print(' Initializing healer_support at beginning of Turn ' .. wesnoth.current.turn)
-- First, modify the attacks aspect to exclude healers
-- Always delete the attacks aspect first, so that we do not end up with 100 copies of the facet
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[attacks].facet[no_healers_attack]"
}
-- First, modify the attacks aspect to exclude healers
-- Always delete the attacks aspect first, so that we do not end up with 100 copies of the facet
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[attacks].facet[no_healers_attack]"
}
-- Then set the aspect to exclude healers
W.modify_ai {
side = wesnoth.current.side,
action = "add",
path = "aspect[attacks].facet",
{ "facet", {
name = "ai_default_rca::aspect_attacks",
id = "no_healers_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", {
{ "not", { ability = "healing", { "and", cfg.filter } } }
} }
} }
}
-- Then set the aspect to exclude healers
W.modify_ai {
side = wesnoth.current.side,
action = "add",
path = "aspect[attacks].facet",
{ "facet", {
name = "ai_default_rca::aspect_attacks",
id = "no_healers_attack",
invalidate_on_gamestate_change = "yes",
{ "filter_own", {
{ "not", { ability = "healing", { "and", cfg.filter } } }
} }
} }
}
-- We also need to set the return score of healer moves to happen _after_ combat at beginning of turn
self.data.HS_return_score = 95000
-- We also need to set the return score of healer moves to happen _after_ combat at beginning of turn
self.data.HS_return_score = 95000
end
return ca_healer_initialize

View file

@ -6,27 +6,27 @@ local ca_healer_may_attack = {}
-- After attacks by all other units are done, reset things so that healers can attack, if desired
-- This will be blacklisted after first execution each turn
function ca_healer_may_attack:evaluation(ai)
local score = 99990
return score
local score = 99990
return score
end
function ca_healer_may_attack:execution(ai, cfg, self)
--print(' Letting healers participate in attacks from now on')
--print(' Letting healers participate in attacks from now on')
--local leader = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
--W.message { speaker = leader.id, message = "I'm done with the RCA AI combat CA for all other units, letting healers participate now (if they cannot find a support position)." }
--local leader = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
--W.message { speaker = leader.id, message = "I'm done with the RCA AI combat CA for all other units, letting healers participate now (if they cannot find a support position)." }
-- Delete the attacks aspect
--print("Deleting attacks aspect")
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[attacks].facet[no_healers_attack]"
}
-- Delete the attacks aspect
--print("Deleting attacks aspect")
W.modify_ai {
side = wesnoth.current.side,
action = "try_delete",
path = "aspect[attacks].facet[no_healers_attack]"
}
-- We also reset the variable containing the return score of the healers CA
-- This will make it use its default value
self.data.HS_return_score = nil
-- We also reset the variable containing the return score of the healers CA
-- This will make it use its default value
self.data.HS_return_score = nil
end
return ca_healer_may_attack

View file

@ -7,145 +7,145 @@ local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local ca_healer_move = {}
function ca_healer_move:evaluation(ai, cfg, self)
-- Should happen with higher priority than attacks, except at beginning of turn,
-- when we want attacks done first
-- This is done so that it is possible for healers to attack, if they do not
-- find an appropriate hex to back up other units
local score = 105000
if self.data.HS_return_score then score = self.data.HS_return_score end
--print('healer_support score:', score)
-- Should happen with higher priority than attacks, except at beginning of turn,
-- when we want attacks done first
-- This is done so that it is possible for healers to attack, if they do not
-- find an appropriate hex to back up other units
local score = 105000
if self.data.HS_return_score then score = self.data.HS_return_score end
--print('healer_support score:', score)
cfg = cfg or {}
cfg = cfg or {}
local healers = wesnoth.get_units { side = wesnoth.current.side, ability = "healing",
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
if (not healers[1]) then return 0 end
local healers = wesnoth.get_units { side = wesnoth.current.side, ability = "healing",
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
if (not healers[1]) then return 0 end
local healers_noMP = wesnoth.get_units { side = wesnoth.current.side, ability = "healing",
formula = '$this_unit.moves = 0', { "and", cfg.filter }
}
local healers_noMP = wesnoth.get_units { side = wesnoth.current.side, ability = "healing",
formula = '$this_unit.moves = 0', { "and", cfg.filter }
}
local all_units = wesnoth.get_units{ side = wesnoth.current.side,
{"and", cfg.filter_second}
}
local all_units = wesnoth.get_units{ side = wesnoth.current.side,
{"and", cfg.filter_second}
}
local healees, units_MP = {}, {}
for i,u in ipairs(all_units) do
-- Potential healees are units without MP that don't already have a healer (also without MP) next to them
-- Also, they cannot be on a village or regenerate
if (u.moves == 0) then
if (not wesnoth.match_unit(u, {ability = "regenerates"})) then
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(u.x, u.y)).village
if (not is_village) then
local healee = true
for j,h in ipairs(healers_noMP) do
if (H.distance_between(u.x, u.y, h.x, h.y) == 1) then
--print('Already next to healer:', u.x, u.y, h.x, h.y)
healee = false
break
end
end
if healee then table.insert(healees, u) end
end
end
else
table.insert(units_MP,u)
end
end
--print('#healees, #units_MP', #healees, #units_MP)
local healees, units_MP = {}, {}
for i,u in ipairs(all_units) do
-- Potential healees are units without MP that don't already have a healer (also without MP) next to them
-- Also, they cannot be on a village or regenerate
if (u.moves == 0) then
if (not wesnoth.match_unit(u, {ability = "regenerates"})) then
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(u.x, u.y)).village
if (not is_village) then
local healee = true
for j,h in ipairs(healers_noMP) do
if (H.distance_between(u.x, u.y, h.x, h.y) == 1) then
--print('Already next to healer:', u.x, u.y, h.x, h.y)
healee = false
break
end
end
if healee then table.insert(healees, u) end
end
end
else
table.insert(units_MP,u)
end
end
--print('#healees, #units_MP', #healees, #units_MP)
-- Take all units with moves left off the map, for enemy path finding
for i,u in ipairs(units_MP) do wesnoth.extract_unit(u) end
-- Take all units with moves left off the map, for enemy path finding
for i,u in ipairs(units_MP) do wesnoth.extract_unit(u) end
-- Enemy attack map
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
local enemy_attack_map = BC.get_attack_map(enemies)
--AH.put_labels(enemy_attack_map.units)
-- Enemy attack map
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
local enemy_attack_map = BC.get_attack_map(enemies)
--AH.put_labels(enemy_attack_map.units)
local avoid_map = LS.of_pairs(ai.get_avoid())
local avoid_map = LS.of_pairs(ai.get_avoid())
-- Put units back out there
for i,u in ipairs(units_MP) do wesnoth.put_unit(u.x, u.y, u) end
-- Put units back out there
for i,u in ipairs(units_MP) do wesnoth.put_unit(u.x, u.y, u) end
-- Now find the best healer move
local max_rating, best_hex = -9e99, {}
for i,h in ipairs(healers) do
--local rating_map = LS.create()
-- Now find the best healer move
local max_rating, best_hex = -9e99, {}
for i,h in ipairs(healers) do
--local rating_map = LS.create()
local reach = wesnoth.find_reach(h)
for j,r in ipairs(reach) do
local reach = wesnoth.find_reach(h)
for j,r in ipairs(reach) do
local rating, adjacent_healer = 0
local rating, adjacent_healer = 0
-- Only consider hexes that are next to at least one noMP unit that
-- - either can be attacked by an enemy (15 points per enemy)
-- - or has non-perfect HP (1 point per missing HP)
-- Only consider hexes that are next to at least one noMP unit that
-- - either can be attacked by an enemy (15 points per enemy)
-- - or has non-perfect HP (1 point per missing HP)
-- Also, hex must be unoccupied by another unit, of course
local unit_in_way = wesnoth.get_unit(r[1], r[2])
if (not avoid_map:get(r[1], r[2])) then
if (not unit_in_way) or ((unit_in_way.x == h.x) and (unit_in_way.y == h.y)) then
for k,u in ipairs(healees) do
if (H.distance_between(u.x, u.y, r[1], r[2]) == 1) then
-- !!!!!!! These ratings have to be positive or the method doesn't work !!!!!!!!!
rating = rating + u.max_hitpoints - u.hitpoints
-- Also, hex must be unoccupied by another unit, of course
local unit_in_way = wesnoth.get_unit(r[1], r[2])
if (not avoid_map:get(r[1], r[2])) then
if (not unit_in_way) or ((unit_in_way.x == h.x) and (unit_in_way.y == h.y)) then
for k,u in ipairs(healees) do
if (H.distance_between(u.x, u.y, r[1], r[2]) == 1) then
-- !!!!!!! These ratings have to be positive or the method doesn't work !!!!!!!!!
rating = rating + u.max_hitpoints - u.hitpoints
-- If injured_units_only = true then don't count units with full HP
if (u.max_hitpoints - u.hitpoints > 0) or (not cfg.injured_units_only) then
rating = rating + 15 * (enemy_attack_map.units:get(u.x, u.y) or 0)
end
end
end
end
end
-- If injured_units_only = true then don't count units with full HP
if (u.max_hitpoints - u.hitpoints > 0) or (not cfg.injured_units_only) then
rating = rating + 15 * (enemy_attack_map.units:get(u.x, u.y) or 0)
end
end
end
end
end
-- Number of enemies that can threaten the healer at that position
-- This has to be no larger than cfg.max_threats for hex to be considered
local enemies_in_reach = enemy_attack_map.units:get(r[1], r[2]) or 0
-- Number of enemies that can threaten the healer at that position
-- This has to be no larger than cfg.max_threats for hex to be considered
local enemies_in_reach = enemy_attack_map.units:get(r[1], r[2]) or 0
-- If this hex fulfills those requirements, 'rating' is now greater than 0
-- and we do the rest of the rating, otherwise set rating to below max_rating
if (rating == 0) or (enemies_in_reach > (cfg.max_threats or 9999)) then
rating = max_rating - 1
else
-- Strongly discourage hexes that can be reached by enemies
rating = rating - enemies_in_reach * 1000
-- If this hex fulfills those requirements, 'rating' is now greater than 0
-- and we do the rest of the rating, otherwise set rating to below max_rating
if (rating == 0) or (enemies_in_reach > (cfg.max_threats or 9999)) then
rating = max_rating - 1
else
-- Strongly discourage hexes that can be reached by enemies
rating = rating - enemies_in_reach * 1000
-- All else being more or less equal, prefer villages and strong terrain
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(r[1], r[2])).village
if is_village then rating = rating + 2 end
-- All else being more or less equal, prefer villages and strong terrain
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(r[1], r[2])).village
if is_village then rating = rating + 2 end
local defense = 100 - wesnoth.unit_defense(h, wesnoth.get_terrain(r[1], r[2]))
rating = rating + defense / 10.
local defense = 100 - wesnoth.unit_defense(h, wesnoth.get_terrain(r[1], r[2]))
rating = rating + defense / 10.
--rating_map:insert(r[1], r[2], rating)
end
--rating_map:insert(r[1], r[2], rating)
end
if (rating > max_rating) then
max_rating, best_healer, best_hex = rating, h, {r[1], r[2]}
end
end
--AH.put_labels(rating_map)
--W.message { speaker = h.id, message = 'Healer rating map for me' }
end
--print('best unit move', best_hex[1], best_hex[2], max_rating)
if (rating > max_rating) then
max_rating, best_healer, best_hex = rating, h, {r[1], r[2]}
end
end
--AH.put_labels(rating_map)
--W.message { speaker = h.id, message = 'Healer rating map for me' }
end
--print('best unit move', best_hex[1], best_hex[2], max_rating)
-- Only move healer if a good move as found
-- Be aware that this means that other CAs will move the healers if not
if (max_rating > -9e99) then
self.data.HS_unit, self.data.HS_hex = best_healer, best_hex
return score
end
-- Only move healer if a good move as found
-- Be aware that this means that other CAs will move the healers if not
if (max_rating > -9e99) then
self.data.HS_unit, self.data.HS_hex = best_healer, best_hex
return score
end
return 0
return 0
end
function ca_healer_move:execution(ai, cfg, self)
AH.movefull_outofway_stopunit(ai, self.data.HS_unit, self.data.HS_hex)
self.data.HS_unit, self.data.HS_hex = nil, nil
AH.movefull_outofway_stopunit(ai, self.data.HS_unit, self.data.HS_hex)
self.data.HS_unit, self.data.HS_hex = nil, nil
end
return ca_healer_move

View file

@ -4,125 +4,125 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_herding_attack_close_enemy = {}
function ca_herding_attack_close_enemy:evaluation(ai, cfg)
-- Any enemy within attention_distance (default = 8) hexes of a sheep will get the dogs' attention
-- with enemies within attack_distance (default: 4) being attacked
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attention_distance or 8),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0'
}
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second} }
-- Any enemy within attention_distance (default = 8) hexes of a sheep will get the dogs' attention
-- with enemies within attack_distance (default: 4) being attacked
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attention_distance or 8),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0'
}
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second} }
if enemies[1] and dogs[1] and sheep[1] then return cfg.ca_score end
return 0
if enemies[1] and dogs[1] and sheep[1] then return cfg.ca_score end
return 0
end
function ca_herding_attack_close_enemy:execution(ai, cfg)
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0' }
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second} }
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0' }
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second} }
-- We start with enemies within attack_distance (default: 4) hexes, which will be attacked
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attack_distance or 4),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
-- We start with enemies within attack_distance (default: 4) hexes, which will be attacked
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attack_distance or 4),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
max_rating, best_dog, best_enemy, best_hex = -9e99, {}, {}, {}
for i,e in ipairs(enemies) do
for j,d in ipairs(dogs) do
local reach_map = AH.get_reachable_unocc(d)
reach_map:iter( function(x, y, v)
-- most important: distance to enemy
local rating = - H.distance_between(x, y, e.x, e.y) * 100.
-- 2nd: distance from any sheep
for k,s in ipairs(sheep) do
rating = rating - H.distance_between(x, y, s.x, s.y)
end
-- 3rd: most distant dog goes first
rating = rating + H.distance_between(e.x, e.y, d.x, d.y) / 100.
reach_map:insert(x, y, rating)
max_rating, best_dog, best_enemy, best_hex = -9e99, {}, {}, {}
for i,e in ipairs(enemies) do
for j,d in ipairs(dogs) do
local reach_map = AH.get_reachable_unocc(d)
reach_map:iter( function(x, y, v)
-- most important: distance to enemy
local rating = - H.distance_between(x, y, e.x, e.y) * 100.
-- 2nd: distance from any sheep
for k,s in ipairs(sheep) do
rating = rating - H.distance_between(x, y, s.x, s.y)
end
-- 3rd: most distant dog goes first
rating = rating + H.distance_between(e.x, e.y, d.x, d.y) / 100.
reach_map:insert(x, y, rating)
if (rating > max_rating) then
max_rating = rating
best_hex = { x, y }
best_dog, best_enemy = d, e
end
end)
--AH.put_labels(reach_map)
--W.message { speaker = d.id, message = 'My turn' }
end
end
if (rating > max_rating) then
max_rating = rating
best_hex = { x, y }
best_dog, best_enemy = d, e
end
end)
--AH.put_labels(reach_map)
--W.message { speaker = d.id, message = 'My turn' }
end
end
-- If we found a move, we do it, and attack if possible
if max_rating > -9e99 then
--print('Dog moving in to attack')
AH.movefull_stopunit(ai, best_dog, best_hex)
if H.distance_between(best_dog.x, best_dog.y, best_enemy.x, best_enemy.y) == 1 then
ai.attack(best_dog, best_enemy)
end
return
end
-- If we found a move, we do it, and attack if possible
if max_rating > -9e99 then
--print('Dog moving in to attack')
AH.movefull_stopunit(ai, best_dog, best_hex)
if H.distance_between(best_dog.x, best_dog.y, best_enemy.x, best_enemy.y) == 1 then
ai.attack(best_dog, best_enemy)
end
return
end
-- If we got here, no enemies to attack where found, so we go on to block other enemies
--print('Dogs: No enemies close enough to warrant attack')
-- Now we get all enemies within attention_distance hexes
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attention_distance or 8),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
-- If we got here, no enemies to attack where found, so we go on to block other enemies
--print('Dogs: No enemies close enough to warrant attack')
-- Now we get all enemies within attention_distance hexes
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attention_distance or 8),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
-- Find closest sheep/enemy pair first
local min_dist, closest_sheep, closest_enemy = 9e99, {}, {}
for i,e in ipairs(enemies) do
for j,s in ipairs(sheep) do
local d = H.distance_between(e.x, e.y, s.x, s.y)
if d < min_dist then
min_dist = d
closest_sheep, closest_enemy = s, e
end
end
end
--print('Closest enemy, sheep:', closest_enemy.id, closest_sheep.id)
-- Find closest sheep/enemy pair first
local min_dist, closest_sheep, closest_enemy = 9e99, {}, {}
for i,e in ipairs(enemies) do
for j,s in ipairs(sheep) do
local d = H.distance_between(e.x, e.y, s.x, s.y)
if d < min_dist then
min_dist = d
closest_sheep, closest_enemy = s, e
end
end
end
--print('Closest enemy, sheep:', closest_enemy.id, closest_sheep.id)
-- Move dogs in between enemies and sheep
max_rating, best_dog, best_hex = -9e99, {}, {}
for i,d in ipairs(dogs) do
local reach_map = AH.get_reachable_unocc(d)
reach_map:iter( function(x, y, v)
-- We want equal distance between enemy and closest sheep
local rating = - math.abs(H.distance_between(x, y, closest_sheep.x, closest_sheep.y) - H.distance_between(x, y, closest_enemy.x, closest_enemy.y)) * 100
-- 2nd: closeness to sheep
rating = rating - H.distance_between(x, y, closest_sheep.x, closest_sheep.y)
reach_map:insert(x, y, rating)
-- 3rd: most distant dog goes first
rating = rating + H.distance_between(closest_enemy.x, closest_enemy.y, d.x, d.y) / 100.
reach_map:insert(x, y, rating)
-- Move dogs in between enemies and sheep
max_rating, best_dog, best_hex = -9e99, {}, {}
for i,d in ipairs(dogs) do
local reach_map = AH.get_reachable_unocc(d)
reach_map:iter( function(x, y, v)
-- We want equal distance between enemy and closest sheep
local rating = - math.abs(H.distance_between(x, y, closest_sheep.x, closest_sheep.y) - H.distance_between(x, y, closest_enemy.x, closest_enemy.y)) * 100
-- 2nd: closeness to sheep
rating = rating - H.distance_between(x, y, closest_sheep.x, closest_sheep.y)
reach_map:insert(x, y, rating)
-- 3rd: most distant dog goes first
rating = rating + H.distance_between(closest_enemy.x, closest_enemy.y, d.x, d.y) / 100.
reach_map:insert(x, y, rating)
if (rating > max_rating) then
max_rating = rating
best_hex = { x, y }
best_dog = d
end
end)
--AH.put_labels(reach_map)
--W.message { speaker = d.id, message = 'My turn' }
end
if (rating > max_rating) then
max_rating = rating
best_hex = { x, y }
best_dog = d
end
end)
--AH.put_labels(reach_map)
--W.message { speaker = d.id, message = 'My turn' }
end
-- Move dog to intercept
--print('Dog moving in to intercept')
AH.movefull_stopunit(ai, best_dog, best_hex)
-- Move dog to intercept
--print('Dog moving in to intercept')
AH.movefull_stopunit(ai, best_dog, best_hex)
end
return ca_herding_attack_close_enemy

View file

@ -5,48 +5,48 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_herding_dog_move = {}
function ca_herding_dog_move:evaluation(ai, cfg)
-- As a final step, any dog not adjacent to a sheep moves within herding_perimeter
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0',
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter_second} } } } }
}
if dogs[1] then return cfg.ca_score end
return 0
-- As a final step, any dog not adjacent to a sheep moves within herding_perimeter
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0',
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter_second} } } } }
}
if dogs[1] then return cfg.ca_score end
return 0
end
function ca_herding_dog_move:execution(ai, cfg)
-- We simply move the first dog first
local dog = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0',
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter_second} } } } }
}[1]
-- We simply move the first dog first
local dog = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0',
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter_second} } } } }
}[1]
local herding_perimeter = LS.of_pairs(wesnoth.get_locations(cfg.filter_location))
--AH.put_labels(herding_perimeter)
local herding_perimeter = LS.of_pairs(wesnoth.get_locations(cfg.filter_location))
--AH.put_labels(herding_perimeter)
-- Find average distance of herding_perimeter from center
local av_dist = 0
herding_perimeter:iter( function(x, y, v)
av_dist = av_dist + H.distance_between(x, y, cfg.herd_x, cfg.herd_y)
end)
av_dist = av_dist / herding_perimeter:size()
--print('Average distance:', av_dist)
-- Find average distance of herding_perimeter from center
local av_dist = 0
herding_perimeter:iter( function(x, y, v)
av_dist = av_dist + H.distance_between(x, y, cfg.herd_x, cfg.herd_y)
end)
av_dist = av_dist / herding_perimeter:size()
--print('Average distance:', av_dist)
local best_hex = AH.find_best_move(dog, function(x, y)
-- Prefer hexes on herding_perimeter, or close to it
-- Or, if dog cannot get there, prefer to be av_dist from the center
local rating = 0
if herding_perimeter:get(x, y) then
rating = rating + 1000 + AH.random(99) / 100.
else
rating = rating - math.abs(H.distance_between(x, y, cfg.herd_x, cfg.herd_y) - av_dist) + AH.random(99) / 100.
end
local best_hex = AH.find_best_move(dog, function(x, y)
-- Prefer hexes on herding_perimeter, or close to it
-- Or, if dog cannot get there, prefer to be av_dist from the center
local rating = 0
if herding_perimeter:get(x, y) then
rating = rating + 1000 + AH.random(99) / 100.
else
rating = rating - math.abs(H.distance_between(x, y, cfg.herd_x, cfg.herd_y) - av_dist) + AH.random(99) / 100.
end
return rating
end)
return rating
end)
--print('Dog wandering')
AH.movefull_stopunit(ai, dog, best_hex)
--print('Dog wandering')
AH.movefull_stopunit(ai, dog, best_hex)
end
return ca_herding_dog_move

View file

@ -2,19 +2,19 @@ local H = wesnoth.require "lua/helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
return function(cfg)
-- Find the area that the sheep can occupy
-- First, find all contiguous hexes around center hex that are inside herding_perimeter
local herding_area = LS.of_pairs(wesnoth.get_locations {
x = cfg.herd_x, y = cfg.herd_y, radius = 999,
{"filter_radius", { { "not", cfg.filter_location } } }
} )
-- Find the area that the sheep can occupy
-- First, find all contiguous hexes around center hex that are inside herding_perimeter
local herding_area = LS.of_pairs(wesnoth.get_locations {
x = cfg.herd_x, y = cfg.herd_y, radius = 999,
{"filter_radius", { { "not", cfg.filter_location } } }
} )
-- Then, also exclude hexes next to herding_perimeter; some of the functions work better like that
herding_area:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
if (wesnoth.match_location(xa, ya, cfg.filter_location) ) then herding_area:remove(x, y) end
end
end)
-- Then, also exclude hexes next to herding_perimeter; some of the functions work better like that
herding_area:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
if (wesnoth.match_location(xa, ya, cfg.filter_location) ) then herding_area:remove(x, y) end
end
end)
return herding_area
return herding_area
end

View file

@ -6,88 +6,88 @@ local ca_herding_herd_sheep = {}
local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area.lua"
function ca_herding_herd_sheep:evaluation(ai, cfg)
-- If dogs have moves left, and there is a sheep with moves left outside the
-- herding area, chase it back
-- We'll do a bunch of nested if's, to speed things up
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter}, formula = '$this_unit.moves > 0' }
if dogs[1] then
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } } } }
}
if sheep[1] then
local herding_area = herding_area(cfg)
for i,s in ipairs(sheep) do
-- If a sheep is found outside the herding area, we want to chase it back
if (not herding_area:get(s.x, s.y)) then return cfg.ca_score end
end
end
end
-- If dogs have moves left, and there is a sheep with moves left outside the
-- herding area, chase it back
-- We'll do a bunch of nested if's, to speed things up
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter}, formula = '$this_unit.moves > 0' }
if dogs[1] then
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } } } }
}
if sheep[1] then
local herding_area = herding_area(cfg)
for i,s in ipairs(sheep) do
-- If a sheep is found outside the herding area, we want to chase it back
if (not herding_area:get(s.x, s.y)) then return cfg.ca_score end
end
end
end
-- If we got here, no valid dog/sheep combos were found
return 0
-- If we got here, no valid dog/sheep combos were found
return 0
end
function ca_herding_herd_sheep:execution(ai, cfg)
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter}, formula = '$this_unit.moves > 0' }
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } } } }
}
local herding_area = herding_area(cfg)
local sheep_to_herd = {}
for i,s in ipairs(sheep) do
-- If a sheep is found outside the herding area, we want to chase it back
if (not herding_area:get(s.x, s.y)) then table.insert(sheep_to_herd, s) end
end
sheep = nil
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter}, formula = '$this_unit.moves > 0' }
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } } } }
}
local herding_area = herding_area(cfg)
local sheep_to_herd = {}
for i,s in ipairs(sheep) do
-- If a sheep is found outside the herding area, we want to chase it back
if (not herding_area:get(s.x, s.y)) then table.insert(sheep_to_herd, s) end
end
sheep = nil
-- Find the farthest out sheep that the dogs can get to (and that has moves left)
-- Find the farthest out sheep that the dogs can get to (and that has moves left)
-- Find all sheep that have stepped out of bound
local max_rating, best_dog, best_hex = -9e99, {}, {}
local c_x, c_y = cfg.herd_x, cfg.herd_y
for i,s in ipairs(sheep_to_herd) do
-- This is the rating that depends only on the sheep's position
-- Farthest sheep goes first
local sheep_rating = H.distance_between(c_x, c_y, s.x, s.y) / 10.
-- Sheep with no movement left gets big hit
if (s.moves == 0) then sheep_rating = sheep_rating - 100. end
-- Find all sheep that have stepped out of bound
local max_rating, best_dog, best_hex = -9e99, {}, {}
local c_x, c_y = cfg.herd_x, cfg.herd_y
for i,s in ipairs(sheep_to_herd) do
-- This is the rating that depends only on the sheep's position
-- Farthest sheep goes first
local sheep_rating = H.distance_between(c_x, c_y, s.x, s.y) / 10.
-- Sheep with no movement left gets big hit
if (s.moves == 0) then sheep_rating = sheep_rating - 100. end
for i,d in ipairs(dogs) do
local reach_map = AH.get_reachable_unocc(d)
reach_map:iter( function(x, y, v)
local dist = H.distance_between(x, y, s.x, s.y)
local rating = sheep_rating - dist
-- Needs to be on "far side" of sheep, wrt center for adjacent hexes
if (H.distance_between(x, y, c_x, c_y) <= H.distance_between(s.x, s.y, c_x, c_y))
and (dist == 1)
then rating = rating - 1000 end
-- And the closer dog goes first (so that it might be able to chase another sheep afterward)
rating = rating - H.distance_between(x, y, d.x, d.y) / 100.
-- Finally, prefer to stay on path, if possible
if (wesnoth.match_location(x, y, cfg.filter_location) ) then rating = rating + 0.001 end
for i,d in ipairs(dogs) do
local reach_map = AH.get_reachable_unocc(d)
reach_map:iter( function(x, y, v)
local dist = H.distance_between(x, y, s.x, s.y)
local rating = sheep_rating - dist
-- Needs to be on "far side" of sheep, wrt center for adjacent hexes
if (H.distance_between(x, y, c_x, c_y) <= H.distance_between(s.x, s.y, c_x, c_y))
and (dist == 1)
then rating = rating - 1000 end
-- And the closer dog goes first (so that it might be able to chase another sheep afterward)
rating = rating - H.distance_between(x, y, d.x, d.y) / 100.
-- Finally, prefer to stay on path, if possible
if (wesnoth.match_location(x, y, cfg.filter_location) ) then rating = rating + 0.001 end
reach_map:insert(x, y, rating)
reach_map:insert(x, y, rating)
if (rating > max_rating) then
max_rating = rating
best_dog = d
best_hex = { x, y }
end
end)
--AH.put_labels(reach_map)
--W.message{ speaker = d.id, message = 'My turn' }
end
end
if (rating > max_rating) then
max_rating = rating
best_dog = d
best_hex = { x, y }
end
end)
--AH.put_labels(reach_map)
--W.message{ speaker = d.id, message = 'My turn' }
end
end
-- Now we move the best dog
-- If it's already in the best position, we just take moves away from it
-- (to avoid black-listing of CA, in the worst case)
if (best_hex[1] == best_dog.x) and (best_hex[2] == best_dog.y) then
ai.stopunit_moves(best_dog)
else
--print('Dog moving to herd sheep')
ai.move(best_dog, best_hex[1], best_hex[2]) -- partial move only
end
-- Now we move the best dog
-- If it's already in the best position, we just take moves away from it
-- (to avoid black-listing of CA, in the worst case)
if (best_hex[1] == best_dog.x) and (best_hex[2] == best_dog.y) then
ai.stopunit_moves(best_dog)
else
--print('Dog moving to herd sheep')
ai.move(best_dog, best_hex[1], best_hex[2]) -- partial move only
end
end
return ca_herding_herd_sheep

View file

@ -8,43 +8,43 @@ local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area
function ca_herding_sheep_move:evaluation(ai, cfg)
-- If nothing else is to be done, the sheep do a random move
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second}, formula = '$this_unit.moves > 0' }
if sheep[1] then return cfg.ca_score end
return 0
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second}, formula = '$this_unit.moves > 0' }
if sheep[1] then return cfg.ca_score end
return 0
end
function ca_herding_sheep_move:execution(ai, cfg)
-- We simply move the first sheep first
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second}, formula = '$this_unit.moves > 0' }[1]
-- We simply move the first sheep first
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second}, formula = '$this_unit.moves > 0' }[1]
local reach_map = AH.get_reachable_unocc(sheep)
-- Exclude those that are next to a dog
reach_map:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
local dog = wesnoth.get_unit(xa, ya)
if dog and (wesnoth.match_unit(dog, cfg.filter)) then
reach_map:remove(x, y)
end
end
end)
--AH.put_labels(reach_map)
local reach_map = AH.get_reachable_unocc(sheep)
-- Exclude those that are next to a dog
reach_map:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
local dog = wesnoth.get_unit(xa, ya)
if dog and (wesnoth.match_unit(dog, cfg.filter)) then
reach_map:remove(x, y)
end
end
end)
--AH.put_labels(reach_map)
-- Choose one of the possible locations at random (or the current location, if no move possible)
local x, y = sheep.x, sheep.y
if (reach_map:size() > 0) then
x, y = AH.LS_random_hex(reach_map)
--print('Sheep -> :', x, y)
end
-- Choose one of the possible locations at random (or the current location, if no move possible)
local x, y = sheep.x, sheep.y
if (reach_map:size() > 0) then
x, y = AH.LS_random_hex(reach_map)
--print('Sheep -> :', x, y)
end
-- If this move remains within herding area or dogs have no moves left, or sheep doesn't move
-- make it a full move, otherwise partial move
local herding_area = herding_area(cfg)
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter}, formula = '$this_unit.moves > 0' }
if herding_area:get(x, y) or (not dogs[1]) or ((x == sheep.x) and (y == sheep.y)) then
AH.movefull_stopunit(ai, sheep, x, y)
else
ai.move(sheep, x, y)
end
-- If this move remains within herding area or dogs have no moves left, or sheep doesn't move
-- make it a full move, otherwise partial move
local herding_area = herding_area(cfg)
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter}, formula = '$this_unit.moves > 0' }
if herding_area:get(x, y) or (not dogs[1]) or ((x == sheep.x) and (y == sheep.y)) then
AH.movefull_stopunit(ai, sheep, x, y)
else
ai.move(sheep, x, y)
end
end
return ca_herding_sheep_move

View file

@ -4,38 +4,38 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_herding_sheep_runs_dog = {}
function ca_herding_sheep_runs_dog:evaluation(ai, cfg)
-- Any sheep with moves left next to a dog runs aways
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } }
}
-- Any sheep with moves left next to a dog runs aways
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } }
}
if sheep[1] then return cfg.ca_score end
return 0
if sheep[1] then return cfg.ca_score end
return 0
end
function ca_herding_sheep_runs_dog:execution(ai, cfg)
-- simply get the first sheep
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } }
}[1]
-- and the first dog it is adjacent to
local dog = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
{ "filter_adjacent", { x = sheep.x, y = sheep.y } }
}[1]
-- simply get the first sheep
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } }
}[1]
-- and the first dog it is adjacent to
local dog = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
{ "filter_adjacent", { x = sheep.x, y = sheep.y } }
}[1]
local c_x, c_y = cfg.herd_x, cfg.herd_y
-- If dog is farther from center, sheep moves in, otherwise it moves out
local sign = 1
if (H.distance_between(dog.x, dog.y, c_x, c_y) >= H.distance_between(sheep.x, sheep.y, c_x, c_y)) then
sign = -1
end
local best_hex = AH.find_best_move(sheep, function(x, y)
return H.distance_between(x, y, c_x, c_y) * sign
end)
local c_x, c_y = cfg.herd_x, cfg.herd_y
-- If dog is farther from center, sheep moves in, otherwise it moves out
local sign = 1
if (H.distance_between(dog.x, dog.y, c_x, c_y) >= H.distance_between(sheep.x, sheep.y, c_x, c_y)) then
sign = -1
end
local best_hex = AH.find_best_move(sheep, function(x, y)
return H.distance_between(x, y, c_x, c_y) * sign
end)
AH.movefull_stopunit(ai, sheep, best_hex)
AH.movefull_stopunit(ai, sheep, best_hex)
end
return ca_herding_sheep_runs_dog

View file

@ -4,51 +4,51 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_herding_sheep_runs_enemy = {}
function ca_herding_sheep_runs_enemy:evaluation(ai, cfg)
-- Sheep runs from any enemy within attention_distance hexes (after the dogs have moved in)
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_location",
{
{ "filter", { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
},
radius = (cfg.attention_distance or 8)
}
}
}
-- Sheep runs from any enemy within attention_distance hexes (after the dogs have moved in)
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_location",
{
{ "filter", { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
},
radius = (cfg.attention_distance or 8)
}
}
}
if sheep[1] then return cfg.ca_score end
return 0
if sheep[1] then return cfg.ca_score end
return 0
end
function ca_herding_sheep_runs_enemy:execution(ai, cfg)
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_location",
{
{ "filter", { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
},
radius = (cfg.attention_distance or 8)
}
}
}
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_location",
{
{ "filter", { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
},
radius = (cfg.attention_distance or 8)
}
}
}
-- Simply start with the first of these sheep
sheep = sheep[1]
-- And find the close enemies
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) } }
}
--print('#enemies', #enemies)
-- Simply start with the first of these sheep
sheep = sheep[1]
-- And find the close enemies
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) } }
}
--print('#enemies', #enemies)
-- Maximize distance between sheep and enemies
local best_hex = AH.find_best_move(sheep, function(x, y)
local rating = 0
for i,e in ipairs(enemies) do rating = rating + H.distance_between(x, y, e.x, e.y) end
return rating
end)
-- Maximize distance between sheep and enemies
local best_hex = AH.find_best_move(sheep, function(x, y)
local rating = 0
for i,e in ipairs(enemies) do rating = rating + H.distance_between(x, y, e.x, e.y) end
return rating
end)
AH.movefull_stopunit(ai, sheep, best_hex)
AH.movefull_stopunit(ai, sheep, best_hex)
end
return ca_herding_sheep_runs_enemy

View file

@ -5,200 +5,200 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_hunter = {}
local function hunter_attack_weakest_adj_enemy(ai, unit)
-- Attack the enemy with the fewest hitpoints adjacent to 'unit', if there is one
-- Returns status of the attack:
-- 'attacked': if a unit was attacked
-- 'killed': if a unit was killed
-- 'no_attack': if no unit was attacked
-- Attack the enemy with the fewest hitpoints adjacent to 'unit', if there is one
-- Returns status of the attack:
-- 'attacked': if a unit was attacked
-- 'killed': if a unit was killed
-- 'no_attack': if no unit was attacked
-- First check that the unit exists and has attacks left
if (not unit.valid) then return 'no_attack' end
if (unit.attacks_left == 0) then return 'no_attack' end
-- First check that the unit exists and has attacks left
if (not unit.valid) then return 'no_attack' end
if (unit.attacks_left == 0) then return 'no_attack' end
local min_hp, target = 9e99, {}
for x, y in H.adjacent_tiles(unit.x, unit.y) do
local enemy = wesnoth.get_unit(x, y)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if (enemy.hitpoints < min_hp) then
min_hp, target = enemy.hitpoints, enemy
end
end
end
local min_hp, target = 9e99, {}
for x, y in H.adjacent_tiles(unit.x, unit.y) do
local enemy = wesnoth.get_unit(x, y)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if (enemy.hitpoints < min_hp) then
min_hp, target = enemy.hitpoints, enemy
end
end
end
if target.id then
--W.message { speaker = unit.id, message = 'Attacking weakest adjacent enemy' }
ai.attack(unit, target)
if target.valid then
return 'attacked'
else
return 'killed'
end
end
if target.id then
--W.message { speaker = unit.id, message = 'Attacking weakest adjacent enemy' }
ai.attack(unit, target)
if target.valid then
return 'attacked'
else
return 'killed'
end
end
return 'no_attack'
return 'no_attack'
end
function ca_hunter:evaluation(ai, cfg)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
if unit then return cfg.ca_score end
return 0
if unit then return cfg.ca_score end
return 0
end
-- cfg parameters: id, hunting_ground, home_x, home_y, rest_turns, show_messages
function ca_hunter:execution(ai, cfg)
-- Unit with the given ID goes on a hunt, doing a random wander in area given by
-- hunting_ground, then retreats to
-- position given by 'home_x,home_y' for 'rest_turns' turns, or until fully healed
-- Unit with the given ID goes on a hunt, doing a random wander in area given by
-- hunting_ground, then retreats to
-- position given by 'home_x,home_y' for 'rest_turns' turns, or until fully healed
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
--print('Hunter: ', unit.id)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
--print('Hunter: ', unit.id)
-- If hunting_status is not set for the unit -> default behavior -> hunting
if (not unit.variables.hunting_status) then
-- Unit gets a new goal if none exist or on any move with 10% random chance
local r = AH.random(10)
if (not unit.variables.goal_x) or (r <= 1) then
-- 'locs' includes border hexes, but that does not matter here
locs = AH.get_passable_locations((cfg.filter_location or {}), unit)
local rand = AH.random(#locs)
--print('#locs', #locs, rand)
unit.variables.goal_x, unit.variables.goal_y = locs[rand][1], locs[rand][2]
end
--print('Hunter goto: ', unit.variables.goal_x, unit.variables.goal_y, r)
-- If hunting_status is not set for the unit -> default behavior -> hunting
if (not unit.variables.hunting_status) then
-- Unit gets a new goal if none exist or on any move with 10% random chance
local r = AH.random(10)
if (not unit.variables.goal_x) or (r <= 1) then
-- 'locs' includes border hexes, but that does not matter here
locs = AH.get_passable_locations((cfg.filter_location or {}), unit)
local rand = AH.random(#locs)
--print('#locs', #locs, rand)
unit.variables.goal_x, unit.variables.goal_y = locs[rand][1], locs[rand][2]
end
--print('Hunter goto: ', unit.variables.goal_x, unit.variables.goal_y, r)
-- Hexes the unit can reach
local reach_map = AH.get_reachable_unocc(unit)
-- Hexes the unit can reach
local reach_map = AH.get_reachable_unocc(unit)
-- Now find the one of these hexes that is closest to the goal
local max_rating, best_hex = -9e99, {}
reach_map:iter( function(x, y, v)
-- Distance from goal is first rating
local rating = - H.distance_between(x, y, unit.variables.goal_x, unit.variables.goal_y)
-- Now find the one of these hexes that is closest to the goal
local max_rating, best_hex = -9e99, {}
reach_map:iter( function(x, y, v)
-- Distance from goal is first rating
local rating = - H.distance_between(x, y, unit.variables.goal_x, unit.variables.goal_y)
-- Proximity to an enemy unit is a plus
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 (enemy.hitpoints < enemy_hp) then enemy_hp = enemy.hitpoints end
end
end
rating = rating + 500 - enemy_hp -- prefer attack on weakest enemy
-- Proximity to an enemy unit is a plus
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 (enemy.hitpoints < enemy_hp) then enemy_hp = enemy.hitpoints end
end
end
rating = rating + 500 - enemy_hp -- prefer attack on weakest enemy
reach_map:insert(x, y, rating)
if (rating > max_rating) then
max_rating, best_hex = rating, { x, y }
end
end)
--print(' best_hex: ', best_hex[1], best_hex[2])
--AH.put_labels(reach_map)
reach_map:insert(x, y, rating)
if (rating > max_rating) then
max_rating, best_hex = rating, { x, y }
end
end)
--print(' best_hex: ', best_hex[1], best_hex[2])
--AH.put_labels(reach_map)
if (best_hex[1] ~= unit.x) or (best_hex[2] ~= unit.y) then
ai.move(unit, best_hex[1], best_hex[2]) -- partial move only
else -- If hunter did not move, we need to stop it (also delete the goal)
ai.stopunit_moves(unit)
unit.variables.goal_x, unit.variables.goal_y = nil, nil
end
if (best_hex[1] ~= unit.x) or (best_hex[2] ~= unit.y) then
ai.move(unit, best_hex[1], best_hex[2]) -- partial move only
else -- If hunter did not move, we need to stop it (also delete the goal)
ai.stopunit_moves(unit)
unit.variables.goal_x, unit.variables.goal_y = nil, nil
end
-- Or if this gets the unit to the goal, we also delete the goal
if (unit.x == unit.variables.goal_x) and (unit.y == unit.variables.goal_y) then
unit.variables.goal_x, unit.variables.goal_y = nil, nil
end
-- Or if this gets the unit to the goal, we also delete the goal
if (unit.x == unit.variables.goal_x) and (unit.y == unit.variables.goal_y) then
unit.variables.goal_x, unit.variables.goal_y = nil, nil
end
-- Finally, if the unit ended up next to enemies, attack the weakest of those
local attack_status = hunter_attack_weakest_adj_enemy(ai, unit)
-- Finally, if the unit ended up next to enemies, attack the weakest of those
local attack_status = hunter_attack_weakest_adj_enemy(ai, unit)
-- If the enemy was killed, hunter returns home
if unit.valid and (attack_status == 'killed') then
unit.variables.goal_x, unit.variables.goal_y = nil, nil
unit.variables.hunting_status = 'return'
if cfg.show_messages then
W.message { speaker = unit.id, message = 'Now that I have eaten, I will go back home.' }
end
end
-- If the enemy was killed, hunter returns home
if unit.valid and (attack_status == 'killed') then
unit.variables.goal_x, unit.variables.goal_y = nil, nil
unit.variables.hunting_status = 'return'
if cfg.show_messages then
W.message { speaker = unit.id, message = 'Now that I have eaten, I will go back home.' }
end
end
-- At this point, issue a 'return', so that no other action takes place this turn
return
end
-- At this point, issue a 'return', so that no other action takes place this turn
return
end
-- If we got here, this means the unit is either returning, or resting
if (unit.variables.hunting_status == 'return') then
goto_x, goto_y = wesnoth.find_vacant_tile(cfg.home_x, cfg.home_y)
--print('Go home:', home_x, home_y, goto_x, goto_y)
-- If we got here, this means the unit is either returning, or resting
if (unit.variables.hunting_status == 'return') then
goto_x, goto_y = wesnoth.find_vacant_tile(cfg.home_x, cfg.home_y)
--print('Go home:', home_x, home_y, goto_x, goto_y)
local next_hop = AH.next_hop(unit, goto_x, goto_y)
if next_hop then
--print(next_hop[1], next_hop[2])
AH.movefull_stopunit(ai, unit, next_hop)
local next_hop = AH.next_hop(unit, goto_x, goto_y)
if next_hop then
--print(next_hop[1], next_hop[2])
AH.movefull_stopunit(ai, unit, next_hop)
-- 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
local enemy = wesnoth.get_unit(cfg.home_x, cfg.home_y)
if enemy and wesnoth.is_enemy(enemy.side, unit.side) then
if cfg.show_messages then
W.message { speaker = unit.id, message = 'Get out of my home!' }
end
ai.attack(unit, enemy)
end
end
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
local enemy = wesnoth.get_unit(cfg.home_x, cfg.home_y)
if enemy and wesnoth.is_enemy(enemy.side, unit.side) then
if cfg.show_messages then
W.message { speaker = unit.id, message = 'Get out of my home!' }
end
ai.attack(unit, enemy)
end
end
end
-- We also attack the weakest adjacent enemy, if still possible
hunter_attack_weakest_adj_enemy(ai, unit)
-- We also attack the weakest adjacent enemy, if still possible
hunter_attack_weakest_adj_enemy(ai, unit)
-- If the unit got home, start the resting counter
if unit.valid and (unit.x == cfg.home_x) and (unit.y == cfg.home_y) then
unit.variables.hunting_status = 'resting'
unit.variables.resting_until = wesnoth.current.turn + (cfg.rest_turns or 3)
if cfg.show_messages then
W.message { speaker = unit.id, message = 'I made it home - resting now until the end of Turn ' .. unit.variables.resting_until .. ' or until fully healed.' }
end
end
-- If the unit got home, start the resting counter
if unit.valid and (unit.x == cfg.home_x) and (unit.y == cfg.home_y) then
unit.variables.hunting_status = 'resting'
unit.variables.resting_until = wesnoth.current.turn + (cfg.rest_turns or 3)
if cfg.show_messages then
W.message { speaker = unit.id, message = 'I made it home - resting now until the end of Turn ' .. unit.variables.resting_until .. ' or until fully healed.' }
end
end
-- At this point, issue a 'return', so that no other action takes place this turn
return
end
-- At this point, issue a 'return', so that no other action takes place this turn
return
end
-- If we got here, the only remaining action is resting
if (unit.variables.hunting_status == 'resting') then
-- So all we need to do is take moves away from the unit
ai.stopunit_moves(unit)
-- If we got here, the only remaining action is resting
if (unit.variables.hunting_status == 'resting') then
-- So all we need to do is take moves away from the unit
ai.stopunit_moves(unit)
-- However, we do also attack the weakest adjacent enemy, if still possible
hunter_attack_weakest_adj_enemy(ai, unit)
-- However, we do also attack the weakest adjacent enemy, if still possible
hunter_attack_weakest_adj_enemy(ai, unit)
-- If this is the last turn of resting, we also remove the status and turn variable
if unit.valid and (unit.hitpoints >= unit.max_hitpoints) and (unit.variables.resting_until <= wesnoth.current.turn) then
unit.variables.hunting_status = nil
unit.variables.resting_until = nil
if cfg.show_messages then
W.message { speaker = unit.id, message = 'I am done resting. It is time to go hunting again next turn.' }
end
end
return
end
-- If this is the last turn of resting, we also remove the status and turn variable
if unit.valid and (unit.hitpoints >= unit.max_hitpoints) and (unit.variables.resting_until <= wesnoth.current.turn) then
unit.variables.hunting_status = nil
unit.variables.resting_until = nil
if cfg.show_messages then
W.message { speaker = unit.id, message = 'I am done resting. It is time to go hunting again next turn.' }
end
end
return
end
-- In principle we should never get here, but just in case: reset variable, so that unit goes hunting on next turn
unit.variables.hunting_status = nil
-- In principle we should never get here, but just in case: reset variable, so that unit goes hunting on next turn
unit.variables.hunting_status = nil
end
return ca_hunter

View file

@ -4,96 +4,96 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_lurkers = {}
function ca_lurkers:evaluation(ai, cfg)
-- If any lurker has moves left, we return score just above standard combat CA
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
-- If any lurker has moves left, we return score just above standard combat CA
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
if units[1] then return cfg.ca_score end
return 0
if units[1] then return cfg.ca_score end
return 0
end
function ca_lurkers:execution(ai, cfg)
-- We simply pick the first of the lurkers, they have no strategy
local me = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}[1]
--print("me at:" .. me.x .. "," .. me.y)
-- We simply pick the first of the lurkers, they have no strategy
local me = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}[1]
--print("me at:" .. me.x .. "," .. me.y)
-- Potential targets
local targets = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- sort targets by hitpoints (lurkers choose lowest HP target)
table.sort(targets, function (a,b) return (a.hitpoints < b.hitpoints) end)
--print("Number of potential targets:", #targets)
-- Potential targets
local targets = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- sort targets by hitpoints (lurkers choose lowest HP target)
table.sort(targets, function (a,b) return (a.hitpoints < b.hitpoints) end)
--print("Number of potential targets:", #targets)
-- all reachable hexes
local reach = LS.of_pairs( wesnoth.find_reach(me.x, me.y) )
-- all reachable attack hexes
local reachable_attack_terrain =
LS.of_pairs( wesnoth.get_locations {
{"and", {x = me.x, y = me.y, radius = me.moves} },
{"and", cfg.filter_location}
} )
reachable_attack_terrain:inter(reach)
--print(" reach: " .. reach:size() .. " reach_attack: " .. reachable_attack_terrain:size())
-- all reachable hexes
local reach = LS.of_pairs( wesnoth.find_reach(me.x, me.y) )
-- all reachable attack hexes
local reachable_attack_terrain =
LS.of_pairs( wesnoth.get_locations {
{"and", {x = me.x, y = me.y, radius = me.moves} },
{"and", cfg.filter_location}
} )
reachable_attack_terrain:inter(reach)
--print(" reach: " .. reach:size() .. " reach_attack: " .. reachable_attack_terrain:size())
-- 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 { x = x, y = y, { "not", { x = me.x, y = me.y } } }[1]
return not occ_hex
end)
--print(" reach: " .. reach:size() .. " reach_attack no allies: " .. reachable_attack_terrain:size())
-- 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 { x = x, y = y, { "not", { x = me.x, y = me.y } } }[1]
return not occ_hex
end)
--print(" reach: " .. reach:size() .. " reach_attack no allies: " .. reachable_attack_terrain:size())
-- Attack the weakest reachable enemy
local attacked = false -- Need this, because unit might die in attack
for j, target in ipairs(targets) do
-- Attack the weakest reachable enemy
local attacked = false -- Need this, because unit might die in attack
for j, target in ipairs(targets) do
-- Get reachable attack terrain next to target unit
local rattack_nt_target = LS.of_pairs(wesnoth.get_locations { x=target.x, y=target.y, radius=1 } )
rattack_nt_target:inter(reachable_attack_terrain)
--print(" targets: " .. target.x .. "," .. target.y .. " adjacent attack terrain: " .. rattack_nt_target:size())
-- Get reachable attack terrain next to target unit
local rattack_nt_target = LS.of_pairs(wesnoth.get_locations { x=target.x, y=target.y, radius=1 } )
rattack_nt_target:inter(reachable_attack_terrain)
--print(" targets: " .. target.x .. "," .. target.y .. " adjacent attack terrain: " .. rattack_nt_target:size())
-- if we found a reachable enemy, attack it
-- since they are sorted by hitpoints, we can simply attack the first enemy found and break the loop
if rattack_nt_target:size() > 0 then
-- if we found a reachable enemy, attack it
-- since they are sorted by hitpoints, we can simply attack the first enemy found and break the loop
if rattack_nt_target:size() > 0 then
-- Choose one of the possible attack locations at random
local rand = AH.random(1, rattack_nt_target:size())
local dst = rattack_nt_target:to_stable_pairs()
AH.movefull_stopunit(ai, me, dst[rand])
ai.attack(dst[rand][1], dst[rand][2], target.x, target.y)
attacked = true
break
end
end
-- Choose one of the possible attack locations at random
local rand = AH.random(1, rattack_nt_target:size())
local dst = rattack_nt_target:to_stable_pairs()
AH.movefull_stopunit(ai, me, dst[rand])
ai.attack(dst[rand][1], dst[rand][2], target.x, target.y)
attacked = true
break
end
end
-- If unit has moves left (that is, it didn't attack), go to random wander terrain hex
-- Check first that unit wasn't killed in the attack
if (not attacked) and (not cfg.stationary) then
-- If unit has moves left (that is, it didn't attack), go to random wander terrain hex
-- Check first that unit wasn't killed in the attack
if (not attacked) and (not cfg.stationary) then
local reachable_wander_terrain =
LS.of_pairs( wesnoth.get_locations {
{"and", {x = me.x, y = me.y, radius = me.moves} },
{"and", (cfg.filter_location_wander or cfg.filter_location)}
} )
reachable_wander_terrain:inter(reach)
local reachable_wander_terrain =
LS.of_pairs( wesnoth.get_locations {
{"and", {x = me.x, y = me.y, radius = me.moves} },
{"and", (cfg.filter_location_wander or cfg.filter_location)}
} )
reachable_wander_terrain:inter(reach)
-- get one of the reachable wander terrain hexes randomly
local rand = AH.random(1, reachable_wander_terrain:size())
--print(" reach_wander no allies: " .. reachable_wander_terrain:size() .. " rand #: " .. rand)
local dst = reachable_wander_terrain:to_stable_pairs()
if dst[1] then
dst = dst[rand]
else
dst = { me.x, me.y }
end
AH.movefull_stopunit(ai, me, dst)
end
-- get one of the reachable wander terrain hexes randomly
local rand = AH.random(1, reachable_wander_terrain:size())
--print(" reach_wander no allies: " .. reachable_wander_terrain:size() .. " rand #: " .. rand)
local dst = reachable_wander_terrain:to_stable_pairs()
if dst[1] then
dst = dst[rand]
else
dst = { me.x, me.y }
end
AH.movefull_stopunit(ai, me, dst)
end
-- If the unit has moves or attacks left at this point, take them away
if me and me.valid then ai.stopunit_all(me) end
-- If the unit has moves or attacks left at this point, take them away
if me and me.valid then ai.stopunit_all(me) end
end
return ca_lurkers

View file

@ -6,158 +6,158 @@ local ca_messenger_attack = {}
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
local function messenger_find_enemies_in_way(unit, goal_x, goal_y)
-- Returns the first unit on or next to the path of the messenger
-- unit: proxy table for the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
-- Returns proxy table for the first unit found, or nil if none was found
-- Returns the first unit on or next to the path of the messenger
-- unit: proxy table for the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
-- Returns proxy table for the first unit found, or nil if none was found
local path, cost = wesnoth.find_path(unit, goal_x, goal_y, { ignore_units = true })
local path, cost = wesnoth.find_path(unit, goal_x, goal_y, { ignore_units = true })
-- If unit cannot get there:
if cost >= 42424242 then return end
-- If unit cannot get there:
if cost >= 42424242 then return end
-- Exclude the hex the unit is currently on
table.remove(path, 1)
if (not path[1]) then return end
-- Exclude the hex the unit is currently on
table.remove(path, 1)
if (not path[1]) then return end
-- Is there an enemy unit on the first path hex itself?
-- This would be caught by the adjacent hex check later, but not in the right order
local enemy = wesnoth.get_units { x = path[1][1], y = path[1][2],
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
}[1]
if enemy then
--print(' enemy on first path hex:',enemy.id)
return enemy
end
-- Is there an enemy unit on the first path hex itself?
-- This would be caught by the adjacent hex check later, but not in the right order
local enemy = wesnoth.get_units { x = path[1][1], y = path[1][2],
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
}[1]
if enemy then
--print(' enemy on first path hex:',enemy.id)
return enemy
end
-- After that, go through adjacent hexes of all the other path hexes
for i, p in ipairs(path) do
local sub_path, sub_cost = wesnoth.find_path( unit, p[1], p[2], { ignore_units = true })
if sub_cost <= unit.moves then
-- Check for enemy units on one of the adjacent hexes (which includes 2 hexes on path)
for x, y in H.adjacent_tiles(p[1], p[2]) do
local enemy = wesnoth.get_units { x = x, y = y,
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
}[1]
if enemy then
--print(' enemy next to path hex:',enemy.id)
return enemy
end
end
else -- If we've reached the end of the path for this turn
return
end
end
-- After that, go through adjacent hexes of all the other path hexes
for i, p in ipairs(path) do
local sub_path, sub_cost = wesnoth.find_path( unit, p[1], p[2], { ignore_units = true })
if sub_cost <= unit.moves then
-- Check for enemy units on one of the adjacent hexes (which includes 2 hexes on path)
for x, y in H.adjacent_tiles(p[1], p[2]) do
local enemy = wesnoth.get_units { x = x, y = y,
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
}[1]
if enemy then
--print(' enemy next to path hex:',enemy.id)
return enemy
end
end
else -- If we've reached the end of the path for this turn
return
end
end
-- If no unit was found, return nil
return
-- If no unit was found, return nil
return
end
local function messenger_find_clearing_attack(unit, goal_x, goal_y)
-- Check if an enemy is in the way of the messenger
-- If so, find attack that would "clear" that enemy out of the way
-- unit: proxy table for the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
-- Returns proxy table containing the attack, or nil if none was found
-- Check if an enemy is in the way of the messenger
-- If so, find attack that would "clear" that enemy out of the way
-- unit: proxy table for the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
-- Returns proxy table containing the attack, or nil if none was found
local enemy_in_way = messenger_find_enemies_in_way(unit, goal_x, goal_y)
-- If none found, don't attack, just move
if not enemy_in_way then return end
local enemy_in_way = messenger_find_enemies_in_way(unit, goal_x, goal_y)
-- If none found, don't attack, just move
if not enemy_in_way then return end
local max_rating, best_attack = -9e99, {}
--print('Finding attacks on',enemy_in_way.name,enemy_in_way.id)
local max_rating, best_attack = -9e99, {}
--print('Finding attacks on',enemy_in_way.name,enemy_in_way.id)
-- Find all units that can attack this enemy
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.attacks_left > 0',
{ "not", { id = unit.id } }
}
-- Find all units that can attack this enemy
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.attacks_left > 0',
{ "not", { id = unit.id } }
}
-- Eliminate units without attacks
for i = #my_units,1,-1 do
if (not H.get_child(my_units[i].__cfg, 'attack')) then
table.remove(my_units, i)
end
end
--print('#my_units', #my_units)
-- Eliminate units without attacks
for i = #my_units,1,-1 do
if (not H.get_child(my_units[i].__cfg, 'attack')) then
table.remove(my_units, i)
end
end
--print('#my_units', #my_units)
if (not my_units[1]) then return end
if (not my_units[1]) then return end
local my_attacks = AH.get_attacks(my_units, { simulate_combat = true })
local my_attacks = AH.get_attacks(my_units, { simulate_combat = true })
for i, att in ipairs(my_attacks) do
if (att.target.x == enemy_in_way.x) and (att.target.y == enemy_in_way.y) then
for i, att in ipairs(my_attacks) do
if (att.target.x == enemy_in_way.x) and (att.target.y == enemy_in_way.y) then
-- Rating: expected HP of attacker and defender
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
--print(' rating:', rating)
-- Rating: expected HP of attacker and defender
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
--print(' rating:', rating)
if (rating > max_rating) then
max_rating = rating
best_attack = att
end
end
end
if (rating > max_rating) then
max_rating = rating
best_attack = att
end
end
end
-- If attack on this enemy_in_way is possible, return it
if (max_rating > -9e99) then return best_attack end
-- If attack on this enemy_in_way is possible, return it
if (max_rating > -9e99) then return best_attack end
-- If we got here, that means there's an enemy in the way, but none of the units can reach it
--> try to fight our way to that enemy
--print('Find different attack to get to enemy in way')
for i, att in ipairs(my_attacks) do
-- If we got here, that means there's an enemy in the way, but none of the units can reach it
--> try to fight our way to that enemy
--print('Find different attack to get to enemy in way')
for i, att in ipairs(my_attacks) do
-- Rating: expected HP of attacker and defender
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
-- Rating: expected HP of attacker and defender
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
-- plus, give a huge bonus for closeness to enemy_in_way
local tmp_defender = wesnoth.get_unit(att.target.x, att.target.y)
local dist = H.distance_between(enemy_in_way.x, enemy_in_way.y, tmp_defender.x, tmp_defender.y)
--print(' distance:',enemy_in_way.id, tmp_defender.id, dist)
-- plus, give a huge bonus for closeness to enemy_in_way
local tmp_defender = wesnoth.get_unit(att.target.x, att.target.y)
local dist = H.distance_between(enemy_in_way.x, enemy_in_way.y, tmp_defender.x, tmp_defender.y)
--print(' distance:',enemy_in_way.id, tmp_defender.id, dist)
rating = rating + 100. / dist
--print(' rating:', rating)
rating = rating + 100. / dist
--print(' rating:', rating)
if (rating > max_rating) then
max_rating = rating
best_attack = att
end
end
if (rating > max_rating) then
max_rating = rating
best_attack = att
end
end
if (max_rating > -9e99) then
return best_attack
else
return
end
if (max_rating > -9e99) then
return best_attack
else
return
end
end
function ca_messenger_attack:evaluation(ai, cfg, self)
-- Attack units in the path of the messenger
-- id: id of the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
-- Attack units in the path of the messenger
-- id: id of the messenger unit
-- goal_x, goal_y: coordinates of the goal toward which the messenger moves
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
if (not messenger) then return 0 end
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
if (not messenger) then return 0 end
local x, y = messenger_next_waypoint(messenger, cfg, self)
local x, y = messenger_next_waypoint(messenger, cfg, self)
-- See if there's an enemy in the way that should be attacked
local attack = messenger_find_clearing_attack(messenger, x, y)
-- See if there's an enemy in the way that should be attacked
local attack = messenger_find_clearing_attack(messenger, x, y)
if attack then
self.data.best_attack = attack
return cfg.ca_score
end
if attack then
self.data.best_attack = attack
return cfg.ca_score
end
return 0
return 0
end
function ca_messenger_attack:execution(ai, cfg, self)
local attacker = wesnoth.get_unit(self.data.best_attack.src.x, self.data.best_attack.src.y)
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
local attacker = wesnoth.get_unit(self.data.best_attack.src.x, self.data.best_attack.src.y)
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
ai.attack(attacker, defender)
self.data.best_attack = nil
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
ai.attack(attacker, defender)
self.data.best_attack = nil
end
return ca_messenger_attack

View file

@ -4,58 +4,58 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_messenger_escort_move = {}
function ca_messenger_escort_move:evaluation(ai, cfg)
-- Move escort units close to messenger, and in between messenger and enemies
-- The messenger has moved at this time, so we don't need to exclude him
-- But we check that he exist (not for this scenario, but for others)
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
if (not messenger) then return 0 end
-- Move escort units close to messenger, and in between messenger and enemies
-- The messenger has moved at this time, so we don't need to exclude him
-- But we check that he exist (not for this scenario, but for others)
local messenger = wesnoth.get_units{ side = wesnoth.current.side, id = cfg.id }[1]
if (not messenger) then return 0 end
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.moves > 0' }
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.moves > 0' }
if my_units[1] then
return cfg.ca_score
end
return 0
if my_units[1] then
return cfg.ca_score
end
return 0
end
function ca_messenger_escort_move:execution(ai, cfg)
local messenger = wesnoth.get_units{ id = cfg.id }[1]
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.moves > 0' }
local messenger = wesnoth.get_units{ id = cfg.id }[1]
local my_units = wesnoth.get_units{ side = wesnoth.current.side, formula = '$this_unit.moves > 0' }
-- Simply move units one at a time
local next_unit = my_units[1]
local reach = LS.of_pairs(wesnoth.find_reach(next_unit))
-- Simply move units one at a time
local next_unit = my_units[1]
local reach = LS.of_pairs(wesnoth.find_reach(next_unit))
-- Distance from messenger for each hex the unit can reach
local dist_messenger = AH.distance_map({messenger}, reach)
-- Distance from messenger for each hex the unit can reach
local dist_messenger = AH.distance_map({messenger}, reach)
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- Rating (in the end, we pick the _minimum _rating):
-- 1. Minimize distance from enemies
local rating = AH.distance_map(enemies, reach)
-- 2. This one favors hexes in between messenger and enemies
rating:union_merge(dist_messenger, function(x, y, v1, v2)
return v1 + v2*#enemies
end)
-- 3. Strongly prefer hexes close to the messenger
rating:union_merge(dist_messenger, function(x, y, v1, v2)
return v1 + v2^2
end)
--AH.put_labels(rating)
-- Rating (in the end, we pick the _minimum _rating):
-- 1. Minimize distance from enemies
local rating = AH.distance_map(enemies, reach)
-- 2. This one favors hexes in between messenger and enemies
rating:union_merge(dist_messenger, function(x, y, v1, v2)
return v1 + v2*#enemies
end)
-- 3. Strongly prefer hexes close to the messenger
rating:union_merge(dist_messenger, function(x, y, v1, v2)
return v1 + v2^2
end)
--AH.put_labels(rating)
-- Now find hex with minimum value that is unoccupied
min_rating, best_hex = 9e99, {}
rating:iter(function(x, y, r)
local unit_in_way = wesnoth.get_units{ x = x, y = y, { "not", { id = next_unit.id } } }[1]
if (not unit_in_way) and (r < min_rating) then
min_rating, best_hex = r, { x, y }
end
end)
-- and move the unit there
AH.movefull_stopunit(ai, next_unit, best_hex)
-- Now find hex with minimum value that is unoccupied
min_rating, best_hex = 9e99, {}
rating:iter(function(x, y, r)
local unit_in_way = wesnoth.get_units{ x = x, y = y, { "not", { id = next_unit.id } } }[1]
if (not unit_in_way) and (r < min_rating) then
min_rating, best_hex = r, { x, y }
end
end)
-- and move the unit there
AH.movefull_stopunit(ai, next_unit, best_hex)
end
return ca_messenger_escort_move

View file

@ -2,24 +2,24 @@ local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
return function(messenger, cfg, self)
-- Variable to store which waypoint to go to next (persistent)
if (not self.data.next_waypoint) then self.data.next_waypoint = 1 end
-- Variable to store which waypoint to go to next (persistent)
if (not self.data.next_waypoint) then self.data.next_waypoint = 1 end
local waypoint_x = AH.split(cfg.waypoint_x, ",")
local waypoint_y = AH.split(cfg.waypoint_y, ",")
for i,w in ipairs(waypoint_x) do
waypoint_x[i] = tonumber(waypoint_x[i])
waypoint_y[i] = tonumber(waypoint_y[i])
end
local waypoint_x = AH.split(cfg.waypoint_x, ",")
local waypoint_y = AH.split(cfg.waypoint_y, ",")
for i,w in ipairs(waypoint_x) do
waypoint_x[i] = tonumber(waypoint_x[i])
waypoint_y[i] = tonumber(waypoint_y[i])
end
-- If we're within 3 hexes of the next waypoint, we go on to the one after that
-- except if that one's the last one already
local dist_wp = H.distance_between(messenger.x, messenger.y,
waypoint_x[self.data.next_waypoint], waypoint_y[self.data.next_waypoint]
)
if (dist_wp <= 3) and (self.data.next_waypoint < #waypoint_x) then
self.data.next_waypoint = self.data.next_waypoint + 1
end
-- If we're within 3 hexes of the next waypoint, we go on to the one after that
-- except if that one's the last one already
local dist_wp = H.distance_between(messenger.x, messenger.y,
waypoint_x[self.data.next_waypoint], waypoint_y[self.data.next_waypoint]
)
if (dist_wp <= 3) and (self.data.next_waypoint < #waypoint_x) then
self.data.next_waypoint = self.data.next_waypoint + 1
end
return waypoint_x[self.data.next_waypoint], waypoint_y[self.data.next_waypoint]
return waypoint_x[self.data.next_waypoint], waypoint_y[self.data.next_waypoint]
end

View file

@ -6,119 +6,119 @@ local ca_messenger_move = {}
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
function ca_messenger_move:evaluation(ai, cfg)
-- Move the messenger (unit with passed id) toward goal, attack adjacent unit if possible
-- without retaliation or little expected damage with high chance of killing the enemy
-- Move the messenger (unit with passed id) toward goal, attack adjacent unit if possible
-- without retaliation or little expected damage with high chance of killing the enemy
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
if messenger then
return cfg.ca_score
end
return 0
if messenger then
return cfg.ca_score
end
return 0
end
function ca_messenger_move:execution(ai, cfg, self)
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
local messenger = wesnoth.get_units{ id = cfg.id, formula = '$this_unit.moves > 0' }[1]
local x, y = messenger_next_waypoint(messenger, cfg, self)
if (messenger.x ~= x) or (messenger.y ~= y) then
x, y = wesnoth.find_vacant_tile( x, y, messenger)
end
local next_hop = AH.next_hop(messenger, x, y)
if (not next_hop) then next_hop = { messenger.x, messenger.y } end
local x, y = messenger_next_waypoint(messenger, cfg, self)
if (messenger.x ~= x) or (messenger.y ~= y) then
x, y = wesnoth.find_vacant_tile( x, y, messenger)
end
local next_hop = AH.next_hop(messenger, x, y)
if (not next_hop) then next_hop = { messenger.x, messenger.y } end
-- Compare this to the "ideal path"
local path, cost = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
local opt_hop, opt_cost = {messenger.x, messenger.y}, 0
for i, p in ipairs(path) do
local sub_path, sub_cost = wesnoth.find_path(messenger, p[1], p[2])
if sub_cost > messenger.moves then
break
else
local unit_in_way = wesnoth.get_unit(p[1], p[2])
if not unit_in_way then
opt_hop, nh_cost = p, sub_cost
end
end
end
-- Compare this to the "ideal path"
local path, cost = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
local opt_hop, opt_cost = {messenger.x, messenger.y}, 0
for i, p in ipairs(path) do
local sub_path, sub_cost = wesnoth.find_path(messenger, p[1], p[2])
if sub_cost > messenger.moves then
break
else
local unit_in_way = wesnoth.get_unit(p[1], p[2])
if not unit_in_way then
opt_hop, nh_cost = p, sub_cost
end
end
end
--print(next_hop[1], next_hop[2], opt_hop[1], opt_hop[2])
-- Now compare how long it would take from the end of both of these options
local x1, y1 = messenger.x, messenger.y
wesnoth.put_unit(next_hop[1], next_hop[2], messenger)
local tmp, cost1 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
wesnoth.put_unit(opt_hop[1], opt_hop[2], messenger)
local tmp, cost2 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
wesnoth.put_unit(x1, y1, messenger)
--print(cost1, cost2)
--print(next_hop[1], next_hop[2], opt_hop[1], opt_hop[2])
-- Now compare how long it would take from the end of both of these options
local x1, y1 = messenger.x, messenger.y
wesnoth.put_unit(next_hop[1], next_hop[2], messenger)
local tmp, cost1 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
wesnoth.put_unit(opt_hop[1], opt_hop[2], messenger)
local tmp, cost2 = wesnoth.find_path(messenger, x, y, {ignore_units = 'yes'})
wesnoth.put_unit(x1, y1, messenger)
--print(cost1, cost2)
-- If cost2 is significantly less, that means that the other path might overall be faster
-- even though it is currently blocked
if (cost2 + messenger.max_moves/2 < cost1) then next_hop = opt_hop end
--print(next_hop[1], next_hop[2])
-- If cost2 is significantly less, that means that the other path might overall be faster
-- even though it is currently blocked
if (cost2 + messenger.max_moves/2 < cost1) then next_hop = opt_hop end
--print(next_hop[1], next_hop[2])
if next_hop and ((next_hop[1] ~= messenger.x) or (next_hop[2] ~= messenger.y)) then
ai.move(messenger, next_hop[1], next_hop[2])
else
ai.stopunit_moves(messenger)
end
if next_hop and ((next_hop[1] ~= messenger.x) or (next_hop[2] ~= messenger.y)) then
ai.move(messenger, next_hop[1], next_hop[2])
else
ai.stopunit_moves(messenger)
end
-- We also test whether an attack without retaliation or with little damage is possible
if (not H.get_child(messenger.__cfg, 'attack')) then return end
-- We also test whether an attack without retaliation or with little damage is possible
if (not H.get_child(messenger.__cfg, 'attack')) then return end
local targets = wesnoth.get_units {
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = cfg.id } }
}
local targets = wesnoth.get_units {
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = cfg.id } }
}
local max_rating, best_tar, best_weapon = -9e99, {}, -1
for i,t in ipairs(targets) do
local n_weapon = 0
for weapon in H.child_range(messenger.__cfg, "attack") do
n_weapon = n_weapon + 1
local max_rating, best_tar, best_weapon = -9e99, {}, -1
for i,t in ipairs(targets) do
local n_weapon = 0
for weapon in H.child_range(messenger.__cfg, "attack") do
n_weapon = n_weapon + 1
local att_stats, def_stats = wesnoth.simulate_combat(messenger, n_weapon, t)
local att_stats, def_stats = wesnoth.simulate_combat(messenger, n_weapon, t)
local rating = -9e99
-- This is an acceptable attack if:
-- 1. There is no counter attack
-- 2. Probability of death is >=67% for enemy, 0% for attacker (default values)
local rating = -9e99
-- This is an acceptable attack if:
-- 1. There is no counter attack
-- 2. Probability of death is >=67% for enemy, 0% for attacker (default values)
local enemy_death_chance = cfg.enemy_death_chance or 0.67
local messenger_death_chance = cfg.messenger_death_chance or 0
local enemy_death_chance = cfg.enemy_death_chance or 0.67
local messenger_death_chance = cfg.messenger_death_chance or 0
if (att_stats.hp_chance[messenger.hitpoints] == 1)
or (def_stats.hp_chance[0] >= tonumber(enemy_death_chance)) and (att_stats.hp_chance[0] <= tonumber(messenger_death_chance))
then
rating = t.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
end
--print(messenger.id, t.id,weapon.name, rating)
if rating > max_rating then
max_rating, best_tar, best_weapon = rating, t, n_weapon
end
end
end
if (att_stats.hp_chance[messenger.hitpoints] == 1)
or (def_stats.hp_chance[0] >= tonumber(enemy_death_chance)) and (att_stats.hp_chance[0] <= tonumber(messenger_death_chance))
then
rating = t.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
end
--print(messenger.id, t.id,weapon.name, rating)
if rating > max_rating then
max_rating, best_tar, best_weapon = rating, t, n_weapon
end
end
end
if max_rating > -9e99 then
ai.attack(messenger, best_tar, best_weapon)
else
-- Otherwise, 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 {
x = tonumber(waypoint_x[#waypoint_x]),
y = tonumber(waypoint_y[#waypoint_y]),
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = cfg.id } }
}[1]
if max_rating > -9e99 then
ai.attack(messenger, best_tar, best_weapon)
else
-- Otherwise, 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 {
x = tonumber(waypoint_x[#waypoint_x]),
y = tonumber(waypoint_y[#waypoint_y]),
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_adjacent", { id = cfg.id } }
}[1]
if target then
ai.attack(messenger, target)
end
end
if target then
ai.attack(messenger, target)
end
end
-- Finally, make sure unit is really done after this
ai.stopunit_attacks(messenger)
-- Finally, make sure unit is really done after this
ai.stopunit_attacks(messenger)
end
return ca_messenger_move

View file

@ -4,157 +4,157 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_patrol = {}
function ca_patrol:evaluation(ai, cfg)
local patrol
if cfg.filter then
patrol = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
patrol = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local patrol
if cfg.filter then
patrol = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
patrol = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
-- Check if unit exists as sticky BCAs are not always removed successfully
if patrol then return cfg.ca_score end
return 0
-- Check if unit exists as sticky BCAs are not always removed successfully
if patrol then return cfg.ca_score end
return 0
end
function ca_patrol:execution(ai, cfg, self)
local patrol
if cfg.filter then
patrol = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
patrol = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local patrol
if cfg.filter then
patrol = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
patrol = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
cfg.waypoint_x = AH.split(cfg.waypoint_x, ",")
cfg.waypoint_y = AH.split(cfg.waypoint_y, ",")
cfg.waypoint_x = AH.split(cfg.waypoint_x, ",")
cfg.waypoint_y = AH.split(cfg.waypoint_y, ",")
local n_wp = #cfg.waypoint_x -- just for convenience
local n_wp = #cfg.waypoint_x -- just for convenience
-- Set up waypoints, taking into account whether 'reverse' is set
-- This works even the first time, when self.data.id_reverse is not set yet
local waypoints = {}
if self.data[patrol.id..'_reverse'] then
for i = 1,n_wp do
waypoints[i] = { tonumber(cfg.waypoint_x[n_wp-i+1]), tonumber(cfg.waypoint_y[n_wp-i+1]) }
end
else
for i = 1,n_wp do
waypoints[i] = { tonumber(cfg.waypoint_x[i]), tonumber(cfg.waypoint_y[i]) }
end
end
-- Set up waypoints, taking into account whether 'reverse' is set
-- This works even the first time, when self.data.id_reverse is not set yet
local waypoints = {}
if self.data[patrol.id..'_reverse'] then
for i = 1,n_wp do
waypoints[i] = { tonumber(cfg.waypoint_x[n_wp-i+1]), tonumber(cfg.waypoint_y[n_wp-i+1]) }
end
else
for i = 1,n_wp do
waypoints[i] = { tonumber(cfg.waypoint_x[i]), tonumber(cfg.waypoint_y[i]) }
end
end
-- if not set, set next location (first move)
-- This needs to be in WML format, so that it persists over save/load cycles
if (not self.data[patrol.id..'_x']) then
self.data[patrol.id..'_x'] = waypoints[1][1]
self.data[patrol.id..'_y'] = waypoints[1][2]
self.data[patrol.id..'_reverse'] = false
end
-- if not set, set next location (first move)
-- This needs to be in WML format, so that it persists over save/load cycles
if (not self.data[patrol.id..'_x']) then
self.data[patrol.id..'_x'] = waypoints[1][1]
self.data[patrol.id..'_y'] = waypoints[1][2]
self.data[patrol.id..'_reverse'] = false
end
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 enemies = wesnoth.get_units {
id = cfg.attack,
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
if next(enemies) then break end
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 enemies = wesnoth.get_units {
id = cfg.attack,
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
if next(enemies) 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 {
x = self.data[patrol.id..'_x'],
y = self.data[patrol.id..'_y'],
{ "filter_adjacent", { id = cfg.id } }
}[1]
-- 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 {
x = self.data[patrol.id..'_x'],
y = self.data[patrol.id..'_y'],
{ "filter_adjacent", { id = cfg.id } }
}[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
if ((patrol.x == wp[1]) and (patrol.y == wp[2]))
or (unit_on_wp and ((unit_on_wp.x == wp[1]) and (unit_on_wp.y == wp[2])))
then
if (i == n_wp) then
-- Move him to the first one (or reverse route), if he's on the last waypoint
-- Unless cfg.one_time_only is set
if cfg.one_time_only then
self.data[patrol.id..'_x'] = waypoints[n_wp][1]
self.data[patrol.id..'_y'] = waypoints[n_wp][2]
else
-- Go back to first WP or reverse direction
if cfg.out_and_back then
self.data[patrol.id..'_x'] = waypoints[n_wp-1][1]
self.data[patrol.id..'_y'] = waypoints[n_wp-1][2]
for i,wp in ipairs(waypoints) do
-- If the patrol is on a waypoint or adjacent to one that is occupied by any unit
if ((patrol.x == wp[1]) and (patrol.y == wp[2]))
or (unit_on_wp and ((unit_on_wp.x == wp[1]) and (unit_on_wp.y == wp[2])))
then
if (i == n_wp) then
-- Move him to the first one (or reverse route), if he's on the last waypoint
-- Unless cfg.one_time_only is set
if cfg.one_time_only then
self.data[patrol.id..'_x'] = waypoints[n_wp][1]
self.data[patrol.id..'_y'] = waypoints[n_wp][2]
else
-- Go back to first WP or reverse direction
if cfg.out_and_back then
self.data[patrol.id..'_x'] = waypoints[n_wp-1][1]
self.data[patrol.id..'_y'] = waypoints[n_wp-1][2]
-- We also need to reverse the waypoints right here, as this might not be the end of the move
self.data[patrol.id..'_reverse'] = not self.data[patrol.id..'_reverse']
local tmp_wp = {}
for i,wp in ipairs(waypoints) do tmp_wp[n_wp-i+1] = wp end
waypoints = tmp_wp
else
self.data[patrol.id..'_x'] = waypoints[1][1]
self.data[patrol.id..'_y'] = waypoints[1][2]
end
end
else
-- ... else move him on the next waypoint
self.data[patrol.id..'_x'] = waypoints[i+1][1]
self.data[patrol.id..'_y'] = waypoints[i+1][2]
end
end
end
-- We also need to reverse the waypoints right here, as this might not be the end of the move
self.data[patrol.id..'_reverse'] = not self.data[patrol.id..'_reverse']
local tmp_wp = {}
for i,wp in ipairs(waypoints) do tmp_wp[n_wp-i+1] = wp end
waypoints = tmp_wp
else
self.data[patrol.id..'_x'] = waypoints[1][1]
self.data[patrol.id..'_y'] = waypoints[1][2]
end
end
else
-- ... else move him on the next waypoint
self.data[patrol.id..'_x'] = waypoints[i+1][1]
self.data[patrol.id..'_y'] = waypoints[i+1][2]
end
end
end
-- If we're on the last waypoint on one_time_only is set, stop here
if cfg.one_time_only and
(patrol.x == waypoints[n_wp][1]) and (patrol.y == waypoints[n_wp][2])
then
ai.stopunit_moves(patrol)
else -- otherwise move toward next WP
local x, y = wesnoth.find_vacant_tile(self.data[patrol.id..'_x'], self.data[patrol.id..'_y'], patrol)
local nh = AH.next_hop(patrol, x, y)
if nh and ((nh[1] ~= patrol.x) or (nh[2] ~= patrol.y)) then
ai.move(patrol, nh[1], nh[2])
else
ai.stopunit_moves(patrol)
end
end
end
-- If we're on the last waypoint on one_time_only is set, stop here
if cfg.one_time_only and
(patrol.x == waypoints[n_wp][1]) and (patrol.y == waypoints[n_wp][2])
then
ai.stopunit_moves(patrol)
else -- otherwise move toward next WP
local x, y = wesnoth.find_vacant_tile(self.data[patrol.id..'_x'], self.data[patrol.id..'_y'], patrol)
local nh = AH.next_hop(patrol, x, y)
if nh and ((nh[1] ~= patrol.x) or (nh[2] ~= patrol.y)) then
ai.move(patrol, nh[1], nh[2])
else
ai.stopunit_moves(patrol)
end
end
end
-- Attack unit on the last waypoint under all circumstances if cfg.one_time_only is set
local enemies = {}
if cfg.one_time_only then
enemies = wesnoth.get_units{
x = waypoints[n_wp][1],
y = waypoints[n_wp][2],
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
end
-- Attack unit on the last waypoint under all circumstances if cfg.one_time_only is set
local enemies = {}
if cfg.one_time_only then
enemies = wesnoth.get_units{
x = waypoints[n_wp][1],
y = waypoints[n_wp][2],
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
end
-- Otherwise attack adjacent enemy (if specified)
if (not next(enemies)) then
enemies = wesnoth.get_units{
id = cfg.attack,
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
end
-- Otherwise attack adjacent enemy (if specified)
if (not next(enemies)) then
enemies = wesnoth.get_units{
id = cfg.attack,
{ "filter_adjacent", { id = cfg.id } },
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} }
}
end
if next(enemies) then
for i,v in ipairs(enemies) do
ai.attack(patrol, v)
break
end
end
if next(enemies) then
for i,v in ipairs(enemies) do
ai.attack(patrol, v)
break
end
end
-- Check that patrol is not killed
if patrol and patrol.valid then ai.stopunit_all(patrol) end
-- Check that patrol is not killed
if patrol and patrol.valid then ai.stopunit_all(patrol) end
end
return ca_patrol

View file

@ -5,130 +5,130 @@ local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local ca_protect_unit_attack = {}
function ca_protect_unit_attack:evaluation(ai, cfg, self)
-- Find possible attacks for the units
-- This is set up very conservatively
-- If unit can die in the worst case, it is not done, even if _really_ unlikely
-- Find possible attacks for the units
-- This is set up very conservatively
-- If unit can die in the worst case, it is not done, even if _really_ unlikely
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.attacks_left > 0' }[1])
end
if (not units[1]) then return 0 end
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.attacks_left > 0' }[1])
end
if (not units[1]) then return 0 end
local attacks = AH.get_attacks(units, { simulate_combat = true })
local attacks = AH.get_attacks(units, { simulate_combat = true })
if (not attacks[1]) then return 0 end
--print('#attacks',#attacks,ids)
if (not attacks[1]) then return 0 end
--print('#attacks',#attacks,ids)
-- All enemy units
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- All enemy units
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- For retaliation calculation:
-- Find all hexes enemies can attack on their next turn
local enemy_attacks = {}
for i,e in ipairs(enemies) do
local attack_map = BC.get_attack_map_unit(e).units
table.insert(enemy_attacks, { enemy = e, attack_map = attack_map })
end
-- For retaliation calculation:
-- Find all hexes enemies can attack on their next turn
local enemy_attacks = {}
for i,e in ipairs(enemies) do
local attack_map = BC.get_attack_map_unit(e).units
table.insert(enemy_attacks, { enemy = e, attack_map = attack_map })
end
-- Set up a retaliation table, as many pairs of attacks will be the same
local retal_table = {}
-- Set up a retaliation table, as many pairs of attacks will be the same
local retal_table = {}
local max_rating, best_attack = -9e99, {}
for i,a in pairs(attacks) do
local max_rating, best_attack = -9e99, {}
for i,a in pairs(attacks) do
--print(i,a.dst.x,a.dst.y)
--print(' chance to die:',a.att_stats.hp_chance[0])
--print(i,a.dst.x,a.dst.y)
--print(' chance to die:',a.att_stats.hp_chance[0])
-- Only consider if there is no chance to die or to be poisoned or slowed
if ((a.att_stats.hp_chance[0] == 0) and (a.att_stats.poisoned == 0) and (a.att_stats.slowed == 0)) then
-- Only consider if there is no chance to die or to be poisoned or slowed
if ((a.att_stats.hp_chance[0] == 0) and (a.att_stats.poisoned == 0) and (a.att_stats.slowed == 0)) then
-- Get maximum possible retaliation possible by enemies on next turn
local my_unit = wesnoth.get_unit(a.src.x, a.src.y)
local max_retal = 0
-- Get maximum possible retaliation possible by enemies on next turn
local my_unit = wesnoth.get_unit(a.src.x, a.src.y)
local max_retal = 0
for j,ea in ipairs(enemy_attacks) do
local can_attack = ea.attack_map:get(a.dst.x, a.dst.y)
if can_attack then
for j,ea in ipairs(enemy_attacks) do
local can_attack = ea.attack_map:get(a.dst.x, a.dst.y)
if can_attack then
-- Check first if this attack combination has already been calculated
local str = (a.src.x + a.src.y * 1000) .. '-' .. (a.target.x + a.target.y * 1000)
--print(str)
if retal_table[str] then -- If so, use saved value
--print(' retal already calculated: ',str,retal_table[str])
max_retal = max_retal + retal_table[str]
else -- if not, calculate it and save value
-- Go thru all weapons, as "best weapon" might be different later on
local n_weapon = 0
local min_hp = my_unit.hitpoints
for weapon in H.child_range(ea.enemy.__cfg, "attack") do
n_weapon = n_weapon + 1
-- Check first if this attack combination has already been calculated
local str = (a.src.x + a.src.y * 1000) .. '-' .. (a.target.x + a.target.y * 1000)
--print(str)
if retal_table[str] then -- If so, use saved value
--print(' retal already calculated: ',str,retal_table[str])
max_retal = max_retal + retal_table[str]
else -- if not, calculate it and save value
-- Go thru all weapons, as "best weapon" might be different later on
local n_weapon = 0
local min_hp = my_unit.hitpoints
for weapon in H.child_range(ea.enemy.__cfg, "attack") do
n_weapon = n_weapon + 1
-- Terrain does not matter for this, we're only interested in the maximum damage
local att_stats, def_stats = wesnoth.simulate_combat(ea.enemy, n_weapon, my_unit)
-- Terrain does not matter for this, we're only interested in the maximum damage
local att_stats, def_stats = wesnoth.simulate_combat(ea.enemy, n_weapon, my_unit)
-- Find minimum HP of our unit
-- find the minimum hp outcome
-- Note: cannot use ipairs() because count starts at 0
local min_hp_weapon = my_unit.hitpoints
for hp,chance in pairs(def_stats.hp_chance) do
if ((chance > 0) and (hp < min_hp_weapon)) then
min_hp_weapon = hp
end
end
if (min_hp_weapon < min_hp) then min_hp = min_hp_weapon end
end
--print(' min_hp:',min_hp, ' max damage:',my_unit.hitpoints-min_hp)
max_retal = max_retal + my_unit.hitpoints - min_hp
retal_table[str] = my_unit.hitpoints - min_hp
end
end
end
--print(' max retaliation:',max_retal)
-- Find minimum HP of our unit
-- find the minimum hp outcome
-- Note: cannot use ipairs() because count starts at 0
local min_hp_weapon = my_unit.hitpoints
for hp,chance in pairs(def_stats.hp_chance) do
if ((chance > 0) and (hp < min_hp_weapon)) then
min_hp_weapon = hp
end
end
if (min_hp_weapon < min_hp) then min_hp = min_hp_weapon end
end
--print(' min_hp:',min_hp, ' max damage:',my_unit.hitpoints-min_hp)
max_retal = max_retal + my_unit.hitpoints - min_hp
retal_table[str] = my_unit.hitpoints - min_hp
end
end
end
--print(' max retaliation:',max_retal)
-- and add this to damage possible on this attack
-- Note: cannot use ipairs() because count starts at 0
local min_hp = 1000
for hp,chance in pairs(a.att_stats.hp_chance) do
--print(hp,chance)
if ((chance > 0) and (hp < min_hp)) then
min_hp = hp
end
end
local min_outcome = min_hp - max_retal
--print(' min hp this attack:',min_hp)
--print(' ave hp defender: ',a.def_stats.average_hp)
--print(' min_outcome',min_outcome)
-- and add this to damage possible on this attack
-- Note: cannot use ipairs() because count starts at 0
local min_hp = 1000
for hp,chance in pairs(a.att_stats.hp_chance) do
--print(hp,chance)
if ((chance > 0) and (hp < min_hp)) then
min_hp = hp
end
end
local min_outcome = min_hp - max_retal
--print(' min hp this attack:',min_hp)
--print(' ave hp defender: ',a.def_stats.average_hp)
--print(' min_outcome',min_outcome)
-- If this is >0, consider the attack
if (min_outcome > 0) then
local rating = min_outcome + a.att_stats.average_hp - a.def_stats.average_hp
--print(' rating:',rating,' min_outcome',min_outcome)
if (rating > max_rating) then
max_rating, best_attack = rating, a
end
end
-- If this is >0, consider the attack
if (min_outcome > 0) then
local rating = min_outcome + a.att_stats.average_hp - a.def_stats.average_hp
--print(' rating:',rating,' min_outcome',min_outcome)
if (rating > max_rating) then
max_rating, best_attack = rating, a
end
end
end
end
--print('Max_rating:', max_rating)
end
end
--print('Max_rating:', max_rating)
if (max_rating > -9e99) then
self.data.best_attack = best_attack
return 95000
end
return 0
if (max_rating > -9e99) then
self.data.best_attack = best_attack
return 95000
end
return 0
end
function ca_protect_unit_attack:execution(ai, cfg, self)
local attacker = wesnoth.get_unit(self.data.best_attack.src.x, self.data.best_attack.src.y)
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
local attacker = wesnoth.get_unit(self.data.best_attack.src.x, self.data.best_attack.src.y)
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
ai.attack(attacker, defender)
self.data.best_attack = nil
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
ai.attack(attacker, defender)
self.data.best_attack = nil
end
return ca_protect_unit_attack

View file

@ -3,25 +3,25 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_protect_unit_finish = {}
function ca_protect_unit_finish:evaluation(ai, cfg, self)
-- If a unit can make it to the goal, this is the first thing that happens
for i,id in ipairs(cfg.id) do
local unit = wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1]
if unit then
local path, cost = wesnoth.find_path(unit, cfg.goal_x[i], cfg.goal_y[i])
if (cost <= unit.moves) and ((unit.x ~= cfg.goal_x[i]) or (unit.y ~= cfg.goal_y[i])) then
self.data.unit = unit
self.data.goal = { cfg.goal_x[i], cfg.goal_y[i] }
return 300000
end
end
end
return 0
-- If a unit can make it to the goal, this is the first thing that happens
for i,id in ipairs(cfg.id) do
local unit = wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1]
if unit then
local path, cost = wesnoth.find_path(unit, cfg.goal_x[i], cfg.goal_y[i])
if (cost <= unit.moves) and ((unit.x ~= cfg.goal_x[i]) or (unit.y ~= cfg.goal_y[i])) then
self.data.unit = unit
self.data.goal = { cfg.goal_x[i], cfg.goal_y[i] }
return 300000
end
end
end
return 0
end
function ca_protect_unit_finish:execution(ai, cfg, self)
AH.movefull_stopunit(ai, self.data.unit, self.data.goal)
self.data.unit = nil
self.data.goal = nil
AH.movefull_stopunit(ai, self.data.unit, self.data.goal)
self.data.unit = nil
self.data.goal = nil
end
return ca_protect_unit_finish

View file

@ -6,126 +6,126 @@ local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local ca_protect_unit_move = {}
function ca_protect_unit_move:evaluation(ai, cfg, self)
-- Always 94999 if one of the units can still move
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1])
end
-- Always 94999 if one of the units can still move
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1])
end
if units[1] then return 94999 end
return 0
if units[1] then return 94999 end
return 0
end
function ca_protect_unit_move:execution(ai, cfg, self)
-- Find and execute best (safest) move toward goal
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1])
end
-- Find and execute best (safest) move toward goal
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1])
end
-- Need to take the units off the map, as they don't count into the map scores
-- (as long as they can still move)
for i,u in ipairs(units) do wesnoth.extract_unit(u) end
-- Need to take the units off the map, as they don't count into the map scores
-- (as long as they can still move)
for i,u in ipairs(units) do wesnoth.extract_unit(u) end
-- All the units on the map
-- Counts all enemies, but only own units (not allies)
local my_units = wesnoth.get_units {side = wesnoth.current.side}
local enemy_units = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- All the units on the map
-- Counts all enemies, but only own units (not allies)
local my_units = wesnoth.get_units {side = wesnoth.current.side}
local enemy_units = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
-- My attack map
local MAM = BC.get_attack_map(my_units).units -- enemy attack map
--AH.put_labels(MAM)
-- My attack map
local MAM = BC.get_attack_map(my_units).units -- enemy attack map
--AH.put_labels(MAM)
-- Enemy attack map
local EAM = BC.get_attack_map(enemy_units).units -- enemy attack map
--AH.put_labels(EAM)
-- Enemy attack map
local EAM = BC.get_attack_map(enemy_units).units -- enemy attack map
--AH.put_labels(EAM)
-- Now put the units back out there
for i,u in ipairs(units) do wesnoth.put_unit(u.x, u.y, u) end
-- Now put the units back out there
for i,u in ipairs(units) do wesnoth.put_unit(u.x, u.y, u) end
-- We move the weakest (fewest HP unit) first
local unit = AH.choose(units, function(tmp) return -tmp.hitpoints end)
--print("Moving: ",unit.id)
-- We move the weakest (fewest HP unit) first
local unit = AH.choose(units, function(tmp) return -tmp.hitpoints end)
--print("Moving: ",unit.id)
-- Also need the goal for this unit
local goal = {}
for i,id in ipairs(cfg.id) do
if (unit.id == id) then goal = { cfg.goal_x[1], cfg.goal_y[i] } end
end
--print("Goal:",goal[1],goal[2])
-- Also need the goal for this unit
local goal = {}
for i,id in ipairs(cfg.id) do
if (unit.id == id) then goal = { cfg.goal_x[1], cfg.goal_y[i] } end
end
--print("Goal:",goal[1],goal[2])
-- Reachable hexes
local reach_map = AH.get_reachable_unocc(unit)
--AH.put_labels(reach_map)
-- Reachable hexes
local reach_map = AH.get_reachable_unocc(unit)
--AH.put_labels(reach_map)
-- Now calculate the enemy inverse distance map
-- This is done here because we only need it for the hexes the unit can reach
-- Enemy distance map
local EIDM = AH.inverse_distance_map(enemy_units, reach_map)
--AH.put_labels(EIDM)
-- Now calculate the enemy inverse distance map
-- This is done here because we only need it for the hexes the unit can reach
-- Enemy distance map
local EIDM = AH.inverse_distance_map(enemy_units, reach_map)
--AH.put_labels(EIDM)
-- Get a terrain defense map of reachable hexes
local TDM = LS.create()
reach_map:iter(function(x, y, data)
TDM:insert(x, y, 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y)))
end)
--AH.put_labels(TDM)
-- Get a terrain defense map of reachable hexes
local TDM = LS.create()
reach_map:iter(function(x, y, data)
TDM:insert(x, y, 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y)))
end)
--AH.put_labels(TDM)
-- And finally, the goal distance map
local GDM = LS.create()
reach_map:iter(function(x, y, data)
GDM:insert(x, y, H.distance_between(x, y, goal[1], goal[2]))
end)
--AH.put_labels(GDM)
-- And finally, the goal distance map
local GDM = LS.create()
reach_map:iter(function(x, y, data)
GDM:insert(x, y, H.distance_between(x, y, goal[1], goal[2]))
end)
--AH.put_labels(GDM)
-- Configuration parameters (no option to change these enable at the moment)
local enemy_weight = self.data.enemy_weight or 100.
local my_unit_weight = self.data.my_unit_weight or 1.
local distance_weight = self.data.distance_weight or 3.
local terrain_weight = self.data.terrain_weight or 0.1
local bearing = self.data.bearing or 1
-- Configuration parameters (no option to change these enable at the moment)
local enemy_weight = self.data.enemy_weight or 100.
local my_unit_weight = self.data.my_unit_weight or 1.
local distance_weight = self.data.distance_weight or 3.
local terrain_weight = self.data.terrain_weight or 0.1
local bearing = self.data.bearing or 1
-- If there are no enemies left, only distance to goal matters
-- This is to avoid rare situations where moving toward goal is canceled by moving away from own units
if (not enemy_units[1]) then
enemy_weight = 0
my_unit_weight = 0
distance_weight = 3
terrain_weight = 0
end
-- If there are no enemies left, only distance to goal matters
-- This is to avoid rare situations where moving toward goal is canceled by moving away from own units
if (not enemy_units[1]) then
enemy_weight = 0
my_unit_weight = 0
distance_weight = 3
terrain_weight = 0
end
local max_rating, best_hex = -9e99, -1
local rating_map = LS.create() -- Also set up rating map, so that it can be displayed
local max_rating, best_hex = -9e99, -1
local rating_map = LS.create() -- Also set up rating map, so that it can be displayed
for ind,r in pairs(reach_map.values) do
-- Most important: stay away from enemy: default weight=100 per enemy unit
-- Staying close to own troops: default weight=1 per own unit (allies don't count)
local rating =
(MAM.values[ind] or 0) * my_unit_weight
- (EAM.values[ind] or 0) * enemy_weight
for ind,r in pairs(reach_map.values) do
-- Most important: stay away from enemy: default weight=100 per enemy unit
-- Staying close to own troops: default weight=1 per own unit (allies don't count)
local rating =
(MAM.values[ind] or 0) * my_unit_weight
- (EAM.values[ind] or 0) * enemy_weight
-- Distance to goal is second most important thing: weight=3 per hex
rating = rating - GDM.values[ind] * distance_weight
-- Note: rating will usually be negative, but that's ok (the least negative hex wins)
-- Distance to goal is second most important thing: weight=3 per hex
rating = rating - GDM.values[ind] * distance_weight
-- Note: rating will usually be negative, but that's ok (the least negative hex wins)
-- Terrain rating. Difference of 30 in defense should be worth ~1 step toward goal
rating = rating + TDM.values[ind] * terrain_weight
-- Terrain rating. Difference of 30 in defense should be worth ~1 step toward goal
rating = rating + TDM.values[ind] * terrain_weight
-- Tie breaker: closer to or farther from enemy
rating = rating + (EIDM.values[ind] or 0) / 10. * bearing
-- Tie breaker: closer to or farther from enemy
rating = rating + (EIDM.values[ind] or 0) / 10. * bearing
if (rating > max_rating) then
max_rating, best_hex = rating, ind
end
if (rating > max_rating) then
max_rating, best_hex = rating, ind
end
rating_map.values[ind] = rating
end
--AH.put_labels(rating_map)
--print("Best rating, hex:", max_rating, best_hex)
rating_map.values[ind] = rating
end
--AH.put_labels(rating_map)
--print("Best rating, hex:", max_rating, best_hex)
AH.movefull_stopunit(ai, unit, AH.get_LS_xy(best_hex))
AH.movefull_stopunit(ai, unit, AH.get_LS_xy(best_hex))
end
return ca_protect_unit_move

View file

@ -5,106 +5,106 @@ local ca_recruit_random = {}
local recruit
function ca_recruit_random:evaluation(ai, cfg)
-- Random recruiting from all the units the side has
-- Random recruiting from all the units the side has
-- Check if leader is on keep
local leader = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
if (not leader) or (not wesnoth.get_terrain_info(wesnoth.get_terrain(leader.x, leader.y)).keep) then
return 0
end
-- Check if leader is on keep
local leader = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
if (not leader) or (not wesnoth.get_terrain_info(wesnoth.get_terrain(leader.x, leader.y)).keep) then
return 0
end
-- Check if there is space left for recruiting
local width, height, border = wesnoth.get_map_size()
local castle = {
locs = wesnoth.get_locations {
x = "1-"..width, y = "1-"..height,
{ "and", {
x = leader.x, y = leader.y, radius = 200,
{ "filter_radius", { terrain = 'C*,K*,C*^*,K*^*,*^K*,*^C*' } }
}}
}
}
local no_space = true
for i,c in ipairs(castle.locs) do
local unit = wesnoth.get_unit(c[1], c[2])
if (not unit) then
no_space = false
break
end
end
if no_space then return 0 end
-- Check if there is space left for recruiting
local width, height, border = wesnoth.get_map_size()
local castle = {
locs = wesnoth.get_locations {
x = "1-"..width, y = "1-"..height,
{ "and", {
x = leader.x, y = leader.y, radius = 200,
{ "filter_radius", { terrain = 'C*,K*,C*^*,K*^*,*^K*,*^C*' } }
}}
}
}
local no_space = true
for i,c in ipairs(castle.locs) do
local unit = wesnoth.get_unit(c[1], c[2])
if (not unit) then
no_space = false
break
end
end
if no_space then return 0 end
-- Set up the probability array
local prob, prob_sum = {}, 0
-- Set up the probability array
local prob, prob_sum = {}, 0
-- Go through all the types listed in [probability] tags (which can be comma-separated lists)
for i,tmp in ipairs(cfg.type) do
tmp = AH.split(tmp, ",")
for j,t in ipairs(tmp) do
-- If this type is in the recruit list, add it
for k,r in ipairs(wesnoth.sides[wesnoth.current.side].recruit) do
if (r == t) then
prob[t] = { value = cfg.prob[i] }
prob_sum = prob_sum + cfg.prob[i]
break
end
end
end
end
-- Go through all the types listed in [probability] tags (which can be comma-separated lists)
for i,tmp in ipairs(cfg.type) do
tmp = AH.split(tmp, ",")
for j,t in ipairs(tmp) do
-- If this type is in the recruit list, add it
for k,r in ipairs(wesnoth.sides[wesnoth.current.side].recruit) do
if (r == t) then
prob[t] = { value = cfg.prob[i] }
prob_sum = prob_sum + cfg.prob[i]
break
end
end
end
end
-- Now we add in all the unit types not listed in [probability] tags
for i,r in ipairs(wesnoth.sides[wesnoth.current.side].recruit) do
if (not prob[r]) then
prob[r] = { value = 1 }
prob_sum =prob_sum + 1
end
end
-- Now we add in all the unit types not listed in [probability] tags
for i,r in ipairs(wesnoth.sides[wesnoth.current.side].recruit) do
if (not prob[r]) then
prob[r] = { value = 1 }
prob_sum =prob_sum + 1
end
end
-- Now eliminate all those that are too expensive (unless cfg.skip_low_gold_recruiting is set)
if cfg.skip_low_gold_recruiting then
for typ,pr in pairs(prob) do
if (wesnoth.unit_types[typ].cost > wesnoth.sides[wesnoth.current.side].gold) then
--print('Eliminating:', typ)
prob_sum = prob_sum - pr.value
prob[typ] = nil
end
end
end
-- Now eliminate all those that are too expensive (unless cfg.skip_low_gold_recruiting is set)
if cfg.skip_low_gold_recruiting then
for typ,pr in pairs(prob) do
if (wesnoth.unit_types[typ].cost > wesnoth.sides[wesnoth.current.side].gold) then
--print('Eliminating:', typ)
prob_sum = prob_sum - pr.value
prob[typ] = nil
end
end
end
-- Now set up the min/max values for each type
-- This needs to be done manually as the order of pairs() is not guaranteed
local cum_prob, n_recruits = 0, 0
for typ,pr in pairs(prob) do
prob[typ].p_i = cum_prob
cum_prob = cum_prob + pr.value / prob_sum * 1e6
prob[typ].p_f = cum_prob
n_recruits = n_recruits + 1
end
-- Now set up the min/max values for each type
-- This needs to be done manually as the order of pairs() is not guaranteed
local cum_prob, n_recruits = 0, 0
for typ,pr in pairs(prob) do
prob[typ].p_i = cum_prob
cum_prob = cum_prob + pr.value / prob_sum * 1e6
prob[typ].p_f = cum_prob
n_recruits = n_recruits + 1
end
-- Now we're ready to pick on of those
-- We always call the exec function, no matter if the selected unit is affordable
-- The point is that this will blacklist the CA if an unaffordable recruit was
-- chosen -> no cheaper recruits will be selected in subsequent calls
if (n_recruits > 0) then
local rand_prob = AH.random(1e6)
for typ,pr in pairs(prob) do
if (pr.p_i <= rand_prob) and (rand_prob < pr.p_f) then
recruit = typ
break
end
end
else
recruit = wesnoth.sides[wesnoth.current.side].recruit[1]
end
-- Now we're ready to pick on of those
-- We always call the exec function, no matter if the selected unit is affordable
-- The point is that this will blacklist the CA if an unaffordable recruit was
-- chosen -> no cheaper recruits will be selected in subsequent calls
if (n_recruits > 0) then
local rand_prob = AH.random(1e6)
for typ,pr in pairs(prob) do
if (pr.p_i <= rand_prob) and (rand_prob < pr.p_f) then
recruit = typ
break
end
end
else
recruit = wesnoth.sides[wesnoth.current.side].recruit[1]
end
return cfg.ca_score
return cfg.ca_score
end
function ca_recruit_random:execution(ai, cfg)
-- Let this function blacklist itself if the chosen recruit is too expensive
if wesnoth.unit_types[recruit].cost <= wesnoth.sides[wesnoth.current.side].gold then
ai.recruit(recruit)
end
-- Let this function blacklist itself if the chosen recruit is too expensive
if wesnoth.unit_types[recruit].cost <= wesnoth.sides[wesnoth.current.side].gold then
ai.recruit(recruit)
end
end
return ca_recruit_random

View file

@ -11,12 +11,12 @@ local internal_params = {}
wesnoth.require("ai/lua/generic_recruit_engine.lua").init(ai, internal_recruit_cas, internal_params)
function ca_recruit_rushers:evaluation(ai, cfg)
internal_params.randomness = cfg.randomness
return internal_recruit_cas:recruit_rushers_eval()
internal_params.randomness = cfg.randomness
return internal_recruit_cas:recruit_rushers_eval()
end
function ca_recruit_rushers:execution(ai)
return internal_recruit_cas:recruit_rushers_exec(ai)
return internal_recruit_cas:recruit_rushers_exec(ai)
end
return ca_recruit_rushers

View file

@ -3,51 +3,51 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_return_guardian = {}
function ca_return_guardian:evaluation(ai, cfg)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
-- Check if unit exists as sticky BCAs are not always removed successfully
if unit then
if ((unit.x ~= cfg.return_x) or (unit.y ~= cfg.return_y)) then
return cfg.ca_score
else
return cfg.ca_score - 20
end
end
return 0
-- Check if unit exists as sticky BCAs are not always removed successfully
if unit then
if ((unit.x ~= cfg.return_x) or (unit.y ~= cfg.return_y)) then
return cfg.ca_score
else
return cfg.ca_score - 20
end
end
return 0
end
function ca_return_guardian:execution(ai, cfg)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
--print("Exec guardian move",unit.id)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
--print("Exec guardian move",unit.id)
-- In case the return hex is occupied:
local x, y = cfg.return_x, cfg.return_y
if (unit.x ~= x) or (unit.y ~= y) then
x, y = wesnoth.find_vacant_tile(x, y, unit)
end
-- In case the return hex is occupied:
local x, y = cfg.return_x, cfg.return_y
if (unit.x ~= x) or (unit.y ~= y) then
x, y = wesnoth.find_vacant_tile(x, y, unit)
end
local nh = AH.next_hop(unit, x, y)
if unit.moves~=0 then
AH.movefull_stopunit(ai, unit, nh)
end
local nh = AH.next_hop(unit, x, y)
if unit.moves~=0 then
AH.movefull_stopunit(ai, unit, nh)
end
end
return ca_return_guardian

View file

@ -7,80 +7,80 @@ local ca_simple_attack = {}
function ca_simple_attack:evaluation(ai, cfg, self)
-- Find all units that can attack and match the SUF
local units = wesnoth.get_units {
side = wesnoth.current.side,
formula = '$this_unit.attacks_left > 0',
{ "and", cfg.filter }
}
-- Find all units that can attack and match the SUF
local units = wesnoth.get_units {
side = wesnoth.current.side,
formula = '$this_unit.attacks_left > 0',
{ "and", cfg.filter }
}
-- Eliminate units without attacks
for i = #units,1,-1 do
if (not H.get_child(units[i].__cfg, 'attack')) then
table.remove(units, i)
end
end
--print('#units', #units)
if (not units[1]) then return 0 end
-- Eliminate units without attacks
for i = #units,1,-1 do
if (not H.get_child(units[i].__cfg, 'attack')) then
table.remove(units, i)
end
end
--print('#units', #units)
if (not units[1]) then return 0 end
-- Get all possible attacks
local attacks = AH.get_attacks(units, { include_occupied = true })
--print('#attacks', #attacks)
if (not attacks[1]) then return 0 end
-- Get all possible attacks
local attacks = AH.get_attacks(units, { include_occupied = true })
--print('#attacks', #attacks)
if (not attacks[1]) then return 0 end
-- If cfg.filter_second is set, set up a map (location set)
-- of enemies that it is okay to attack
local enemy_map
if cfg.filter_second then
local enemies = wesnoth.get_units {
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} },
{ "and", cfg.filter_second }
}
--print('#enemies', #enemies)
if (not enemies[1]) then return 0 end
-- If cfg.filter_second is set, set up a map (location set)
-- of enemies that it is okay to attack
local enemy_map
if cfg.filter_second then
local enemies = wesnoth.get_units {
{ "filter_side", {{ "enemy_of", { side = wesnoth.current.side } }} },
{ "and", cfg.filter_second }
}
--print('#enemies', #enemies)
if (not enemies[1]) then return 0 end
enemy_map = LS.create()
for i,e in ipairs(enemies) do enemy_map:insert(e.x, e.y) end
end
enemy_map = LS.create()
for i,e in ipairs(enemies) do enemy_map:insert(e.x, e.y) end
end
-- Now find the best of the possible attacks
local max_rating, best_attack = -9e99, {}
for i, att in ipairs(attacks) do
local valid_target = true
if cfg.filter_second and (not enemy_map:get(att.target.x, att.target.y)) then
valid_target = false
end
-- Now find the best of the possible attacks
local max_rating, best_attack = -9e99, {}
for i, att in ipairs(attacks) do
local valid_target = true
if cfg.filter_second and (not enemy_map:get(att.target.x, att.target.y)) then
valid_target = false
end
if valid_target then
local attacker = wesnoth.get_unit(att.src.x, att.src.y)
local enemy = wesnoth.get_unit(att.target.x, att.target.y)
local dst = { att.dst.x, att.dst.y }
if valid_target then
local attacker = wesnoth.get_unit(att.src.x, att.src.y)
local enemy = wesnoth.get_unit(att.target.x, att.target.y)
local dst = { att.dst.x, att.dst.y }
local rating = BC.attack_rating(attacker, enemy, dst)
--print('rating:', rating, attacker.id, enemy.id)
local rating = BC.attack_rating(attacker, enemy, dst)
--print('rating:', rating, attacker.id, enemy.id)
if (rating > max_rating) then
max_rating = rating
best_attack = att
end
end
end
if (rating > max_rating) then
max_rating = rating
best_attack = att
end
end
end
if (max_rating > -9e99) then
self.data.attack = best_attack
return cfg.ca_score
end
if (max_rating > -9e99) then
self.data.attack = best_attack
return cfg.ca_score
end
return 0
return 0
end
function ca_simple_attack:execution(ai, cfg, self)
local attacker = wesnoth.get_unit(self.data.attack.src.x, self.data.attack.src.y)
local defender = wesnoth.get_unit(self.data.attack.target.x, self.data.attack.target.y)
local attacker = wesnoth.get_unit(self.data.attack.src.x, self.data.attack.src.y)
local defender = wesnoth.get_unit(self.data.attack.target.x, self.data.attack.target.y)
AH.movefull_outofway_stopunit(ai, attacker, self.data.attack.dst.x, self.data.attack.dst.y)
ai.attack(attacker, defender)
self.data.attack = nil
AH.movefull_outofway_stopunit(ai, attacker, self.data.attack.dst.x, self.data.attack.dst.y)
ai.attack(attacker, defender)
self.data.attack = nil
end
return ca_simple_attack

View file

@ -4,131 +4,131 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_stationed_guardian = {}
function ca_stationed_guardian:evaluation(ai, cfg)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
-- Check if unit exists as sticky BCAs are not always removed successfully
if unit then return cfg.ca_score end
return 0
-- Check if unit exists as sticky BCAs are not always removed successfully
if unit then return cfg.ca_score end
return 0
end
function ca_stationed_guardian:execution(ai, cfg)
-- (s_x,s_y): coordinates where unit is stationed; tries to move here if there is nobody to attack
-- (g_x,g_y): location that the unit guards
-- (s_x,s_y): coordinates where unit is stationed; tries to move here if there is nobody to attack
-- (g_x,g_y): location that the unit guards
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
-- find if there are enemies within 'distance'
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
}
-- find if there are enemies within 'distance'
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
}
-- if no enemies are within 'distance': keep unit from doing anything and exit
if not enemies[1] then
--print("No enemies close -> sleeping:",unit.id)
ai.stopunit_moves(unit)
return
end
-- if no enemies are within 'distance': keep unit from doing anything and exit
if not enemies[1] then
--print("No enemies close -> sleeping:",unit.id)
ai.stopunit_moves(unit)
return
end
-- Otherwise, unit will either attack or move toward station
--print("Guardian unit waking up",unit.id)
-- enemies must be within 'distance' of guard, (s_x,s_y) *and* (g_x,g_y)
-- simultaneous for guard to attack
local target = {}
local min_dist = 9999
for i,e in ipairs(enemies) do
local ds = H.distance_between(cfg.station_x, cfg.station_y, e.x, e.y)
local dg = H.distance_between(cfg.guard_x, cfg.guard_y, e.x, e.y)
-- Otherwise, unit will either attack or move toward station
--print("Guardian unit waking up",unit.id)
-- enemies must be within 'distance' of guard, (s_x,s_y) *and* (g_x,g_y)
-- simultaneous for guard to attack
local target = {}
local min_dist = 9999
for i,e in ipairs(enemies) do
local ds = H.distance_between(cfg.station_x, cfg.station_y, e.x, e.y)
local dg = H.distance_between(cfg.guard_x, cfg.guard_y, e.x, e.y)
-- If valid target found, save the one with the shortest distance from (g_x,g_y)
if (ds <= cfg.distance) and (dg <= cfg.distance) and (dg < min_dist) then
--print("target:", e.id, ds, dg)
target = e
min_dist = dg
end
end
-- If valid target found, save the one with the shortest distance from (g_x,g_y)
if (ds <= cfg.distance) and (dg <= cfg.distance) and (dg < min_dist) then
--print("target:", e.id, ds, dg)
target = e
min_dist = dg
end
end
-- If a valid target was found, unit attacks this target, or moves toward it
if (min_dist ~= 9999) then
--print ("Go for enemy unit:", target.id)
-- If a valid target was found, unit attacks this target, or moves toward it
if (min_dist ~= 9999) then
--print ("Go for enemy unit:", target.id)
-- Find tiles adjacent to the target, and save the one that our unit
-- can reach with the highest defense rating
local best_defense, attack_loc = -9e99, {}
for x,y in H.adjacent_tiles(target.x, target.y) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = unit.id } } }[1]
if not occ_hex then
-- defense rating of the hex
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
--print(x,y,defense)
local nh = AH.next_hop(unit, x, y)
-- if this is best defense rating and unit can reach it, save this location
if (nh[1] == x) and (nh[2] == y) and (defense > best_defense) then
best_defense, attack_loc = defense, {x, y}
end
end
end
-- Find tiles adjacent to the target, and save the one that our unit
-- can reach with the highest defense rating
local best_defense, attack_loc = -9e99, {}
for x,y in H.adjacent_tiles(target.x, target.y) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = unit.id } } }[1]
if not occ_hex then
-- defense rating of the hex
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
--print(x,y,defense)
local nh = AH.next_hop(unit, x, y)
-- if this is best defense rating and unit can reach it, save this location
if (nh[1] == x) and (nh[2] == y) and (defense > best_defense) then
best_defense, attack_loc = defense, {x, y}
end
end
end
-- If a valid hex was found: move there and attack
if (best_defense ~= -9e99) then
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
AH.movefull_stopunit(ai, unit, attack_loc)
-- There should be an ai.check_attack_action() here in case something weird is
-- done in a 'moveto' event.
ai.attack(unit, target)
else -- otherwise move toward that enemy
--print("Cannot reach target, moving toward it")
local reach = wesnoth.find_reach(unit)
-- If a valid hex was found: move there and attack
if (best_defense ~= -9e99) then
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
AH.movefull_stopunit(ai, unit, attack_loc)
-- There should be an ai.check_attack_action() here in case something weird is
-- done in a 'moveto' event.
ai.attack(unit, target)
else -- otherwise move toward that enemy
--print("Cannot reach target, moving toward it")
local reach = wesnoth.find_reach(unit)
-- Go through all hexes the unit can reach, find closest to target
local nh = {} -- cannot use next_hop here since target hex is occupied by enemy
local min_dist = 9999
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
local d = H.distance_between(r[1], r[2], target.x, target.y)
if d < min_dist then
min_dist = d
nh = {r[1], r[2]}
end
end
end
-- Go through all hexes the unit can reach, find closest to target
local nh = {} -- cannot use next_hop here since target hex is occupied by enemy
local min_dist = 9999
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
local d = H.distance_between(r[1], r[2], target.x, target.y)
if d < min_dist then
min_dist = d
nh = {r[1], r[2]}
end
end
end
-- Finally, execute the move toward the target
AH.movefull_stopunit(ai, unit, nh)
end
-- Finally, execute the move toward the target
AH.movefull_stopunit(ai, unit, nh)
end
-- If no enemy within the target zone, move toward station position
else
--print "Move toward station"
local nh = AH.next_hop(unit, cfg.station_x, cfg.station_y)
AH.movefull_stopunit(ai, unit, nh)
end
-- If no enemy within the target zone, move toward station position
else
--print "Move toward station"
local nh = AH.next_hop(unit, cfg.station_x, cfg.station_y)
AH.movefull_stopunit(ai, unit, nh)
end
-- Get unit again, just in case something was done to it in a 'moveto' or 'attack' event
local unit = wesnoth.get_units{ id = cfg.id }[1]
if unit then ai.stopunit_moves(unit) end
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
-- Get unit again, just in case something was done to it in a 'moveto' or 'attack' event
local unit = wesnoth.get_units{ id = cfg.id }[1]
if unit then ai.stopunit_moves(unit) end
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
end
return ca_stationed_guardian

View file

@ -4,73 +4,73 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local swarm_move = {}
function swarm_move:evaluation(ai, cfg)
local units = wesnoth.get_units { side = wesnoth.current.side }
for i,u in ipairs(units) do
if (u.moves > 0) then return cfg.ca_score end
end
local units = wesnoth.get_units { side = wesnoth.current.side }
for i,u in ipairs(units) do
if (u.moves > 0) then return cfg.ca_score end
end
return 0
return 0
end
function swarm_move:execution(ai, cfg)
local enemy_distance = cfg.enemy_distance or 5
local vision_distance = cfg.vision_distance or 12
local enemy_distance = cfg.enemy_distance or 5
local vision_distance = cfg.vision_distance or 12
-- If no close enemies, swarm will move semi-randomly, staying close together, but away from enemies
local all_units = wesnoth.get_units { side = wesnoth.current.side }
local units, units_no_moves = {}, {}
for i,u in ipairs(all_units) do
if (u.moves > 0) then
table.insert(units, u)
else
table.insert(units_no_moves, u)
end
end
-- If no close enemies, swarm will move semi-randomly, staying close together, but away from enemies
local all_units = wesnoth.get_units { side = wesnoth.current.side }
local units, units_no_moves = {}, {}
for i,u in ipairs(all_units) do
if (u.moves > 0) then
table.insert(units, u)
else
table.insert(units_no_moves, u)
end
end
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
--print('#units, #units_no_moves, #enemies', #units, #units_no_moves, #enemies)
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
--print('#units, #units_no_moves, #enemies', #units, #units_no_moves, #enemies)
-- pick a random unit and remove it from 'units'
local rand = AH.random(#units)
local unit = units[rand]
table.remove(units, rand)
-- pick a random unit and remove it from 'units'
local rand = AH.random(#units)
local unit = units[rand]
table.remove(units, rand)
-- Find best place for that unit to move to
local best_hex = AH.find_best_move(unit, function(x, y)
local rating = 0
-- Find best place for that unit to move to
local best_hex = AH.find_best_move(unit, function(x, y)
local rating = 0
-- Only units within 'vision_distance' count for rejoining
local close_units_no_moves = {}
for i,u in ipairs(units_no_moves) do
if (H.distance_between(unit.x, unit.y, u.x, u.y) <= vision_distance) then
table.insert(close_units_no_moves, u)
end
end
-- Only units within 'vision_distance' count for rejoining
local close_units_no_moves = {}
for i,u in ipairs(units_no_moves) do
if (H.distance_between(unit.x, unit.y, u.x, u.y) <= vision_distance) then
table.insert(close_units_no_moves, u)
end
end
-- If all units on the side have moves left, simply go to a hex far away
if (not close_units_no_moves[1]) then
rating = rating + H.distance_between(x, y, unit.x, unit.y)
else -- otherwise, minimize distance from units that have already moved
for i,u in ipairs(close_units_no_moves) do
rating = rating - H.distance_between(x, y, u.x, u.y)
end
end
-- If all units on the side have moves left, simply go to a hex far away
if (not close_units_no_moves[1]) then
rating = rating + H.distance_between(x, y, unit.x, unit.y)
else -- otherwise, minimize distance from units that have already moved
for i,u in ipairs(close_units_no_moves) do
rating = rating - H.distance_between(x, y, u.x, u.y)
end
end
-- We also try to stay out of attack range of any enemy
for i,e in ipairs(enemies) do
local dist = H.distance_between(x, y, e.x, e.y)
-- If enemy is within attack range, avoid those hexes
if (dist < enemy_distance) then
rating = rating - (enemy_distance - dist) * 10.
end
end
-- We also try to stay out of attack range of any enemy
for i,e in ipairs(enemies) do
local dist = H.distance_between(x, y, e.x, e.y)
-- If enemy is within attack range, avoid those hexes
if (dist < enemy_distance) then
rating = rating - (enemy_distance - dist) * 10.
end
end
return rating
end)
return rating
end)
AH.movefull_stopunit(ai, unit, best_hex)
AH.movefull_stopunit(ai, unit, best_hex)
end
return swarm_move

View file

@ -4,65 +4,65 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local swarm_scatter = {}
function swarm_scatter:evaluation(ai, cfg)
local scatter_distance = cfg.scatter_distance or 3
local scatter_distance = cfg.scatter_distance or 3
-- Any enemy within "scatter_distance" hexes of a unit will cause swarm to scatter
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
}
}
-- Any enemy within "scatter_distance" hexes of a unit will cause swarm to scatter
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
}
}
if enemies[1] then -- don't use 'formula=' for moves, it's slow
local units = wesnoth.get_units { side = wesnoth.current.side }
for i,u in ipairs(units) do
if (u.moves > 0) then return cfg.ca_score end
end
end
if enemies[1] then -- don't use 'formula=' for moves, it's slow
local units = wesnoth.get_units { side = wesnoth.current.side }
for i,u in ipairs(units) do
if (u.moves > 0) then return cfg.ca_score end
end
end
return 0
return 0
end
function swarm_scatter:execution(ai, cfg)
local scatter_distance = cfg.scatter_distance or 3
local vision_distance = cfg.vision_distance or 12
local scatter_distance = cfg.scatter_distance or 3
local vision_distance = cfg.vision_distance or 12
-- Any enemy within "scatter_distance" hexes of a unit will cause swarm to scatter
local units = wesnoth.get_units { side = wesnoth.current.side }
for i = #units,1,-1 do
if (units[i].moves == 0) then table.remove(units, i) end
end
-- Any enemy within "scatter_distance" hexes of a unit will cause swarm to scatter
local units = wesnoth.get_units { side = wesnoth.current.side }
for i = #units,1,-1 do
if (units[i].moves == 0) then table.remove(units, i) end
end
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
}
}
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
}
}
-- In this case we simply maximize the distance from all these close enemies
-- but only for units that are within 'vision_distance' of one of those enemies
for i,unit in ipairs(units) do
local unit_enemies = {}
for i,e in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= vision_distance) then
table.insert(unit_enemies, e)
end
end
-- In this case we simply maximize the distance from all these close enemies
-- but only for units that are within 'vision_distance' of one of those enemies
for i,unit in ipairs(units) do
local unit_enemies = {}
for i,e in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= vision_distance) then
table.insert(unit_enemies, e)
end
end
if unit_enemies[1] then
local best_hex = AH.find_best_move(unit, function(x, y)
local rating = 0
for i,e in ipairs(unit_enemies) do
rating = rating + H.distance_between(x, y, e.x, e.y)
end
return rating
end)
if unit_enemies[1] then
local best_hex = AH.find_best_move(unit, function(x, y)
local rating = 0
for i,e in ipairs(unit_enemies) do
rating = rating + H.distance_between(x, y, e.x, e.y)
end
return rating
end)
AH.movefull_stopunit(ai, unit, best_hex)
end
end
AH.movefull_stopunit(ai, unit, best_hex)
end
end
end
return swarm_scatter

View file

@ -6,99 +6,99 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_wolves_move = {}
function ca_wolves_move:evaluation(ai, cfg)
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
local prey = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "and", cfg.filter_second }
}
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
local prey = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "and", cfg.filter_second }
}
if wolves[1] and prey[1] then return cfg.ca_score end
return 0
if wolves[1] and prey[1] then return cfg.ca_score end
return 0
end
function ca_wolves_move:execution(ai, cfg)
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
local prey = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "and", cfg.filter_second }
}
--print('#wolves, prey', #wolves, #prey)
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
local prey = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "and", cfg.filter_second }
}
--print('#wolves, prey', #wolves, #prey)
-- When wandering (later) they avoid dogs, but not here
local avoid_units = wesnoth.get_units { type = cfg.avoid_type,
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
--print('#avoid_units', #avoid_units)
-- negative hit for hexes these types of units can attack
local avoid = BC.get_attack_map(avoid_units).units -- max_moves=true is always set for enemy units
-- When wandering (later) they avoid dogs, but not here
local avoid_units = wesnoth.get_units { type = cfg.avoid_type,
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
--print('#avoid_units', #avoid_units)
-- negative hit for hexes these types of units can attack
local avoid = BC.get_attack_map(avoid_units).units -- max_moves=true is always set for enemy units
-- Find prey that is closest to all 3 wolves
local target, min_dist = {}, 9999
for i,p in ipairs(prey) do
local dist = 0
for j,w in ipairs(wolves) do
dist = dist + H.distance_between(w.x, w.y, p.x, p.y)
end
if (dist < min_dist) then
min_dist, target = dist, p
end
end
--print('target:', target.x, target.y, target.id)
-- Find prey that is closest to all 3 wolves
local target, min_dist = {}, 9999
for i,p in ipairs(prey) do
local dist = 0
for j,w in ipairs(wolves) do
dist = dist + H.distance_between(w.x, w.y, p.x, p.y)
end
if (dist < min_dist) then
min_dist, target = dist, p
end
end
--print('target:', target.x, target.y, target.id)
-- Now sort wolf from furthest to closest
table.sort(wolves, function(a, b)
return H.distance_between(a.x, a.y, target.x, target.y) > H.distance_between(b.x, b.y, target.x, target.y)
end)
-- Now sort wolf from furthest to closest
table.sort(wolves, function(a, b)
return H.distance_between(a.x, a.y, target.x, target.y) > H.distance_between(b.x, b.y, target.x, target.y)
end)
-- First wolf moves toward target, but tries to stay away from map edges
local w,h,b = wesnoth.get_map_size()
local wolf1 = AH.find_best_move(wolves[1], function(x, y)
local d_1t = H.distance_between(x, y, target.x, target.y)
local rating = -d_1t
if x <= 5 then rating = rating - (6 - x) / 1.4 end
if y <= 5 then rating = rating - (6 - y) / 1.4 end
if (w - x) <= 5 then rating = rating - (6 - (w - x)) / 1.4 end
if (h - y) <= 5 then rating = rating - (6 - (h - y)) / 1.4 end
-- First wolf moves toward target, but tries to stay away from map edges
local w,h,b = wesnoth.get_map_size()
local wolf1 = AH.find_best_move(wolves[1], function(x, y)
local d_1t = H.distance_between(x, y, target.x, target.y)
local rating = -d_1t
if x <= 5 then rating = rating - (6 - x) / 1.4 end
if y <= 5 then rating = rating - (6 - y) / 1.4 end
if (w - x) <= 5 then rating = rating - (6 - (w - x)) / 1.4 end
if (h - y) <= 5 then rating = rating - (6 - (h - y)) / 1.4 end
-- Hexes that avoid_type units can reach get a massive negative hit
-- meaning that they will only ever be chosen if there's no way around them
if avoid:get(x, y) then rating = rating - 1000 end
-- Hexes that avoid_type units can reach get a massive negative hit
-- meaning that they will only ever be chosen if there's no way around them
if avoid:get(x, y) then rating = rating - 1000 end
return rating
end)
--print('wolf 1 ->', wolves[1].x, wolves[1].y, wolf1[1], wolf1[2])
--W.message { speaker = wolves[1].id, message = "Me first"}
AH.movefull_stopunit(ai, wolves[1], wolf1)
return rating
end)
--print('wolf 1 ->', wolves[1].x, wolves[1].y, wolf1[1], wolf1[2])
--W.message { speaker = wolves[1].id, message = "Me first"}
AH.movefull_stopunit(ai, wolves[1], wolf1)
for i = 2,#wolves do
move = AH.find_best_move(wolves[i], function(x,y)
local rating = 0
for i = 2,#wolves do
move = AH.find_best_move(wolves[i], function(x,y)
local rating = 0
-- We ideally want wolves to be 2-3 hexes from each other
-- but this requirement gets weaker and weaker with increasing wolf number
for j = 1,i-1 do
local dst = H.distance_between(x, y, wolves[j].x, wolves[j].y)
rating = rating - (dst - 2.7 * j)^2 / j
end
-- We ideally want wolves to be 2-3 hexes from each other
-- but this requirement gets weaker and weaker with increasing wolf number
for j = 1,i-1 do
local dst = H.distance_between(x, y, wolves[j].x, wolves[j].y)
rating = rating - (dst - 2.7 * j)^2 / j
end
-- Same distance from Wolf 1 and target for all the wolves
local dst_t = H.distance_between(x, y, target.x, target.y)
local dst_1t = H.distance_between(wolf1[1], wolf1[2], target.x, target.y)
rating = rating - (dst_t - dst_1t)^2
-- Same distance from Wolf 1 and target for all the wolves
local dst_t = H.distance_between(x, y, target.x, target.y)
local dst_1t = H.distance_between(wolf1[1], wolf1[2], target.x, target.y)
rating = rating - (dst_t - dst_1t)^2
-- Hexes that avoid_type units can reach get a massive negative hit
-- meaning that they will only ever be chosen if there's no way around them
if avoid:get(x, y) then rating = rating - 1000 end
-- Hexes that avoid_type units can reach get a massive negative hit
-- meaning that they will only ever be chosen if there's no way around them
if avoid:get(x, y) then rating = rating - 1000 end
return rating
end)
return rating
end)
AH.movefull_stopunit(ai, wolves[i], move)
end
AH.movefull_stopunit(ai, wolves[i], move)
end
end
return ca_wolves_move

View file

@ -7,188 +7,188 @@ local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lu
local ca_wolves_multipacks_attack = {}
function ca_wolves_multipacks_attack:evaluation(ai, cfg)
local unit_type = cfg.type or "Wolf"
local unit_type = cfg.type or "Wolf"
-- If wolves have attacks left, call this CA
-- It will generally be disabled by being black-listed, so as to avoid
-- having to do the full attack evaluation for every single move
local wolves = wesnoth.get_units { side = wesnoth.current.side, type = unit_type, formula = '$this_unit.attacks_left > 0' }
-- If wolves have attacks left, call this CA
-- It will generally be disabled by being black-listed, so as to avoid
-- having to do the full attack evaluation for every single move
local wolves = wesnoth.get_units { side = wesnoth.current.side, type = unit_type, formula = '$this_unit.attacks_left > 0' }
if wolves[1] then return cfg.ca_score end
return 0
if wolves[1] then return cfg.ca_score end
return 0
end
function ca_wolves_multipacks_attack:execution(ai, cfg)
-- First get all the packs
local packs = WMPF.assign_packs(cfg)
-- First get all the packs
local packs = WMPF.assign_packs(cfg)
-- Attacks are dealt with on a pack by pack basis
-- and I want all wolves in a pack to move first, before going on to the next pack
-- which makes this slightly more complicated than it would be otherwise
for pack_number,pack in pairs(packs) do
-- Attacks are dealt with on a pack by pack basis
-- and I want all wolves in a pack to move first, before going on to the next pack
-- which makes this slightly more complicated than it would be otherwise
for pack_number,pack in pairs(packs) do
local keep_attacking_this_pack = true -- whether there might be attacks left
local pack_attacked = false -- whether an attack by the pack has happened
local keep_attacking_this_pack = true -- whether there might be attacks left
local pack_attacked = false -- whether an attack by the pack has happened
-- This repeats until all wolves in a pack have attacked, or none can attack any more
while keep_attacking_this_pack do
-- Get the wolves in the pack ...
local wolves, attacks = {}, {}
for i,p in ipairs(pack) do
-- Wolf might have moved in previous attack -> use id to identify it
local wolf = wesnoth.get_units { id = p.id }
-- Wolf could have died in previous attack
-- and only include wolves with attacks left to calc. possible attacks
if wolf[1] and (wolf[1].attacks_left > 0) then table.insert(wolves, wolf[1]) end
end
-- This repeats until all wolves in a pack have attacked, or none can attack any more
while keep_attacking_this_pack do
-- Get the wolves in the pack ...
local wolves, attacks = {}, {}
for i,p in ipairs(pack) do
-- Wolf might have moved in previous attack -> use id to identify it
local wolf = wesnoth.get_units { id = p.id }
-- Wolf could have died in previous attack
-- and only include wolves with attacks left to calc. possible attacks
if wolf[1] and (wolf[1].attacks_left > 0) then table.insert(wolves, wolf[1]) end
end
-- ... and check if any targets are in reach
local attacks = {}
if wolves[1] then attacks = AH.get_attacks(wolves, { simulate_combat = true }) end
--print('pack, wolves, attacks:', pack_number, #wolves, #attacks)
-- ... and check if any targets are in reach
local attacks = {}
if wolves[1] then attacks = AH.get_attacks(wolves, { simulate_combat = true }) end
--print('pack, wolves, attacks:', pack_number, #wolves, #attacks)
-- Eliminate targets that would split up the wolves by more than 3 hexes
-- This also takes care of wolves joining as a pack rather than attacking individually
for i=#attacks,1,-1 do
--print(i, attacks[i].x, attacks[i].y)
for j,w in ipairs(wolves) do
local nh = AH.next_hop(w, attacks[i].dst.x, attacks[i].dst.y)
local d = H.distance_between(nh[1], nh[2], attacks[i].dst.x, attacks[i].dst.y)
--print(' ', i, w.x, w.y, d)
if d > 3 then
table.remove(attacks, i)
--print('Removing attack')
break
end
end
end
--print('-> pack, wolves, attacks:', pack_number, #wolves, #attacks)
-- Eliminate targets that would split up the wolves by more than 3 hexes
-- This also takes care of wolves joining as a pack rather than attacking individually
for i=#attacks,1,-1 do
--print(i, attacks[i].x, attacks[i].y)
for j,w in ipairs(wolves) do
local nh = AH.next_hop(w, attacks[i].dst.x, attacks[i].dst.y)
local d = H.distance_between(nh[1], nh[2], attacks[i].dst.x, attacks[i].dst.y)
--print(' ', i, w.x, w.y, d)
if d > 3 then
table.remove(attacks, i)
--print('Removing attack')
break
end
end
end
--print('-> pack, wolves, attacks:', pack_number, #wolves, #attacks)
-- If valid attacks were found for this pack
if attacks[1] then
-- Figure out how many different wolves can reach each target, and on how many hexes
-- The target with the largest value for the smaller of these two numbers is chosen
-- This is not an exact method, but good enough in most cases
local diff_wolves, diff_hexes = {}, {}
for i,a in ipairs(attacks) do
-- Number different wolves
local att_xy = a.src.x + a.src.y * 1000
local def_xy = a.target.x + a.target.y * 1000
if (not diff_wolves[def_xy]) then diff_wolves[def_xy] = {} end
diff_wolves[def_xy][att_xy] = 1
-- Number different hexes
if (not diff_hexes[def_xy]) then diff_hexes[def_xy] = {} end
diff_hexes[def_xy][a.dst.x + a.dst.y * 1000] = 1
end
-- If valid attacks were found for this pack
if attacks[1] then
-- Figure out how many different wolves can reach each target, and on how many hexes
-- The target with the largest value for the smaller of these two numbers is chosen
-- This is not an exact method, but good enough in most cases
local diff_wolves, diff_hexes = {}, {}
for i,a in ipairs(attacks) do
-- Number different wolves
local att_xy = a.src.x + a.src.y * 1000
local def_xy = a.target.x + a.target.y * 1000
if (not diff_wolves[def_xy]) then diff_wolves[def_xy] = {} end
diff_wolves[def_xy][att_xy] = 1
-- Number different hexes
if (not diff_hexes[def_xy]) then diff_hexes[def_xy] = {} end
diff_hexes[def_xy][a.dst.x + a.dst.y * 1000] = 1
end
-- Find which target can be attacked by the most units, from the most hexes; and rate by fewest HP if equal
local max_rating, best_target = -9e99, {}
for k,t in pairs(diff_wolves) do
local n_w, n_h = 0, 0
for k1,w in pairs(t) do n_w = n_w + 1 end
for k2,h in pairs(diff_hexes[k]) do n_h = n_h + 1 end
local rating = math.min(n_w, n_h)
-- Find which target can be attacked by the most units, from the most hexes; and rate by fewest HP if equal
local max_rating, best_target = -9e99, {}
for k,t in pairs(diff_wolves) do
local n_w, n_h = 0, 0
for k1,w in pairs(t) do n_w = n_w + 1 end
for k2,h in pairs(diff_hexes[k]) do n_h = n_h + 1 end
local rating = math.min(n_w, n_h)
local target = wesnoth.get_unit( k % 1000, math.floor(k / 1000))
rating = rating - target.hitpoints / 100.
local target = wesnoth.get_unit( k % 1000, math.floor(k / 1000))
rating = rating - target.hitpoints / 100.
-- Also, any target sitting next to a wolf of the same pack that has
-- no attacks left is priority targeted (in order to stick with
-- the same target for all wolves of the pack)
for x, y in H.adjacent_tiles(target.x, target.y) do
local adj_unit = wesnoth.get_unit(x, y)
if adj_unit and (adj_unit.variables.pack == pack_number)
and (adj_unit.side == wesnoth.current.side) and (adj_unit.attacks_left == 0)
then
rating = rating + 10 -- very strongly favors this target
end
end
-- Also, any target sitting next to a wolf of the same pack that has
-- no attacks left is priority targeted (in order to stick with
-- the same target for all wolves of the pack)
for x, y in H.adjacent_tiles(target.x, target.y) do
local adj_unit = wesnoth.get_unit(x, y)
if adj_unit and (adj_unit.variables.pack == pack_number)
and (adj_unit.side == wesnoth.current.side) and (adj_unit.attacks_left == 0)
then
rating = rating + 10 -- very strongly favors this target
end
end
--print(k, n_w, n_h, rating)
if rating > max_rating then
max_rating, best_target = rating, target
end
end
--print('Best target:', best_target.id, best_target.x, best_target.y)
--print(k, n_w, n_h, rating)
if rating > max_rating then
max_rating, best_target = rating, target
end
end
--print('Best target:', best_target.id, best_target.x, best_target.y)
-- Now we know what the best target is, we need to attack now
-- This is done on a wolf-by-wolf basis, the outside while loop taking care of
-- the next wolf in the pack on subsequent iterations
local max_rating, best_attack = -9e99, {}
for i,a in ipairs(attacks) do
if (a.target.x == best_target.x) and (a.target.y == best_target.y) then
-- HP outcome is rating, twice as important for target as for attacker
local rating = a.att_stats.average_hp / 2. - a.def_stats.average_hp
if (rating > max_rating) then
max_rating, best_attack = rating, a
end
end
end
-- Now we know what the best target is, we need to attack now
-- This is done on a wolf-by-wolf basis, the outside while loop taking care of
-- the next wolf in the pack on subsequent iterations
local max_rating, best_attack = -9e99, {}
for i,a in ipairs(attacks) do
if (a.target.x == best_target.x) and (a.target.y == best_target.y) then
-- HP outcome is rating, twice as important for target as for attacker
local rating = a.att_stats.average_hp / 2. - a.def_stats.average_hp
if (rating > max_rating) then
max_rating, best_attack = rating, a
end
end
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
W.label { x = attacker.x, y = attacker.y, text = "" }
end
AH.movefull_stopunit(ai, attacker, best_attack.dst.x, best_attack.dst.y)
if cfg.show_pack_number then
WMPF.color_label(attacker.x, attacker.y, pack_number)
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
W.label { x = attacker.x, y = attacker.y, text = "" }
end
AH.movefull_stopunit(ai, attacker, best_attack.dst.x, best_attack.dst.y)
if cfg.show_pack_number then
WMPF.color_label(attacker.x, attacker.y, pack_number)
end
local a_x, a_y, d_x, d_y = attacker.x, attacker.y, defender.x, defender.y
ai.attack(attacker, defender)
-- Remove the labels, if one of the units died
if cfg.show_pack_number then
if (not attacker.valid) then W.label { x = a_x, y = a_y, text = "" } end
if (not defender.valid) then W.label { x = d_x, y = d_y, text = "" } end
end
local a_x, a_y, d_x, d_y = attacker.x, attacker.y, defender.x, defender.y
ai.attack(attacker, defender)
-- Remove the labels, if one of the units died
if cfg.show_pack_number then
if (not attacker.valid) then W.label { x = a_x, y = a_y, text = "" } end
if (not defender.valid) then W.label { x = d_x, y = d_y, text = "" } end
end
pack_attacked = true -- This pack has done an attack
else
keep_attacking_this_pack = false -- no more valid attacks found
end
end
pack_attacked = true -- This pack has done an attack
else
keep_attacking_this_pack = false -- no more valid attacks found
end
end
-- Finally, if any of the wolves in this pack did attack, move the rest of the pack in close
if pack_attacked then
local wolves_moves, wolves_no_moves = {}, {}
for i,p in ipairs(pack) do
-- Wolf might have moved in previous attack -> use id to identify it
local wolf = wesnoth.get_unit(p.x, p.y)
-- Wolf could have died in previous attack
if wolf then
if (wolf.moves > 0) then
table.insert(wolves_moves, wolf)
else
table.insert(wolves_no_moves, wolf)
end
end
end
--print('#wolves_moves, #wolves_no_moves', #wolves_moves, #wolves_no_moves)
-- Finally, if any of the wolves in this pack did attack, move the rest of the pack in close
if pack_attacked then
local wolves_moves, wolves_no_moves = {}, {}
for i,p in ipairs(pack) do
-- Wolf might have moved in previous attack -> use id to identify it
local wolf = wesnoth.get_unit(p.x, p.y)
-- Wolf could have died in previous attack
if wolf then
if (wolf.moves > 0) then
table.insert(wolves_moves, wolf)
else
table.insert(wolves_no_moves, wolf)
end
end
end
--print('#wolves_moves, #wolves_no_moves', #wolves_moves, #wolves_no_moves)
-- If we have both wolves that have moved and those that have not moved,
-- move the latter toward the former
if wolves_moves[1] and wolves_no_moves[1] then
--print('Collecting stragglers')
for i,w in ipairs(wolves_moves) do
local best_hex = AH.find_best_move(w, function(x, y)
local rating = 0
for j,w_nm in ipairs(wolves_no_moves) do
rating = rating - H.distance_between(x, y, w_nm.x, w_nm.y)
end
return rating
end)
if cfg.show_pack_number then
W.label { x = w.x, y = w.y, text = "" }
end
AH.movefull_stopunit(ai, w, best_hex)
if cfg.show_pack_number then
WMPF.color_label(w.x, w.y, pack_number)
end
end
end
end
end
-- If we have both wolves that have moved and those that have not moved,
-- move the latter toward the former
if wolves_moves[1] and wolves_no_moves[1] then
--print('Collecting stragglers')
for i,w in ipairs(wolves_moves) do
local best_hex = AH.find_best_move(w, function(x, y)
local rating = 0
for j,w_nm in ipairs(wolves_no_moves) do
rating = rating - H.distance_between(x, y, w_nm.x, w_nm.y)
end
return rating
end)
if cfg.show_pack_number then
W.label { x = w.x, y = w.y, text = "" }
end
AH.movefull_stopunit(ai, w, best_hex)
if cfg.show_pack_number then
WMPF.color_label(w.x, w.y, pack_number)
end
end
end
end
end
end
return ca_wolves_multipacks_attack

View file

@ -4,139 +4,139 @@ local W = H.set_wml_action_metatable {}
local wolves_multipacks_functions = {}
function wolves_multipacks_functions.color_label(x, y, text)
-- For displaying the wolf pack number in color underneath each wolf
-- only using gray for the time being
text = "<span color='#c0c0c0'>" .. text .. "</span>"
W.label{ x = x, y = y, text = text }
-- For displaying the wolf pack number in color underneath each wolf
-- only using gray for the time being
text = "<span color='#c0c0c0'>" .. text .. "</span>"
W.label{ x = x, y = y, text = text }
end
function wolves_multipacks_functions.assign_packs(cfg)
local unit_type = cfg.type or "Wolf"
local pack_size = cfg.pack_size or 3
local unit_type = cfg.type or "Wolf"
local pack_size = cfg.pack_size or 3
-- Assign the pack numbers to each wolf. Keeps numbers of existing packs
-- (unless pack size is down to one). Pack number is stored in wolf unit variables
-- Also returns a table with the packs (locations and id's of each wolf in a pack)
local wolves = wesnoth.get_units { side = wesnoth.current.side, type = unit_type }
--print('#wolves:', #wolves)
-- Assign the pack numbers to each wolf. Keeps numbers of existing packs
-- (unless pack size is down to one). Pack number is stored in wolf unit variables
-- Also returns a table with the packs (locations and id's of each wolf in a pack)
local wolves = wesnoth.get_units { side = wesnoth.current.side, type = unit_type }
--print('#wolves:', #wolves)
-- Array for holding the packs
local packs = {}
-- Find wolves that already have a pack number assigned
for i,w in ipairs(wolves) do
if w.variables.pack then
if (not packs[w.variables.pack]) then packs[w.variables.pack] = {} end
table.insert(packs[w.variables.pack], { x = w.x, y = w.y, id = w.id })
end
end
-- Array for holding the packs
local packs = {}
-- Find wolves that already have a pack number assigned
for i,w in ipairs(wolves) do
if w.variables.pack then
if (not packs[w.variables.pack]) then packs[w.variables.pack] = {} end
table.insert(packs[w.variables.pack], { x = w.x, y = w.y, id = w.id })
end
end
-- Remove packs of one
-- Pack numbers might not be consecutive after a while -> need pairs(), not ipairs()
for k,p in pairs(packs) do
--print(' have pack:', k, ' #members:', #p)
if (#p == 1) then
local wolf = wesnoth.get_unit(p[1].x, p[1].y)
wolf.variables.pack, wolf.variables.goal_x, wolf.variables.goal_y = nil, nil, nil
packs[k] = nil
end
end
--print('After removing packs of 1')
--for k,p in pairs(packs) do print(' have pack:', k, ' #members:', #p) end
-- Remove packs of one
-- Pack numbers might not be consecutive after a while -> need pairs(), not ipairs()
for k,p in pairs(packs) do
--print(' have pack:', k, ' #members:', #p)
if (#p == 1) then
local wolf = wesnoth.get_unit(p[1].x, p[1].y)
wolf.variables.pack, wolf.variables.goal_x, wolf.variables.goal_y = nil, nil, nil
packs[k] = nil
end
end
--print('After removing packs of 1')
--for k,p in pairs(packs) do print(' have pack:', k, ' #members:', #p) end
-- Wolves that are not in a pack (new ones or those removed above)
local nopack_wolves = {}
for i,w in ipairs(wolves) do
if (not w.variables.pack) then
table.insert(nopack_wolves, w)
-- Also erase any goal one of these might have
w.variables.pack, w.variables.goal_x, w.variables.goal_y = nil, nil, nil
end
end
--print('#nopack_wolves:', #nopack_wolves)
-- Wolves that are not in a pack (new ones or those removed above)
local nopack_wolves = {}
for i,w in ipairs(wolves) do
if (not w.variables.pack) then
table.insert(nopack_wolves, w)
-- Also erase any goal one of these might have
w.variables.pack, w.variables.goal_x, w.variables.goal_y = nil, nil, nil
end
end
--print('#nopack_wolves:', #nopack_wolves)
-- Now assign the nopack wolves to packs
-- First, go through packs that have less than pack_size members
for k,p in pairs(packs) do
if (#p < pack_size) then
local min_dist, best_wolf, best_ind = 9e99, {}, -1
for i,w in ipairs(nopack_wolves) do
local d1 = H.distance_between(w.x, w.y, p[1].x, p[1].y)
local d2 = H.distance_between(w.x, w.y, p[2].x, p[2].y)
if (d1 + d2 < min_dist) then
min_dist = d1 + d2
best_wolf, best_ind = w, i
end
end
if (min_dist < 9e99) then
table.insert(packs[k], { x = best_wolf.x, y = best_wolf.y, id = best_wolf.id })
best_wolf.variables.pack = k
table.remove(nopack_wolves, best_ind)
end
end
end
--print('After completing packs of 2')
--for k,p in pairs(packs) do print(' have pack:', k, ' #members:', #p) end
-- Now assign the nopack wolves to packs
-- First, go through packs that have less than pack_size members
for k,p in pairs(packs) do
if (#p < pack_size) then
local min_dist, best_wolf, best_ind = 9e99, {}, -1
for i,w in ipairs(nopack_wolves) do
local d1 = H.distance_between(w.x, w.y, p[1].x, p[1].y)
local d2 = H.distance_between(w.x, w.y, p[2].x, p[2].y)
if (d1 + d2 < min_dist) then
min_dist = d1 + d2
best_wolf, best_ind = w, i
end
end
if (min_dist < 9e99) then
table.insert(packs[k], { x = best_wolf.x, y = best_wolf.y, id = best_wolf.id })
best_wolf.variables.pack = k
table.remove(nopack_wolves, best_ind)
end
end
end
--print('After completing packs of 2')
--for k,p in pairs(packs) do print(' have pack:', k, ' #members:', #p) end
-- Second, group remaining single wolves
-- At the beginning of the scenario, this is all wolves
while (#nopack_wolves > 0) do
--print('Grouping the remaining wolves', #nopack_wolves)
-- First find the first available pack number
new_pack = 1
while packs[new_pack] do new_pack = new_pack + 1 end
--print('Building pack', new_pack)
-- Second, group remaining single wolves
-- At the beginning of the scenario, this is all wolves
while (#nopack_wolves > 0) do
--print('Grouping the remaining wolves', #nopack_wolves)
-- First find the first available pack number
new_pack = 1
while packs[new_pack] do new_pack = new_pack + 1 end
--print('Building pack', new_pack)
-- If there are <=pack_size wolves left, that's the pack (we also assign a single wolf to a 1-wolf pack here)
if (#nopack_wolves <= pack_size) then
--print('<=pack_size nopack wolves left', #nopack_wolves)
packs[new_pack] = {}
for i,w in ipairs(nopack_wolves) do
table.insert(packs[new_pack], { x = w.x, y = w.y, id = w.id })
w.variables.pack = new_pack
end
break
end
-- If there are <=pack_size wolves left, that's the pack (we also assign a single wolf to a 1-wolf pack here)
if (#nopack_wolves <= pack_size) then
--print('<=pack_size nopack wolves left', #nopack_wolves)
packs[new_pack] = {}
for i,w in ipairs(nopack_wolves) do
table.insert(packs[new_pack], { x = w.x, y = w.y, id = w.id })
w.variables.pack = new_pack
end
break
end
-- If more than pack_size wolves left, find those that are closest together
-- They form the next pack
--print('More than pack_size nopack wolves left', #nopack_wolves)
local best_wolves = {}
while #best_wolves < pack_size do
local min_dist, best_wolf, best_wolf_i = 9999, {}, -1
for i,tw in ipairs(nopack_wolves) do
local dist = 0
for j,sw in ipairs(best_wolves) do
dist = dist + H.distance_between(tw.x, tw.y, sw.x, sw.y)
end
if dist < min_dist then
min_dist, best_wolf, best_wolf_i = dist, tw, i
end
end
table.insert(best_wolves, best_wolf)
table.remove(nopack_wolves, best_wolf_i)
end
-- Now insert the best pack into that 'packs' array
packs[new_pack] = {}
-- Need to count down for table.remove to work correctly
for i = pack_size,1,-1 do
table.insert(packs[new_pack], { x = best_wolves[i].x, y = best_wolves[i].y, id = best_wolves[i].id })
best_wolves[i].variables.pack = new_pack
end
end
--print('After grouping remaining single wolves')
--for k,p in pairs(packs) do print(' have pack:', k, ' #members:', #p) end
-- If more than pack_size wolves left, find those that are closest together
-- They form the next pack
--print('More than pack_size nopack wolves left', #nopack_wolves)
local best_wolves = {}
while #best_wolves < pack_size do
local min_dist, best_wolf, best_wolf_i = 9999, {}, -1
for i,tw in ipairs(nopack_wolves) do
local dist = 0
for j,sw in ipairs(best_wolves) do
dist = dist + H.distance_between(tw.x, tw.y, sw.x, sw.y)
end
if dist < min_dist then
min_dist, best_wolf, best_wolf_i = dist, tw, i
end
end
table.insert(best_wolves, best_wolf)
table.remove(nopack_wolves, best_wolf_i)
end
-- Now insert the best pack into that 'packs' array
packs[new_pack] = {}
-- Need to count down for table.remove to work correctly
for i = pack_size,1,-1 do
table.insert(packs[new_pack], { x = best_wolves[i].x, y = best_wolves[i].y, id = best_wolves[i].id })
best_wolves[i].variables.pack = new_pack
end
end
--print('After grouping remaining single wolves')
--for k,p in pairs(packs) do print(' have pack:', k, ' #members:', #p) end
-- Put labels out there for all wolves
if cfg.show_pack_number then
for k,p in pairs(packs) do
for i,loc in ipairs(p) do
wolves_multipacks_functions.color_label(loc.x, loc.y, k)
end
end
end
-- Put labels out there for all wolves
if cfg.show_pack_number then
for k,p in pairs(packs) do
for i,loc in ipairs(p) do
wolves_multipacks_functions.color_label(loc.x, loc.y, k)
end
end
end
return packs
return packs
end
return wolves_multipacks_functions

View file

@ -7,136 +7,136 @@ local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lu
local ca_wolves_multipacks_wander = {}
function ca_wolves_multipacks_wander:evaluation(ai, cfg)
local unit_type = cfg.type or "Wolf"
local unit_type = cfg.type or "Wolf"
-- When there's nothing to attack, the wolves wander and regroup into their packs
local wolves = wesnoth.get_units { side = wesnoth.current.side, type = unit_type, formula = '$this_unit.moves > 0' }
-- When there's nothing to attack, the wolves wander and regroup into their packs
local wolves = wesnoth.get_units { side = wesnoth.current.side, type = unit_type, formula = '$this_unit.moves > 0' }
if wolves[1] then return cfg.ca_score end
return 0
if wolves[1] then return cfg.ca_score end
return 0
end
function ca_wolves_multipacks_wander:execution(ai, cfg)
-- First get all the packs
local packs = WMPF.assign_packs(cfg)
-- First get all the packs
local packs = WMPF.assign_packs(cfg)
for k,pack in pairs(packs) do
-- If any of the wolves has a goal set, this is used for the entire pack
local wolves, goal = {}, {}
for i,loc in ipairs(pack) do
local wolf = wesnoth.get_unit(loc.x, loc.y)
--print(k, i, wolf.id)
table.insert(wolves, wolf)
-- If any of the wolves in the pack has a goal set, we use that one
if wolf.variables.goal_x then
goal = { wolf.variables.goal_x, wolf.variables.goal_y }
end
end
for k,pack in pairs(packs) do
-- If any of the wolves has a goal set, this is used for the entire pack
local wolves, goal = {}, {}
for i,loc in ipairs(pack) do
local wolf = wesnoth.get_unit(loc.x, loc.y)
--print(k, i, wolf.id)
table.insert(wolves, wolf)
-- If any of the wolves in the pack has a goal set, we use that one
if wolf.variables.goal_x then
goal = { wolf.variables.goal_x, wolf.variables.goal_y }
end
end
-- If the position of any of the wolves is at the goal, delete it
for i,w in ipairs(wolves) do
if (w.x == goal[1]) and (w.y == goal[2]) then goal = {} end
end
-- If the position of any of the wolves is at the goal, delete it
for i,w in ipairs(wolves) do
if (w.x == goal[1]) and (w.y == goal[2]) then goal = {} end
end
-- Pack gets a new goal if none exist or on any move with 10% random chance
local r = AH.random(10)
if (not goal[1]) or (r == 1) then
local w,h,b = wesnoth.get_map_size()
local locs = {}
locs = wesnoth.get_locations { x = '1-'..w, y = '1-'..h }
-- Pack gets a new goal if none exist or on any move with 10% random chance
local r = AH.random(10)
if (not goal[1]) or (r == 1) then
local w,h,b = wesnoth.get_map_size()
local locs = {}
locs = wesnoth.get_locations { x = '1-'..w, y = '1-'..h }
-- Need to find reachable terrain for this to be a viable goal
-- We only check whether the first wolf can get there
local unreachable = true
while unreachable do
local rand = AH.random(#locs)
local next_hop = AH.next_hop(wolves[1], locs[rand][1], locs[rand][2])
if next_hop then
goal = { locs[rand][1], locs[rand][2] }
unreachable = nil
end
end
end
--print('Pack goal: ', goal[1], goal[2])
-- Need to find reachable terrain for this to be a viable goal
-- We only check whether the first wolf can get there
local unreachable = true
while unreachable do
local rand = AH.random(#locs)
local next_hop = AH.next_hop(wolves[1], locs[rand][1], locs[rand][2])
if next_hop then
goal = { locs[rand][1], locs[rand][2] }
unreachable = nil
end
end
end
--print('Pack goal: ', goal[1], goal[2])
-- This goal is saved with every wolf of the pack
for i,w in ipairs(wolves) do
w.variables.goal_x, w.variables.goal_y = goal[1], goal[2]
end
-- This goal is saved with every wolf of the pack
for i,w in ipairs(wolves) do
w.variables.goal_x, w.variables.goal_y = goal[1], goal[2]
end
-- The pack wanders with only 2 considerations
-- 1. Keeping the pack together (most important)
-- Going through all combinations of all hexes for all wolves is too expensive
-- -> find hexes that can be reached by all wolves
-- 2. Getting closest to the goal (secondary to 1.)
-- The pack wanders with only 2 considerations
-- 1. Keeping the pack together (most important)
-- Going through all combinations of all hexes for all wolves is too expensive
-- -> find hexes that can be reached by all wolves
-- 2. Getting closest to the goal (secondary to 1.)
-- Number of wolves that can reach each hex,
local reach_map = LS.create()
for i,w in ipairs(wolves) do
local reach = wesnoth.find_reach(w)
for j,loc in ipairs(reach) do
reach_map:insert(loc[1], loc[2], (reach_map:get(loc[1], loc[2]) or 0) + 100)
end
end
-- Number of wolves that can reach each hex,
local reach_map = LS.create()
for i,w in ipairs(wolves) do
local reach = wesnoth.find_reach(w)
for j,loc in ipairs(reach) do
reach_map:insert(loc[1], loc[2], (reach_map:get(loc[1], loc[2]) or 0) + 100)
end
end
-- Keep only those hexes that can be reached by all wolves in the pack
-- and add distance from goal for those
local max_rating, goto_hex = -9e99, {}
reach_map:iter( function(x, y, v)
local rating = reach_map:get(x, y)
if (rating == #pack * 100) then
rating = rating - H.distance_between(x, y, goal[1], goal[2])
reach_map:insert(x,y, rating)
if rating > max_rating then
max_rating, goto_hex = rating, { x, y }
end
else
reach_map:remove(x, y)
end
end)
-- Keep only those hexes that can be reached by all wolves in the pack
-- and add distance from goal for those
local max_rating, goto_hex = -9e99, {}
reach_map:iter( function(x, y, v)
local rating = reach_map:get(x, y)
if (rating == #pack * 100) then
rating = rating - H.distance_between(x, y, goal[1], goal[2])
reach_map:insert(x,y, rating)
if rating > max_rating then
max_rating, goto_hex = rating, { x, y }
end
else
reach_map:remove(x, y)
end
end)
-- Sort wolves by MP, the one with fewest moves goes first
table.sort(wolves, function(a, b) return a.moves < b.moves end)
-- Sort wolves by MP, the one with fewest moves goes first
table.sort(wolves, function(a, b) return a.moves < b.moves end)
-- If there's no hex that all units can reach, use the 'center of gravity' between them
-- Then we move the first wolf (fewest MP) toward that hex, and the position of that wolf
-- becomes the goto coordinates for the others
if (not goto_hex[1]) then
local cg = { 0, 0 } -- Center of gravity hex
for i,w in ipairs(wolves) do
cg = { cg[1] + w.x, cg[2] + w.y }
end
cg[1] = math.floor(cg[1] / #pack)
cg[2] = math.floor(cg[2] / #pack)
--print('cg', cg[1], cg[2])
-- If there's no hex that all units can reach, use the 'center of gravity' between them
-- Then we move the first wolf (fewest MP) toward that hex, and the position of that wolf
-- becomes the goto coordinates for the others
if (not goto_hex[1]) then
local cg = { 0, 0 } -- Center of gravity hex
for i,w in ipairs(wolves) do
cg = { cg[1] + w.x, cg[2] + w.y }
end
cg[1] = math.floor(cg[1] / #pack)
cg[2] = math.floor(cg[2] / #pack)
--print('cg', cg[1], cg[2])
-- Find closest move for Wolf #1 to that position, which then becomes the goto hex
goto_hex = AH.find_best_move(wolves[1], function(x, y)
return -H.distance_between(x, y, cg[1], cg[2])
end)
-- We could move this wolf right here, but for convenience all the actual moves are
-- grouped together below. Speed wise that should not really make a difference, but could be optimized
end
--print('goto_hex', goto_hex[1], goto_hex[2])
--AH.put_labels(reach_map)
-- Find closest move for Wolf #1 to that position, which then becomes the goto hex
goto_hex = AH.find_best_move(wolves[1], function(x, y)
return -H.distance_between(x, y, cg[1], cg[2])
end)
-- We could move this wolf right here, but for convenience all the actual moves are
-- grouped together below. Speed wise that should not really make a difference, but could be optimized
end
--print('goto_hex', goto_hex[1], goto_hex[2])
--AH.put_labels(reach_map)
-- Now all wolves in the pack are moved toward goto_hex, starting with the one with fewest MP
-- Distance to goal hex is taken into account as secondary criterion
for i,w in ipairs(wolves) do
local best_hex = AH.find_best_move(w, function(x, y)
local rating = - H.distance_between(x, y, goto_hex[1], goto_hex[2])
rating = rating - H.distance_between(x, y, goal[1], goal[2]) / 100.
return rating
end)
if cfg.show_pack_number then
W.label { x = w.x, y = w.y, text = "" }
end
AH.movefull_stopunit(ai, w, best_hex)
if cfg.show_pack_number then
WMPF.color_label(w.x, w.y, k)
end
end
end
-- Now all wolves in the pack are moved toward goto_hex, starting with the one with fewest MP
-- Distance to goal hex is taken into account as secondary criterion
for i,w in ipairs(wolves) do
local best_hex = AH.find_best_move(w, function(x, y)
local rating = - H.distance_between(x, y, goto_hex[1], goto_hex[2])
rating = rating - H.distance_between(x, y, goal[1], goal[2]) / 100.
return rating
end)
if cfg.show_pack_number then
W.label { x = w.x, y = w.y, text = "" }
end
AH.movefull_stopunit(ai, w, best_hex)
if cfg.show_pack_number then
WMPF.color_label(w.x, w.y, k)
end
end
end
end
return ca_wolves_multipacks_wander

View file

@ -6,58 +6,58 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_wolves_wander = {}
function ca_wolves_wander:evaluation(ai, cfg)
-- When there's no prey left, the wolves wander and regroup
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
-- When there's no prey left, the wolves wander and regroup
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
if wolves[1] then return cfg.ca_score end
return 0
if wolves[1] then return cfg.ca_score end
return 0
end
function ca_wolves_wander:execution(ai, cfg)
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
-- Number of wolves that can reach each hex
local reach_map = LS.create()
for i,w in ipairs(wolves) do
local r = AH.get_reachable_unocc(w)
reach_map:union_merge(r, function(x, y, v1, v2) return (v1 or 0) + (v2 or 0) end)
end
-- Number of wolves that can reach each hex
local reach_map = LS.create()
for i,w in ipairs(wolves) do
local r = AH.get_reachable_unocc(w)
reach_map:union_merge(r, function(x, y, v1, v2) return (v1 or 0) + (v2 or 0) end)
end
-- Add a random rating; avoid avoid_type units
local avoid_units = wesnoth.get_units { type = cfg.avoid_type,
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
--print('#avoid_units', #avoid_units)
-- negative hit for hexes these units can attack
local avoid = BC.get_attack_map(avoid_units).units
-- Add a random rating; avoid avoid_type units
local avoid_units = wesnoth.get_units { type = cfg.avoid_type,
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
--print('#avoid_units', #avoid_units)
-- negative hit for hexes these units can attack
local avoid = BC.get_attack_map(avoid_units).units
local max_rating, goal_hex = -9e99, {}
reach_map:iter( function (x, y, v)
local rating = v + AH.random(99)/100.
if avoid:get(x, y) then rating = rating - 1000 end
local max_rating, goal_hex = -9e99, {}
reach_map:iter( function (x, y, v)
local rating = v + AH.random(99)/100.
if avoid:get(x, y) then rating = rating - 1000 end
if (rating > max_rating) then
max_rating, goal_hex = rating, { x, y }
end
if (rating > max_rating) then
max_rating, goal_hex = rating, { x, y }
end
reach_map:insert(x, y, rating)
end)
--AH.put_labels(reach_map)
--W.message { speaker = 'narrator', message = "Wolves random wander"}
reach_map:insert(x, y, rating)
end)
--AH.put_labels(reach_map)
--W.message { speaker = 'narrator', message = "Wolves random wander"}
for i,w in ipairs(wolves) do
-- For each wolf, we need to check that goal hex is reachable, and out of harm's way
local best_hex = AH.find_best_move(w, function(x, y)
local rating = - H.distance_between(x, y, goal_hex[1], goal_hex[2])
if avoid:get(x, y) then rating = rating - 1000 end
return rating
end)
AH.movefull_stopunit(ai, w, best_hex)
end
for i,w in ipairs(wolves) do
-- For each wolf, we need to check that goal hex is reachable, and out of harm's way
local best_hex = AH.find_best_move(w, function(x, y)
local rating = - H.distance_between(x, y, goal_hex[1], goal_hex[2])
if avoid:get(x, y) then rating = rating - 1000 end
return rating
end)
AH.movefull_stopunit(ai, w, best_hex)
end
end
return ca_wolves_wander

View file

@ -5,159 +5,159 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_zone_guardian = {}
function ca_zone_guardian:evaluation(ai, cfg)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
-- Check if unit exists as sticky BCAs are not always removed successfully
if unit then return cfg.ca_score end
return 0
-- Check if unit exists as sticky BCAs are not always removed successfully
if unit then return cfg.ca_score end
return 0
end
function ca_zone_guardian:execution(ai, cfg)
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local unit
if cfg.filter then
unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0' }
)[1]
else
unit = wesnoth.get_units({ id = cfg.id, formula = '$this_unit.moves > 0' })[1]
end
local reach = wesnoth.find_reach(unit)
local zone_enemy = cfg.filter_location_enemy or cfg.filter_location
-- enemy units within reach
local reach = wesnoth.find_reach(unit)
local zone_enemy = cfg.filter_location_enemy or cfg.filter_location
-- enemy units within reach
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", zone_enemy }
}
if enemies[1] then
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", zone_enemy }
}
if enemies[1] then
local target = {}
local min_dist = 9999
for i,e in ipairs(enemies) do
local dg = H.distance_between(unit.x, unit.y, e.x, e.y)
local target = {}
local min_dist = 9999
for i,e in ipairs(enemies) do
local dg = H.distance_between(unit.x, unit.y, e.x, e.y)
-- If valid target found, save the one with the shortest distance from unit
if (dg < min_dist) then
--print("target:", e.id, ds, dg)
target = e
min_dist = dg
end
end
-- If valid target found, save the one with the shortest distance from unit
if (dg < min_dist) then
--print("target:", e.id, ds, dg)
target = e
min_dist = dg
end
end
-- If a valid target was found, unit attacks this target, or moves toward it
if (min_dist ~= 9999) then
--print ("Go for enemy unit:", target.id)
-- If a valid target was found, unit attacks this target, or moves toward it
if (min_dist ~= 9999) then
--print ("Go for enemy unit:", target.id)
-- Find tiles adjacent to the target, and save the one that our unit
-- can reach with the highest defense rating
local best_defense, attack_loc = -9e99, {}
for x,y in H.adjacent_tiles(target.x, target.y) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = unit.id } } }[1]
if not occ_hex then
-- defense rating of the hex
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
--print(x,y,defense)
local nh = AH.next_hop(unit, x, y)
-- if this is best defense rating and unit can reach it, save this location
if nh then
if (nh[1] == x) and (nh[2] == y) and (defense > best_defense) then
best_defense, attack_loc = defense, {x, y}
end
end
end
end
-- Find tiles adjacent to the target, and save the one that our unit
-- can reach with the highest defense rating
local best_defense, attack_loc = -9e99, {}
for x,y in H.adjacent_tiles(target.x, target.y) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = unit.id } } }[1]
if not occ_hex then
-- defense rating of the hex
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
--print(x,y,defense)
local nh = AH.next_hop(unit, x, y)
-- if this is best defense rating and unit can reach it, save this location
if nh then
if (nh[1] == x) and (nh[2] == y) and (defense > best_defense) then
best_defense, attack_loc = defense, {x, y}
end
end
end
end
-- If a valid hex was found: move there and attack
if (best_defense ~= -9e99) then
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
AH.movefull_stopunit(ai, unit, attack_loc)
ai.attack(unit, target)
else -- otherwise move toward that enemy
--print("Cannot reach target, moving toward it")
local reach = wesnoth.find_reach(unit)
-- If a valid hex was found: move there and attack
if (best_defense ~= -9e99) then
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
AH.movefull_stopunit(ai, unit, attack_loc)
ai.attack(unit, target)
else -- otherwise move toward that enemy
--print("Cannot reach target, moving toward it")
local reach = wesnoth.find_reach(unit)
-- Go through all hexes the unit can reach, find closest to target
local nh = {} -- cannot use next_hop here since target hex is occupied by enemy
local min_dist = 9999
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
local d = H.distance_between(r[1], r[2], target.x, target.y)
if d < min_dist then
min_dist = d
nh = {r[1], r[2]}
end
end
end
-- Go through all hexes the unit can reach, find closest to target
local nh = {} -- cannot use next_hop here since target hex is occupied by enemy
local min_dist = 9999
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
local d = H.distance_between(r[1], r[2], target.x, target.y)
if d < min_dist then
min_dist = d
nh = {r[1], r[2]}
end
end
end
-- Finally, execute the move toward the target
AH.movefull_stopunit(ai, unit, nh)
end
end
-- Finally, execute the move toward the target
AH.movefull_stopunit(ai, unit, nh)
end
end
-- If no enemy around or within the zone, move toward "random" position which are mainy the borders
else
--print "Move toward newpos"
local newpos
-- If cfg.station_x/y are given, move toward that location
if cfg.station_x and cfg.station_y then
newpos = { cfg.station_x, cfg.station_y }
-- Otherwise choose one randomly from those given in filter_location
else
local width, height = wesnoth.get_map_size()
local locs_map = LS.of_pairs(wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", cfg.filter_location }
})
-- If no enemy around or within the zone, move toward "random" position which are mainy the borders
else
--print "Move toward newpos"
local newpos
-- If cfg.station_x/y are given, move toward that location
if cfg.station_x and cfg.station_y then
newpos = { cfg.station_x, cfg.station_y }
-- Otherwise choose one randomly from those given in filter_location
else
local width, height = wesnoth.get_map_size()
local locs_map = LS.of_pairs(wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", cfg.filter_location }
})
-- Check out which of those hexes the unit can reach
local reach_map = LS.of_pairs(wesnoth.find_reach(unit))
reach_map:inter(locs_map)
-- Check out which of those hexes the unit can reach
local reach_map = LS.of_pairs(wesnoth.find_reach(unit))
reach_map:inter(locs_map)
-- If it can reach some hexes, use only reachable locations,
-- otherwise move toward any (random) one from the entire set
if (reach_map:size() > 0) then
locs_map = reach_map
end
-- If it can reach some hexes, use only reachable locations,
-- otherwise move toward any (random) one from the entire set
if (reach_map:size() > 0) then
locs_map = reach_map
end
local locs = locs_map:to_pairs()
local locs = locs_map:to_pairs()
-- If possible locations were found, move unit toward a random one,
-- otherwise the unit stays where it is
if (#locs > 0) then
local newind = math.random(#locs)
newpos = { locs[newind][1], locs[newind][2] }
else
newpos = { unit.x, unit.y }
end
end
-- If possible locations were found, move unit toward a random one,
-- otherwise the unit stays where it is
if (#locs > 0) then
local newind = math.random(#locs)
newpos = { locs[newind][1], locs[newind][2] }
else
newpos = { unit.x, unit.y }
end
end
-- Next hop toward that position
local nh = AH.next_hop(unit, newpos[1], newpos[2])
if nh then
AH.movefull_stopunit(ai, unit, nh)
end
end
-- Next hop toward that position
local nh = AH.next_hop(unit, newpos[1], newpos[2])
if nh then
AH.movefull_stopunit(ai, unit, nh)
end
end
-- Get unit again, just in case something was done to it in a 'moveto' or 'attack' event
local unit = wesnoth.get_units{ id = cfg.id }[1]
if unit then ai.stopunit_moves(unit) end
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
-- Get unit again, just in case something was done to it in a 'moveto' or 'attack' event
local unit = wesnoth.get_units{ id = cfg.id }[1]
if unit then ai.stopunit_moves(unit) end
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
end
return ca_zone_guardian