Compare commits

..

No commits in common. "main" and "feature/shadows" have entirely different histories.

31 changed files with 98 additions and 649 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,6 @@
local easing = require "lib.utils.easing"
--- @alias voidCallback fun(): nil
--- @alias animationRunner fun(node: AnimationNode)
--- Узел дерева анимаций.
@ -22,7 +23,6 @@ local easing = require "lib.utils.easing"
--- }
--- }:run()
--- ```
--- @deprecated
--- @class AnimationNode
--- @field count integer
--- @field run animationRunner
@ -73,7 +73,6 @@ function animation:update(dt)
end
end
--- @deprecated
--- @param data {[1]: animationRunner?, onEnd?: voidCallback, duration: number?, easing: ease?, children?: AnimationNode[]}
--- @return AnimationNode
local function new(data)

View File

@ -8,6 +8,3 @@ Tree.behaviors.light = require "character.behaviors.light"
Tree.behaviors.positioned = require "character.behaviors.positioned"
Tree.behaviors.tiled = require "character.behaviors.tiled"
Tree.behaviors.cursor = require "character.behaviors.cursor"
Tree.behaviors.ai = require "lib.character.behaviors.ai"
--- @alias voidCallback fun(): nil

View File

@ -1,91 +0,0 @@
local ease = require "lib.utils.easing"
local AnimationNode = require "lib.animation_node"
local EFFECTS_SUPPORTED = love.audio.isEffectsSupported()
--- @alias SourceFilter { type: "bandpass"|"highpass"|"lowpass", volume: number, highgain: number, lowgain: number }
--- @class Audio
--- @field musicVolume number
--- @field soundVolume number
--- @field looped boolean
--- @field animationNode AnimationNode?
--- @field from love.Source?
--- @field to love.Source?
audio = {}
audio.__index = audio
--- здесь мы должны выгружать значения из файлика с сохранением настроек
local function new(musicVolume, soundVolume)
return setmetatable({
musicVolume = musicVolume,
soundVolume = soundVolume,
looped = true
}, audio)
end
function audio:update(dt)
if not self.animationNode then return end
self.from:setVolume(self.musicVolume - self.animationNode:getValue() * self.musicVolume)
self.to:setVolume(self.animationNode:getValue() * self.musicVolume)
self.animationNode:update(dt)
-- print(self.animationNode.t)
end
--- if from is nil, than we have fade in to;
--- if to is nil, than we have fade out from
---
--- also we should guarantee, that from and to have the same volume
--- @param from love.Source
--- @param to love.Source
--- @param ms number? in milliseconds
function audio:crossfade(from, to, ms)
print("[Audio]: Triggered crossfade")
self:play(to)
to:setVolume(0)
self.from = from
self.to = to
self.animationNode = AnimationNode {
function(node) end,
onEnd = function()
self.from:setVolume(0)
self.to:setVolume(self.musicVolume)
self.from:stop()
self.animationNode = nil
print("[Audio]: Crossfade done")
end,
duration = ms or 1000,
easing = ease.easeOutCubic,
}
self.animationNode:run()
end
--- @param source love.Source
--- @param settings SourceFilter?
--- @param effectName string?
function audio:play(source, settings, effectName)
if source:getType() == "stream" then
source:setLooping(self.looped)
source:setVolume(self.musicVolume)
source:play()
else
source:setVolume(self.soundVolume)
source:play()
end
if settings and EFFECTS_SUPPORTED then
source.setFilter(source, settings)
end
if effectName and EFFECTS_SUPPORTED then
source:setEffect(effectName, true)
end
end
function audio:setMusicVolume(volume)
self.musicVolume = volume
end
function audio:setSoundVolume(volume)
self.soundVolume = volume
end
return { new = new }

View File

@ -1,59 +0,0 @@
local AnimationNode = require "lib.animation_node"
local easing = require "lib.utils.easing"
local function closestCharacter(char)
local caster = Vec3 {}
char:try(Tree.behaviors.positioned, function(b)
caster = b.position
end)
local charTarget
local minDist = 88005553535 -- spooky magic number
for k, v in pairs(Tree.level.characters) do
v:try(Tree.behaviors.positioned, function(b)
local dist = ((caster.x - b.position.x) ^ 2 + (caster.y - b.position.y) ^ 2) ^ 0.5
if dist < minDist and dist ~= 0 then
minDist = dist
charTarget = v
end
-- print(k, b.position)
end)
end
return charTarget
end
--- @class AIBehavior : Behavior
--- @field animationNode AnimationNode?
--- @field target Vec3?
local behavior = {}
behavior.__index = behavior
behavior.id = "ai"
function behavior.new()
return setmetatable({}, behavior)
end
--- @return Task<nil>
function behavior:makeTurn()
return function(callback) -- почему так, описано в Task
self.owner:try(Tree.behaviors.spellcaster, function(spellB)
local charTarget = closestCharacter(self.owner)
charTarget:try(Tree.behaviors.positioned, function(b)
self.target = Vec3 { b.position.x, b.position.y + 1 } --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти
end)
spellB.spellbook[1]:cast(self.owner, self.target)(function()
-- здесь мы оказываемся после того, как сходили в первый раз
print("[AI]: finished move 1")
local newTarget = Vec3 { 1, 1 }
-- поэтому позиция персонажа для нового каста пересчитается динамически
spellB.spellbook[1]:cast(self.owner, newTarget)(function()
print("[AI]: finished move 2")
-- дергаем функцию после завершения хода
callback()
end)
end)
end)
end
end
return behavior

View File

@ -1,12 +1,8 @@
local AnimationNode = require "lib.animation_node"
local easing = require "lib.utils.easing"
--- @class LightBehavior : Behavior
--- @field intensity number
--- @field color Vec3
--- @field seed integer
--- @field colorAnimationNode? AnimationNode
--- @field private animateColorCallback? fun(): nil
--- @field targetColor? Vec3
--- @field sourceColor? Vec3
local behavior = {}
@ -30,24 +26,11 @@ function behavior:update(dt)
self.colorAnimationNode:update(dt)
end
--- @TODO: refactor
function behavior:animateColor(targetColor)
function behavior:animateColor(targetColor, animationNode)
if self.colorAnimationNode then self.colorAnimationNode:finish() end
self.colorAnimationNode = AnimationNode {
function(_) end,
easing = easing.easeInQuad,
duration = 800,
onEnd = function()
if self.animateColorCallback then self.animateColorCallback() end
end
}
self.colorAnimationNode:run()
self.colorAnimationNode = animationNode
self.sourceColor = self.color
self.targetColor = targetColor
return function(callback)
self.animateColorCallback = callback
end
end
function behavior:draw()

View File

@ -1,37 +1,21 @@
--- Умеет асинхронно ждать какое-то время (для анимаций)
--- @class ResidentSleeperBehavior : Behavior
--- @field private t0 number?
--- @field private sleepTime number?
--- @field private callback voidCallback?
--- @field private state 'running' | 'finished'
--- @field animationNode? AnimationNode
local behavior = {}
behavior.__index = behavior
behavior.id = "residentsleeper"
function behavior.new() return setmetatable({}, behavior) end
function behavior:update(_)
if self.state ~= 'running' then return end
local t = love.timer.getTime()
if t >= self.t0 + self.sleepTime then
self.state = 'finished'
self.callback()
end
function behavior:update(dt)
if not self.animationNode then return end
self.animationNode:update(dt)
end
--- @return Task<nil>
function behavior:sleep(ms)
self.sleepTime = ms / 1000
return function(callback)
if self.state == 'running' then
self.callback()
end
self.t0 = love.timer.getTime()
self.callback = callback
self.state = 'running'
end
--- @param node AnimationNode
function behavior:sleep(node)
if self.animationNode then self.animationNode:finish() end
self.animationNode = node
end
return behavior

View File

@ -69,20 +69,17 @@ function sprite:draw()
)
end
--- @return Task<nil>
function sprite:animate(state)
return function(callback)
--- @param node AnimationNode
function sprite:animate(state, node)
if not self.animationGrid[state] then
print("[SpriteBehavior]: no animation for '" .. state .. "'")
callback()
return print("[SpriteBehavior]: no animation for '" .. state .. "'")
end
self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED,
function()
self:loop("idle")
callback()
node:finish()
end)
self.state = state
end
end
function sprite:loop(state)

View File

@ -5,7 +5,7 @@ local utils = require "lib.utils.utils"
--- @field private runSource? Vec3 точка, из которой бежит персонаж
--- @field private runTarget? Vec3 точка, в которую в данный момент бежит персонаж
--- @field private path? Deque путь, по которому сейчас бежит персонаж
--- @field private followPathCallback? fun()
--- @field private animationNode? AnimationNode AnimationNode, с которым связана анимация перемещения
--- @field private t0 number время начала движения
--- @field size Vec3
local behavior = {}
@ -20,8 +20,10 @@ function behavior.new(size)
end
--- @param path Deque
--- @return Task<nil>
function behavior:followPath(path)
--- @param animationNode AnimationNode
function behavior:followPath(path, animationNode)
if path:is_empty() then return animationNode:finish() end
self.animationNode = animationNode
self.owner:try(Tree.behaviors.sprite, function(sprite)
sprite:loop("run")
end)
@ -30,10 +32,6 @@ function behavior:followPath(path)
local nextCell = path:peek_front()
self:runTo(nextCell)
path:pop_front()
return function(callback)
self.followPathCallback = callback
end
end
--- @param target Vec3
@ -74,10 +72,7 @@ function behavior:update(dt)
sprite:loop("idle")
end)
self.runTarget = nil
if self.followPathCallback then
self.followPathCallback()
end
if self.animationNode then self.animationNode:finish() end
end
else -- анимация перемещения не завершена
positioned.position = utils.lerp(self.runSource, self.runTarget, fraction) -- линейный интерполятор

View File

@ -8,7 +8,7 @@ grid.__index = grid
--- adds a value to the grid
--- @param value any
function grid:add(value)
self.__grid[tostring(value.position)] = value
grid[tostring(value.position)] = value
end
--- @param position Vec3

View File

@ -19,9 +19,6 @@ level.__index = level
local function new(type, template)
local size = Vec3 { 30, 30 } -- magic numbers for testing purposes only
print(type, template, size)
Tree.audio:play(Tree.assets.files.audio.music.level1.battle)
return setmetatable({
size = size,
characters = {},
@ -40,7 +37,6 @@ end
function level:update(dt)
utils.each(self.deadIds, function(id)
self.characters[id] = nil
self.turnOrder:remove(id)
end)
self.deadIds = {}

View File

@ -37,16 +37,9 @@ function selector:update(dt)
if not selectedId then self:select(nil) end
return
end
local task = b.cast:cast(char, mousePosition) -- в task функция, которая запускает анимацию спелла
if task then
if b.cast:cast(char, mousePosition) then
self:lock()
b.state = "running"
task(
function(_) -- это коллбэк, который сработает по окончании анимации спелла
b:endCast()
end
)
end
end)
end

View File

@ -34,15 +34,6 @@ function turnOrder:next()
local next = self.pendingQueue:peek()
if not next then return self:endRound() end
self.current = self.pendingQueue:pop()
local char = Tree.level.characters[self.current]
char:try(Tree.behaviors.ai, function(ai)
Tree.level.selector:lock()
ai:makeTurn()(function()
Tree.level.selector:unlock()
self:next()
end)
end)
end
--- Меняем местами очередь сходивших и не сходивших (пустую)
@ -117,29 +108,4 @@ function turnOrder:add(id)
self.actedQueue:insert(id) -- новые персонажи по умолчанию попадают в очередь следующего хода
end
--- Удалить персонажа из очереди хода (например, при смерти)
--- @param id Id
function turnOrder:remove(id)
if self.current == id then
self.current = self.pendingQueue:pop()
if not self.current then
self:endRound()
end
return
end
local function filterQueue(q, targetId)
local newQ = PriorityQueue.new(initiativeComparator)
for _, val in ipairs(q.data) do
if val ~= targetId then
newQ:insert(val)
end
end
return newQ
end
self.actedQueue = filterQueue(self.actedQueue, id)
self.pendingQueue = filterQueue(self.pendingQueue, id)
end
return { new = new }

View File

@ -1,52 +0,0 @@
-- --- @class Music
-- --- @field source table<string, love.Source> audio streams, that supports multitrack (kind of)
-- --- @field offset number
-- music = {}
-- music.__index = music
-- --- @param path string accepts path to dir with some music files (example: "main_ambient"; "player/theme1" and etc etc)
-- local function new(path)
-- local dir = Tree.assets.files.audio.music[path]
-- --- @type table<string, love.Source>
-- local source = {}
-- print(dir)
-- for _, v in pairs(dir) do
-- print(v.filename)
-- source[v.filename] = v.source
-- print(v.filename)
-- end
-- print('[music]: new source: ', table.concat(source, ' '))
-- return setmetatable({ source = source, offset = 0 }, music)
-- end
-- function music:update()
-- for _, v in ipairs(self.source) do
-- v:seek()
-- end
-- end
-- --- pause stemfile or music at all
-- --- @param filename? string
-- function music:pause(filename)
-- if filename then
-- self.source[filename]:pause()
-- else
-- for _, v in pairs(self.source) do
-- v:pause()
-- end
-- end
-- end
-- --- play music stemfile by his name
-- --- @param filename string
-- --- @return boolean
-- function music:play(filename)
-- print('[music]: ', table.concat(self.source, ' '))
-- self.source[filename]:seek(self.offset, "seconds")
-- return self.source[filename]:play()
-- end
-- return { new = new }

View File

@ -57,7 +57,7 @@ function endTurnButton:onClick()
end,
duration = 1500,
easing = easing.easeInOutCubic,
onEnd = function() if not playing:has(Tree.behaviors.ai) then Tree.level.selector:select(cid) end end
onEnd = function() Tree.level.selector:select(cid) end
}:run()
end

View File

@ -1,9 +0,0 @@
-- --- @class Sound
-- --- @field source love.Source just a sound
-- sound = {}
-- local function new()
-- return setmetatable({}, sound)
-- end
-- return { new }

View File

@ -7,13 +7,14 @@
--- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла
--- Да, это Future/Promise/await/async
local task = require 'lib.utils.task'
local AnimationNode = require "lib.animation_node"
local easing = require "lib.utils.easing"
--- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell
--- @field tag string
--- @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> | nil Вызывается в момент каста, изменяет мир.
--- @field cast fun(self: Spell, caster: Character, target: Vec3): boolean Вызывается в момент каста, изменяет мир. Возвращает bool в зависимости от того, получилось ли скастовать
local spell = {}
spell.__index = spell
spell.tag = "base"
@ -22,7 +23,7 @@ function spell:update(caster, dt) end
function spell:draw() end
function spell:cast(caster, target) return end
function spell:cast(caster, target) return true end
local walk = setmetatable({
--- @type Deque
@ -34,28 +35,28 @@ function walk:cast(caster, target)
if not caster:try(Tree.behaviors.stats, function(stats)
return stats.mana >= 2
end) then
return
return false
end
local initialPos = caster:has(Tree.behaviors.positioned).position:floor()
local path = require "lib.pathfinder" (initialPos, target)
local path = self.path
path:pop_front()
if path:is_empty() then
print("[Walk]: the path is empty", initialPos, target)
return
end
if path:is_empty() then return false end
for p in path:values() do print(p) end
caster:try(Tree.behaviors.stats, function(stats)
stats.mana = stats.mana - 2
print(stats.mana)
end)
local sprite = caster:has(Tree.behaviors.sprite)
assert(sprite, "[Walk]", "WTF DUDE WHERE'S YOUR SPRITE")
if not sprite then
return
end
if not sprite then return true end
AnimationNode {
function(node) caster:has(Tree.behaviors.tiled):followPath(path, node) end,
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end,
}:run()
return caster:has(Tree.behaviors.tiled):followPath(path)
return true
end
function walk:update(caster, dt)
@ -87,10 +88,9 @@ function regenerateMana:cast(caster, target)
stats.mana = 10
stats.initiative = stats.initiative + 10
end)
local sprite = caster:has(Tree.behaviors.sprite)
if not sprite then return nil end
print(caster.id, "has regenerated mana and gained initiative")
local sprite = caster:has(Tree.behaviors.sprite)
if not sprite then return true end
local light = require "lib/character/character".spawn("Light Effect")
light:addBehavior {
@ -98,11 +98,23 @@ function regenerateMana:cast(caster, target)
Tree.behaviors.residentsleeper.new(),
Tree.behaviors.positioned.new(caster:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }),
}
AnimationNode {
function(node)
sprite:animate("hurt", node)
end,
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end
}:run()
return task.wait {
light:has(Tree.behaviors.light):animateColor(Vec3 {}),
sprite:animate("hurt")
}
AnimationNode {
function(node)
light:has(Tree.behaviors.light):animateColor(Vec3 {}, node)
end,
easing = easing.easeInQuad,
duration = 800,
onEnd = function() light:die() end
}:run()
return true
end
local attack = setmetatable({}, spell)
@ -114,7 +126,7 @@ function attack:cast(caster, target)
print("dist:", dist)
return dist > 2
end) then
return
return false
end
caster:try(Tree.behaviors.stats, function(stats)
@ -123,7 +135,7 @@ function attack:cast(caster, target)
--- @type Character
local targetCharacterId = Tree.level.characterGrid:get(target)
if not targetCharacterId or targetCharacterId == caster.id then return end
if not targetCharacterId or targetCharacterId == caster.id then return false end
local targetCharacter = Tree.level.characters[targetCharacterId]
targetCharacter:try(Tree.behaviors.stats, function(stats)
stats.hp = stats.hp - 4
@ -131,20 +143,35 @@ function attack:cast(caster, target)
local sprite = caster:has(Tree.behaviors.sprite)
local targetSprite = targetCharacter:has(Tree.behaviors.sprite)
if not sprite or not targetSprite then return end
if not sprite or not targetSprite then return true 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)
AnimationNode {
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end,
children = {
AnimationNode {
function(node)
sprite:animate("attack", node)
end
},
AnimationNode {
function(node)
targetCharacter:has(Tree.behaviors.residentsleeper):sleep(node)
end,
duration = 200,
children = {
AnimationNode {
function(node)
targetSprite:animate("hurt", node)
end
}
}
}
}
}:run()
return true
end
----------------------------------------
@ -159,7 +186,6 @@ local spellbook = {
function spellbook.of(list)
local spb = {}
for i, sp in ipairs(list) do
print(i)
spb[i] = setmetatable({}, { __index = sp })
end
return spb

View File

@ -9,11 +9,9 @@ Tree = {
Tree.fonts = (require "lib.utils.font_manager"):load("WDXL_Lubrifont_TC"):loadTheme("Roboto_Mono") -- дефолтный шрифт
Tree.panning = require "lib/panning"
Tree.controls = require "lib.controls"
Tree.audio = (require "lib.audio").new(1, 1)
Tree.level = (require "lib.level.level").new("procedural", "flower_plains") -- для теста у нас только один уровень, который сразу же загружен
Tree.behaviors = (require "lib.utils.behavior_loader")("lib/character/behaviors") --- @todo написать нормальную загрузку поведений
-- Tree.audio = (require "lib.audio").new(1, 1)
-- Tree.behaviors.map = require "lib.character.behaviors.map"
-- Tree.behaviors.spellcaster = require "lib.character.behaviors.spellcaster"
-- Tree.behaviors.sprite = require "lib.character.behaviors.sprite"

View File

@ -50,10 +50,6 @@ function AssetBundle.loadFile(path)
return love.graphics.newShader(path);
elseif (ext == "lua") then
return require(string.gsub(path, ".lua", ""))
elseif (ext == "ogg") and string.find(path, "sounds") then
return love.audio.newSource(path, 'static')
elseif (ext == "ogg") and string.find(path, "music") then
return love.audio.newSource(path, 'stream')
end
return filedata
end

View File

@ -1,40 +0,0 @@
--- @class Counter
--- @field private count integer
--- @field private onFinish fun(): nil
--- @field private isAlive boolean
--- @field push fun():nil добавить 1 к счетчику
--- @field pop fun():nil убавить 1 у счетчика
--- @field set fun(count: integer): nil установить значение на счетчике
local counter = {}
counter.__index = counter
--- @private
function counter:_push()
self.count = self.count + 1
end
--- @private
function counter:_pop()
self.count = self.count - 1
if self.count == 0 and self.isAlive then
self.isAlive = false
self.onFinish()
end
end
--- @param onFinish fun(): nil
local function new(onFinish)
local t = {
count = 0,
onFinish = onFinish,
isAlive = true,
}
t.push = function() counter._push(t) end
t.pop = function() counter._pop(t) end
t.set = function(count) t.count = count end
return setmetatable(t, counter)
end
return new

View File

@ -1,83 +0,0 @@
--- Обобщенная асинхронная функция
---
--- Использование в общих чертах выглядит так:
--- ```lua
--- local multiplyByTwoCallback = nil
--- local n = nil
--- local function multiplyByTwoAsync(number)
--- -- императивно сохраняем/обрабатываем параметр
--- n = number
--- return function(callback) -- это функция, которая запускает задачу
--- multiplyByTwoCallback = callback
--- end
--- end
---
--- local function update(dt)
--- --- ждем нужного момента времени...
---
--- if multiplyByTwoCallback then -- завершаем вычисление
--- local result = n * 2
--- multiplyByTwoCallback(result) -- результат асинхронного вычисления идет в параметр коллбека!
--- multiplyByTwoCallback = nil
--- end
--- end
---
---
--- --- потом это можно вызывать так:
--- local task = multiplyByTwoAsync(21)
--- -- это ленивое вычисление, так что в этот момент ничего не произойдет
--- -- запускаем
--- task(
--- function(result) print(result) end -- выведет 42 после завершения вычисления, т.е. аналогично `task.then((res) => print(res))` на JS
--- )
---
--- ```
--- @generic T
--- @alias Task<T> fun(callback: fun(value: T): nil): nil
--- Возвращает новый Task, который завершится после завершения всех переданных `tasks`.
---
--- Значение созданного Task будет содержать список значений `tasks` в том же порядке.
---
--- См. также https://api.dart.dev/dart-async/Future/wait.html
--- @generic T
--- @param tasks Task[]
--- @return Task<T[]>
local function wait(tasks)
local count = #tasks
local results = {}
return function(callback)
for i, task in ipairs(tasks) do
task(
function(result)
results[i] = result
count = count - 1
if count == 0 then callback(results) end
end
)
end
end
end
--- Последовательно объединяет два `Task` в один.
--- @generic T
--- @generic R
--- @param task Task<T> `Task`, который выполнится первым
--- @param onCompleted fun(value: T): Task<R> Конструктор второго `Task`. Принимает результат выполнения первого `Task`
--- @return Task<R>
local function chain(task, onCompleted)
return function(callback)
task(function(value)
local task2 = onCompleted(value)
task2(callback)
end)
end
end
return {
wait = wait,
chain = chain
}

View File

@ -2,8 +2,6 @@
local character = require "lib/character/character"
local testLayout
local TestRunner = require "test.runner"
TestRunner:register(require "test.task")
function love.conf(t)
t.console = true
@ -25,36 +23,15 @@ function love.load()
Tree.behaviors.shadowcaster.new(),
Tree.behaviors.spellcaster.new()
},
character.spawn("Foodor")
:addBehavior {
Tree.behaviors.residentsleeper.new(),
Tree.behaviors.stats.new(nil, nil, 1),
Tree.behaviors.positioned.new(Vec3 { 4, 3 }),
Tree.behaviors.tiled.new(),
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
Tree.behaviors.shadowcaster.new(),
Tree.behaviors.spellcaster.new()
},
character.spawn("Foodor")
:addBehavior {
Tree.behaviors.residentsleeper.new(),
Tree.behaviors.stats.new(nil, nil, 3),
Tree.behaviors.positioned.new(Vec3 { 5, 3 }),
Tree.behaviors.tiled.new(),
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
Tree.behaviors.shadowcaster.new(),
Tree.behaviors.spellcaster.new()
},
character.spawn("Baris")
:addBehavior {
Tree.behaviors.residentsleeper.new(),
Tree.behaviors.stats.new(nil, nil, 2),
Tree.behaviors.stats.new(nil, nil, 1),
Tree.behaviors.positioned.new(Vec3 { 5, 5 }),
Tree.behaviors.tiled.new(),
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
Tree.behaviors.shadowcaster.new(),
Tree.behaviors.spellcaster.new(),
Tree.behaviors.ai.new()
Tree.behaviors.spellcaster.new()
},
}
@ -69,15 +46,12 @@ end
local lt = "0"
function love.update(dt)
TestRunner:update(dt) -- закомментировать для отключения тестов
local t1 = love.timer.getTime()
Tree.controls:poll()
Tree.level.camera:update(dt) -- сначала логика камеры, потому что на нее завязан UI
testLayout:update(dt) -- потом UI, потому что нужно перехватить жесты и не пустить их дальше
Tree.panning:update(dt)
Tree.level:update(dt)
Tree.audio:update(dt)
Tree.controls:cache()

View File

@ -1,46 +0,0 @@
--- @class Test
local test = {}
function test:run(complete) end
function test:update(dt) end
--- @class TestRunner
--- @field private tests Test[]
--- @field private state "loading" | "running" | "completed"
--- @field private completedCount integer
local runner = {}
runner.tests = {}
runner.state = "loading"
runner.completedCount = 0
--- глобальный update для тестов, нужен для тестирования фич, зависимых от времени
function runner:update(dt)
if self.state == "loading" then
print("[TestRunner]: running " .. #self.tests .. " tests")
for _, t in ipairs(self.tests) do
t:run(
function()
self.completedCount = self.completedCount + 1
if self.completedCount == #self.tests then
self.state = "completed"
print("[TestRunner]: tests completed")
end
end
)
end
self.state = "running"
end
for _, t in ipairs(self.tests) do
if t.update then t:update(dt) end
end
end
--- добавляет тест для прохождения
--- @param t Test
function runner:register(t)
table.insert(self.tests, t)
end
return runner

View File

@ -1,75 +0,0 @@
local task = require "lib.utils.task"
local test = {}
local t0
local task1Start, task2Start
local task1Callback, task2Callback
--- @return Task<number>
local function task1()
return function(callback)
task1Start = love.timer.getTime()
task1Callback = callback
end
end
--- @return Task<number>
local function task2()
return function(callback)
task2Start = love.timer.getTime()
task2Callback = callback
end
end
function test:run(complete)
t0 = love.timer.getTime()
task.wait {
task1(),
task2()
} (function(values)
local tWait = love.timer.getTime()
local dt = tWait - t0
local t1 = values[1]
local t2 = values[2]
assert(type(t1) == "number" and type(t2) == "number")
assert(t2 > t1)
assert(dt >= 2, "dt = " .. dt)
print("task.wait completed in " .. dt .. " sec", "t1 = " .. t1 - t0, "t2 = " .. t2 - t0)
t0 = love.timer.getTime()
task.chain(task1(), function(value)
t1 = value
assert(t1 - t0 >= 1)
return task2()
end)(
function(value)
t2 = value
assert(t2 - t0 >= 2)
print("task.chain completed in " .. t2 - t0 .. " sec")
complete()
end
)
end)
end
function test:update(dt)
local t = love.timer.getTime()
if task1Start and t - task1Start >= 1 then
task1Callback(t)
task1Start = nil
end
if task2Start and t - task2Start >= 2 then
task2Callback(t)
task2Start = nil
end
end
return test