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 function closestCharacter(char)
|
||||||
local caster = Vec3 {}
|
local caster = Vec3 {}
|
||||||
@ -39,17 +39,24 @@ function behavior:makeTurn()
|
|||||||
self.target = Vec3 { b.position.x, b.position.y + 1 } --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти
|
self.target = Vec3 { b.position.x, b.position.y + 1 } --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти
|
||||||
end)
|
end)
|
||||||
|
|
||||||
spellB.spellbook[1]:cast(self.owner, self.target)(function()
|
local task1 = spellB.spellbook[1]:cast(self.owner, self.target)
|
||||||
-- здесь мы оказываемся после того, как сходили в первый раз
|
if task1 then
|
||||||
print("[AI]: finished move 1")
|
task1(
|
||||||
local newTarget = Vec3 { 1, 1 }
|
function()
|
||||||
-- поэтому позиция персонажа для нового каста пересчитается динамически
|
-- здесь мы оказываемся после того, как сходили в первый раз
|
||||||
spellB.spellbook[1]:cast(self.owner, newTarget)(function()
|
local newTarget = Vec3 { 1, 1 }
|
||||||
print("[AI]: finished move 2")
|
local task2 = spellB.spellbook[1]:cast(self.owner, newTarget)
|
||||||
-- дергаем функцию после завершения хода
|
if task2 then
|
||||||
callback()
|
-- дергаем функцию после завершения хода
|
||||||
end)
|
task2(callback)
|
||||||
end)
|
else
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
else
|
||||||
|
callback()
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
--- @class SpellcasterBehavior : Behavior
|
--- @class SpellcasterBehavior : Behavior
|
||||||
--- @field spellbook Spell[] собственный набор спеллов персонажа
|
--- @field spellbook Spell[] собственный набор спеллов персонажа
|
||||||
--- @field cast Spell | nil ссылка на активный спелл из спеллбука
|
--- @field cast Spell | nil ссылка на активный спелл из спеллбука
|
||||||
|
--- @field cooldowns {[string]: integer} текущий кулдаун спеллов по тегам
|
||||||
--- @field state "idle" | "casting" | "running"
|
--- @field state "idle" | "casting" | "running"
|
||||||
local behavior = {}
|
local behavior = {}
|
||||||
behavior.__index = behavior
|
behavior.__index = behavior
|
||||||
behavior.id = "spellcaster"
|
behavior.id = "spellcaster"
|
||||||
behavior.state = "idle"
|
behavior.state = "idle"
|
||||||
|
behavior.cooldowns = {}
|
||||||
|
|
||||||
---@param spellbook Spell[] | nil
|
---@param spellbook Spell[] | nil
|
||||||
---@return SpellcasterBehavior
|
---@return SpellcasterBehavior
|
||||||
@ -13,6 +15,7 @@ function behavior.new(spellbook)
|
|||||||
local spb = require "lib.spellbook" --- @todo временное добавление ходьбы (и читов) всем персонажам
|
local spb = require "lib.spellbook" --- @todo временное добавление ходьбы (и читов) всем персонажам
|
||||||
local t = {}
|
local t = {}
|
||||||
t.spellbook = spellbook or spb.of { spb.walk, spb.regenerateMana, spb.attack }
|
t.spellbook = spellbook or spb.of { spb.walk, spb.regenerateMana, spb.attack }
|
||||||
|
t.cooldowns = {}
|
||||||
return setmetatable(t, behavior)
|
return setmetatable(t, behavior)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -23,6 +26,14 @@ function behavior:endCast()
|
|||||||
Tree.level.selector:unlock()
|
Tree.level.selector:unlock()
|
||||||
end
|
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)
|
function behavior:update(dt)
|
||||||
if Tree.level.selector:deselected() then
|
if Tree.level.selector:deselected() then
|
||||||
self.state = "idle"
|
self.state = "idle"
|
||||||
|
|||||||
@ -44,8 +44,9 @@ function selector:update(dt)
|
|||||||
b.state = "running"
|
b.state = "running"
|
||||||
|
|
||||||
task(
|
task(
|
||||||
function(_) -- это коллбэк, который сработает по окончании анимации спелла
|
function(_) -- это коллбэк, который сработает по окончании анимации спелла
|
||||||
b:endCast()
|
b:endCast()
|
||||||
|
if not char:has(Tree.behaviors.ai) then self:select(char.id) end -- выделяем персонажа обратно после того, как посмотрели на каст
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|||||||
@ -57,10 +57,20 @@ function turnOrder:next()
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Производим действия в конце раунда
|
||||||
|
---
|
||||||
--- Меняем местами очередь сходивших и не сходивших (пустую)
|
--- Меняем местами очередь сходивших и не сходивших (пустую)
|
||||||
function turnOrder:endRound()
|
function turnOrder:endRound()
|
||||||
assert(self.pendingQueue:size() == 0, "[TurnOrder]: tried to end the round before everyone had a turn")
|
assert(self.pendingQueue:size() == 0, "[TurnOrder]: tried to end the round before everyone had a turn")
|
||||||
print("[TurnOrder]: end of the round")
|
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.actedQueue, self.pendingQueue = self.pendingQueue, self.actedQueue
|
||||||
self.current = self.pendingQueue:pop()
|
self.current = self.pendingQueue:pop()
|
||||||
end
|
end
|
||||||
|
|||||||
@ -57,7 +57,7 @@ function barElement:draw()
|
|||||||
love.graphics.setColor(1, 1, 1)
|
love.graphics.setColor(1, 1, 1)
|
||||||
--- текст поверх
|
--- текст поверх
|
||||||
if self.drawText then
|
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))
|
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),
|
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))
|
math.floor(self.bounds.y + self.bounds.height / 2 - t:getHeight() / 2))
|
||||||
|
|||||||
@ -22,7 +22,7 @@ function endTurnButton:update(dt)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function endTurnButton:layout()
|
function endTurnButton:layout()
|
||||||
local font = Tree.fonts:getDefaultTheme():getVariant("headline")
|
local font = Tree.fonts:getDefaultTheme():getVariant("large")
|
||||||
self.text = love.graphics.newText(font, "Завершить ход")
|
self.text = love.graphics.newText(font, "Завершить ход")
|
||||||
self.bounds.width = self.text:getWidth() + 32
|
self.bounds.width = self.text:getWidth() + 32
|
||||||
self.bounds.height = self.text:getHeight() + 16
|
self.bounds.height = self.text:getHeight() + 16
|
||||||
|
|||||||
@ -8,6 +8,7 @@ local UI_SCALE = require "lib.simple_ui.level.scale"
|
|||||||
--- @field hovered boolean
|
--- @field hovered boolean
|
||||||
--- @field selected boolean
|
--- @field selected boolean
|
||||||
--- @field onClick function?
|
--- @field onClick function?
|
||||||
|
--- @field getCooldown function?
|
||||||
--- @field icon? string
|
--- @field icon? string
|
||||||
local skillButton = setmetatable({}, Element)
|
local skillButton = setmetatable({}, Element)
|
||||||
skillButton.__index = skillButton
|
skillButton.__index = skillButton
|
||||||
@ -18,7 +19,11 @@ function skillButton:update(dt)
|
|||||||
if self:hitTest(mx, my) then
|
if self:hitTest(mx, my) then
|
||||||
self.hovered = true
|
self.hovered = true
|
||||||
if Tree.controls:isJustPressed("select") then
|
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")
|
Tree.controls:consume("select")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -29,6 +34,7 @@ end
|
|||||||
function skillButton:draw()
|
function skillButton:draw()
|
||||||
love.graphics.setLineWidth(2)
|
love.graphics.setLineWidth(2)
|
||||||
|
|
||||||
|
local cd = self.getCooldown and self.getCooldown() or 0
|
||||||
|
|
||||||
if not self.icon then
|
if not self.icon then
|
||||||
love.graphics.setColor(0.05, 0.05, 0.05)
|
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.setColor(0.7, 1, 0.7, 0.5)
|
||||||
love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height)
|
love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height)
|
||||||
end
|
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)
|
love.graphics.setColor(1, 1, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -92,6 +117,9 @@ function skillRow.new(characterId)
|
|||||||
behavior.cast = nil
|
behavior.cast = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
skb.getCooldown = function()
|
||||||
|
return behavior.cooldowns[spell.tag] or 0
|
||||||
|
end
|
||||||
t.children[i] = skb
|
t.children[i] = skb
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|||||||
@ -73,6 +73,12 @@ function spell.new(data)
|
|||||||
or newSpell.targetQuery
|
or newSpell.targetQuery
|
||||||
|
|
||||||
function newSpell:cast(caster, target)
|
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 -- проверка корректности цели
|
if not self.targetQuery.test(caster, target) then return end -- проверка корректности цели
|
||||||
|
|
||||||
-- проверка на достаточное количество маны
|
-- проверка на достаточное количество маны
|
||||||
@ -86,6 +92,9 @@ function spell.new(data)
|
|||||||
stats.mana = stats.mana - self.baseCost
|
stats.mana = stats.mana - self.baseCost
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
caster:try(Tree.behaviors.spellcaster, function(spellcaster)
|
||||||
|
spellcaster.cooldowns[self.tag] = self.baseCooldown
|
||||||
|
end)
|
||||||
return data.onCast(caster, target)
|
return data.onCast(caster, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -4,11 +4,11 @@
|
|||||||
--- @field private _sizes {[FontVariant]: integer}
|
--- @field private _sizes {[FontVariant]: integer}
|
||||||
local theme = {
|
local theme = {
|
||||||
_sizes = {
|
_sizes = {
|
||||||
smallest = 10,
|
smallest = 12,
|
||||||
small = 12,
|
small = 14,
|
||||||
medium = 14,
|
medium = 16,
|
||||||
large = 16,
|
large = 22,
|
||||||
headline = 20,
|
headline = 32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
theme.__index = theme
|
theme.__index = theme
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---@class PriorityQueue
|
---@class PriorityQueue
|
||||||
---@field private data any[] внутренний массив-куча (индексация с 1)
|
---@field data any[] внутренний массив-куча (индексация с 1)
|
||||||
---@field private cmp fun(a:any, b:any):boolean компаратор: true, если a выше по приоритету, чем b
|
---@field private cmp fun(a:any, b:any):boolean компаратор: true, если a выше по приоритету, чем b
|
||||||
local PriorityQueue = {}
|
local PriorityQueue = {}
|
||||||
PriorityQueue.__index = PriorityQueue
|
PriorityQueue.__index = PriorityQueue
|
||||||
|
|||||||
2
main.lua
2
main.lua
@ -111,7 +111,7 @@ function love.draw()
|
|||||||
testLayout:draw()
|
testLayout:draw()
|
||||||
love.graphics.setColor(1, 1, 1)
|
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: " ..
|
local stats = "fps: " ..
|
||||||
love.timer.getFPS() ..
|
love.timer.getFPS() ..
|
||||||
" lt: " .. lt .. " dt: " .. dt .. " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000)
|
" lt: " .. lt .. " dt: " .. dt .. " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user