--- @alias SpellTarget "any" Любой тайл --- | "caster" Сам кастующий --- | "enemy" Противники --- | "ally" Союзники --- | "character" Любой персонаж --- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell --- @field tag string --- @field baseCost integer Базовые затраты маны на каст --- @field baseCooldown integer Базовый кулдаун в ходах --- @field possibleTarget SpellTarget Возможная цель --- @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.possibleTarget = "any" function spell:update(caster, dt) end function spell:draw() end function spell:cast(caster, target) return end --- @param caster Character --- @param spellTarget SpellTarget --- @param targetPosition Vec3 local function checkTarget(caster, spellTarget, targetPosition) --- @TODO имплементировать все варианты SpellTarget local targetCharacterId = Tree.level.characterGrid:get(targetPosition) if spellTarget == "caster" then return targetCharacterId == caster.id end if spellTarget == "character" then return not not targetCharacterId end if spellTarget == "enemy" then return targetCharacterId and targetCharacterId ~= caster.id end return true end --- Конструктор [Spell] --- @param data {tag: string, baseCost: integer, baseCooldown: integer, possibleTarget: SpellTarget, distance: integer?, onCast: fun(caster: Character, target: Vec3): Task} --- @return Spell function spell.new(data) local newSpell = { tag = data.tag, baseCost = data.baseCost, baseCooldown = data.baseCooldown, possibleTarget = data.possibleTarget, distance = data.distance } function newSpell:cast(caster, target) -- проверка на расстояние до цели if self.distance and caster:try(Tree.behaviors.positioned, function(p) local dist = math.max(math.abs(p.position.x - target.x), math.abs(p.position.y - target.y)) print("dist:", dist) return dist > self.distance end) then return end -- проверка корректности цели if not checkTarget(caster, self.possibleTarget, 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 setmetatable(newSpell, spell) end return spell