- 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 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) -- линейный интерполятор

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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