From 6b2fb08a5b70fc5b527a737459d96d38903881d3 Mon Sep 17 00:00:00 2001 From: PeaAshMeter Date: Sun, 9 Nov 2025 06:35:51 +0300 Subject: [PATCH] Reimplement turn order management and update character initiative logic --- lib/character/behaviors/spellcaster.lua | 1 + lib/character/character.lua | 1 - lib/level/selector.lua | 8 +-- lib/level/turn_order.lua | 93 +++++++++++++++++++------ lib/spellbook.lua | 3 +- lib/utils/priority_queue.lua | 22 ++++-- main.lua | 14 ++-- 7 files changed, 106 insertions(+), 36 deletions(-) diff --git a/lib/character/behaviors/spellcaster.lua b/lib/character/behaviors/spellcaster.lua index f1ec586..c6cd9af 100644 --- a/lib/character/behaviors/spellcaster.lua +++ b/lib/character/behaviors/spellcaster.lua @@ -19,6 +19,7 @@ end function behavior:endCast() self.state = "idle" self.cast = nil + Tree.level.turnOrder:reorder() Tree.level.selector:unlock() end diff --git a/lib/character/character.lua b/lib/character/character.lua index a11e069..bde0c2c 100644 --- a/lib/character/character.lua +++ b/lib/character/character.lua @@ -35,7 +35,6 @@ local function spawn(name, spriteDir, position, size, initiative) } Tree.level.characters[char.id] = char - Tree.level.turnOrder:updateOrder() return char end diff --git a/lib/level/selector.lua b/lib/level/selector.lua index b89946d..c5db37d 100644 --- a/lib/level/selector.lua +++ b/lib/level/selector.lua @@ -25,7 +25,7 @@ function selector:update(dt) local selectedId = Tree.level.characterGrid:get(Vec3 { mousePosition.x, mousePosition.y }) if not self.id then - if selectedId ~= Tree.level.turnOrder.notWalked[1] and Tree.level.turnOrder.isTurnsEnabled then return end + if selectedId ~= Tree.level.turnOrder.current and Tree.level.turnOrder.isTurnsEnabled then return end return self:select(selectedId) else local char = Tree.level.characters[self.id] @@ -35,10 +35,8 @@ function selector:update(dt) -- тут какая-то страшная дичь, я даже не уверен что оно работает -- зато я точно уверен, что это надо было писать не так if not selectedId then self:select(selectedId) end - -- print(selectedId ~= next(Tree.level.turnOrder.notWalked), Tree.level.turnOrder.isTurnsEnabled) - if selectedId ~= Tree.level.turnOrder.notWalked[1] and Tree.level.turnOrder.isTurnsEnabled then return end - self:select(selectedId) - return + if selectedId ~= Tree.level.turnOrder.current and Tree.level.turnOrder.isTurnsEnabled then return end + return self:select(selectedId) end if b.cast:cast(char, mousePosition) then self:lock() diff --git a/lib/level/turn_order.lua b/lib/level/turn_order.lua index 427228f..6ea46ba 100644 --- a/lib/level/turn_order.lua +++ b/lib/level/turn_order.lua @@ -1,6 +1,6 @@ local PriorityQueue = require "lib.utils.priority_queue" -local sortInitiative = function(id_a, id_b) +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 @@ -11,49 +11,102 @@ local sortInitiative = function(id_a, id_b) end --- @class TurnOrder ---- @field characterIds Id[] ---- @field walked PriorityQueue ---- @field notWalked PriorityQueue +--- @field actedQueue PriorityQueue Очередь тех, кто сделал ход в текущем раунде +--- @field pendingQueue PriorityQueue Очередь тех, кто ждет своего хода в текущем раунде +--- @field current? Id Считаем того, кто сейчас ходит, отдельно, т.к. он ВСЕГДА первый в списке --- @field isTurnsEnabled boolean local turnOrder = {} turnOrder.__index = turnOrder local function new() return setmetatable({ - walked = PriorityQueue.new(sortInitiative), - notWalked = PriorityQueue.new(sortInitiative), - isTurnsEnabled = true + actedQueue = PriorityQueue.new(initiativeComparator), + pendingQueue = PriorityQueue.new(initiativeComparator), + isTurnsEnabled = true, }, turnOrder) end +--- Перемещаем активного персонажа в очередь сходивших +--- +--- Если в очереди на ход больше никого нет, заканчиваем раунд function turnOrder:next() - local cur = self.notWalked:pop() - self.walked:insert(cur) + Tree.level.selector.id = nil + self.actedQueue:insert(self.current) + local next = self.pendingQueue:peek() + if not next then return self:endRound() end + self.current = self.pendingQueue:pop() 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 _walked, _notWalked = PriorityQueue.new(sortInitiative), PriorityQueue.new(sortInitiative) - local charId = self.walked:pop() - while charId do - _walked:insert(charId) - charId = self.walked:pop() + local _acted, _pending = PriorityQueue.new(initiativeComparator), PriorityQueue.new(initiativeComparator) + + --- сортировка отдельно кучи не ходивших и ходивших + while self.pendingQueue:peek() do + _pending:insert(self.pendingQueue:pop()) end - charId = self.notWalked:pop() - while charId do - _notWalked:insert(charId) - charId = self.notWalked:pop() + 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 number +--- Итератор по бесконечной цикличной очереди хода +--- @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) - table.insert(self.characterIds, id) + self.actedQueue:insert(id) -- новые персонажи по умолчанию попадают в очередь следующего хода end return { new = new } diff --git a/lib/spellbook.lua b/lib/spellbook.lua index e117065..69be855 100644 --- a/lib/spellbook.lua +++ b/lib/spellbook.lua @@ -77,8 +77,9 @@ local regenerateMana = setmetatable({}, spell) function regenerateMana:cast(caster, target) caster:try(Tree.behaviors.stats, function(stats) stats.mana = 10 + stats.initiative = stats.initiative + 10 end) - print(caster.id, "has regenerated mana") + print(caster.id, "has regenerated mana and gained initiative") local sprite = caster:has(Tree.behaviors.sprite) if not sprite then return true end AnimationNode { diff --git a/lib/utils/priority_queue.lua b/lib/utils/priority_queue.lua index 89f2b32..0097604 100644 --- a/lib/utils/priority_queue.lua +++ b/lib/utils/priority_queue.lua @@ -1,11 +1,11 @@ ---@class PriorityQueue ----@field private data any[] # внутренний массив-куча (индексация с 1) ----@field private cmp fun(a:any, b:any):boolean # компаратор: true, если a выше по приоритету, чем b +---@field private data any[] внутренний массив-куча (индексация с 1) +---@field private cmp fun(a:any, b:any):boolean компаратор: true, если a выше по приоритету, чем b local PriorityQueue = {} PriorityQueue.__index = PriorityQueue ---Создать очередь с приоритетом. ----@param cmp fun(a:any, b:any):boolean|nil # если nil, используется a < b (мин-куча) +---@param cmp fun(a:any, b:any):boolean|nil если nil, используется a < b (мин-куча) ---@return PriorityQueue function PriorityQueue.new(cmp) local self = setmetatable({}, PriorityQueue) @@ -16,8 +16,8 @@ end -- ===== Внутренние утилиты ===== ----@param i integer @индекс узла ----@param j integer @индекс узла +---@param i integer индекс узла +---@param j integer индекс узла function PriorityQueue:_swap(i, j) self.data[i], self.data[j] = self.data[j], self.data[i] end @@ -103,4 +103,16 @@ function PriorityQueue:is_empty() return #self.data == 0 end +--- Shallow-копирование очереди +function PriorityQueue:copy() + local _data = {} + for i, v in ipairs(self.data) do + _data[i] = v + end + return setmetatable({ + data = _data, + cmp = self.cmp + }, PriorityQueue) +end + return PriorityQueue diff --git a/main.lua b/main.lua index 6fc77d0..cccd3a5 100644 --- a/main.lua +++ b/main.lua @@ -10,10 +10,15 @@ function love.conf(t) end function love.load() - character.spawn("Foodor", Tree.assets.files.sprites.character) - character.spawn("Baris", Tree.assets.files.sprites.character, Vec3 { 3, 3 }, nil, 12) - character.spawn("Foodor Jr", Tree.assets.files.sprites.character, Vec3 { 0, 3 }) - character.spawn("Baris Jr", Tree.assets.files.sprites.character, Vec3 { 0, 6 }) + character.spawn("Foodor", Tree.assets.files.sprites.character, nil, nil, 1) + character.spawn("Baris", Tree.assets.files.sprites.character, Vec3 { 3, 3 }, nil, 2) + character.spawn("Foodor Jr", Tree.assets.files.sprites.character, Vec3 { 0, 3 }, nil, 3) + character.spawn("Baris Jr", Tree.assets.files.sprites.character, Vec3 { 0, 6 }, nil, 4) + for id, _ in pairs(Tree.level.characters) do + Tree.level.turnOrder:add(id) + end + Tree.level.turnOrder:endRound() + print("Now playing:", Tree.level.turnOrder.current) love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true }) end @@ -30,6 +35,7 @@ function love.update(dt) -- удалить как только появится ui для людей if Tree.controls:isJustPressed("endTurnTest") then Tree.level.turnOrder:next() + print("Now playing:", Tree.level.turnOrder.current) end if Tree.controls:isJustPressed("toggleTurns") then print('toggle turns')