- implement selector locking when processing a spell (players gonna hate

that)
- implement spellcaster state handling
This commit is contained in:
PeaAshMeter 2025-10-12 03:03:02 +03:00
parent 83115e82f8
commit 8bcae25a2e
5 changed files with 52 additions and 15 deletions

View File

@ -7,6 +7,7 @@ local utils = require "lib.utils.utils"
--- @field displayedPosition Vec3 точка, в которой персонаж отображается --- @field displayedPosition Vec3 точка, в которой персонаж отображается
--- @field t0 number время начала движения для анимациии --- @field t0 number время начала движения для анимациии
--- @field path Deque путь, по которому сейчас бежит персонаж --- @field path Deque путь, по которому сейчас бежит персонаж
--- @field onWalkEnd nil | fun() : nil Функция, которая будет вызвана по завершению [followPath]
--- @field size Vec3 --- @field size Vec3
local mapBehavior = {} local mapBehavior = {}
mapBehavior.__index = mapBehavior mapBehavior.__index = mapBehavior
@ -24,8 +25,9 @@ function mapBehavior.new(position, size)
end end
--- @param path Deque --- @param path Deque
function mapBehavior:followPath(path) function mapBehavior:followPath(path, onEnd)
if path:is_empty() then return end if path:is_empty() then return onEnd() end
self.onWalkEnd = onEnd
self.position = self.displayedPosition self.position = self.displayedPosition
self.owner:try(Tree.behaviors.sprite, function(sprite) self.owner:try(Tree.behaviors.sprite, function(sprite)
sprite:play("run", true) sprite:play("run", true)
@ -67,6 +69,10 @@ function mapBehavior:update(dt)
sprite:play("idle", true) sprite:play("idle", true)
end) end)
self.runTarget = nil self.runTarget = nil
if self.onWalkEnd then
self.onWalkEnd()
self.onWalkEnd = nil
end
end end
else -- анимация перемещения не завершена else -- анимация перемещения не завершена
self.displayedPosition = utils.lerp(self.position, self.runTarget, fraction) -- линейный интерполятор self.displayedPosition = utils.lerp(self.position, self.runTarget, fraction) -- линейный интерполятор

View File

@ -1,9 +1,11 @@
--- @class SpellcasterBehavior : Behavior --- @class SpellcasterBehavior : Behavior
--- @field spellbook Spell[] собственный набор спеллов персонажа --- @field spellbook Spell[] собственный набор спеллов персонажа
--- @field cast Spell | nil ссылка на активный спелл из спеллбука --- @field cast Spell | nil ссылка на активный спелл из спеллбука
--- @field state "idle" | "casting" | "running"
local behavior = {} local behavior = {}
behavior.__index = behavior behavior.__index = behavior
behavior.id = "spellcaster" behavior.id = "spellcaster"
behavior.state = "idle"
---@param spellbook Spell[] | nil ---@param spellbook Spell[] | nil
---@return SpellcasterBehavior ---@return SpellcasterBehavior
@ -14,12 +16,18 @@ function behavior.new(spellbook)
return setmetatable(t, behavior) return setmetatable(t, behavior)
end end
function behavior:endCast()
self.state = "idle"
self.cast = nil
Tree.level.selector:unlock()
end
function behavior:update(dt) 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 end
function behavior:draw() function behavior:draw()
if self.cast then self.cast:draw() end if self.cast and self.state == "casting" then self.cast:draw() end
end end
return behavior return behavior

View File

@ -1,5 +1,6 @@
--- @class Selector --- @class Selector
--- @field id Id | nil --- @field id Id | nil
--- @field locked boolean
local selector = {} local selector = {}
selector.__index = selector selector.__index = selector
@ -14,7 +15,7 @@ function selector:select(characterId)
end end
function selector:update(dt) 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() 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) self:select(selectedId)
return return
end end
b.cast:cast(char, mousePosition) if b.cast:cast(char, mousePosition) then
b.cast = nil self:lock()
b.state = "running"
end
end) end)
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 { return {
new = new new = new
} }

View File

@ -1,7 +1,7 @@
--- @class Spell --- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell
--- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла --- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла
--- @field draw fun(self: Spell): 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 = {} local spell = {}
spell.__index = spell spell.__index = spell
@ -9,7 +9,7 @@ function spell:update(caster, dt) end
function spell:draw() end function spell:draw() end
function spell:cast(caster, target) end function spell:cast(caster, target) return true end
local walk = setmetatable({ local walk = setmetatable({
--- @type Deque --- @type Deque
@ -18,13 +18,15 @@ local walk = setmetatable({
function walk:cast(caster, target) function walk:cast(caster, target)
local path = self.path local path = self.path
if path:is_empty() then return end if path:is_empty() then return false end
path:pop_front() path:pop_front()
print("Following path: ")
for p in path:values() do print(p) end 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: списать деньги за каст (антиутопия какая-то)
-- TODO: привязка тинькоффа -- TODO: привязка тинькоффа
return true
end end
function walk:update(caster, dt) function walk:update(caster, dt)

View File

@ -12,9 +12,16 @@ local SkillButton = ui.Rectangle {
function SkillButton:update(dt) function SkillButton:update(dt)
ui.Rectangle.update(self, dt) ui.Rectangle.update(self, dt)
self.owner:try(Tree.behaviors.spellcaster, function(spellcaster) 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() 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) end)
end end