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? Вызывается в момент каста, изменяет мир. 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?} --- @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 caster:try(Tree.behaviors.spellcaster, function(spellcaster) -- проверка на кулдаун return (spellcaster.cooldowns[self.tag] or 0) > 0 end) then return end 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) caster:try(Tree.behaviors.spellcaster, function(spellcaster) spellcaster.cooldowns[self.tag] = self.baseCooldown end) return data.onCast(caster, target) end return newSpell end return spell