158 lines
5.8 KiB
Lua
158 lines
5.8 KiB
Lua
local PriorityQueue = require "lib.utils.priority_queue"
|
||
local easing = require "lib.utils.easing"
|
||
|
||
local initiativeComparator = function(id_a, id_b)
|
||
local res = Tree.level.characters[id_a]:try(Tree.behaviors.stats, function(astats)
|
||
local res = Tree.level.characters[id_b]:try(Tree.behaviors.stats, function(bstats)
|
||
return astats.initiative > bstats.initiative
|
||
end)
|
||
return res
|
||
end)
|
||
return res or false
|
||
end
|
||
|
||
--- @class TurnOrder
|
||
--- @field actedQueue PriorityQueue Очередь тех, кто сделал ход в текущем раунде
|
||
--- @field pendingQueue PriorityQueue Очередь тех, кто ждет своего хода в текущем раунде
|
||
--- @field current? Id Считаем того, кто сейчас ходит, отдельно, т.к. он ВСЕГДА первый в списке
|
||
--- @field isTurnsEnabled boolean
|
||
local turnOrder = {}
|
||
turnOrder.__index = turnOrder
|
||
|
||
local function new()
|
||
return setmetatable({
|
||
actedQueue = PriorityQueue.new(initiativeComparator),
|
||
pendingQueue = PriorityQueue.new(initiativeComparator),
|
||
isTurnsEnabled = true,
|
||
}, turnOrder)
|
||
end
|
||
|
||
--- Перемещаем активного персонажа в очередь сходивших
|
||
---
|
||
--- Если в очереди на ход больше никого нет, заканчиваем раунд
|
||
---
|
||
--- Анимируем камеру к следующему персонажу. Если это ИИ, то активируем его логику.
|
||
function turnOrder:next()
|
||
self.actedQueue:insert(self.current)
|
||
local next = self.pendingQueue:peek()
|
||
if not next then self:endRound() else self.current = self.pendingQueue:pop() end
|
||
|
||
local char = Tree.level.characters[self.current]
|
||
|
||
Tree.level.selector:lock()
|
||
char:try(Tree.behaviors.positioned, function(positioned)
|
||
Tree.level.camera:animateTo(positioned.position, 1500, easing.easeInOutCubic)(
|
||
function()
|
||
if char:has(Tree.behaviors.ai) then
|
||
char:has(Tree.behaviors.ai):makeTurn()(
|
||
function()
|
||
self:next()
|
||
end)
|
||
else
|
||
Tree.level.selector:unlock()
|
||
Tree.level.selector:select(self.current)
|
||
end
|
||
end
|
||
)
|
||
end)
|
||
end
|
||
|
||
--- Меняем местами очередь сходивших и не сходивших (пустую)
|
||
function turnOrder:endRound()
|
||
assert(self.pendingQueue:size() == 0, "[TurnOrder]: tried to end the round before everyone had a turn")
|
||
print("[TurnOrder]: end of the round")
|
||
self.actedQueue, self.pendingQueue = self.pendingQueue, self.actedQueue
|
||
self.current = self.pendingQueue:pop()
|
||
end
|
||
|
||
--- Пересчитать очередность хода
|
||
function turnOrder:reorder()
|
||
local _acted, _pending = PriorityQueue.new(initiativeComparator), PriorityQueue.new(initiativeComparator)
|
||
|
||
--- сортировка отдельно кучи не ходивших и ходивших
|
||
while self.pendingQueue:peek() do
|
||
_pending:insert(self.pendingQueue:pop())
|
||
end
|
||
|
||
while self.actedQueue:peek() do
|
||
_acted:insert(self.actedQueue:pop())
|
||
end
|
||
|
||
self.actedQueue, self.pendingQueue = _acted, _pending
|
||
|
||
local t = {}
|
||
for id in self:getOrder(10) do
|
||
table.insert(t, id)
|
||
end
|
||
print("[TurnOrder]: next 10 turns")
|
||
print(table.concat(t, ", "))
|
||
end
|
||
|
||
--- Итератор по бесконечной цикличной очереди хода
|
||
--- @param count integer?
|
||
function turnOrder:getOrder(count)
|
||
local order = { self.current }
|
||
local _acted, _pending = self.actedQueue:copy(), self.pendingQueue:copy()
|
||
|
||
local i, j = 0, 0
|
||
local nextTurn = false -- если вышли за пределы текущего хода, то сортируем список, чтобы поставить активного персонажа на свое место
|
||
return function()
|
||
-------------------- Очередь этого хода: активный + не сходившие
|
||
i = i + 1
|
||
if count and count < 1 then return nil end
|
||
if count and i > count then return nil end
|
||
if i == 1 then return self.current end
|
||
|
||
if _pending:peek() then
|
||
local next = _pending:pop()
|
||
table.insert(order, next)
|
||
return next
|
||
end
|
||
|
||
-------------------- Очередь следующих ходов: цикл по всем персонажам в порядке инициативы
|
||
if not nextTurn then
|
||
while _acted:peek() do
|
||
table.insert(order, _acted:pop())
|
||
end
|
||
table.sort(order, initiativeComparator)
|
||
nextTurn = true
|
||
end
|
||
|
||
j = j + 1
|
||
if j % #order == 0 then return order[#order] end
|
||
return order[j % #order]
|
||
end
|
||
end
|
||
|
||
--- @param id Id
|
||
function turnOrder:add(id)
|
||
self.actedQueue:insert(id) -- новые персонажи по умолчанию попадают в очередь следующего хода
|
||
end
|
||
|
||
--- Удалить персонажа из очереди хода (например, при смерти)
|
||
--- @param id Id
|
||
function turnOrder:remove(id)
|
||
if self.current == id then
|
||
self.current = self.pendingQueue:pop()
|
||
if not self.current then
|
||
self:endRound()
|
||
end
|
||
return
|
||
end
|
||
|
||
local function filterQueue(q, targetId)
|
||
local newQ = PriorityQueue.new(initiativeComparator)
|
||
for _, val in ipairs(q.data) do
|
||
if val ~= targetId then
|
||
newQ:insert(val)
|
||
end
|
||
end
|
||
return newQ
|
||
end
|
||
|
||
self.actedQueue = filterQueue(self.actedQueue, id)
|
||
self.pendingQueue = filterQueue(self.pendingQueue, id)
|
||
end
|
||
|
||
return { new = new }
|