From 2e96ec821df4c110c2c7d6fce97bce8cae5ab747 Mon Sep 17 00:00:00 2001 From: PeaAshMeter Date: Wed, 18 Mar 2026 05:09:35 +0300 Subject: [PATCH] Add cooldown handling for spells and display in UI - Implement cooldown tracking in SpellcasterBehavior - Decrease cooldowns at end of each round in turn order - Prevent casting spells on cooldown in spell.cast - Show cooldown overlay and block click on skill buttons - Adjust font sizes for better UI consistency --- lib/character/behaviors/ai.lua | 31 +++++++++++++++---------- lib/character/behaviors/spellcaster.lua | 11 +++++++++ lib/level/selector.lua | 3 ++- lib/level/turn_order.lua | 10 ++++++++ lib/simple_ui/level/bar.lua | 2 +- lib/simple_ui/level/end_turn.lua | 2 +- lib/simple_ui/level/skill_row.lua | 30 +++++++++++++++++++++++- lib/spell/spell.lua | 9 +++++++ lib/utils/font_manager.lua | 10 ++++---- lib/utils/priority_queue.lua | 2 +- main.lua | 2 +- 11 files changed, 89 insertions(+), 23 deletions(-) diff --git a/lib/character/behaviors/ai.lua b/lib/character/behaviors/ai.lua index 5aea6d3..b5e7b82 100644 --- a/lib/character/behaviors/ai.lua +++ b/lib/character/behaviors/ai.lua @@ -1,4 +1,4 @@ -local easing = require "lib.utils.easing" +local easing = require "lib.utils.easing" local function closestCharacter(char) local caster = Vec3 {} @@ -39,17 +39,24 @@ function behavior:makeTurn() self.target = Vec3 { b.position.x, b.position.y + 1 } --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти end) - spellB.spellbook[1]:cast(self.owner, self.target)(function() - -- здесь мы оказываемся после того, как сходили в первый раз - print("[AI]: finished move 1") - local newTarget = Vec3 { 1, 1 } - -- поэтому позиция персонажа для нового каста пересчитается динамически - spellB.spellbook[1]:cast(self.owner, newTarget)(function() - print("[AI]: finished move 2") - -- дергаем функцию после завершения хода - callback() - end) - end) + local task1 = spellB.spellbook[1]:cast(self.owner, self.target) + if task1 then + task1( + function() + -- здесь мы оказываемся после того, как сходили в первый раз + local newTarget = Vec3 { 1, 1 } + local task2 = spellB.spellbook[1]:cast(self.owner, newTarget) + if task2 then + -- дергаем функцию после завершения хода + task2(callback) + else + callback() + end + end + ) + else + callback() + end end) end end diff --git a/lib/character/behaviors/spellcaster.lua b/lib/character/behaviors/spellcaster.lua index 37c128b..aa0ad60 100644 --- a/lib/character/behaviors/spellcaster.lua +++ b/lib/character/behaviors/spellcaster.lua @@ -1,11 +1,13 @@ --- @class SpellcasterBehavior : Behavior --- @field spellbook Spell[] собственный набор спеллов персонажа --- @field cast Spell | nil ссылка на активный спелл из спеллбука +--- @field cooldowns {[string]: integer} текущий кулдаун спеллов по тегам --- @field state "idle" | "casting" | "running" local behavior = {} behavior.__index = behavior behavior.id = "spellcaster" behavior.state = "idle" +behavior.cooldowns = {} ---@param spellbook Spell[] | nil ---@return SpellcasterBehavior @@ -13,6 +15,7 @@ function behavior.new(spellbook) local spb = require "lib.spellbook" --- @todo временное добавление ходьбы (и читов) всем персонажам local t = {} t.spellbook = spellbook or spb.of { spb.walk, spb.regenerateMana, spb.attack } + t.cooldowns = {} return setmetatable(t, behavior) end @@ -23,6 +26,14 @@ function behavior:endCast() Tree.level.selector:unlock() end +function behavior:processCooldowns() + local cds = {} + for tag, cd in pairs(self.cooldowns) do + cds[tag] = (cd - 1) >= 0 and cd - 1 or 0 + end + self.cooldowns = cds +end + function behavior:update(dt) if Tree.level.selector:deselected() then self.state = "idle" diff --git a/lib/level/selector.lua b/lib/level/selector.lua index 2d100bf..06ec4a9 100644 --- a/lib/level/selector.lua +++ b/lib/level/selector.lua @@ -44,8 +44,9 @@ function selector:update(dt) b.state = "running" task( - function(_) -- это коллбэк, который сработает по окончании анимации спелла + function(_) -- это коллбэк, который сработает по окончании анимации спелла b:endCast() + if not char:has(Tree.behaviors.ai) then self:select(char.id) end -- выделяем персонажа обратно после того, как посмотрели на каст end ) end) diff --git a/lib/level/turn_order.lua b/lib/level/turn_order.lua index a059197..08e9eec 100644 --- a/lib/level/turn_order.lua +++ b/lib/level/turn_order.lua @@ -57,10 +57,20 @@ function turnOrder:next() 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") + + for _, id in ipairs(self.actedQueue.data) do + local char = Tree.level.characters[id] + char:try(Tree.behaviors.spellcaster, function(spellcaster) + spellcaster:processCooldowns() + end) + end + self.actedQueue, self.pendingQueue = self.pendingQueue, self.actedQueue self.current = self.pendingQueue:pop() end diff --git a/lib/simple_ui/level/bar.lua b/lib/simple_ui/level/bar.lua index 9bacb90..35601af 100644 --- a/lib/simple_ui/level/bar.lua +++ b/lib/simple_ui/level/bar.lua @@ -57,7 +57,7 @@ function barElement:draw() love.graphics.setColor(1, 1, 1) --- текст поверх if self.drawText then - local font = Tree.fonts:getDefaultTheme():getVariant("medium") + local font = Tree.fonts:getDefaultTheme():getVariant("small") local t = love.graphics.newText(font, tostring(self.value) .. "/" .. tostring(self.maxValue)) love.graphics.draw(t, math.floor(self.bounds.x + self.bounds.width / 2 - t:getWidth() / 2), math.floor(self.bounds.y + self.bounds.height / 2 - t:getHeight() / 2)) diff --git a/lib/simple_ui/level/end_turn.lua b/lib/simple_ui/level/end_turn.lua index 5621311..21b95d7 100644 --- a/lib/simple_ui/level/end_turn.lua +++ b/lib/simple_ui/level/end_turn.lua @@ -22,7 +22,7 @@ function endTurnButton:update(dt) end function endTurnButton:layout() - local font = Tree.fonts:getDefaultTheme():getVariant("headline") + local font = Tree.fonts:getDefaultTheme():getVariant("large") self.text = love.graphics.newText(font, "Завершить ход") self.bounds.width = self.text:getWidth() + 32 self.bounds.height = self.text:getHeight() + 16 diff --git a/lib/simple_ui/level/skill_row.lua b/lib/simple_ui/level/skill_row.lua index 6f7ddc7..b02e5c2 100644 --- a/lib/simple_ui/level/skill_row.lua +++ b/lib/simple_ui/level/skill_row.lua @@ -8,6 +8,7 @@ local UI_SCALE = require "lib.simple_ui.level.scale" --- @field hovered boolean --- @field selected boolean --- @field onClick function? +--- @field getCooldown function? --- @field icon? string local skillButton = setmetatable({}, Element) skillButton.__index = skillButton @@ -18,7 +19,11 @@ function skillButton:update(dt) if self:hitTest(mx, my) then self.hovered = true if Tree.controls:isJustPressed("select") then - if self.onClick then self.onClick() end + local cd = self.getCooldown and self.getCooldown() or 0 + if cd == 0 then + if self.onClick then self.onClick() end + end + Tree.controls:consume("select") end else @@ -29,6 +34,7 @@ end function skillButton:draw() love.graphics.setLineWidth(2) + local cd = self.getCooldown and self.getCooldown() or 0 if not self.icon then love.graphics.setColor(0.05, 0.05, 0.05) @@ -53,6 +59,25 @@ function skillButton:draw() love.graphics.setColor(0.7, 1, 0.7, 0.5) love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) end + + + if cd > 0 then + love.graphics.setColor(0, 0, 0, 0.5) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + + local font = Tree.fonts:getDefaultTheme():getVariant("headline") + love.graphics.setColor(0, 0, 0) + local t = love.graphics.newText(font, tostring(cd)) + love.graphics.draw(t, math.floor(self.bounds.x + 2 + self.bounds.width / 2 - t:getWidth() / 2), + math.floor(self.bounds.y + 2 + self.bounds.height / 2 - t:getHeight() / 2)) + + love.graphics.setColor(1, 1, 1) + love.graphics.draw(t, math.floor(self.bounds.x + self.bounds.width / 2 - t:getWidth() / 2), + math.floor(self.bounds.y + self.bounds.height / 2 - t:getHeight() / 2)) + else + + end + love.graphics.setColor(1, 1, 1) end @@ -92,6 +117,9 @@ function skillRow.new(characterId) behavior.cast = nil end end + skb.getCooldown = function() + return behavior.cooldowns[spell.tag] or 0 + end t.children[i] = skb end end) diff --git a/lib/spell/spell.lua b/lib/spell/spell.lua index 337a02c..577b01b 100644 --- a/lib/spell/spell.lua +++ b/lib/spell/spell.lua @@ -73,6 +73,12 @@ function spell.new(data) or newSpell.targetQuery function newSpell:cast(caster, target) + if caster:try(Tree.behaviors.spellcaster, function(spellcaster) -- проверка на кулдаун + return (spellcaster.cooldowns[self.tag] or 0) > 0 + end) then + return + end + if not self.targetQuery.test(caster, target) then return end -- проверка корректности цели -- проверка на достаточное количество маны @@ -86,6 +92,9 @@ function spell.new(data) stats.mana = stats.mana - self.baseCost end) + caster:try(Tree.behaviors.spellcaster, function(spellcaster) + spellcaster.cooldowns[self.tag] = self.baseCooldown + end) return data.onCast(caster, target) end diff --git a/lib/utils/font_manager.lua b/lib/utils/font_manager.lua index 0bec070..f9c48df 100644 --- a/lib/utils/font_manager.lua +++ b/lib/utils/font_manager.lua @@ -4,11 +4,11 @@ --- @field private _sizes {[FontVariant]: integer} local theme = { _sizes = { - smallest = 10, - small = 12, - medium = 14, - large = 16, - headline = 20, + smallest = 12, + small = 14, + medium = 16, + large = 22, + headline = 32, } } theme.__index = theme diff --git a/lib/utils/priority_queue.lua b/lib/utils/priority_queue.lua index 0097604..4b09c2e 100644 --- a/lib/utils/priority_queue.lua +++ b/lib/utils/priority_queue.lua @@ -1,5 +1,5 @@ ---@class PriorityQueue ----@field private data any[] внутренний массив-куча (индексация с 1) +---@field data any[] внутренний массив-куча (индексация с 1) ---@field private cmp fun(a:any, b:any):boolean компаратор: true, если a выше по приоритету, чем b local PriorityQueue = {} PriorityQueue.__index = PriorityQueue diff --git a/main.lua b/main.lua index a0127ce..25db8f6 100644 --- a/main.lua +++ b/main.lua @@ -111,7 +111,7 @@ function love.draw() testLayout:draw() love.graphics.setColor(1, 1, 1) - love.graphics.setFont(Tree.fonts:getTheme("Roboto_Mono"):getVariant("medium")) + love.graphics.setFont(Tree.fonts:getTheme("Roboto_Mono"):getVariant("small")) local stats = "fps: " .. love.timer.getFPS() .. " lt: " .. lt .. " dt: " .. dt .. " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000)