--- Алгоритм обработки заклинания (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 task = require 'lib.utils.task' local spell = require 'lib.spell.spell' local walk = setmetatable({ --- @type Deque path = nil }, spell) walk.tag = "dev_move" function walk:cast(caster, target) if not caster:try(Tree.behaviors.stats, function(stats) return stats.mana >= 2 end) then return end local initialPos = caster:has(Tree.behaviors.positioned).position:floor() local path = require "lib.pathfinder" (initialPos, target) path:pop_front() if path:is_empty() then print("[Walk]: the path is empty", initialPos, target) return end caster:try(Tree.behaviors.stats, function(stats) stats.mana = stats.mana - 2 end) local sprite = caster:has(Tree.behaviors.sprite) assert(sprite, "[Walk]", "WTF DUDE WHERE'S YOUR SPRITE") if not sprite then return end return caster:has(Tree.behaviors.tiled):followPath(path) end function walk:update(caster, dt) 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() self.path = require "lib.pathfinder" (charPos, mpos) end function walk:draw() if not self.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 self.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 local regenerateMana = spell.new { tag = "dev_mana", baseCooldown = 2, baseCost = 0, possibleTarget = "caster", distance = 0, onCast = function(caster, target) caster:try(Tree.behaviors.stats, function(stats) stats.mana = 10 stats.initiative = stats.initiative + 10 end) local sprite = caster:has(Tree.behaviors.sprite) if not sprite then return task.fromValue() end print(caster.id, "has regenerated mana and gained initiative") local light = require "lib/character/character".spawn("Light Effect") light:addBehavior { Tree.behaviors.light.new { color = Vec3 { 0.6, 0.3, 0.3 }, intensity = 4 }, Tree.behaviors.residentsleeper.new(), Tree.behaviors.positioned.new(caster:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }), } local flash = function(callback) light:has(Tree.behaviors.light):animateColor(Vec3 {})( function() light:die() callback() end ) end return task.wait { flash, sprite:animate("hurt") } end } local attack = spell.new { tag = "dev_attack", baseCooldown = 1, baseCost = 2, possibleTarget = "enemy", distance = 1, onCast = function(caster, target) --- @type Character local targetCharacterId = Tree.level.characterGrid:get(target) 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 task.fromValue() end caster:try(Tree.behaviors.positioned, function(b) b:lookAt(target) end) return task.wait { sprite:animate("attack"), task.wait { task.chain(targetCharacter:has(Tree.behaviors.residentsleeper):sleep(200), function() return targetSprite:animate("hurt") end ), Tree.audio:play(Tree.assets.files.audio.sounds.hurt) } } 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 print(i) spb[i] = setmetatable({}, { __index = sp }) end return spb end return spellbook