Compare commits

..

No commits in common. "4431934e6b0ec71e54dc352b84a3e9f0d9501b47" and "2e6155aea4c59ac302c76ff59d50f718bf3f452d" have entirely different histories.

4 changed files with 43 additions and 106 deletions

View File

@ -1,15 +1,14 @@
local Query = require "lib.spell.target_query"
local targetTest = require "lib.spell.target_test"
--- @alias SpellTarget "any" Любой тайл
--- | "caster" Сам кастующий
--- | "enemy" Противники
--- | "ally" Союзники
--- | "character" Любой персонаж
--- @alias SpellPreview "default" Подсветка возможных целей
--- | "path" Подсветка пути до цели
--- @class Spell
--- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell
--- @field tag string
--- @field baseCost integer Базовые затраты маны на каст
--- @field baseCooldown integer Базовый кулдаун в ходах
--- @field targetQuery SpellTargetQuery Селектор возможных целей
--- @field previewType SpellPreview Вид превью во время каста
--- @field possibleTarget SpellTarget Возможная цель
--- @field distance? integer Сторона квадрата с центром в позиции кастера, в пределах которого должна находиться цель, либо отсутствие ограничения
--- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла
--- @field draw fun(self: Spell): nil Рисует превью каста, ничего не должна изменять в идеальном мире
@ -19,8 +18,7 @@ spell.__index = spell
spell.tag = "spell_base"
spell.baseCost = 1
spell.baseCooldown = 1
spell.targetQuery = Query(targetTest.any)
spell.previewType = "default"
spell.possibleTarget = "any"
function spell:update(caster, dt) end
@ -28,15 +26,34 @@ 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, targetQuery: SpellTargetQuery, distance: integer?, onCast: fun(caster: Character, target: Vec3): Task}
--- @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,
targetQuery = data.targetQuery,
possibleTarget = data.possibleTarget,
distance = data.distance
}
@ -50,7 +67,8 @@ function spell.new(data)
return
end
if not self.targetQuery.test(caster, target) then return end -- проверка корректности цели
-- проверка корректности цели
if not checkTarget(caster, self.possibleTarget, target) then return end
-- проверка на достаточное количество маны
if caster:try(Tree.behaviors.stats, function(stats)

View File

@ -1,67 +0,0 @@
--- Тип, отвечающий за выбор и фильтрацию подходящих тайлов как цели спелла
--- теория множеств my beloved?
--- @class SpellTargetQuery
local query = {}
query.__index = query
--- Проверяет координаты на соответствие внутреннему условию
--- @param caster Character
--- @param position Vec3
--- @return boolean
function query.test(caster, position)
return true
end
--- Объединение
--- @param q SpellTargetQuery
function query:join(q)
return setmetatable({
test = function(caster, pos)
return self.test(caster, pos) or q.test(caster, pos)
end
}, q)
end
--- Пересечение
--- @param q SpellTargetQuery
function query:intersect(q)
return setmetatable({
test = function(caster, pos)
return self.test(caster, pos) and q.test(caster, pos)
end
}, q)
end
--- Исключение (не коммутативное, "те, что есть в query, но нет в q")
--- @param q SpellTargetQuery
function query:exclude(q)
return setmetatable({
test = function(caster, pos)
return self.test(caster, pos) and not q.test(caster, pos)
end
}, q)
end
--- Находит все соответствующие условиям координаты тайлов и возвращает их в виде списка
--- @param caster Character
--- @return Vec3[]
function query:asSet(caster)
--- @TODO: оптимизировать и брать не всю карту для выборки
local res = {}
for _, tile in pairs(Tree.level.tileGrid) do
if self.test(caster, tile.position) then
table.insert(res, tile.position)
end
end
return res
end
--- @param test SpellTargetTest
local function new(test)
return setmetatable({
test = test
}, query)
end
return new

View File

@ -1,13 +0,0 @@
--- @alias SpellTargetTest fun(caster: Character, targetPosition: Vec3) : boolean
return {
any = function() return true end,
caster = function(caster, targetPosition)
local targetCharacterId = Tree.level.characterGrid:get(targetPosition)
return caster.id == targetCharacterId
end,
character = function(caster, targetPosition)
local targetCharacterId = Tree.level.characterGrid:get(targetPosition)
return not not targetCharacterId
end
}

View File

@ -9,8 +9,6 @@
local task = require 'lib.utils.task'
local spell = require 'lib.spell.spell'
local targetTest = require 'lib.spell.target_test'
local Query = require "lib.spell.target_query"
local walk = setmetatable({
--- @type Deque
@ -71,7 +69,7 @@ local regenerateMana = spell.new {
tag = "dev_mana",
baseCooldown = 2,
baseCost = 0,
targetQuery = Query(targetTest.caster),
possibleTarget = "caster",
distance = 0,
onCast = function(caster, target)
caster:try(Tree.behaviors.stats, function(stats)
@ -86,6 +84,7 @@ local regenerateMana = spell.new {
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 }),
}
@ -109,7 +108,7 @@ local attack = spell.new {
tag = "dev_attack",
baseCooldown = 1,
baseCost = 2,
targetQuery = Query(targetTest.character):exclude(Query(targetTest.caster)),
possibleTarget = "enemy",
distance = 1,
onCast = function(caster, target)
--- @type Character