198 lines
7.3 KiB
Lua
198 lines
7.3 KiB
Lua
local easing = require "lib.utils.easing"
|
|
local pf = require "lib.pathfinder"
|
|
local utils = require "lib.utils.utils"
|
|
|
|
--- @alias AIAction fun(self: AIBehavior): Task<nil>
|
|
|
|
--- @return Character
|
|
local function closestCharacter(char)
|
|
local caster = Vec3 {}
|
|
char:try(Tree.behaviors.positioned, function(b)
|
|
caster = b.position
|
|
end)
|
|
local charTarget
|
|
local minDist = 88005553535 -- spooky magic number
|
|
for k, v in pairs(Tree.level.characters) do
|
|
v:try(Tree.behaviors.positioned, function(b)
|
|
local dist = ((caster.x - b.position.x) ^ 2 + (caster.y - b.position.y) ^ 2) ^ 0.5
|
|
if dist < minDist and dist ~= 0 then
|
|
minDist = dist
|
|
charTarget = v
|
|
end
|
|
-- print(k, b.position)
|
|
end)
|
|
end
|
|
return charTarget
|
|
end
|
|
|
|
-- --- Возвращает все точки в радиусе в виде векторов (должен по крайней мере)
|
|
-- --- @param radius integer
|
|
-- --- @param center Vec3
|
|
-- --- @return Vec3[]
|
|
-- local function circleVectors(center, radius)
|
|
-- local vecs = {}
|
|
-- local res = {}
|
|
-- for t = 0, 2 * math.pi, EPSILON do
|
|
-- local x = math.cos(t) * radius + center.x
|
|
-- local y = math.sin(t) * radius + center.y
|
|
-- table.insert(vecs, Vec3 { math.floor(x), math.floor(y) })
|
|
-- end
|
|
-- for _, v in pairs(vecs) do
|
|
-- local i = 1
|
|
-- while i <= #res and (res[i].x ~= v.x or res[i].y ~= v.y) do
|
|
-- i = i + 1
|
|
-- end
|
|
-- if i == #res + 1 or #res == 0 then
|
|
-- table.insert(res, v)
|
|
-- print('[AI]: circle vecs:', v)
|
|
-- end
|
|
-- end
|
|
-- return res
|
|
-- end
|
|
|
|
--- Возвращает все точки в радиусе в виде векторов (должен по крайней мере)
|
|
--- @param radius integer
|
|
--- @param center Vec3
|
|
--- @return Vec3[]
|
|
local function circleVectors(center, radius)
|
|
local dx, dy, err = radius, 0, 1 - radius
|
|
local vecs, res = {}, {}
|
|
while dx >= dy do
|
|
table.insert(vecs, Vec3 { center.x + dx, center.y + dy })
|
|
table.insert(vecs, Vec3 { center.x - dx, center.y + dy })
|
|
table.insert(vecs, Vec3 { center.x + dx, center.y - dy })
|
|
table.insert(vecs, Vec3 { center.x - dx, center.y - dy })
|
|
table.insert(vecs, Vec3 { center.x + dy, center.y + dx })
|
|
table.insert(vecs, Vec3 { center.x - dy, center.y + dx })
|
|
table.insert(vecs, Vec3 { center.x + dy, center.y - dx })
|
|
table.insert(vecs, Vec3 { center.x - dy, center.y - dx })
|
|
dy = dy + 1
|
|
if err < 0 then
|
|
err = err + 2 * dy + 1
|
|
else
|
|
dx, err = dx - 1, err + 2 * (dy - dx) + 1
|
|
end
|
|
end
|
|
for _, v in pairs(vecs) do
|
|
local i = 1
|
|
while i <= #res and (res[i].x ~= v.x or res[i].y ~= v.y) and v.x >= 0 and v.y >= 0 do
|
|
i = i + 1
|
|
end
|
|
if i == #res + 1 or #res == 0 then
|
|
table.insert(res, v)
|
|
print('[AI]: circle vecs:', v)
|
|
end
|
|
end
|
|
return vecs
|
|
end
|
|
|
|
--- ищет пути к ближайшему персу в определённом радиусе
|
|
--- @param owner Character
|
|
--- @param radius integer здесь мы должны сами определять, сколько должны не доходить до персонажа (1 <= n)
|
|
--- @return Vec3|nil
|
|
local function pathToClosestCharacter(owner, radius)
|
|
local charTarget = closestCharacter(owner)
|
|
local targetPosition, ownerPosition = charTarget:has(Tree.behaviors.positioned), owner:has(Tree.behaviors.positioned)
|
|
if not targetPosition or not ownerPosition then return end
|
|
|
|
local circleVecs = circleVectors(targetPosition.position, radius)
|
|
local target = circleVecs[#circleVecs]
|
|
local path = pf(ownerPosition.position, target)
|
|
for i, c in ipairs(circleVecs) do
|
|
local newPath = pf(ownerPosition.position, c)
|
|
if newPath:size() < path:size() then
|
|
path = newPath
|
|
target = c
|
|
end
|
|
end
|
|
return target
|
|
end
|
|
|
|
--- @type table<Class, AIAction>
|
|
local aiNature = {
|
|
dev_warrior = function(self)
|
|
return function(callback) -- почему так, описано в Task
|
|
self.owner:try(Tree.behaviors.spellcaster, function(spellB)
|
|
self.target = pathToClosestCharacter(self.owner, 1)
|
|
local attackTarget = closestCharacter(self.owner):has(Tree.behaviors.positioned)
|
|
if not attackTarget then return end
|
|
local task1 = spellB.spellbook[1]:cast(self.owner, self.target)
|
|
if task1 then
|
|
task1(
|
|
function()
|
|
-- здесь мы оказываемся после того, как сходили в первый раз
|
|
print('[AI]: я походил')
|
|
local task2 = spellB.spellbook[3]:cast(self.owner, attackTarget.position)
|
|
if task2 then
|
|
-- дергаем функцию после завершения хода
|
|
print('[AI]: и ударил')
|
|
task2(callback)
|
|
else
|
|
print('[AI]: чёт не бьётся')
|
|
callback()
|
|
end
|
|
end
|
|
)
|
|
else
|
|
print('рот этого казино')
|
|
callback()
|
|
end
|
|
end)
|
|
end
|
|
end,
|
|
dev_mage = function(self)
|
|
return function(callback)
|
|
print("etoh... bleh")
|
|
callback()
|
|
end
|
|
end
|
|
}
|
|
|
|
|
|
|
|
--- @class AIBehavior : Behavior
|
|
--- @field target Vec3?
|
|
local behavior = {}
|
|
behavior.__index = behavior
|
|
behavior.id = "ai"
|
|
|
|
function behavior:dev_warrior()
|
|
return function(callback) -- почему так, описано в Task
|
|
self.owner:try(Tree.behaviors.spellcaster, function(spellB)
|
|
self.target = pathToClosestCharacter(self.owner, 1)
|
|
local attackTarget = closestCharacter(self.owner):has(Tree.behaviors.positioned)
|
|
if not attackTarget then return end
|
|
local task1 = spellB.spellbook[1]:cast(self.owner, self.target)
|
|
if task1 then
|
|
task1(
|
|
function()
|
|
-- здесь мы оказываемся после того, как сходили в первый раз
|
|
print('[AI]: я походил')
|
|
local task2 = spellB.spellbook[3]:cast(self.owner, attackTarget.position)
|
|
if task2 then
|
|
-- дергаем функцию после завершения хода
|
|
print('[AI]: и ударил')
|
|
task2(callback)
|
|
else
|
|
print('[AI]: чёт не бьётся')
|
|
callback()
|
|
end
|
|
end
|
|
)
|
|
else
|
|
print('рот этого казино')
|
|
callback()
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
--- @param class Class
|
|
function behavior.new(class)
|
|
return setmetatable({
|
|
makeTurn = aiNature[class]
|
|
}, behavior)
|
|
end
|
|
|
|
return behavior
|