From 8bcae25a2efdd5cc51c9accdecac596f5e42adb3 Mon Sep 17 00:00:00 2001 From: PeaAshMeter Date: Sun, 12 Oct 2025 03:03:02 +0300 Subject: [PATCH] - implement selector locking when processing a spell (players gonna hate that) - implement spellcaster state handling --- lib/character/behaviors/map.lua | 10 ++++++++-- lib/character/behaviors/spellcaster.lua | 12 ++++++++++-- lib/level/selector.lua | 20 +++++++++++++++++--- lib/spellbook.lua | 14 ++++++++------ lib/ui/layout.lua | 11 +++++++++-- 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/lib/character/behaviors/map.lua b/lib/character/behaviors/map.lua index 535087e..7083ac0 100644 --- a/lib/character/behaviors/map.lua +++ b/lib/character/behaviors/map.lua @@ -7,6 +7,7 @@ local utils = require "lib.utils.utils" --- @field displayedPosition Vec3 точка, в которой персонаж отображается --- @field t0 number время начала движения для анимациии --- @field path Deque путь, по которому сейчас бежит персонаж +--- @field onWalkEnd nil | fun() : nil Функция, которая будет вызвана по завершению [followPath] --- @field size Vec3 local mapBehavior = {} mapBehavior.__index = mapBehavior @@ -24,8 +25,9 @@ function mapBehavior.new(position, size) end --- @param path Deque -function mapBehavior:followPath(path) - if path:is_empty() then return end +function mapBehavior:followPath(path, onEnd) + if path:is_empty() then return onEnd() end + self.onWalkEnd = onEnd self.position = self.displayedPosition self.owner:try(Tree.behaviors.sprite, function(sprite) sprite:play("run", true) @@ -67,6 +69,10 @@ function mapBehavior:update(dt) sprite:play("idle", true) end) self.runTarget = nil + if self.onWalkEnd then + self.onWalkEnd() + self.onWalkEnd = nil + end end else -- анимация перемещения не завершена self.displayedPosition = utils.lerp(self.position, self.runTarget, fraction) -- линейный интерполятор diff --git a/lib/character/behaviors/spellcaster.lua b/lib/character/behaviors/spellcaster.lua index 2195b8f..73aafc2 100644 --- a/lib/character/behaviors/spellcaster.lua +++ b/lib/character/behaviors/spellcaster.lua @@ -1,9 +1,11 @@ --- @class SpellcasterBehavior : Behavior --- @field spellbook Spell[] собственный набор спеллов персонажа --- @field cast Spell | nil ссылка на активный спелл из спеллбука +--- @field state "idle" | "casting" | "running" local behavior = {} behavior.__index = behavior behavior.id = "spellcaster" +behavior.state = "idle" ---@param spellbook Spell[] | nil ---@return SpellcasterBehavior @@ -14,12 +16,18 @@ function behavior.new(spellbook) return setmetatable(t, behavior) end +function behavior:endCast() + self.state = "idle" + self.cast = nil + Tree.level.selector:unlock() +end + function behavior:update(dt) - if self.cast then self.cast:update(self.owner, dt) end + if self.cast and self.state == "casting" then self.cast:update(self.owner, dt) end end function behavior:draw() - if self.cast then self.cast:draw() end + if self.cast and self.state == "casting" then self.cast:draw() end end return behavior diff --git a/lib/level/selector.lua b/lib/level/selector.lua index 5e0a3c2..b32b617 100644 --- a/lib/level/selector.lua +++ b/lib/level/selector.lua @@ -1,5 +1,6 @@ --- @class Selector --- @field id Id | nil +--- @field locked boolean local selector = {} selector.__index = selector @@ -14,7 +15,7 @@ function selector:select(characterId) end function selector:update(dt) - if not Tree.controls:isJustPressed("select") then return end + if self.locked or not Tree.controls:isJustPressed("select") then return end local mousePosition = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor() @@ -32,12 +33,25 @@ function selector:update(dt) self:select(selectedId) return end - b.cast:cast(char, mousePosition) - b.cast = nil + if b.cast:cast(char, mousePosition) then + self:lock() + b.state = "running" + end end) end end +--- Disables the selector until [unlock] is called +function selector:lock() + self.id = nil + self.locked = true +end + +--- Cancels the effect of [lock] +function selector:unlock() + self.locked = false +end + return { new = new } diff --git a/lib/spellbook.lua b/lib/spellbook.lua index 55e227a..45687c0 100644 --- a/lib/spellbook.lua +++ b/lib/spellbook.lua @@ -1,7 +1,7 @@ ---- @class Spell +--- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell --- @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): nil Вызывается в момент каста, изменяет мир +--- @field cast fun(self: Spell, caster: Character, target: Vec3): boolean Вызывается в момент каста, изменяет мир. Возвращает bool в зависимости от того, получилось ли скастовать local spell = {} spell.__index = spell @@ -9,7 +9,7 @@ function spell:update(caster, dt) end function spell:draw() end -function spell:cast(caster, target) end +function spell:cast(caster, target) return true end local walk = setmetatable({ --- @type Deque @@ -18,13 +18,15 @@ local walk = setmetatable({ function walk:cast(caster, target) local path = self.path - if path:is_empty() then return end + if path:is_empty() then return false end path:pop_front() - print("Following path: ") for p in path:values() do print(p) end - caster:has(Tree.behaviors.map):followPath(path) + caster:has(Tree.behaviors.map):followPath(path, function() + caster:has(Tree.behaviors.spellcaster):endCast() + end) -- TODO: списать деньги за каст (антиутопия какая-то) -- TODO: привязка тинькоффа + return true end function walk:update(caster, dt) diff --git a/lib/ui/layout.lua b/lib/ui/layout.lua index 3f0e03f..1f7d783 100644 --- a/lib/ui/layout.lua +++ b/lib/ui/layout.lua @@ -12,9 +12,16 @@ local SkillButton = ui.Rectangle { function SkillButton:update(dt) ui.Rectangle.update(self, dt) self.owner:try(Tree.behaviors.spellcaster, function(spellcaster) - self.color = spellcaster.cast and { 0, 1, 0 } or { 1, 0, 0 } + self.color = spellcaster.state == "casting" and { 0, 1, 0 } or { 1, 0, 0 } self:onTap(function() - spellcaster.cast = spellcaster.cast and nil or spellcaster.spellbook[self.spellId] + if not spellcaster.cast then + spellcaster.cast = spellcaster.spellbook + [self.spellId] + spellcaster.state = "casting" + else + spellcaster.state = "idle" + spellcaster.cast = nil + end end) end) end