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:
parent
9d6331ac19
commit
9976eac873
44 changed files with 3601 additions and 3601 deletions
|
@ -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*',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue