heroes-of-nerevelon/lib/spellbook.lua

165 lines
6.6 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--- Алгоритм обработки заклинания (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
local AnimationNode = require "lib.animation_node"
--- @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): boolean Вызывается в момент каста, изменяет мир. Возвращает bool в зависимости от того, получилось ли скастовать
local spell = {}
spell.__index = spell
function spell:update(caster, dt) end
function spell:draw() end
function spell:cast(caster, target) return true end
local walk = setmetatable({
--- @type Deque
path = nil
}, spell)
function walk:cast(caster, target)
if not caster:try(Tree.behaviors.stats, function(stats)
return stats.mana >= 2
end) then
return false
end
local path = self.path
path:pop_front()
if path:is_empty() then return false end
for p in path:values() do print(p) end
caster:try(Tree.behaviors.stats, function(stats)
stats.mana = stats.mana - 2
print(stats.mana)
end)
local sprite = caster:has(Tree.behaviors.sprite)
if not sprite then return true end
AnimationNode {
function(node) caster:has(Tree.behaviors.map):followPath(path, node) end,
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end,
}:run()
return true
end
function walk:update(caster, dt)
local charPos = caster:has(Tree.behaviors.map).position:floor()
--- @type Vec3
local mpos = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor()
self.path = require "lib.pathfinder" (charPos, mpos)
end
function walk:draw()
if not self.path then return end
--- Это отрисовка пути персонажа к мышке
love.graphics.setColor(0.6, 0.75, 0.5)
for p in self.path:values() do
love.graphics.circle("fill", p.x + 0.45, p.y + 0.45, 0.1)
end
love.graphics.setColor(1, 1, 1)
end
local regenerateMana = setmetatable({}, spell)
function regenerateMana:cast(caster, target)
caster:try(Tree.behaviors.stats, function(stats)
stats.mana = 10
end)
print(caster.id, "has regenerated mana")
local sprite = caster:has(Tree.behaviors.sprite)
if not sprite then return true end
AnimationNode {
function(node)
sprite:animate("hurt", node)
end,
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end
}:run()
return true
end
local attack = setmetatable({}, spell)
function attack:cast(caster, target)
if caster:try(Tree.behaviors.map, function(map)
local dist = math.max(math.abs(map.position.x - target.x), math.abs(map.position.y - target.y))
print("dist:", dist)
return dist > 2
end) then
return false
end
caster:try(Tree.behaviors.stats, function (stats)
stats.mana = stats.mana - 2
end)
--- @type Character
local targetCharacterId = Tree.level.characterGrid:get(target)
if not targetCharacterId then return false end
local targetCharacter = Tree.level.characters[targetCharacterId]
targetCharacter:try(Tree.behaviors.stats, function(stats)
stats.hp = stats.hp - 4
end)
local sprite = caster:has(Tree.behaviors.sprite)
local targetSprite = targetCharacter:has(Tree.behaviors.sprite)
if not sprite or not targetSprite then return true end
AnimationNode {
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end,
children = {
AnimationNode {
function(node)
sprite:animate("attack", node)
end
},
AnimationNode {
function(node)
targetCharacter:has(Tree.behaviors.residentsleeper):sleep(200, node)
end,
children = {
AnimationNode {
function(node)
targetSprite:animate("hurt", node)
end
}
}
}
}
}:run()
return true
end
----------------------------------------
local spellbook = {
walk = walk,
regenerateMana = regenerateMana,
attack = attack
}
--- Создает новый спеллбук с уникальными спеллами (а не ссылками на шаблоны)
--- @param list Spell[]
function spellbook.of(list)
local spb = {}
for i, sp in ipairs(list) do
spb[i] = setmetatable({}, { __index = sp })
end
return spb
end
return spellbook