Micro AIs: avoid code duplication between eval and exec functions
This commit is contained in:
parent
6f40ed8046
commit
7fc8e9797b
23 changed files with 492 additions and 484 deletions
|
@ -3,16 +3,19 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
local ca_big_animals = {}
|
||||
|
||||
function ca_big_animals:evaluation(ai, cfg)
|
||||
local units = wesnoth.get_units {
|
||||
local function get_big_animals(cfg)
|
||||
local big_animals = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and" , cfg.filter },
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
return big_animals
|
||||
end
|
||||
|
||||
if units[1] then return cfg.ca_score end
|
||||
local ca_big_animals = {}
|
||||
|
||||
function ca_big_animals:evaluation(ai, cfg)
|
||||
if get_big_animals(cfg)[1] then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
|
@ -21,11 +24,7 @@ function ca_big_animals:execution(ai, cfg)
|
|||
-- 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 big_animals = get_big_animals(cfg)
|
||||
local avoid = LS.of_pairs(wesnoth.get_locations { radius = 1,
|
||||
{ "filter", { { "and", cfg.avoid_unit },
|
||||
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
|
||||
|
@ -33,7 +32,7 @@ function ca_big_animals:execution(ai, cfg)
|
|||
})
|
||||
--AH.put_labels(avoid)
|
||||
|
||||
for i,unit in ipairs(units) do
|
||||
for i,unit in ipairs(big_animals) do
|
||||
local goal = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
|
||||
|
||||
-- Unit gets a new goal if none exist or on any move with 10% random chance
|
||||
|
|
|
@ -1,41 +1,38 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local ca_coward = {}
|
||||
|
||||
function ca_coward:evaluation(ai, cfg)
|
||||
local function get_coward(cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
local coward = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
return coward
|
||||
end
|
||||
|
||||
if unit then return cfg.ca_score end
|
||||
local ca_coward = {}
|
||||
|
||||
function ca_coward:evaluation(ai, cfg)
|
||||
if get_coward(cfg) 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)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
local reach = wesnoth.find_reach(unit)
|
||||
local coward = get_coward(cfg)
|
||||
local reach = wesnoth.find_reach(coward)
|
||||
|
||||
-- enemy units within reach
|
||||
local filter_second = cfg.filter_second or { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "and", filter_second },
|
||||
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
|
||||
{ "filter_location", {x = coward.x, y = coward.y, radius = cfg.distance} }
|
||||
}
|
||||
|
||||
-- if no enemies are within reach: keep unit from doing anything and exit
|
||||
if not enemies[1] then
|
||||
AH.checked_stopunit_all(ai, unit)
|
||||
AH.checked_stopunit_all(ai, coward)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -43,7 +40,7 @@ function ca_coward:execution(ai, cfg)
|
|||
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]
|
||||
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = coward.id } } }[1]
|
||||
if not occ_hex then
|
||||
-- Find combined distance weighting of all enemy units within distance
|
||||
local value = 0
|
||||
|
@ -102,10 +99,10 @@ function ca_coward:execution(ai, cfg)
|
|||
end
|
||||
--items.place_image(mx, my, "items/ring-gold.png")
|
||||
|
||||
AH.movefull_stopunit(ai, unit, mx, my)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
AH.movefull_stopunit(ai, coward, mx, my)
|
||||
if (not coward) or (not coward.valid) then return end
|
||||
|
||||
AH.checked_stopunit_all(ai, unit)
|
||||
AH.checked_stopunit_all(ai, coward)
|
||||
end
|
||||
|
||||
return ca_coward
|
||||
|
|
|
@ -3,48 +3,46 @@ local W = H.set_wml_action_metatable {}
|
|||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
local function get_forest_animals(cfg)
|
||||
-- We want the deer/rabbits to move first, tuskers later
|
||||
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 forest_animals = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
type = deer_type .. ',' .. rabbit_type,
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
|
||||
local tusker_type = cfg.tusker_type or "no_unit_of_this_type"
|
||||
local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type }
|
||||
for i,t in ipairs(all_tuskers) do
|
||||
if (t.moves > 0) then table.insert(forest_animals, t) end
|
||||
end
|
||||
|
||||
-- Tusklets get moved by this CA if there are no tuskers left
|
||||
if not all_tuskers[1] then
|
||||
local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type"
|
||||
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type }
|
||||
for i,t in ipairs(tusklets) do
|
||||
if (t.moves > 0) then table.insert(forest_animals, t) end
|
||||
end
|
||||
end
|
||||
|
||||
return forest_animals
|
||||
end
|
||||
|
||||
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 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
|
||||
if get_forest_animals(cfg)[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 {}
|
||||
|
||||
-- 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
|
||||
local forest_animals = get_forest_animals(cfg)
|
||||
|
||||
-- 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' }
|
||||
|
@ -63,7 +61,7 @@ function ca_forest_animals_move:execution(ai, cfg)
|
|||
--AH.put_labels(hole_map)
|
||||
|
||||
-- Each unit moves independently
|
||||
for i,unit in ipairs(units) do
|
||||
for i,unit in ipairs(forest_animals) do
|
||||
--print('Unit', i, unit.x, unit.y)
|
||||
-- Behavior is different depending on whether a predator is close or not
|
||||
local close_enemies = {}
|
||||
|
@ -75,6 +73,7 @@ function ca_forest_animals_move:execution(ai, cfg)
|
|||
--print(' #close_enemies', #close_enemies)
|
||||
|
||||
-- If no close enemies, do a random move
|
||||
local wander_terrain = cfg.filter_location or {}
|
||||
if (not close_enemies[1]) then
|
||||
-- All hexes the unit can reach that are unoccupied
|
||||
local reach = AH.get_reachable_unocc(unit)
|
||||
|
@ -135,6 +134,7 @@ function ca_forest_animals_move:execution(ai, cfg)
|
|||
--print(' #close_enemies after move', #close_enemies, #enemies, unit.id)
|
||||
|
||||
-- If there are close enemies, run away (and rabbits disappear into holes)
|
||||
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
|
||||
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
|
||||
|
|
|
@ -1,6 +1,23 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local function get_tuskers(cfg)
|
||||
local tuskers = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
type = cfg.tusker_type,
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
return tuskers
|
||||
end
|
||||
|
||||
local function get_adjacent_enemies(cfg)
|
||||
local adjacent_enemies = wesnoth.get_units {
|
||||
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
|
||||
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
|
||||
}
|
||||
return adjacent_enemies
|
||||
end
|
||||
|
||||
local ca_forest_animals_tusker_attack = {}
|
||||
|
||||
function ca_forest_animals_tusker_attack:evaluation(ai, cfg)
|
||||
|
@ -9,28 +26,19 @@ function ca_forest_animals_tusker_attack:evaluation(ai, cfg)
|
|||
-- 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)
|
||||
|
||||
if tuskers[1] and adj_enemies[1] then return cfg.ca_score end
|
||||
return 0
|
||||
if (not get_tuskers(cfg)[1]) then return 0 end
|
||||
if (not get_adjacent_enemies(cfg)[1]) then return 0 end
|
||||
return cfg.ca_score
|
||||
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 = get_tuskers(cfg)
|
||||
local adjacent_enemies = get_adjacent_enemies(cfg)
|
||||
|
||||
-- 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
|
||||
for j,e in ipairs(adjacent_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
|
||||
|
|
|
@ -1,6 +1,23 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local function get_tusklets(cfg)
|
||||
local tusklets = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
type = cfg.tusklet_type,
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
return tusklets
|
||||
end
|
||||
|
||||
local function get_tuskers(cfg)
|
||||
local tuskers = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
type = cfg.tusker_type
|
||||
}
|
||||
return tuskers
|
||||
end
|
||||
|
||||
local ca_forest_animals_tusklet_move = {}
|
||||
|
||||
function ca_forest_animals_tusklet_move:evaluation(ai, cfg)
|
||||
|
@ -10,17 +27,14 @@ function ca_forest_animals_tusklet_move:evaluation(ai, cfg)
|
|||
-- 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 }
|
||||
|
||||
if tusklets[1] and tuskers[1] then return cfg.ca_score end
|
||||
return 0
|
||||
if (not get_tusklets(cfg)[1]) then return 0 end
|
||||
if (not get_tuskers(cfg)[1]) then return 0 end
|
||||
return cfg.ca_score
|
||||
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 = get_tusklets(cfg)
|
||||
local tuskers = get_tuskers(cfg)
|
||||
|
||||
for i,tusklet in ipairs(tusklets) do
|
||||
-- find closest tusker
|
||||
|
|
|
@ -4,11 +4,18 @@ local LS = wesnoth.require "lua/location_set.lua"
|
|||
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
local MAISD = wesnoth.require "ai/micro_ais/micro_ai_self_data.lua"
|
||||
|
||||
local function get_hang_out_units(cfg)
|
||||
local units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
return units
|
||||
end
|
||||
|
||||
local ca_hang_out = {}
|
||||
|
||||
function ca_hang_out:evaluation(ai, cfg, self)
|
||||
cfg = cfg or {}
|
||||
|
||||
-- Return 0 if the mobilize condition has previously been met
|
||||
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "mobilize_units") then
|
||||
return 0
|
||||
|
@ -29,22 +36,12 @@ function ca_hang_out:evaluation(ai, cfg, self)
|
|||
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
|
||||
if get_hang_out_units(cfg)[1] then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ca_hang_out:execution(ai, cfg, self)
|
||||
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 = get_hang_out_units(cfg)
|
||||
|
||||
-- Get the locations close to which the units should hang out
|
||||
-- cfg.filter_location defaults to the location of the side leader(s)
|
||||
|
|
|
@ -1,40 +1,55 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local function get_sheep(cfg)
|
||||
local sheep = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
return sheep
|
||||
end
|
||||
|
||||
local function get_dogs(cfg)
|
||||
local dogs = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
return dogs
|
||||
end
|
||||
|
||||
local function get_enemies(cfg, radius)
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
|
||||
{ "filter_location",
|
||||
{ radius = radius,
|
||||
{ "filter", { side = wesnoth.current.side, { "and", cfg.filter_second } } } }
|
||||
}
|
||||
}
|
||||
return enemies
|
||||
end
|
||||
|
||||
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} }
|
||||
if not get_sheep(cfg)[1] then return 0 end
|
||||
if not get_dogs(cfg)[1] then return 0 end
|
||||
|
||||
if enemies[1] and dogs[1] and sheep[1] then return cfg.ca_score end
|
||||
local radius = cfg.attention_distance or 8
|
||||
if get_enemies(cfg, radius)[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 = get_sheep(cfg)
|
||||
local dogs = get_dogs(cfg)
|
||||
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} } } }
|
||||
}
|
||||
}
|
||||
local radius = cfg.attack_distance or 4
|
||||
local enemies = get_enemies(cfg, radius)
|
||||
|
||||
max_rating, best_dog, best_enemy, best_hex = -9e99, {}, {}, {}
|
||||
for i,e in ipairs(enemies) do
|
||||
|
@ -75,13 +90,8 @@ function ca_herding_attack_close_enemy:execution(ai, cfg)
|
|||
-- 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} } } }
|
||||
}
|
||||
}
|
||||
local radius = cfg.attention_distance or 8
|
||||
local enemies = get_enemies(cfg, radius)
|
||||
|
||||
-- Find closest sheep/enemy pair first
|
||||
local min_dist, closest_sheep, closest_enemy = 9e99, {}, {}
|
||||
|
|
|
@ -2,27 +2,28 @@ local H = wesnoth.require "lua/helper.lua"
|
|||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
local function get_dog(cfg)
|
||||
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} } } } }
|
||||
}
|
||||
return dogs[1]
|
||||
end
|
||||
|
||||
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
|
||||
if get_dog(cfg) 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, order does not matter
|
||||
local dog = get_dog(cfg)
|
||||
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
|
||||
|
|
|
@ -3,46 +3,44 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|||
|
||||
local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area.lua"
|
||||
|
||||
local function get_dogs(cfg)
|
||||
local dogs = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
return dogs
|
||||
end
|
||||
|
||||
local function get_sheep_to_herd(cfg)
|
||||
local all_sheep = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter_second },
|
||||
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } } } }
|
||||
}
|
||||
local sheep_to_herd = {}
|
||||
local herding_area = herding_area(cfg)
|
||||
for i,s in ipairs(all_sheep) do
|
||||
if (not herding_area:get(s.x, s.y)) then table.insert(sheep_to_herd, s) end
|
||||
end
|
||||
return sheep_to_herd
|
||||
end
|
||||
|
||||
local ca_herding_herd_sheep = {}
|
||||
|
||||
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
|
||||
if get_dogs(cfg)[1] then
|
||||
if get_sheep_to_herd(cfg)[1] then return cfg.ca_score end
|
||||
end
|
||||
|
||||
-- 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 = get_dogs(cfg)
|
||||
local sheep_to_herd = get_sheep_to_herd(cfg)
|
||||
|
||||
-- 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
|
||||
|
|
|
@ -4,18 +4,26 @@ local LS = wesnoth.require "lua/location_set.lua"
|
|||
|
||||
local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area.lua"
|
||||
|
||||
local function get_next_sheep(cfg)
|
||||
local sheep = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter_second },
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
return sheep[1]
|
||||
end
|
||||
|
||||
local ca_herding_sheep_move = {}
|
||||
|
||||
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
|
||||
if get_next_sheep(cfg) 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, the order does not matter
|
||||
local sheep = get_next_sheep(cfg)
|
||||
|
||||
local reach_map = AH.get_reachable_unocc(sheep)
|
||||
-- Exclude those that are next to a dog
|
||||
|
|
|
@ -1,26 +1,27 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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 function get_next_sheep(cfg)
|
||||
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} } }
|
||||
}
|
||||
return sheep[1]
|
||||
end
|
||||
|
||||
if sheep[1] then return cfg.ca_score end
|
||||
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 away
|
||||
if get_next_sheep(cfg) 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
|
||||
-- Simply get the first sheep, order does not matter
|
||||
local sheep = get_next_sheep(cfg)
|
||||
|
||||
-- Get 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]
|
||||
|
|
|
@ -1,39 +1,32 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local function get_next_sheep(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sheep[1]
|
||||
end
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sheep[1] then return cfg.ca_score end
|
||||
if get_next_sheep(cfg) 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
-- Simply start with the first of the sheep, order does not matter
|
||||
local sheep = get_next_sheep(cfg)
|
||||
|
||||
-- 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} }} },
|
||||
|
|
|
@ -3,21 +3,19 @@ local W = H.set_wml_action_metatable {}
|
|||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.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
|
||||
local function hunter_attack_weakest_adj_enemy(ai, hunter)
|
||||
-- Attack the enemy with the fewest hitpoints adjacent to 'hunter', 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 hunter exists and has attacks left
|
||||
if (not hunter.valid) then return 'no_attack' end
|
||||
if (hunter.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
|
||||
for x, y in H.adjacent_tiles(hunter.x, hunter.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
|
||||
|
@ -27,8 +25,8 @@ local function hunter_attack_weakest_adj_enemy(ai, unit)
|
|||
end
|
||||
|
||||
if target.id then
|
||||
--W.message { speaker = unit.id, message = 'Attacking weakest adjacent enemy' }
|
||||
AH.checked_attack(ai, unit, target)
|
||||
--W.message { speaker = hunter.id, message = 'Attacking weakest adjacent enemy' }
|
||||
AH.checked_attack(ai, hunter, target)
|
||||
if target.valid then
|
||||
return 'attacked'
|
||||
else
|
||||
|
@ -39,15 +37,20 @@ local function hunter_attack_weakest_adj_enemy(ai, unit)
|
|||
return 'no_attack'
|
||||
end
|
||||
|
||||
function ca_hunter:evaluation(ai, cfg)
|
||||
local function get_hunter(cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
local hunter = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
return hunter
|
||||
end
|
||||
|
||||
if unit then return cfg.ca_score end
|
||||
local ca_hunter = {}
|
||||
|
||||
function ca_hunter:evaluation(ai, cfg)
|
||||
if get_hunter(cfg) then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
|
@ -57,30 +60,25 @@ function ca_hunter:execution(ai, cfg)
|
|||
-- hunting_ground, then retreats to
|
||||
-- position given by 'home_x,home_y' for 'rest_turns' turns, or until fully healed
|
||||
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
local hunter = get_hunter(cfg)
|
||||
|
||||
-- If hunting_status is not set for the unit -> default behavior -> hunting
|
||||
local hunter_vars = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
|
||||
-- If hunting_status is not set for the hunter -> default behavior -> hunting
|
||||
local hunter_vars = MAIUV.get_mai_unit_variables(hunter, cfg.ai_id)
|
||||
if (not hunter_vars.hunting_status) then
|
||||
-- Unit gets a new goal if none exist or on any move with 10% random chance
|
||||
-- Hunter gets a new goal if none exist or on any move with 10% random chance
|
||||
local r = math.random(10)
|
||||
if (not hunter_vars.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)
|
||||
locs = AH.get_passable_locations((cfg.filter_location or {}), hunter)
|
||||
local rand = math.random(#locs)
|
||||
--print('#locs', #locs, rand)
|
||||
hunter_vars.goal_x, hunter_vars.goal_y = locs[rand][1], locs[rand][2]
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
|
||||
end
|
||||
--print('Hunter goto: ', hunter_vars.goal_x, hunter_vars.goal_y, r)
|
||||
|
||||
-- Hexes the unit can reach
|
||||
local reach_map = AH.get_reachable_unocc(unit)
|
||||
-- Hexes the hunter can reach
|
||||
local reach_map = AH.get_reachable_unocc(hunter)
|
||||
|
||||
-- Now find the one of these hexes that is closest to the goal
|
||||
local max_rating, best_hex = -9e99, {}
|
||||
|
@ -106,32 +104,32 @@ function ca_hunter:execution(ai, cfg)
|
|||
--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
|
||||
AH.checked_move(ai, unit, best_hex[1], best_hex[2]) -- partial move only
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
if (best_hex[1] ~= hunter.x) or (best_hex[2] ~= hunter.y) then
|
||||
AH.checked_move(ai, hunter, best_hex[1], best_hex[2]) -- partial move only
|
||||
if (not hunter) or (not hunter.valid) then return end
|
||||
else -- If hunter did not move, we need to stop it (also delete the goal)
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
AH.checked_stopunit_moves(ai, hunter)
|
||||
if (not hunter) or (not hunter.valid) then return end
|
||||
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
|
||||
end
|
||||
|
||||
-- Or if this gets the unit to the goal, we also delete the goal
|
||||
if (unit.x == hunter_vars.goal_x) and (unit.y == hunter_vars.goal_y) then
|
||||
-- Or if this gets the hunter to the goal, we also delete the goal
|
||||
if (hunter.x == hunter_vars.goal_x) and (hunter.y == hunter_vars.goal_y) then
|
||||
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
|
||||
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 hunter ended up next to enemies, attack the weakest of those
|
||||
local attack_status = hunter_attack_weakest_adj_enemy(ai, hunter)
|
||||
|
||||
-- If the enemy was killed, hunter returns home
|
||||
if unit.valid and (attack_status == 'killed') then
|
||||
if hunter.valid and (attack_status == 'killed') then
|
||||
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
|
||||
hunter_vars.hunting_status = 'return'
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
|
||||
if cfg.show_messages then
|
||||
W.message { speaker = unit.id, message = 'Now that I have eaten, I will go back home.' }
|
||||
W.message { speaker = hunter.id, message = 'Now that I have eaten, I will go back home.' }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -139,41 +137,41 @@ function ca_hunter:execution(ai, cfg)
|
|||
return
|
||||
end
|
||||
|
||||
-- If we got here, this means the unit is either returning, or resting
|
||||
-- If we got here, this means the hunter is either returning, or resting
|
||||
if (hunter_vars.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)
|
||||
local next_hop = AH.next_hop(hunter, goto_x, goto_y)
|
||||
if next_hop then
|
||||
--print(next_hop[1], next_hop[2])
|
||||
AH.movefull_stopunit(ai, unit, next_hop)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
AH.movefull_stopunit(ai, hunter, next_hop)
|
||||
if (not hunter) or (not hunter.valid) then return end
|
||||
|
||||
-- If there's an enemy on the 'home' hex and we got right next to it, attack that enemy
|
||||
if (H.distance_between(cfg.home_x, cfg.home_y, next_hop[1], next_hop[2]) == 1) then
|
||||
local enemy = wesnoth.get_unit(cfg.home_x, cfg.home_y)
|
||||
if enemy and wesnoth.is_enemy(enemy.side, unit.side) then
|
||||
if enemy and wesnoth.is_enemy(enemy.side, hunter.side) then
|
||||
if cfg.show_messages then
|
||||
W.message { speaker = unit.id, message = 'Get out of my home!' }
|
||||
W.message { speaker = hunter.id, message = 'Get out of my home!' }
|
||||
end
|
||||
AH.checked_attack(ai, unit, enemy)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
AH.checked_attack(ai, hunter, enemy)
|
||||
if (not hunter) or (not hunter.valid) then return end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- We also attack the weakest adjacent enemy, if still possible
|
||||
hunter_attack_weakest_adj_enemy(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
hunter_attack_weakest_adj_enemy(ai, hunter)
|
||||
if (not hunter) or (not hunter.valid) then return end
|
||||
|
||||
-- If the unit got home, start the resting counter
|
||||
if (unit.x == cfg.home_x) and (unit.y == cfg.home_y) then
|
||||
-- If the hunter got home, start the resting counter
|
||||
if (hunter.x == cfg.home_x) and (hunter.y == cfg.home_y) then
|
||||
hunter_vars.hunting_status = 'resting'
|
||||
hunter_vars.resting_until = wesnoth.current.turn + (cfg.rest_turns or 3)
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
|
||||
if cfg.show_messages then
|
||||
W.message { speaker = unit.id, message = 'I made it home - resting now until the end of Turn ' .. hunter_vars.resting_until .. ' or until fully healed.' }
|
||||
W.message { speaker = hunter.id, message = 'I made it home - resting now until the end of Turn ' .. hunter_vars.resting_until .. ' or until fully healed.' }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -183,29 +181,29 @@ function ca_hunter:execution(ai, cfg)
|
|||
|
||||
-- If we got here, the only remaining action is resting
|
||||
if (hunter_vars.hunting_status == 'resting') then
|
||||
-- So all we need to do is take moves away from the unit
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
-- So all we need to do is take moves away from the hunter
|
||||
AH.checked_stopunit_moves(ai, hunter)
|
||||
if (not hunter) or (not hunter.valid) then return end
|
||||
|
||||
-- However, we do also attack the weakest adjacent enemy, if still possible
|
||||
hunter_attack_weakest_adj_enemy(ai, unit)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
hunter_attack_weakest_adj_enemy(ai, hunter)
|
||||
if (not hunter) or (not hunter.valid) then return end
|
||||
|
||||
-- If this is the last turn of resting, we also remove the status and turn variable
|
||||
if (unit.hitpoints >= unit.max_hitpoints) and (hunter_vars.resting_until <= wesnoth.current.turn) then
|
||||
if (hunter.hitpoints >= hunter.max_hitpoints) and (hunter_vars.resting_until <= wesnoth.current.turn) then
|
||||
hunter_vars.hunting_status = nil
|
||||
hunter_vars.resting_until = nil
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
|
||||
if cfg.show_messages then
|
||||
W.message { speaker = unit.id, message = 'I am done resting. It is time to go hunting again next turn.' }
|
||||
W.message { speaker = hunter.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
|
||||
-- In principle we should never get here, but just in case: reset variable, so that hunter goes hunting on next turn
|
||||
hunter_vars.hunting_status = nil
|
||||
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
|
||||
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
|
||||
end
|
||||
|
||||
return ca_hunter
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local function get_lurker(cfg)
|
||||
local lurker = wesnoth.get_units { side = wesnoth.current.side,
|
||||
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
|
||||
}[1]
|
||||
return lurker
|
||||
end
|
||||
|
||||
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 units[1] then return cfg.ca_score end
|
||||
if get_lurker(cfg) 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)
|
||||
|
||||
-- Potential targets
|
||||
local lurker = get_lurker(cfg)
|
||||
local targets = wesnoth.get_units {
|
||||
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
|
||||
}
|
||||
|
@ -29,11 +26,11 @@ function ca_lurkers:execution(ai, cfg)
|
|||
--print("Number of potential targets:", #targets)
|
||||
|
||||
-- all reachable hexes
|
||||
local reach = LS.of_pairs( wesnoth.find_reach(me.x, me.y) )
|
||||
local reach = LS.of_pairs(wesnoth.find_reach(lurker.x, lurker.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", {x = lurker.x, y = lurker.y, radius = lurker.moves} },
|
||||
{"and", cfg.filter_location}
|
||||
} )
|
||||
reachable_attack_terrain:inter(reach)
|
||||
|
@ -41,7 +38,7 @@ function ca_lurkers:execution(ai, cfg)
|
|||
|
||||
-- need to restrict that to reachable and not occupied by an ally (except own position)
|
||||
local reachable_attack_terrain = reachable_attack_terrain:filter(function(x, y, v)
|
||||
local occ_hex = wesnoth.get_units { x = x, y = y, { "not", { x = me.x, y = me.y } } }[1]
|
||||
local occ_hex = wesnoth.get_units { x = x, y = y, { "not", { x = lurker.x, y = lurker.y } } }[1]
|
||||
return not occ_hex
|
||||
end)
|
||||
--print(" reach: " .. reach:size() .. " reach_attack no allies: " .. reachable_attack_terrain:size())
|
||||
|
@ -62,14 +59,14 @@ function ca_lurkers:execution(ai, cfg)
|
|||
-- Choose one of the possible attack locations at random
|
||||
local rand = math.random(1, rattack_nt_target:size())
|
||||
local dst = rattack_nt_target:to_stable_pairs()
|
||||
AH.movefull_stopunit(ai, me, dst[rand])
|
||||
if (not me) or (not me.valid) then return end
|
||||
AH.checked_attack(ai, me, target)
|
||||
AH.movefull_stopunit(ai, lurker, dst[rand])
|
||||
if (not lurker) or (not lurker.valid) then return end
|
||||
AH.checked_attack(ai, lurker, target)
|
||||
attacked = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if (not me) or (not me.valid) then return end
|
||||
if (not lurker) or (not lurker.valid) then return 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
|
||||
|
@ -77,7 +74,7 @@ function ca_lurkers:execution(ai, cfg)
|
|||
|
||||
local reachable_wander_terrain =
|
||||
LS.of_pairs( wesnoth.get_locations {
|
||||
{"and", {x = me.x, y = me.y, radius = me.moves} },
|
||||
{"and", {x = lurker.x, y = lurker.y, radius = lurker.moves} },
|
||||
{"and", (cfg.filter_location_wander or cfg.filter_location)}
|
||||
} )
|
||||
reachable_wander_terrain:inter(reach)
|
||||
|
@ -89,14 +86,14 @@ function ca_lurkers:execution(ai, cfg)
|
|||
if dst[1] then
|
||||
dst = dst[rand]
|
||||
else
|
||||
dst = { me.x, me.y }
|
||||
dst = { lurker.x, lurker.y }
|
||||
end
|
||||
AH.movefull_stopunit(ai, me, dst)
|
||||
AH.movefull_stopunit(ai, lurker, dst)
|
||||
end
|
||||
if (not me) or (not me.valid) then return end
|
||||
if (not lurker) or (not lurker.valid) then return end
|
||||
|
||||
-- If the unit has moves or attacks left at this point, take them away
|
||||
AH.checked_stopunit_all(ai, me)
|
||||
AH.checked_stopunit_all(ai, lurker)
|
||||
end
|
||||
|
||||
return ca_lurkers
|
||||
|
|
|
@ -5,19 +5,23 @@ local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
|
|||
|
||||
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
|
||||
|
||||
local function get_escorts(cfg)
|
||||
local escorts = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0',
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
return escorts
|
||||
end
|
||||
|
||||
local ca_messenger_escort_move = {}
|
||||
|
||||
function ca_messenger_escort_move:evaluation(ai, cfg)
|
||||
-- Move escort units close to messengers, and in between messengers and enemies
|
||||
-- The messengers have moved at this time, so we don't need to exclude them,
|
||||
-- but we check that there are messengers left
|
||||
|
||||
local my_units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0',
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
if (not my_units[1]) then return 0 end
|
||||
local escort = get_escorts(cfg)[1]
|
||||
if (not escort) then return 0 end
|
||||
|
||||
local _, _, _, messengers = messenger_next_waypoint(cfg)
|
||||
if (not messengers) or (not messengers[1]) then return 0 end
|
||||
|
@ -26,12 +30,7 @@ function ca_messenger_escort_move:evaluation(ai, cfg)
|
|||
end
|
||||
|
||||
function ca_messenger_escort_move:execution(ai, cfg)
|
||||
local my_units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0',
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
|
||||
local escorts = get_escorts(cfg)
|
||||
local _, _, _, messengers = messenger_next_waypoint(cfg)
|
||||
|
||||
local enemies = wesnoth.get_units {
|
||||
|
@ -40,7 +39,7 @@ function ca_messenger_escort_move:execution(ai, cfg)
|
|||
|
||||
local base_rating_map = LS.create()
|
||||
local max_rating, best_unit, best_hex = -9e99
|
||||
for _,unit in ipairs(my_units) do
|
||||
for _,unit in ipairs(escorts) do
|
||||
-- Only considering hexes unoccupied by other units is good enough for this
|
||||
local reach_map = AH.get_reachable_unocc(unit)
|
||||
|
||||
|
|
|
@ -2,28 +2,25 @@ local H = wesnoth.require "lua/helper.lua"
|
|||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
|
||||
|
||||
local ca_patrol = {}
|
||||
|
||||
function ca_patrol:evaluation(ai, cfg)
|
||||
local function get_patrol(cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local patrol = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
return patrol
|
||||
end
|
||||
|
||||
if patrol then return cfg.ca_score end
|
||||
local ca_patrol = {}
|
||||
|
||||
function ca_patrol:evaluation(ai, cfg)
|
||||
if get_patrol(cfg) then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ca_patrol:execution(ai, cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local patrol = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
local patrol = get_patrol(cfg)
|
||||
cfg.waypoint_x = AH.split(cfg.waypoint_x, ",")
|
||||
cfg.waypoint_y = AH.split(cfg.waypoint_y, ",")
|
||||
|
||||
|
|
|
@ -3,25 +3,24 @@ local LS = wesnoth.require "lua/location_set.lua"
|
|||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
||||
|
||||
local function get_protected_units(cfg)
|
||||
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
|
||||
return units
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
if units[1] then return 94999 end
|
||||
if get_protected_units(cfg)[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
|
||||
local units = get_protected_units(cfg)
|
||||
|
||||
-- Need to take the units off the map, as they don't count into the map scores
|
||||
-- (as long as they can still move)
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local ca_return_guardian = {}
|
||||
|
||||
function ca_return_guardian:evaluation(ai, cfg)
|
||||
local function get_guardian(cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
local guardian = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
return guardian
|
||||
end
|
||||
|
||||
if unit then
|
||||
if ((unit.x ~= cfg.return_x) or (unit.y ~= cfg.return_y)) then
|
||||
local ca_return_guardian = {}
|
||||
|
||||
function ca_return_guardian:evaluation(ai, cfg)
|
||||
local guardian = get_guardian(cfg)
|
||||
if guardian then
|
||||
if ((guardian.x ~= cfg.return_x) or (guardian.y ~= cfg.return_y)) then
|
||||
return cfg.ca_score
|
||||
else
|
||||
return cfg.ca_score - 20
|
||||
|
@ -21,21 +25,16 @@ function ca_return_guardian:evaluation(ai, cfg)
|
|||
end
|
||||
|
||||
function ca_return_guardian:execution(ai, cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
local guardian = get_guardian(cfg)
|
||||
|
||||
-- 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)
|
||||
if (guardian.x ~= x) or (guardian.y ~= y) then
|
||||
x, y = wesnoth.find_vacant_tile(x, y, guardian)
|
||||
end
|
||||
|
||||
local nh = AH.next_hop(unit, x, y)
|
||||
AH.movefull_stopunit(ai, unit, nh)
|
||||
local nh = AH.next_hop(guardian, x, y)
|
||||
AH.movefull_stopunit(ai, guardian, nh)
|
||||
end
|
||||
|
||||
return ca_return_guardian
|
||||
|
|
|
@ -1,46 +1,44 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local function get_guardian(cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local guardian = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
return guardian
|
||||
end
|
||||
|
||||
local ca_stationed_guardian = {}
|
||||
|
||||
function ca_stationed_guardian:evaluation(ai, cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
if unit then return cfg.ca_score end
|
||||
if get_guardian(cfg) 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 guardian is stationed; tries to move here if there is nobody to attack
|
||||
-- (g_x,g_y): location that the guardian guards
|
||||
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
local guardian = get_guardian(cfg)
|
||||
|
||||
-- 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} }
|
||||
{ "filter_location", {x = guardian.x, y = guardian.y, radius = cfg.distance} }
|
||||
}
|
||||
|
||||
-- if no enemies are within 'distance': keep unit from doing anything and exit
|
||||
-- if no enemies are within 'distance': keep guardian from doing anything and exit
|
||||
if not enemies[1] then
|
||||
--print("No enemies close -> sleeping:",unit.id)
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
--print("No enemies close -> sleeping:",guardian.id)
|
||||
AH.checked_stopunit_moves(ai, guardian)
|
||||
return
|
||||
end
|
||||
|
||||
-- Otherwise, unit will either attack or move toward station
|
||||
--print("Guardian unit waking up",unit.id)
|
||||
-- Otherwise, guardian will either attack or move toward station
|
||||
--print("Guardian unit waking up",guardian.id)
|
||||
-- enemies must be within 'distance' of guard, (s_x,s_y) *and* (g_x,g_y)
|
||||
-- simultaneous for guard to attack
|
||||
local target = {}
|
||||
|
@ -57,22 +55,22 @@ function ca_stationed_guardian:execution(ai, cfg)
|
|||
end
|
||||
end
|
||||
|
||||
-- If a valid target was found, unit attacks this target, or moves toward it
|
||||
-- If a valid target was found, guardian 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
|
||||
-- Find tiles adjacent to the target, and save the one that our guardian
|
||||
-- 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]
|
||||
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = guardian.id } } }[1]
|
||||
if not occ_hex then
|
||||
-- defense rating of the hex
|
||||
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
|
||||
local defense = 100 - wesnoth.unit_defense(guardian, 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
|
||||
local nh = AH.next_hop(guardian, x, y)
|
||||
-- if this is best defense rating and guardian 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
|
||||
|
@ -82,20 +80,20 @@ function ca_stationed_guardian:execution(ai, cfg)
|
|||
-- 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)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
AH.movefull_stopunit(ai, guardian, attack_loc)
|
||||
if (not guardian) or (not guardian.valid) then return end
|
||||
if (not target) or (not target.valid) then return end
|
||||
AH.checked_attack(ai, unit, target)
|
||||
AH.checked_attack(ai, guardian, target)
|
||||
else -- otherwise move toward that enemy
|
||||
--print("Cannot reach target, moving toward it")
|
||||
local reach = wesnoth.find_reach(unit)
|
||||
local reach = wesnoth.find_reach(guardian)
|
||||
|
||||
-- Go through all hexes the unit can reach, find closest to target
|
||||
-- Go through all hexes the guardian 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]
|
||||
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = guardian.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
|
||||
|
@ -106,20 +104,20 @@ function ca_stationed_guardian:execution(ai, cfg)
|
|||
end
|
||||
|
||||
-- Finally, execute the move toward the target
|
||||
AH.movefull_stopunit(ai, unit, nh)
|
||||
AH.movefull_stopunit(ai, guardian, 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)
|
||||
local nh = AH.next_hop(guardian, cfg.station_x, cfg.station_y)
|
||||
AH.movefull_stopunit(ai, guardian, nh)
|
||||
end
|
||||
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
if (not guardian) or (not guardian.valid) then return end
|
||||
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
|
||||
AH.checked_stopunit_moves(ai, guardian)
|
||||
-- If there are attacks left and guardian ended up next to an enemy, we'll leave this to RCA AI
|
||||
end
|
||||
|
||||
return ca_stationed_guardian
|
||||
|
|
|
@ -1,46 +1,39 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
local function get_enemies(cfg)
|
||||
local scatter_distance = cfg.scatter_distance or 3
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
|
||||
{ "filter_location",
|
||||
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
|
||||
}
|
||||
}
|
||||
return enemies
|
||||
end
|
||||
|
||||
local function get_swarm_units(cfg)
|
||||
local units = {}
|
||||
local all_units = wesnoth.get_units { side = wesnoth.current.side }
|
||||
for i,u in ipairs(all_units) do
|
||||
if (u.moves > 0) then table.insert(units, u) end
|
||||
end
|
||||
return units
|
||||
end
|
||||
|
||||
local swarm_scatter = {}
|
||||
|
||||
function swarm_scatter:evaluation(ai, cfg)
|
||||
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 } } }
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (not get_enemies(cfg)[1]) then return 0 end
|
||||
if (not get_swarm_units(cfg)[1]) then return 0 end
|
||||
return cfg.ca_score
|
||||
end
|
||||
|
||||
function swarm_scatter:execution(ai, cfg)
|
||||
local scatter_distance = cfg.scatter_distance or 3
|
||||
local enemies = get_enemies(cfg)
|
||||
local units = get_swarm_units(cfg)
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
@ -3,30 +3,34 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|||
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
local function get_wolves(cfg)
|
||||
local wolves = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter },
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
return wolves
|
||||
end
|
||||
|
||||
local function get_prey(cfg)
|
||||
local prey = wesnoth.get_units {
|
||||
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
return prey
|
||||
end
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
if wolves[1] and prey[1] then return cfg.ca_score end
|
||||
return 0
|
||||
if (not get_wolves(cfg)[1]) then return 0 end
|
||||
if (not get_prey(cfg)[1]) then return 0 end
|
||||
return cfg.ca_score
|
||||
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 = get_wolves(cfg)
|
||||
local prey = get_prey(cfg)
|
||||
|
||||
-- When wandering (later) they avoid dogs, but not here
|
||||
local avoid_units = wesnoth.get_units { type = cfg.avoid_type,
|
||||
|
|
|
@ -3,22 +3,23 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|||
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
local function get_wolves(cfg)
|
||||
local wolves = wesnoth.get_units { side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0', { "and", cfg.filter }
|
||||
}
|
||||
return wolves
|
||||
end
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
if wolves[1] then return cfg.ca_score end
|
||||
if get_wolves(cfg)[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 = get_wolves(cfg)
|
||||
|
||||
-- Number of wolves that can reach each hex
|
||||
local reach_map = LS.create()
|
||||
|
|
|
@ -2,29 +2,26 @@ local H = wesnoth.require "lua/helper.lua"
|
|||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
local ca_zone_guardian = {}
|
||||
|
||||
function ca_zone_guardian:evaluation(ai, cfg)
|
||||
local function get_guardian(cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
local guardian = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
return guardian
|
||||
end
|
||||
|
||||
if unit then return cfg.ca_score end
|
||||
local ca_zone_guardian = {}
|
||||
|
||||
function ca_zone_guardian:evaluation(ai, cfg)
|
||||
if get_guardian(cfg) then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ca_zone_guardian:execution(ai, cfg)
|
||||
local filter = cfg.filter or { id = cfg.id }
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
{ "and", filter },
|
||||
formula = '$this_unit.moves > 0' }
|
||||
)[1]
|
||||
|
||||
local reach = wesnoth.find_reach(unit)
|
||||
local guardian = get_guardian(cfg)
|
||||
local reach = wesnoth.find_reach(guardian)
|
||||
local zone_enemy = cfg.filter_location_enemy or cfg.filter_location
|
||||
-- enemy units within reach
|
||||
|
||||
|
@ -37,9 +34,9 @@ function ca_zone_guardian:execution(ai, cfg)
|
|||
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 dg = H.distance_between(guardian.x, guardian.y, e.x, e.y)
|
||||
|
||||
-- If valid target found, save the one with the shortest distance from unit
|
||||
-- If valid target found, save the one with the shortest distance from guardian
|
||||
if (dg < min_dist) then
|
||||
--print("target:", e.id, ds, dg)
|
||||
target = e
|
||||
|
@ -47,22 +44,22 @@ function ca_zone_guardian:execution(ai, cfg)
|
|||
end
|
||||
end
|
||||
|
||||
-- If a valid target was found, unit attacks this target, or moves toward it
|
||||
-- If a valid target was found, guardian 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
|
||||
-- Find tiles adjacent to the target, and save the one that our guardian
|
||||
-- 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]
|
||||
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = guardian.id } } }[1]
|
||||
if not occ_hex then
|
||||
-- defense rating of the hex
|
||||
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
|
||||
local defense = 100 - wesnoth.unit_defense(guardian, 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
|
||||
local nh = AH.next_hop(guardian, x, y)
|
||||
-- if this is best defense rating and guardian 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}
|
||||
|
@ -74,19 +71,19 @@ function ca_zone_guardian:execution(ai, cfg)
|
|||
-- 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)
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
AH.checked_attack(ai, unit, target)
|
||||
AH.movefull_stopunit(ai, guardian, attack_loc)
|
||||
if (not guardian) or (not guardian.valid) then return end
|
||||
AH.checked_attack(ai, guardian, target)
|
||||
else -- otherwise move toward that enemy
|
||||
--print("Cannot reach target, moving toward it")
|
||||
local reach = wesnoth.find_reach(unit)
|
||||
local reach = wesnoth.find_reach(guardian)
|
||||
|
||||
-- Go through all hexes the unit can reach, find closest to target
|
||||
-- Go through all hexes the guardian 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]
|
||||
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = guardian.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
|
||||
|
@ -97,7 +94,7 @@ function ca_zone_guardian:execution(ai, cfg)
|
|||
end
|
||||
|
||||
-- Finally, execute the move toward the target
|
||||
AH.movefull_stopunit(ai, unit, nh)
|
||||
AH.movefull_stopunit(ai, guardian, nh)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -117,8 +114,8 @@ function ca_zone_guardian:execution(ai, cfg)
|
|||
{ "and", cfg.filter_location }
|
||||
})
|
||||
|
||||
-- Check out which of those hexes the unit can reach
|
||||
local reach_map = LS.of_pairs(wesnoth.find_reach(unit))
|
||||
-- Check out which of those hexes the guardian can reach
|
||||
local reach_map = LS.of_pairs(wesnoth.find_reach(guardian))
|
||||
reach_map:inter(locs_map)
|
||||
|
||||
-- If it can reach some hexes, use only reachable locations,
|
||||
|
@ -129,26 +126,26 @@ function ca_zone_guardian:execution(ai, cfg)
|
|||
|
||||
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 possible locations were found, move guardian toward a random one,
|
||||
-- otherwise the guardian 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 }
|
||||
newpos = { guardian.x, guardian.y }
|
||||
end
|
||||
end
|
||||
|
||||
-- Next hop toward that position
|
||||
local nh = AH.next_hop(unit, newpos[1], newpos[2])
|
||||
local nh = AH.next_hop(guardian, newpos[1], newpos[2])
|
||||
if nh then
|
||||
AH.movefull_stopunit(ai, unit, nh)
|
||||
AH.movefull_stopunit(ai, guardian, nh)
|
||||
end
|
||||
end
|
||||
if (not unit) or (not unit.valid) then return end
|
||||
if (not guardian) or (not guardian.valid) then return end
|
||||
|
||||
AH.checked_stopunit_moves(ai, unit)
|
||||
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
|
||||
AH.checked_stopunit_moves(ai, guardian)
|
||||
-- If there are attacks left and guardian 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