feat: manapool (kind of bad manapool)

Co-authored-by: Ivan Yuriev <peaashmeter@users.noreply.github.com>
This commit is contained in:
neckrat 2025-10-12 23:41:16 +03:00
parent 8bcae25a2e
commit d2caa40a0a
7 changed files with 51 additions and 12 deletions

View File

@ -10,9 +10,9 @@ behavior.state = "idle"
---@param spellbook Spell[] | nil ---@param spellbook Spell[] | nil
---@return SpellcasterBehavior ---@return SpellcasterBehavior
function behavior.new(spellbook) function behavior.new(spellbook)
local spb = require "lib.spellbook" --- @todo временное добавление ходьбы всем персонажам local spb = require "lib.spellbook" --- @todo временное добавление ходьбы (и читов) всем персонажам
local t = {} local t = {}
t.spellbook = spellbook or spb.of { spb.walk } t.spellbook = spellbook or spb.of { spb.walk, spb.regenerateMana }
return setmetatable(t, behavior) return setmetatable(t, behavior)
end end

View File

@ -56,12 +56,14 @@ function sprite:draw()
end end
--- @param state string --- @param state string
--- @param loop boolean | nil --- @param loop nil | boolean | fun(): nil
function sprite:play(state, loop) function sprite:play(state, loop)
if not self.animationGrid[state] then if not self.animationGrid[state] then
return print("[SpriteBehavior]: no animation for '" .. state .. "'") return print("[SpriteBehavior]: no animation for '" .. state .. "'")
end end
self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED, function() self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED,
type(loop) == "function" and loop or
function()
if not loop then self:play("idle", true) end if not loop then self:play("idle", true) end
end) end)
self.state = state self.state = state

View File

@ -1,12 +1,12 @@
require 'lib.utils.vec3' require 'lib.utils.vec3'
--- @alias Id integer --- @alias Id integer
--- @type Id --- @type Id
local characterId = 1 local characterId = 1
--- @class Character --- @class Character
--- @field id Id --- @field id Id
--- @field stats Stats
--- @field behaviors Behavior[] --- @field behaviors Behavior[]
--- @field _behaviorsIdx {string: integer} --- @field _behaviorsIdx {string: integer}
local character = {} local character = {}
@ -25,6 +25,7 @@ local function spawn(name, template, spriteDir, position, size, level)
char = setmetatable(char, character) char = setmetatable(char, character)
char.id = characterId char.id = characterId
characterId = characterId + 1 characterId = characterId + 1
char.stats = require('lib.character.stats').new()
char.behaviors = {} char.behaviors = {}
char._behaviorsIdx = {} char._behaviorsIdx = {}

View File

@ -1,14 +1,15 @@
--- @class Stats --- @class Stats
--- @field level integer
--- @field initiative integer
--- @field hp integer --- @field hp integer
--- @field damage integer --- @field mana integer
--- @field defence integer
local stats = {} local stats = {}
stats.__index = stats
--- @param level? integer --- @param level? integer
local function new(level) local function new(level)
return {
hp = 20,
mana = 10
}
end end
--- creates stats from character template (like warrior etc etc) --- creates stats from character template (like warrior etc etc)

View File

@ -17,6 +17,7 @@ controls.keymap = {
cameraMoveRight = control("key", "d"), cameraMoveRight = control("key", "d"),
cameraMoveDown = control("key", "s"), cameraMoveDown = control("key", "s"),
cameraMoveScroll = control("mouse", "3"), cameraMoveScroll = control("mouse", "3"),
fullMana = control("key", "m"),
select = control("mouse", "1") select = control("mouse", "1")
} }

View File

@ -1,3 +1,12 @@
--- Алгоритм обработки заклинания (for dummies):
--- 1) ПОКА выделен персонаж И он находится в режиме каста, вызывать spell:update() и spell:draw() каждый кадр (это отвечает за обработку и отрисовку превьюшки каста, например, превью пути или зоны поражения; реализуется через установку spellcaster.cast, см. код в кнопке)
--- ЕСЛИ выбран тайл, ТО вызвать spell:cast() (это запрос на обработку последствий применения заклинания, например, старт анимации ходьбы, выпуск снаряда и т.д.; реализовано в selector)
--- ЕСЛИ spell:cast() ИСТИНА, ТО вызвать selector:lock() (отключить обработку выделения всего на уровне; реализовано в selector)
---
--- 2) ПОКА анимация каста НЕ завершена, ничего не делать, ИНАЧЕ вызвать behaviors.spellcaster:endCast() (вот это сейчас нужно вызывать самостоятельно, т.к. нет возможности обобщенно отследить завершение анимаций)
--- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла
--- Да, это Future/Promise/await/async
--- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый 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 Рисует превью каста, ничего не должна изменять в идеальном мире
@ -17,6 +26,11 @@ local walk = setmetatable({
}, spell) }, spell)
function walk:cast(caster, target) function walk:cast(caster, target)
if caster.stats.mana < 2 then
print("not enough mana!")
return false
end
local path = self.path local path = self.path
if path:is_empty() then return false end if path:is_empty() then return false end
path:pop_front() path:pop_front()
@ -26,6 +40,8 @@ function walk:cast(caster, target)
end) end)
-- TODO: списать деньги за каст (антиутопия какая-то) -- TODO: списать деньги за каст (антиутопия какая-то)
-- TODO: привязка тинькоффа -- TODO: привязка тинькоффа
caster.stats.mana = caster.stats.mana - 2
print(caster.stats.mana)
return true return true
end end
@ -46,9 +62,26 @@ function walk:draw()
love.graphics.setColor(1, 1, 1) love.graphics.setColor(1, 1, 1)
end end
local regenerateMana = setmetatable({}, spell)
function regenerateMana:cast(caster, target)
caster.stats.mana = 10
print(caster.id, "has regenerated mana")
caster:try(Tree.behaviors.sprite, function (sprite) -- бойлерплейт (временный)
-- В данный момент заклинание не позволяет отслеживать состояние последствий своего применения, так что надо повесить хоть какую-то анимашку просто для того, чтобы отложить завершение каста куда-то в будущее
-- См. также https://learn.javascript.ru/settimeout-setinterval
sprite:play("hurt", function ()
sprite:play("idle")
caster:has(Tree.behaviors.spellcaster):endCast()
end)
end)
return true
end
---------------------------------------- ----------------------------------------
local spellbook = { local spellbook = {
walk = walk walk = walk,
regenerateMana = regenerateMana
} }
--- Создает новый спеллбук с уникальными спеллами (а не ссылками на шаблоны) --- Создает новый спеллбук с уникальными спеллами (а не ссылками на шаблоны)

View File

@ -43,7 +43,8 @@ function layout:build()
local r = local r =
ui.Row { ui.Row {
children = { children = {
setmetatable({ owner = Tree.level.characters[id], spellId = 1 }, { __index = SkillButton }) setmetatable({ owner = Tree.level.characters[id], spellId = 1 }, { __index = SkillButton }),
setmetatable({ owner = Tree.level.characters[id], spellId = 2 }, { __index = SkillButton })
} }
} }
skillRows[id] = r skillRows[id] = r