From 59cc0fba0bbd4d385a2ae8d46ee54aacb56f3ec8 Mon Sep 17 00:00:00 2001 From: PeaAshMeter Date: Fri, 30 Jan 2026 00:32:05 +0300 Subject: [PATCH] rewrite sprite:animate, residentsleeper:sleep, attack:cast to use callback trees --- lib/animation_node.lua | 1 - lib/annotations.lua | 2 + lib/character/behaviors/residentsleeper.lua | 32 ++++++++--- lib/character/behaviors/sprite.lua | 21 ++++--- lib/spellbook.lua | 63 +++++++-------------- 5 files changed, 58 insertions(+), 61 deletions(-) diff --git a/lib/animation_node.lua b/lib/animation_node.lua index 90bcf1b..ece3aa7 100644 --- a/lib/animation_node.lua +++ b/lib/animation_node.lua @@ -1,6 +1,5 @@ local easing = require "lib.utils.easing" ---- @alias voidCallback fun(): nil --- @alias animationRunner fun(node: AnimationNode) --- Узел дерева анимаций. diff --git a/lib/annotations.lua b/lib/annotations.lua index cc32630..a6be927 100644 --- a/lib/annotations.lua +++ b/lib/annotations.lua @@ -9,3 +9,5 @@ Tree.behaviors.positioned = require "character.behaviors.positioned" Tree.behaviors.tiled = require "character.behaviors.tiled" Tree.behaviors.cursor = require "character.behaviors.cursor" Tree.behaviors.ai = require "lib.character.behaviors.ai" + +--- @alias voidCallback fun(): nil diff --git a/lib/character/behaviors/residentsleeper.lua b/lib/character/behaviors/residentsleeper.lua index c46b701..a1e1aef 100644 --- a/lib/character/behaviors/residentsleeper.lua +++ b/lib/character/behaviors/residentsleeper.lua @@ -1,21 +1,37 @@ --- Умеет асинхронно ждать какое-то время (для анимаций) --- @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 = {} behavior.__index = behavior behavior.id = "residentsleeper" function behavior.new() return setmetatable({}, behavior) end -function behavior:update(dt) - if not self.animationNode then return end - self.animationNode:update(dt) +function behavior:update(_) + if self.state ~= 'running' then return end + + local t = love.timer.getTime() + if t >= self.t0 + self.sleepTime then + self.state = 'finished' + self.callback() + end end ---- @param node AnimationNode -function behavior:sleep(node) - if self.animationNode then self.animationNode:finish() end - self.animationNode = node +--- @return Task +function behavior:sleep(ms) + self.sleepTime = ms / 1000 + return function(callback) + if self.state == 'running' then + self.callback() + end + + self.t0 = love.timer.getTime() + self.callback = callback + self.state = 'running' + end end return behavior diff --git a/lib/character/behaviors/sprite.lua b/lib/character/behaviors/sprite.lua index 33f076b..96eac8f 100644 --- a/lib/character/behaviors/sprite.lua +++ b/lib/character/behaviors/sprite.lua @@ -69,17 +69,20 @@ function sprite:draw() ) end ---- @param node AnimationNode +--- @return Task function sprite:animate(state, node) - if not self.animationGrid[state] then - return print("[SpriteBehavior]: no animation for '" .. state .. "'") + return function(callback) + if not self.animationGrid[state] then + print("[SpriteBehavior]: no animation for '" .. state .. "'") + callback() + end + self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED, + function() + self:loop("idle") + callback() + end) + self.state = state end - self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED, - function() - self:loop("idle") - node:finish() - end) - self.state = state end function sprite:loop(state) diff --git a/lib/spellbook.lua b/lib/spellbook.lua index 1767c4f..34af175 100644 --- a/lib/spellbook.lua +++ b/lib/spellbook.lua @@ -7,17 +7,16 @@ --- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла --- Да, это Future/Promise/await/async -local AnimationNode = require "lib.animation_node" -local easing = require "lib.utils.easing" +local Counter = require 'lib.utils.counter' --- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell --- @field tag string --- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла --- @field draw fun(self: Spell): nil Рисует превью каста, ничего не должна изменять в идеальном мире --- @field cast fun(self: Spell, caster: Character, target: Vec3): Task | nil Вызывается в момент каста, изменяет мир. -local spell = {} -spell.__index = spell -spell.tag = "base" +local spell = {} +spell.__index = spell +spell.tag = "base" function spell:update(caster, dt) end @@ -59,9 +58,6 @@ function walk:cast(caster, target) local testChar = Tree.level.characters[1]; - - - return function(callback) -- <- вызовется после всех анимаций local counter = require 'lib.utils.counter' (callback) counter.push() @@ -147,7 +143,7 @@ function attack:cast(caster, target) print("dist:", dist) return dist > 2 end) then - return false + return end caster:try(Tree.behaviors.stats, function(stats) @@ -156,7 +152,7 @@ function attack:cast(caster, target) --- @type Character 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] targetCharacter:try(Tree.behaviors.stats, function(stats) stats.hp = stats.hp - 4 @@ -164,43 +160,24 @@ function attack:cast(caster, target) local sprite = caster: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) - AnimationNode { - onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end, - 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 function(callback) + local c = Counter(callback) - 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 ----------------------------------------