96 lines
3.9 KiB
Lua
96 lines
3.9 KiB
Lua
local Query = require "lib.spell.target_query"
|
||
local targetTest = require "lib.spell.target_test"
|
||
local task = require "lib.utils.task"
|
||
|
||
--- @alias SpellPreview "default" Подсветка возможных целей
|
||
--- | "path" Подсветка пути до цели
|
||
|
||
--- @class Spell
|
||
--- @field tag string
|
||
--- @field baseCost integer Базовые затраты маны на каст
|
||
--- @field baseCooldown integer Базовый кулдаун в ходах
|
||
--- @field targetQuery SpellTargetQuery Селектор возможных целей
|
||
--- @field previewType SpellPreview Вид превью во время каста
|
||
--- @field distance? integer Сторона квадрата с центром в позиции кастера, в пределах которого должна находиться цель, либо отсутствие ограничения
|
||
--- @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): Task<nil>? Вызывается в момент каста, изменяет мир.
|
||
local spell = {}
|
||
spell.__index = spell
|
||
spell.tag = "spell_base"
|
||
spell.baseCost = 1
|
||
spell.baseCooldown = 1
|
||
spell.targetQuery = Query(targetTest.any)
|
||
spell.previewType = "default"
|
||
|
||
function spell:update(caster, dt)
|
||
if self.previewType == "path" then
|
||
local charPos = caster:has(Tree.behaviors.positioned).position:floor()
|
||
--- @type Vec3
|
||
local mpos = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor()
|
||
if self.targetQuery.test(caster, mpos) then
|
||
self.path = require "lib.pathfinder" (charPos, mpos)
|
||
else
|
||
self.path = nil
|
||
end
|
||
end
|
||
end
|
||
|
||
function spell:draw()
|
||
if self.previewType == "path" then
|
||
local path = self.path --[[@as Deque?]]
|
||
if not path then return end
|
||
--- Это отрисовка пути персонажа к мышке
|
||
Tree.level.camera:attach()
|
||
love.graphics.setCanvas(Tree.level.render.textures.overlayLayer)
|
||
love.graphics.setColor(0.6, 0.75, 0.5)
|
||
for p in path:values() do
|
||
love.graphics.circle("fill", p.x + 0.45, p.y + 0.45, 0.1)
|
||
end
|
||
love.graphics.setCanvas()
|
||
Tree.level.camera:detach()
|
||
love.graphics.setColor(1, 1, 1)
|
||
end
|
||
end
|
||
|
||
function spell:cast(caster, target) return task.fromValue() end
|
||
|
||
--- Конструктор [Spell]
|
||
--- @param data {tag: string, baseCost: integer, baseCooldown: integer, targetQuery: SpellTargetQuery?, previewType: SpellPreview?, distance: integer?, onCast: fun(caster: Character, target: Vec3): Task<nil>?}
|
||
--- @return Spell
|
||
function spell.new(data)
|
||
local newSpell = setmetatable({
|
||
tag = data.tag,
|
||
baseCost = data.baseCost,
|
||
baseCooldown = data.baseCooldown,
|
||
targetQuery = data.targetQuery,
|
||
previewType = data.previewType,
|
||
distance = data.distance
|
||
}, spell)
|
||
|
||
newSpell.targetQuery = newSpell.distance
|
||
and newSpell.targetQuery:intersect(Query(targetTest.distance(newSpell.distance)))
|
||
or newSpell.targetQuery
|
||
|
||
function newSpell:cast(caster, target)
|
||
if not self.targetQuery.test(caster, target) then return end -- проверка корректности цели
|
||
|
||
-- проверка на достаточное количество маны
|
||
if caster:try(Tree.behaviors.stats, function(stats)
|
||
return stats.mana < self.baseCost
|
||
end) then
|
||
return
|
||
end
|
||
|
||
caster:try(Tree.behaviors.stats, function(stats)
|
||
stats.mana = stats.mana - self.baseCost
|
||
end)
|
||
|
||
return data.onCast(caster, target)
|
||
end
|
||
|
||
return newSpell
|
||
end
|
||
|
||
return spell
|