All Remaining Animal MAIs: switch to using external CAs
This commit is contained in:
parent
d486c093ac
commit
4d92a3068e
26 changed files with 1574 additions and 1533 deletions
|
@ -1,119 +0,0 @@
|
|||
return {
|
||||
init = function(ai, existing_engine)
|
||||
local engine = existing_engine or {}
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
function engine:mai_big_animals_eval(cfg)
|
||||
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 engine:mai_big_animals_exec(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
|
||||
|
||||
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)
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return engine
|
||||
end
|
||||
}
|
|
@ -1,343 +0,0 @@
|
|||
return {
|
||||
init = function(ai, existing_engine)
|
||||
local engine = existing_engine or {}
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
-- While all these CAs have been generalized to be usable with different types of
|
||||
-- units, we keep their original names in order to indicate their kind of behavior
|
||||
function engine:mai_forest_animals_new_rabbit_eval(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
|
||||
|
||||
if (not cfg.rabbit_type) then return 0 end
|
||||
return cfg.ca_score
|
||||
end
|
||||
|
||||
function engine:mai_forest_animals_new_rabbit_exec(cfg)
|
||||
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' }
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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
|
||||
|
||||
function engine:mai_forest_animals_tusker_attack_eval(cfg)
|
||||
-- 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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
function engine:mai_forest_animals_tusker_attack_exec(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 } }
|
||||
}
|
||||
|
||||
-- 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 } }
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
-- 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
|
||||
|
||||
function engine:mai_forest_animals_forest_move_eval(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
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_forest_animals_forest_move_exec(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
|
||||
|
||||
-- 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' }
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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())
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
function engine:mai_forest_animals_tusklet_eval(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
|
||||
|
||||
-- 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
|
||||
end
|
||||
|
||||
function engine:mai_forest_animals_tusklet_exec(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)
|
||||
|
||||
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)
|
||||
|
||||
-- Also make sure tusklets never attack
|
||||
ai.stopunit_all(tusklet)
|
||||
end
|
||||
end
|
||||
|
||||
return engine
|
||||
end
|
||||
}
|
|
@ -1,408 +0,0 @@
|
|||
return {
|
||||
init = function(ai, existing_engine)
|
||||
local engine = existing_engine or {}
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
----- Beginning of Herding Animals AI -----
|
||||
-- We'll keep a lot of things denoted as sheep/dogs, because herder/herded is too similar
|
||||
function engine:mai_herding_area(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 } } }
|
||||
} )
|
||||
|
||||
-- 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)
|
||||
--AH.put_labels(herding_area)
|
||||
|
||||
return herding_area
|
||||
end
|
||||
|
||||
function engine:mai_herding_attack_close_enemy_eval(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 enemies[1] and dogs[1] and sheep[1] then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_herding_attack_close_enemy_exec(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} }
|
||||
|
||||
-- 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)
|
||||
|
||||
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 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)
|
||||
|
||||
-- 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
|
||||
|
||||
-- Move dog to intercept
|
||||
--print('Dog moving in to intercept')
|
||||
AH.movefull_stopunit(ai, best_dog, best_hex)
|
||||
end
|
||||
|
||||
function engine:mai_herding_sheep_runs_enemy_eval(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
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_herding_sheep_runs_enemy_exec(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 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)
|
||||
|
||||
AH.movefull_stopunit(ai, sheep, best_hex)
|
||||
end
|
||||
|
||||
function engine:mai_herding_sheep_runs_dog_eval(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} } }
|
||||
}
|
||||
|
||||
if sheep[1] then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_herding_sheep_runs_dog_exec(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]
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
function engine:mai_herding_herd_sheep_eval(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 = self:mai_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
|
||||
end
|
||||
|
||||
function engine:mai_herding_herd_sheep_exec(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 = self:mai_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 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
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
function engine:mai_herding_sheep_move_eval(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
|
||||
end
|
||||
|
||||
function engine:mai_herding_sheep_move_exec(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]
|
||||
|
||||
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
|
||||
|
||||
-- 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 = self:mai_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
|
||||
|
||||
function engine:mai_herding_dog_move_eval(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
|
||||
end
|
||||
|
||||
function engine:mai_herding_dog_move_exec(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]
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
--print('Dog wandering')
|
||||
AH.movefull_stopunit(ai, dog, best_hex)
|
||||
end
|
||||
|
||||
return engine
|
||||
end
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
return {
|
||||
init = function(ai, existing_engine)
|
||||
local engine = existing_engine or {}
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
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"
|
||||
|
||||
function engine:mai_wolves_move_eval(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
|
||||
end
|
||||
|
||||
function engine:mai_wolves_move_exec(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)
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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)
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
return rating
|
||||
end)
|
||||
|
||||
AH.movefull_stopunit(ai, wolves[i], move)
|
||||
end
|
||||
end
|
||||
|
||||
function engine:mai_wolves_wander_eval(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
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_wolves_wander_exec(cfg)
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
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"}
|
||||
|
||||
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 engine
|
||||
end
|
||||
}
|
|
@ -1,467 +0,0 @@
|
|||
return {
|
||||
init = function(ai, existing_engine)
|
||||
local engine = existing_engine or {}
|
||||
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
|
||||
function engine:mai_wolves_multipacks_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 }
|
||||
end
|
||||
|
||||
function engine:mai_wolves_multipacks_assign_packs(cfg)
|
||||
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)
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 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
|
||||
self:mai_wolves_multipacks_color_label(loc.x, loc.y, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return packs
|
||||
end
|
||||
|
||||
function engine:mai_wolves_multipacks_attack_eval(cfg)
|
||||
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[1] then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_wolves_multipacks_attack_exec(cfg)
|
||||
-- First get all the packs
|
||||
local packs = self:mai_wolves_multipacks_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
|
||||
|
||||
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
|
||||
|
||||
-- ... 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)
|
||||
|
||||
-- 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)
|
||||
|
||||
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
|
||||
|
||||
--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
|
||||
|
||||
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
|
||||
self:mai_wolves_multipacks_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
|
||||
|
||||
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)
|
||||
|
||||
-- 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
|
||||
self:mai_wolves_multipacks_color_label(w.x, w.y, pack_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function engine:mai_wolves_multipacks_wander_eval(cfg)
|
||||
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' }
|
||||
|
||||
if wolves[1] then return cfg.ca_score end
|
||||
return 0
|
||||
end
|
||||
|
||||
function engine:mai_wolves_multipacks_wander_exec(cfg)
|
||||
-- First get all the packs
|
||||
local packs = self:mai_wolves_multipacks_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
|
||||
|
||||
-- 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 }
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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
|
||||
self:mai_wolves_multipacks_color_label(w.x, w.y, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return engine
|
||||
end
|
||||
}
|
|
@ -327,15 +327,15 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
elseif (cfg.ai_type == 'big_animals') then
|
||||
required_keys = { "filter"}
|
||||
optional_keys = { "avoid_unit", "filter_location", "filter_location_wander" }
|
||||
CA_parms = { { ca_id = "mai_big_animals", score = cfg.ca_score or 300000 } }
|
||||
CA_parms = { { ca_id = "mai_big_animals", location = 'ai/micro_ais/cas/ca_big_animals.lua', score = cfg.ca_score or 300000 } }
|
||||
|
||||
elseif (cfg.ai_type == 'wolves') then
|
||||
required_keys = { "filter", "filter_second" }
|
||||
optional_keys = { "avoid_type" }
|
||||
local score = cfg.ca_score or 90000
|
||||
CA_parms = {
|
||||
{ ca_id = "mai_wolves_move", score = score },
|
||||
{ ca_id = "mai_wolves_wander", score = score - 1 }
|
||||
{ ca_id = "mai_wolves_move", location = 'ai/micro_ais/cas/ca_wolves_move.lua', score = score },
|
||||
{ ca_id = "mai_wolves_wander", location = 'ai/micro_ais/cas/ca_wolves_wander.lua', score = score - 1 }
|
||||
}
|
||||
|
||||
local wolves_aspects = {
|
||||
|
@ -364,12 +364,12 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
optional_keys = { "attention_distance", "attack_distance" }
|
||||
local score = cfg.ca_score or 300000
|
||||
CA_parms = {
|
||||
{ ca_id = "mai_herding_attack_close_enemy", score = score },
|
||||
{ ca_id = "mai_herding_sheep_runs_enemy", score = score - 1 },
|
||||
{ ca_id = "mai_herding_sheep_runs_dog", score = score - 2 },
|
||||
{ ca_id = "mai_herding_herd_sheep", score = score - 3 },
|
||||
{ ca_id = "mai_herding_sheep_move", score = score - 4 },
|
||||
{ ca_id = "mai_herding_dog_move", score = score - 5 }
|
||||
{ ca_id = "mai_herding_attack_close_enemy", location = 'ai/micro_ais/cas/ca_herding_attack_close_enemy.lua', score = score },
|
||||
{ ca_id = "mai_herding_sheep_runs_enemy", location = 'ai/micro_ais/cas/ca_herding_sheep_runs_enemy.lua', score = score - 1 },
|
||||
{ ca_id = "mai_herding_sheep_runs_dog", location = 'ai/micro_ais/cas/ca_herding_sheep_runs_dog.lua', score = score - 2 },
|
||||
{ ca_id = "mai_herding_herd_sheep", location = 'ai/micro_ais/cas/ca_herding_herd_sheep.lua', score = score - 3 },
|
||||
{ ca_id = "mai_herding_sheep_move", location = 'ai/micro_ais/cas/ca_herding_sheep_move.lua', score = score - 4 },
|
||||
{ ca_id = "mai_herding_dog_move", location = 'ai/micro_ais/cas/ca_herding_dog_move.lua', score = score - 5 }
|
||||
}
|
||||
|
||||
elseif (cfg.ai_type == 'forest_animals') then
|
||||
|
@ -378,10 +378,10 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
}
|
||||
local score = cfg.ca_score or 300000
|
||||
CA_parms = {
|
||||
{ ca_id = "mai_forest_animals_new_rabbit", score = score },
|
||||
{ ca_id = "mai_forest_animals_tusker_attack", score = score - 1 },
|
||||
{ ca_id = "mai_forest_animals_forest_move", score = score - 2 },
|
||||
{ ca_id = "mai_forest_animals_tusklet", score = score - 3 }
|
||||
{ ca_id = "mai_forest_animals_new_rabbit", location = 'ai/micro_ais/cas/ca_forest_animals_new_rabbit.lua', score = score },
|
||||
{ ca_id = "mai_forest_animals_tusker_attack", location = 'ai/micro_ais/cas/ca_forest_animals_tusker_attack.lua', score = score - 1 },
|
||||
{ ca_id = "mai_forest_animals_move", location = 'ai/micro_ais/cas/ca_forest_animals_move.lua', score = score - 2 },
|
||||
{ ca_id = "mai_forest_animals_tusklet_move", location = 'ai/micro_ais/cas/ca_forest_animals_tusklet_move.lua', score = score - 3 }
|
||||
}
|
||||
|
||||
elseif (cfg.ai_type == 'swarm') then
|
||||
|
@ -396,8 +396,8 @@ function wesnoth.wml_actions.micro_ai(cfg)
|
|||
optional_keys = { "type", "pack_size", "show_pack_number" }
|
||||
local score = cfg.ca_score or 300000
|
||||
CA_parms = {
|
||||
{ ca_id = "mai_wolves_multipacks_attack", score = score },
|
||||
{ ca_id = "mai_wolves_multipacks_wander", score = score - 1 }
|
||||
{ ca_id = "mai_wolves_multipacks_attack", location = 'ai/micro_ais/cas/ca_wolves_multipacks_attack.lua', score = score },
|
||||
{ ca_id = "mai_wolves_multipacks_wander", location = 'ai/micro_ais/cas/ca_wolves_multipacks_wander.lua', score = score - 1 }
|
||||
}
|
||||
|
||||
elseif (cfg.ai_type == 'hunter') then
|
||||
|
|
115
data/ai/micro_ais/cas/ca_big_animals.lua
Normal file
115
data/ai/micro_ais/cas/ca_big_animals.lua
Normal file
|
@ -0,0 +1,115 @@
|
|||
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_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'
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return ca_big_animals
|
169
data/ai/micro_ais/cas/ca_forest_animals_move.lua
Normal file
169
data/ai/micro_ais/cas/ca_forest_animals_move.lua
Normal file
|
@ -0,0 +1,169 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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 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
|
||||
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
|
||||
|
||||
-- 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' }
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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())
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
71
data/ai/micro_ais/cas/ca_forest_animals_new_rabbit.lua
Normal file
71
data/ai/micro_ais/cas/ca_forest_animals_new_rabbit.lua
Normal file
|
@ -0,0 +1,71 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
-- 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)
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
return ca_forest_animals_new_rabbit
|
68
data/ai/micro_ais/cas/ca_forest_animals_tusker_attack.lua
Normal file
68
data/ai/micro_ais/cas/ca_forest_animals_tusker_attack.lua
Normal file
|
@ -0,0 +1,68 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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)
|
||||
|
||||
-- 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
|
||||
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 } }
|
||||
}
|
||||
|
||||
-- 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 } }
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
-- 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
|
48
data/ai/micro_ais/cas/ca_forest_animals_tusklet_move.lua
Normal file
48
data/ai/micro_ais/cas/ca_forest_animals_tusklet_move.lua
Normal file
|
@ -0,0 +1,48 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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
|
||||
|
||||
-- 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
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
-- Also make sure tusklets never attack
|
||||
ai.stopunit_all(tusklet)
|
||||
end
|
||||
end
|
||||
|
||||
return ca_forest_animals_tusklet_move
|
128
data/ai/micro_ais/cas/ca_herding_attack_close_enemy.lua
Normal file
128
data/ai/micro_ais/cas/ca_herding_attack_close_enemy.lua
Normal file
|
@ -0,0 +1,128 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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} }
|
||||
|
||||
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} }
|
||||
|
||||
-- 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)
|
||||
|
||||
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 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)
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
52
data/ai/micro_ais/cas/ca_herding_dog_move.lua
Normal file
52
data/ai/micro_ais/cas/ca_herding_dog_move.lua
Normal file
|
@ -0,0 +1,52 @@
|
|||
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_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
|
||||
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]
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
--print('Dog wandering')
|
||||
AH.movefull_stopunit(ai, dog, best_hex)
|
||||
end
|
||||
|
||||
return ca_herding_dog_move
|
20
data/ai/micro_ais/cas/ca_herding_f_herding_area.lua
Normal file
20
data/ai/micro_ais/cas/ca_herding_f_herding_area.lua
Normal file
|
@ -0,0 +1,20 @@
|
|||
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 } } }
|
||||
} )
|
||||
|
||||
-- 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
|
||||
end
|
93
data/ai/micro_ais/cas/ca_herding_herd_sheep.lua
Normal file
93
data/ai/micro_ais/cas/ca_herding_herd_sheep.lua
Normal file
|
@ -0,0 +1,93 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
|
||||
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 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
|
||||
|
||||
-- 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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
return ca_herding_herd_sheep
|
50
data/ai/micro_ais/cas/ca_herding_sheep_move.lua
Normal file
50
data/ai/micro_ais/cas/ca_herding_sheep_move.lua
Normal file
|
@ -0,0 +1,50 @@
|
|||
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_herding_sheep_move = {}
|
||||
|
||||
local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area.lua"
|
||||
|
||||
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
|
||||
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]
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
41
data/ai/micro_ais/cas/ca_herding_sheep_runs_dog.lua
Normal file
41
data/ai/micro_ais/cas/ca_herding_sheep_runs_dog.lua
Normal file
|
@ -0,0 +1,41 @@
|
|||
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 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
|
||||
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]
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
return ca_herding_sheep_runs_dog
|
54
data/ai/micro_ais/cas/ca_herding_sheep_runs_enemy.lua
Normal file
54
data/ai/micro_ais/cas/ca_herding_sheep_runs_enemy.lua
Normal file
|
@ -0,0 +1,54 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- 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)
|
||||
|
||||
AH.movefull_stopunit(ai, sheep, best_hex)
|
||||
end
|
||||
|
||||
return ca_herding_sheep_runs_enemy
|
104
data/ai/micro_ais/cas/ca_wolves_move.lua
Normal file
104
data/ai/micro_ais/cas/ca_wolves_move.lua
Normal file
|
@ -0,0 +1,104 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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 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
|
||||
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)
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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)
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
return rating
|
||||
end)
|
||||
|
||||
AH.movefull_stopunit(ai, wolves[i], move)
|
||||
end
|
||||
end
|
||||
|
||||
return ca_wolves_move
|
194
data/ai/micro_ais/cas/ca_wolves_multipacks_attack.lua
Normal file
194
data/ai/micro_ais/cas/ca_wolves_multipacks_attack.lua
Normal file
|
@ -0,0 +1,194 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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 WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lua"
|
||||
|
||||
local ca_wolves_multipacks_attack = {}
|
||||
|
||||
function ca_wolves_multipacks_attack:evaluation(ai, cfg)
|
||||
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[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)
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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.
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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 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
|
||||
|
||||
-- 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
|
||||
end
|
||||
|
||||
return ca_wolves_multipacks_attack
|
143
data/ai/micro_ais/cas/ca_wolves_multipacks_functions.lua
Normal file
143
data/ai/micro_ais/cas/ca_wolves_multipacks_functions.lua
Normal file
|
@ -0,0 +1,143 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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 }
|
||||
end
|
||||
|
||||
|
||||
function wolves_multipacks_functions.assign_packs(cfg)
|
||||
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)
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 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
|
||||
|
||||
return packs
|
||||
end
|
||||
|
||||
return wolves_multipacks_functions
|
||||
|
142
data/ai/micro_ais/cas/ca_wolves_multipacks_wander.lua
Normal file
142
data/ai/micro_ais/cas/ca_wolves_multipacks_wander.lua
Normal file
|
@ -0,0 +1,142 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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 WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lua"
|
||||
|
||||
local ca_wolves_multipacks_wander = {}
|
||||
|
||||
function ca_wolves_multipacks_wander:evaluation(ai, cfg)
|
||||
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' }
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
-- 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])
|
||||
|
||||
-- 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.)
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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])
|
||||
|
||||
-- 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
|
||||
end
|
||||
|
||||
return ca_wolves_multipacks_wander
|
63
data/ai/micro_ais/cas/ca_wolves_wander.lua
Normal file
63
data/ai/micro_ais/cas/ca_wolves_wander.lua
Normal file
|
@ -0,0 +1,63 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
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 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
|
||||
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 }
|
||||
}
|
||||
|
||||
-- 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
return ca_wolves_wander
|
|
@ -62,8 +62,6 @@
|
|||
|
||||
gold=0
|
||||
income=-2
|
||||
|
||||
{MICRO_AI_FOREST_ANIMALS}
|
||||
[/side]
|
||||
|
||||
[side]
|
||||
|
@ -77,8 +75,6 @@
|
|||
|
||||
gold=0
|
||||
income=-2
|
||||
|
||||
{MICRO_AI_BIG_ANIMALS}
|
||||
[/side]
|
||||
|
||||
[side]
|
||||
|
@ -92,8 +88,6 @@
|
|||
|
||||
gold=0
|
||||
income=-2
|
||||
|
||||
{MICRO_AI_BIG_ANIMALS}
|
||||
[/side]
|
||||
|
||||
[side]
|
||||
|
@ -107,8 +101,6 @@
|
|||
|
||||
gold=0
|
||||
income=-2
|
||||
|
||||
{MICRO_AI_BIG_ANIMALS}
|
||||
[/side]
|
||||
|
||||
[side]
|
||||
|
@ -122,8 +114,6 @@
|
|||
|
||||
gold=0
|
||||
income=-2
|
||||
|
||||
{MICRO_AI_WOLVES}
|
||||
[/side]
|
||||
|
||||
[side]
|
||||
|
@ -137,8 +127,6 @@
|
|||
|
||||
gold=0
|
||||
income=-2
|
||||
|
||||
{MICRO_AI_HERDING}
|
||||
[/side]
|
||||
|
||||
[side] # This side is only here because we need one persistent side for the game to go on
|
||||
|
|
|
@ -41,8 +41,6 @@
|
|||
|
||||
gold=0
|
||||
income=-2
|
||||
|
||||
{MICRO_AI_WOLVES_MULTIPACKS}
|
||||
[/side]
|
||||
|
||||
[side]
|
||||
|
@ -56,8 +54,6 @@
|
|||
|
||||
gold=0
|
||||
income=-3
|
||||
|
||||
{MICRO_AI_WOLVES_MULTIPACKS}
|
||||
[/side]
|
||||
|
||||
[side] # This side is only here because we need one persistent side for the game to go on
|
||||
|
|
|
@ -4900,6 +4900,7 @@
|
|||
ENABLE_OPENMP_SUPPORT = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_VERSION = "";
|
||||
INSTALL_PATH = /usr/local/bin;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -4907,6 +4908,7 @@
|
|||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = wesnothd;
|
||||
SDKROOT = macosx;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
|
@ -4936,7 +4938,7 @@
|
|||
"\"$(SRCROOT)/lib\"",
|
||||
);
|
||||
PRODUCT_NAME = Wesnoth;
|
||||
SDKROOT = "";
|
||||
SDKROOT = macosx;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
name = Debug;
|
||||
|
@ -4966,7 +4968,7 @@
|
|||
);
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_NAME = Wesnoth;
|
||||
SDKROOT = "";
|
||||
SDKROOT = macosx;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
name = Release;
|
||||
|
|
Loading…
Add table
Reference in a new issue