From d2caa40a0a7c71c6ff141d864f3932415adca829 Mon Sep 17 00:00:00 2001 From: neckrat Date: Sun, 12 Oct 2025 23:41:16 +0300 Subject: [PATCH] feat: manapool (kind of bad manapool) Co-authored-by: Ivan Yuriev --- lib/character/behaviors/spellcaster.lua | 4 +-- lib/character/behaviors/sprite.lua | 6 +++-- lib/character/character.lua | 3 ++- lib/character/stats.lua | 11 ++++---- lib/controls.lua | 1 + lib/spellbook.lua | 35 ++++++++++++++++++++++++- lib/ui/layout.lua | 3 ++- 7 files changed, 51 insertions(+), 12 deletions(-) diff --git a/lib/character/behaviors/spellcaster.lua b/lib/character/behaviors/spellcaster.lua index 73aafc2..e9041a9 100644 --- a/lib/character/behaviors/spellcaster.lua +++ b/lib/character/behaviors/spellcaster.lua @@ -10,9 +10,9 @@ behavior.state = "idle" ---@param spellbook Spell[] | nil ---@return SpellcasterBehavior function behavior.new(spellbook) - local spb = require "lib.spellbook" --- @todo временное добавление ходьбы всем персонажам + local spb = require "lib.spellbook" --- @todo временное добавление ходьбы (и читов) всем персонажам local t = {} - t.spellbook = spellbook or spb.of { spb.walk } + t.spellbook = spellbook or spb.of { spb.walk, spb.regenerateMana } return setmetatable(t, behavior) end diff --git a/lib/character/behaviors/sprite.lua b/lib/character/behaviors/sprite.lua index 89eecb7..0e35f8b 100644 --- a/lib/character/behaviors/sprite.lua +++ b/lib/character/behaviors/sprite.lua @@ -56,12 +56,14 @@ function sprite:draw() end --- @param state string ---- @param loop boolean | nil +--- @param loop nil | boolean | fun(): nil function sprite:play(state, loop) if not self.animationGrid[state] then return print("[SpriteBehavior]: no animation for '" .. state .. "'") 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 end) self.state = state diff --git a/lib/character/character.lua b/lib/character/character.lua index fd14504..f2c1a2d 100644 --- a/lib/character/character.lua +++ b/lib/character/character.lua @@ -1,12 +1,12 @@ require 'lib.utils.vec3' - --- @alias Id integer --- @type Id local characterId = 1 --- @class Character --- @field id Id +--- @field stats Stats --- @field behaviors Behavior[] --- @field _behaviorsIdx {string: integer} local character = {} @@ -25,6 +25,7 @@ local function spawn(name, template, spriteDir, position, size, level) char = setmetatable(char, character) char.id = characterId characterId = characterId + 1 + char.stats = require('lib.character.stats').new() char.behaviors = {} char._behaviorsIdx = {} diff --git a/lib/character/stats.lua b/lib/character/stats.lua index f535b28..4f65ae7 100644 --- a/lib/character/stats.lua +++ b/lib/character/stats.lua @@ -1,14 +1,15 @@ --- @class Stats ---- @field level integer ---- @field initiative integer --- @field hp integer ---- @field damage integer ---- @field defence integer +--- @field mana integer local stats = {} +stats.__index = stats --- @param level? integer local function new(level) - + return { + hp = 20, + mana = 10 + } end --- creates stats from character template (like warrior etc etc) diff --git a/lib/controls.lua b/lib/controls.lua index 5f283c5..ea0a42f 100644 --- a/lib/controls.lua +++ b/lib/controls.lua @@ -17,6 +17,7 @@ controls.keymap = { cameraMoveRight = control("key", "d"), cameraMoveDown = control("key", "s"), cameraMoveScroll = control("mouse", "3"), + fullMana = control("key", "m"), select = control("mouse", "1") } diff --git a/lib/spellbook.lua b/lib/spellbook.lua index 45687c0..3fb2199 100644 --- a/lib/spellbook.lua +++ b/lib/spellbook.lua @@ -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 --- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла --- @field draw fun(self: Spell): nil Рисует превью каста, ничего не должна изменять в идеальном мире @@ -17,6 +26,11 @@ local walk = setmetatable({ }, spell) function walk:cast(caster, target) + if caster.stats.mana < 2 then + print("not enough mana!") + return false + end + local path = self.path if path:is_empty() then return false end path:pop_front() @@ -26,6 +40,8 @@ function walk:cast(caster, target) end) -- TODO: списать деньги за каст (антиутопия какая-то) -- TODO: привязка тинькоффа + caster.stats.mana = caster.stats.mana - 2 + print(caster.stats.mana) return true end @@ -46,9 +62,26 @@ function walk:draw() love.graphics.setColor(1, 1, 1) 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 = { - walk = walk + walk = walk, + regenerateMana = regenerateMana } --- Создает новый спеллбук с уникальными спеллами (а не ссылками на шаблоны) diff --git a/lib/ui/layout.lua b/lib/ui/layout.lua index 1f7d783..96e8976 100644 --- a/lib/ui/layout.lua +++ b/lib/ui/layout.lua @@ -43,7 +43,8 @@ function layout:build() local r = ui.Row { 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