Compare commits

...

9 Commits

11 changed files with 218 additions and 31 deletions

View File

@ -6,6 +6,5 @@
"love.filesystem.load": "loadfile" "love.filesystem.load": "loadfile"
}, },
"workspace.ignoreDir": ["dev_utils"], "workspace.ignoreDir": ["dev_utils"],
"diagnostics.ignoredFiles": "Disable", "diagnostics.ignoredFiles": "Disable"
"completion.autoRequire": false
} }

View File

@ -70,7 +70,7 @@ function sprite:draw()
end end
--- @return Task<nil> --- @return Task<nil>
function sprite:animate(state, node) function sprite:animate(state)
return function(callback) return function(callback)
if not self.animationGrid[state] then if not self.animationGrid[state] then
print("[SpriteBehavior]: no animation for '" .. state .. "'") print("[SpriteBehavior]: no animation for '" .. state .. "'")

View File

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

View File

@ -40,6 +40,7 @@ end
function level:update(dt) function level:update(dt)
utils.each(self.deadIds, function(id) utils.each(self.deadIds, function(id)
self.characters[id] = nil self.characters[id] = nil
self.turnOrder:remove(id)
end) end)
self.deadIds = {} self.deadIds = {}

View File

@ -117,4 +117,29 @@ function turnOrder:add(id)
self.actedQueue:insert(id) -- новые персонажи по умолчанию попадают в очередь следующего хода self.actedQueue:insert(id) -- новые персонажи по умолчанию попадают в очередь следующего хода
end 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 } return { new = new }

View File

@ -7,7 +7,7 @@
--- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла --- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла
--- Да, это Future/Promise/await/async --- Да, это Future/Promise/await/async
local Counter = require 'lib.utils.counter' local task = require 'lib.utils.task'
--- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell --- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell
--- @field tag string --- @field tag string
@ -55,11 +55,7 @@ function walk:cast(caster, target)
return return
end end
return function(callback) return caster:has(Tree.behaviors.tiled):followPath(path)
return caster:has(Tree.behaviors.tiled):followPath(path)(
callback
) -- <- callback вызовется после followPath
end
end end
function walk:update(caster, dt) function walk:update(caster, dt)
@ -103,14 +99,10 @@ function regenerateMana:cast(caster, target)
Tree.behaviors.positioned.new(caster:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }), Tree.behaviors.positioned.new(caster:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }),
} }
return function(callback) return task.wait {
light:has(Tree.behaviors.light):animateColor(Vec3 {})( light:has(Tree.behaviors.light):animateColor(Vec3 {}),
function() sprite:animate("hurt")
light:die() }
end
)
sprite:animate("hurt")(callback)
end
end end
local attack = setmetatable({}, spell) local attack = setmetatable({}, spell)
@ -143,20 +135,16 @@ function attack:cast(caster, target)
caster:try(Tree.behaviors.positioned, function(b) b:lookAt(target) end) caster:try(Tree.behaviors.positioned, function(b) b:lookAt(target) end)
return function(callback) return
local c = Counter(callback) task.wait {
sprite:animate("attack"),
c.push() task.wait {
sprite:animate("attack")(c.pop) task.chain(targetCharacter:has(Tree.behaviors.residentsleeper):sleep(200),
function() return targetSprite:animate("hurt") end
c.push() ),
targetCharacter:has(Tree.behaviors.residentsleeper):sleep(200)(
function()
targetSprite:animate("hurt")(c.pop)
Tree.audio:play(Tree.assets.files.audio.sounds.hurt) Tree.audio:play(Tree.assets.files.audio.sounds.hurt)
end }
) }
end
end end
---------------------------------------- ----------------------------------------

View File

@ -4,6 +4,7 @@
--- @field private isAlive boolean --- @field private isAlive boolean
--- @field push fun():nil добавить 1 к счетчику --- @field push fun():nil добавить 1 к счетчику
--- @field pop fun():nil убавить 1 у счетчика --- @field pop fun():nil убавить 1 у счетчика
--- @field set fun(count: integer): nil установить значение на счетчике
local counter = {} local counter = {}
counter.__index = counter counter.__index = counter
@ -31,6 +32,7 @@ local function new(onFinish)
} }
t.push = function() counter._push(t) end t.push = function() counter._push(t) end
t.pop = function() counter._pop(t) end t.pop = function() counter._pop(t) end
t.set = function(count) t.count = count end
return setmetatable(t, counter) return setmetatable(t, counter)
end end

View File

@ -34,3 +34,50 @@
--- ``` --- ```
--- @generic T --- @generic T
--- @alias Task<T> fun(callback: fun(value: T): nil): nil --- @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,6 +2,8 @@
local character = require "lib/character/character" local character = require "lib/character/character"
local testLayout local testLayout
local TestRunner = require "test.runner"
TestRunner:register(require "test.task")
function love.conf(t) function love.conf(t)
t.console = true t.console = true
@ -67,6 +69,8 @@ end
local lt = "0" local lt = "0"
function love.update(dt) function love.update(dt)
TestRunner:update(dt) -- закомментировать для отключения тестов
local t1 = love.timer.getTime() local t1 = love.timer.getTime()
Tree.controls:poll() Tree.controls:poll()
Tree.level.camera:update(dt) -- сначала логика камеры, потому что на нее завязан UI Tree.level.camera:update(dt) -- сначала логика камеры, потому что на нее завязан UI

46
test/runner.lua Normal file
View File

@ -0,0 +1,46 @@
--- @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

75
test/task.lua Normal file
View File

@ -0,0 +1,75 @@
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