diff --git a/.luarc.json b/.luarc.json index 0bef050..25bbd47 100644 --- a/.luarc.json +++ b/.luarc.json @@ -6,5 +6,6 @@ "love.filesystem.load": "loadfile" }, "workspace.ignoreDir": ["dev_utils"], - "diagnostics.ignoredFiles": "Disable" + "diagnostics.ignoredFiles": "Disable", + "completion.autoRequire": false } diff --git a/lib/animation_node.lua b/lib/animation_node.lua index e3f5aff..90bcf1b 100644 --- a/lib/animation_node.lua +++ b/lib/animation_node.lua @@ -23,6 +23,7 @@ local easing = require "lib.utils.easing" --- } --- }:run() --- ``` +--- @deprecated --- @class AnimationNode --- @field count integer --- @field run animationRunner @@ -73,6 +74,7 @@ function animation:update(dt) end end +--- @deprecated --- @param data {[1]: animationRunner?, onEnd?: voidCallback, duration: number?, easing: ease?, children?: AnimationNode[]} --- @return AnimationNode local function new(data) diff --git a/lib/character/behaviors/ai.lua b/lib/character/behaviors/ai.lua index 20a3a83..7c52624 100644 --- a/lib/character/behaviors/ai.lua +++ b/lib/character/behaviors/ai.lua @@ -1,4 +1,5 @@ local AnimationNode = require "lib.animation_node" +local easing = require "lib.utils.easing" local function closestCharacter(char) local caster = Vec3 {} @@ -31,76 +32,28 @@ function behavior.new() return setmetatable({}, behavior) end -function behavior:update(dt) - self.owner:try(Tree.behaviors.spellcaster, function(b) - if b.state == "casting" then - b.cast:update(self.owner, dt) - end - end) - if self.animationNode and self.animationNode.state == "running" then - self.animationNode:update(dt) - -- print(self.animationNode.t) +--- @return Task +function behavior:makeTurn() + return function(callback) -- почему так, описано в Task + self.owner:try(Tree.behaviors.spellcaster, function(spellB) + local charTarget = closestCharacter(self.owner) + charTarget:try(Tree.behaviors.positioned, function(b) + 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) + end) end end -function behavior:draw() - self.owner:try(Tree.behaviors.spellcaster, function(b) - if b.state == "casting" then - b.cast:draw() - end - end) -end - -function behavior:makeMove() - self.owner:try(Tree.behaviors.spellcaster, function(spellB) - local charTarget = closestCharacter(self.owner) - charTarget:try(Tree.behaviors.positioned, function(b) - self.target = Vec3 { b.position.x, b.position.y + 1 } --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти - end) - - spellB.spellbook[1]:cast(self.owner, self.target):chain { - spellB.spellbook[1]:cast(self.owner, Vec3 { 10, 10 }):chain { - AnimationNode { - onEnd = function() - Tree.level.turnOrder:next() - end - } - } - }:run() - - - - -- -- print('какещке') - -- self.animationNode = AnimationNode { -- кринж - -- function(node) end, - -- onEnd = function() - -- -- print('kakeshke') - -- end, - -- children = { - -- AnimationNode { - -- function(node) --тяжело - -- local charTarget = closestCharacter(self.owner) - -- charTarget:try(Tree.behaviors.positioned, function(b) - -- self.target = Vec3 { b.position.x, b.position.y + 1 } --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти - -- end) - -- spellB.spellbook[1]:cast(self.owner, self.target) - -- end, - -- onEnd = function() --база - -- end, - -- children = { - -- AnimationNode { - -- function(node) - -- -- if not self.target then return end - -- print("пупупупупупупупупупуупупуууууу") - -- print(spellB.spellbook[3]:cast(self.owner, self.target)) - -- end - -- } - -- } - -- } - -- } - -- } - -- self.animationNode:run() - end) -end - return behavior diff --git a/lib/character/behaviors/light.lua b/lib/character/behaviors/light.lua index 1c629c6..16301e4 100644 --- a/lib/character/behaviors/light.lua +++ b/lib/character/behaviors/light.lua @@ -1,13 +1,17 @@ +local AnimationNode = require "lib.animation_node" +local easing = require "lib.utils.easing" + --- @class LightBehavior : Behavior --- @field intensity number --- @field color Vec3 --- @field seed integer --- @field colorAnimationNode? AnimationNode +--- @field private animateColorCallback? fun(): nil --- @field targetColor? Vec3 --- @field sourceColor? Vec3 -local behavior = {} -behavior.__index = behavior -behavior.id = "light" +local behavior = {} +behavior.__index = behavior +behavior.id = "light" ---@param values {intensity: number?, color: Vec3?, seed: integer?} ---@return LightBehavior @@ -26,11 +30,24 @@ function behavior:update(dt) self.colorAnimationNode:update(dt) end -function behavior:animateColor(targetColor, animationNode) +--- @TODO: refactor +function behavior:animateColor(targetColor) if self.colorAnimationNode then self.colorAnimationNode:finish() end - self.colorAnimationNode = animationNode + self.colorAnimationNode = AnimationNode { + function(_) end, + easing = easing.easeInQuad, + duration = 800, + onEnd = function() + if self.animateColorCallback then self.animateColorCallback() end + end + } + self.colorAnimationNode:run() self.sourceColor = self.color self.targetColor = targetColor + + return function(callback) + self.animateColorCallback = callback + end end function behavior:draw() diff --git a/lib/character/behaviors/tiled.lua b/lib/character/behaviors/tiled.lua index 3c8fbc5..b7b937b 100644 --- a/lib/character/behaviors/tiled.lua +++ b/lib/character/behaviors/tiled.lua @@ -5,7 +5,7 @@ local utils = require "lib.utils.utils" --- @field private runSource? Vec3 точка, из которой бежит персонаж --- @field private runTarget? Vec3 точка, в которую в данный момент бежит персонаж --- @field private path? Deque путь, по которому сейчас бежит персонаж ---- @field private animationNode? AnimationNode AnimationNode, с которым связана анимация перемещения +--- @field private followPathCallback? fun() --- @field private t0 number время начала движения --- @field size Vec3 local behavior = {} @@ -20,10 +20,8 @@ function behavior.new(size) end --- @param path Deque ---- @param animationNode AnimationNode -function behavior:followPath(path, animationNode) - if path:is_empty() then return animationNode:finish() end - self.animationNode = animationNode +--- @return Task +function behavior:followPath(path) self.owner:try(Tree.behaviors.sprite, function(sprite) sprite:loop("run") end) @@ -32,6 +30,10 @@ function behavior:followPath(path, animationNode) local nextCell = path:peek_front() self:runTo(nextCell) path:pop_front() + + return function(callback) + self.followPathCallback = callback + end end --- @param target Vec3 @@ -72,7 +74,10 @@ function behavior:update(dt) sprite:loop("idle") end) self.runTarget = nil - if self.animationNode then self.animationNode:finish() end + + if self.followPathCallback then + self.followPathCallback() + end end else -- анимация перемещения не завершена positioned.position = utils.lerp(self.runSource, self.runTarget, fraction) -- линейный интерполятор diff --git a/lib/level/selector.lua b/lib/level/selector.lua index fd93bbd..3c60a6f 100644 --- a/lib/level/selector.lua +++ b/lib/level/selector.lua @@ -37,11 +37,16 @@ function selector:update(dt) if not selectedId then self:select(nil) end return end - local task = b.cast:cast(char, mousePosition) + local task = b.cast:cast(char, mousePosition) -- в task функция, которая запускает анимацию спелла if task then - task:run() self:lock() b.state = "running" + + task( + function(_) -- это коллбэк, который сработает по окончании анимации спелла + b:endCast() + end + ) end end) end diff --git a/lib/level/turn_order.lua b/lib/level/turn_order.lua index 97e04d1..d4d09d9 100644 --- a/lib/level/turn_order.lua +++ b/lib/level/turn_order.lua @@ -37,7 +37,9 @@ function turnOrder:next() local char = Tree.level.characters[self.current] char:try(Tree.behaviors.ai, function(ai) - ai:makeMove() + ai:makeTurn()(function() + self:next() + end) end) end diff --git a/lib/simple_ui/level/end_turn.lua b/lib/simple_ui/level/end_turn.lua index 8e36e29..ddc0c0b 100644 --- a/lib/simple_ui/level/end_turn.lua +++ b/lib/simple_ui/level/end_turn.lua @@ -57,7 +57,7 @@ function endTurnButton:onClick() end, duration = 1500, easing = easing.easeInOutCubic, - onEnd = function() Tree.level.selector:select(cid) end + onEnd = function() if not playing:has(Tree.behaviors.ai) then Tree.level.selector:select(cid) end end }:run() end diff --git a/lib/spellbook.lua b/lib/spellbook.lua index 1df36f3..6c8c51e 100644 --- a/lib/spellbook.lua +++ b/lib/spellbook.lua @@ -14,7 +14,7 @@ local easing = require "lib.utils.easing" --- @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): AnimationNode | nil Вызывается в момент каста, изменяет мир. +--- @field cast fun(self: Spell, caster: Character, target: Vec3): Task | nil Вызывается в момент каста, изменяет мир. local spell = {} spell.__index = spell spell.tag = "base" @@ -38,25 +38,25 @@ function walk:cast(caster, target) return end - local path = require "lib.pathfinder" (caster:has(Tree.behaviors.positioned).position:floor(), target) + local initialPos = caster:has(Tree.behaviors.positioned).position:floor() + local path = require "lib.pathfinder" (initialPos, target) path:pop_front() - if path:is_empty() then return end - - for p in path:values() do print(p) end + if path:is_empty() then + print("[Walk]: the path is empty", initialPos, target) + return + end caster:try(Tree.behaviors.stats, function(stats) stats.mana = stats.mana - 2 - print(stats.mana) end) local sprite = caster:has(Tree.behaviors.sprite) - if not sprite then return end - return AnimationNode { - function(node) - caster:has(Tree.behaviors.tiled):followPath(path, node) - end, - onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end, - } + assert(sprite, "[Walk]", "WTF DUDE WHERE'S YOUR SPRITE") + if not sprite then + return + end + + return caster:has(Tree.behaviors.tiled):followPath(path) end function walk:update(caster, dt) @@ -90,7 +90,7 @@ function regenerateMana:cast(caster, target) end) print(caster.id, "has regenerated mana and gained initiative") local sprite = caster:has(Tree.behaviors.sprite) - if not sprite then return true end + if not sprite then return nil end local light = require "lib/character/character".spawn("Light Effect") light:addBehavior { @@ -98,29 +98,14 @@ function regenerateMana:cast(caster, target) Tree.behaviors.residentsleeper.new(), Tree.behaviors.positioned.new(caster:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }), } - AnimationNode { - function(node) - local audioPath = Tree.assets.files.audio - sprite:animate("hurt", node) - Tree.audio:crossfade(audioPath.music.level1.battle, - audioPath.music.level1.choral, 5000) - caster:try(Tree.behaviors.ai, function(b) - b:makeMove() - end) - end, - onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end - }:run() - AnimationNode { - function(node) - light:has(Tree.behaviors.light):animateColor(Vec3 {}, node) - end, - easing = easing.easeInQuad, - duration = 800, - onEnd = function() light:die() end - }:run() - - return true + return function(callback) + print(light:has(Tree.behaviors.light).animateColor) + light:has(Tree.behaviors.light):animateColor(Vec3 {})(function() + light:die() + callback() + end) + end end local attack = setmetatable({}, spell) diff --git a/lib/task.lua b/lib/task.lua new file mode 100644 index 0000000..e69de29 diff --git a/lib/utils/task.lua b/lib/utils/task.lua new file mode 100644 index 0000000..a64ba49 --- /dev/null +++ b/lib/utils/task.lua @@ -0,0 +1,36 @@ +--- Обобщенная асинхронная функция +--- +--- Использование в общих чертах выглядит так: +--- ```lua +--- local multiplyByTwoCallback = nil +--- local n = nil +--- local function multiplyByTwoAsync(number) +--- -- императивно сохраняем/обрабатываем параметр +--- n = number +--- return function(callback) -- это функция, которая запускает задачу +--- multiplyByTwoCallback = callback +--- end +--- end +--- +--- local function update(dt) +--- --- ждем нужного момента времени... +--- +--- if multiplyByTwoCallback then -- завершаем вычисление +--- local result = n * 2 +--- multiplyByTwoCallback(result) -- результат асинхронного вычисления идет в параметр коллбека! +--- multiplyByTwoCallback = nil +--- end +--- end +--- +--- +--- --- потом это можно вызывать так: +--- local task = multiplyByTwoAsync(21) +--- -- это ленивое вычисление, так что в этот момент ничего не произойдет +--- -- запускаем +--- task( +--- function(result) print(result) end -- выведет 42 после завершения вычисления, т.е. аналогично `task.then((res) => print(res))` на JS +--- ) +--- +--- ``` +--- @generic T +--- @alias Task fun(callback: fun(value: T): nil): nil