rewrite sprite:animate, residentsleeper:sleep, attack:cast to use

callback trees
This commit is contained in:
PeaAshMeter 2026-01-30 00:32:05 +03:00
parent 86a599723e
commit 59cc0fba0b
5 changed files with 58 additions and 61 deletions

View File

@ -1,6 +1,5 @@
local easing = require "lib.utils.easing" local easing = require "lib.utils.easing"
--- @alias voidCallback fun(): nil
--- @alias animationRunner fun(node: AnimationNode) --- @alias animationRunner fun(node: AnimationNode)
--- Узел дерева анимаций. --- Узел дерева анимаций.

View File

@ -9,3 +9,5 @@ Tree.behaviors.positioned = require "character.behaviors.positioned"
Tree.behaviors.tiled = require "character.behaviors.tiled" Tree.behaviors.tiled = require "character.behaviors.tiled"
Tree.behaviors.cursor = require "character.behaviors.cursor" Tree.behaviors.cursor = require "character.behaviors.cursor"
Tree.behaviors.ai = require "lib.character.behaviors.ai" Tree.behaviors.ai = require "lib.character.behaviors.ai"
--- @alias voidCallback fun(): nil

View File

@ -1,21 +1,37 @@
--- Умеет асинхронно ждать какое-то время (для анимаций) --- Умеет асинхронно ждать какое-то время (для анимаций)
--- @class ResidentSleeperBehavior : Behavior --- @class ResidentSleeperBehavior : Behavior
--- @field animationNode? AnimationNode --- @field private t0 number?
--- @field private sleepTime number?
--- @field private callback voidCallback?
--- @field private state 'running' | 'finished'
local behavior = {} local behavior = {}
behavior.__index = behavior behavior.__index = behavior
behavior.id = "residentsleeper" behavior.id = "residentsleeper"
function behavior.new() return setmetatable({}, behavior) end function behavior.new() return setmetatable({}, behavior) end
function behavior:update(dt) function behavior:update(_)
if not self.animationNode then return end if self.state ~= 'running' then return end
self.animationNode:update(dt)
local t = love.timer.getTime()
if t >= self.t0 + self.sleepTime then
self.state = 'finished'
self.callback()
end
end end
--- @param node AnimationNode --- @return Task<nil>
function behavior:sleep(node) function behavior:sleep(ms)
if self.animationNode then self.animationNode:finish() end self.sleepTime = ms / 1000
self.animationNode = node return function(callback)
if self.state == 'running' then
self.callback()
end
self.t0 = love.timer.getTime()
self.callback = callback
self.state = 'running'
end
end end
return behavior return behavior

View File

@ -69,18 +69,21 @@ function sprite:draw()
) )
end end
--- @param node AnimationNode --- @return Task<nil>
function sprite:animate(state, node) function sprite:animate(state, node)
return function(callback)
if not self.animationGrid[state] then if not self.animationGrid[state] then
return print("[SpriteBehavior]: no animation for '" .. state .. "'") print("[SpriteBehavior]: no animation for '" .. state .. "'")
callback()
end end
self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED, self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED,
function() function()
self:loop("idle") self:loop("idle")
node:finish() callback()
end) end)
self.state = state self.state = state
end end
end
function sprite:loop(state) function sprite:loop(state)
if not self.animationGrid[state] then if not self.animationGrid[state] then

View File

@ -7,8 +7,7 @@
--- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла --- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла
--- Да, это Future/Promise/await/async --- Да, это Future/Promise/await/async
local AnimationNode = require "lib.animation_node" local Counter = require 'lib.utils.counter'
local easing = require "lib.utils.easing"
--- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell --- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell
--- @field tag string --- @field tag string
@ -59,9 +58,6 @@ function walk:cast(caster, target)
local testChar = Tree.level.characters[1]; local testChar = Tree.level.characters[1];
return function(callback) -- <- вызовется после всех анимаций return function(callback) -- <- вызовется после всех анимаций
local counter = require 'lib.utils.counter' (callback) local counter = require 'lib.utils.counter' (callback)
counter.push() counter.push()
@ -147,7 +143,7 @@ function attack:cast(caster, target)
print("dist:", dist) print("dist:", dist)
return dist > 2 return dist > 2
end) then end) then
return false return
end end
caster:try(Tree.behaviors.stats, function(stats) caster:try(Tree.behaviors.stats, function(stats)
@ -156,7 +152,7 @@ function attack:cast(caster, target)
--- @type Character --- @type Character
local targetCharacterId = Tree.level.characterGrid:get(target) local targetCharacterId = Tree.level.characterGrid:get(target)
if not targetCharacterId or targetCharacterId == caster.id then return false end if not targetCharacterId or targetCharacterId == caster.id then return end
local targetCharacter = Tree.level.characters[targetCharacterId] local targetCharacter = Tree.level.characters[targetCharacterId]
targetCharacter:try(Tree.behaviors.stats, function(stats) targetCharacter:try(Tree.behaviors.stats, function(stats)
stats.hp = stats.hp - 4 stats.hp = stats.hp - 4
@ -164,43 +160,24 @@ function attack:cast(caster, target)
local sprite = caster:has(Tree.behaviors.sprite) local sprite = caster:has(Tree.behaviors.sprite)
local targetSprite = targetCharacter:has(Tree.behaviors.sprite) local targetSprite = targetCharacter:has(Tree.behaviors.sprite)
if not sprite or not targetSprite then return true end if not sprite or not targetSprite then return end
caster:try(Tree.behaviors.positioned, function(b) b:lookAt(target) end) caster:try(Tree.behaviors.positioned, function(b) b:lookAt(target) end)
AnimationNode { return function(callback)
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end, local c = Counter(callback)
children = {
AnimationNode {
function(node)
sprite:animate("attack", node)
end
},
AnimationNode {
function(node)
targetCharacter:has(Tree.behaviors.residentsleeper):sleep(node)
end,
duration = 200,
children = {
AnimationNode {
function(node)
local audioPath = Tree.assets.files.audio
targetSprite:animate("hurt", node)
--- @type SourceFilter
local settings = {
type = "highpass",
volume = 1,
lowgain = 0.1
}
Tree.audio:play(audioPath.sounds.hurt)
end
}
}
}
}
}:run()
return true c.push()
sprite:animate("attack")(c.pop)
c.push()
targetCharacter:has(Tree.behaviors.residentsleeper):sleep(200)(
function()
targetSprite:animate("hurt")(c.pop)
Tree.audio:play(Tree.assets.files.audio.sounds.hurt)
end
)
end
end end
---------------------------------------- ----------------------------------------