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
This commit is contained in:
parent
ec816eb666
commit
2e96ec821d
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
2
main.lua
2
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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user