Compare commits

...

12 Commits

Author SHA1 Message Date
8fc4ca5483 Merge pull request 'feature/spell-constraints' (#32) from feature/spell-constraints into main
Reviewed-on: #32
2026-03-18 05:11:54 +03:00
2e96ec821d Add cooldown handling for spells and display in UI
- Implement cooldown tracking in SpellcasterBehavior
- Decrease cooldowns at end of each round in turn order
- Prevent casting spells on cooldown in spell.cast
- Show cooldown overlay and block click on skill buttons
- Adjust font sizes for better UI consistency
2026-03-18 05:09:35 +03:00
ec816eb666 Refactor spell cast and task returns for optional tasks 2026-03-18 04:05:04 +03:00
d84fc4a7c2 Add path preview and refactor spells with new Spell API
- Add path preview support to Spell with update and draw methods
- Refactor spell:cast to always return a Task
- Simplify spell.new constructor and apply distance constraint uniformly
- Replace walk spell with new Spell-based implementation supporting path
  preview
- Remove debug print from target_test.lua
2026-03-18 03:53:27 +03:00
ecec540251 Improve target validation logic in spell casting function 2026-03-18 02:03:30 +03:00
a6578ec8dd Refactor target distance check using query intersection
Replace manual distance check with combined targetQuery and
distance query intersection for cleaner spell targeting logic

Fix query combinators to correctly reference self in closures
2026-03-18 02:01:59 +03:00
ef5ff5f847 tweak light effects in spells 2026-03-18 01:47:09 +03:00
0d2ed101d6 Refactor light animation in regenerateMana spell using easing 2026-03-18 00:56:19 +03:00
4431934e6b Refactor spell target selection to use composable queries
Introduce SpellTargetQuery abstraction for flexible target filtering.
Replace fixed target types with query-based system supporting union,
intersection, and exclusion of target conditions. Update spells
accordingly.
2026-03-17 23:09:36 +03:00
e0b08b07ec Remove residentsleeper behavior from light effect in regenerateMana
spell
2026-03-17 20:39:30 +03:00
2e6155aea4 Add target validation and refactor spells with new cast logic 2026-02-13 01:28:36 +03:00
95f2230302 Add simple spell framework and refactor dev_attack spell to use it 2026-02-13 00:43:54 +03:00
14 changed files with 392 additions and 172 deletions

View File

@ -1,4 +1,4 @@
local easing = require "lib.utils.easing"
local easing = require "lib.utils.easing"
local function closestCharacter(char)
local caster = Vec3 {}
@ -39,17 +39,24 @@ function behavior:makeTurn()
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)
local task1 = spellB.spellbook[1]:cast(self.owner, self.target)
if task1 then
task1(
function()
-- здесь мы оказываемся после того, как сходили в первый раз
local newTarget = Vec3 { 1, 1 }
local task2 = spellB.spellbook[1]:cast(self.owner, newTarget)
if task2 then
-- дергаем функцию после завершения хода
task2(callback)
else
callback()
end
end
)
else
callback()
end
end)
end
end

View File

@ -1,11 +1,13 @@
--- @class SpellcasterBehavior : Behavior
--- @field spellbook Spell[] собственный набор спеллов персонажа
--- @field cast Spell | nil ссылка на активный спелл из спеллбука
--- @field cooldowns {[string]: integer} текущий кулдаун спеллов по тегам
--- @field state "idle" | "casting" | "running"
local behavior = {}
behavior.__index = behavior
behavior.id = "spellcaster"
behavior.state = "idle"
behavior.cooldowns = {}
---@param spellbook Spell[] | nil
---@return SpellcasterBehavior
@ -13,6 +15,7 @@ function behavior.new(spellbook)
local spb = require "lib.spellbook" --- @todo временное добавление ходьбы (и читов) всем персонажам
local t = {}
t.spellbook = spellbook or spb.of { spb.walk, spb.regenerateMana, spb.attack }
t.cooldowns = {}
return setmetatable(t, behavior)
end
@ -23,6 +26,14 @@ function behavior:endCast()
Tree.level.selector:unlock()
end
function behavior:processCooldowns()
local cds = {}
for tag, cd in pairs(self.cooldowns) do
cds[tag] = (cd - 1) >= 0 and cd - 1 or 0
end
self.cooldowns = cds
end
function behavior:update(dt)
if Tree.level.selector:deselected() then
self.state = "idle"

View File

@ -38,16 +38,17 @@ function selector:update(dt)
return
end
local task = b.cast:cast(char, mousePosition) -- в task функция, которая запускает анимацию спелла
if task then
self:lock()
b.state = "running"
if not task then return end -- не получилось скастовать
task(
function(_) -- это коллбэк, который сработает по окончании анимации спелла
b:endCast()
end
)
end
self:lock()
b.state = "running"
task(
function(_) -- это коллбэк, который сработает по окончании анимации спелла
b:endCast()
if not char:has(Tree.behaviors.ai) then self:select(char.id) end -- выделяем персонажа обратно после того, как посмотрели на каст
end
)
end)
end
end

View File

@ -57,10 +57,20 @@ function turnOrder:next()
end)
end
--- Производим действия в конце раунда
---
--- Меняем местами очередь сходивших и не сходивших (пустую)
function turnOrder:endRound()
assert(self.pendingQueue:size() == 0, "[TurnOrder]: tried to end the round before everyone had a turn")
print("[TurnOrder]: end of the round")
for _, id in ipairs(self.actedQueue.data) do
local char = Tree.level.characters[id]
char:try(Tree.behaviors.spellcaster, function(spellcaster)
spellcaster:processCooldowns()
end)
end
self.actedQueue, self.pendingQueue = self.pendingQueue, self.actedQueue
self.current = self.pendingQueue:pop()
end

View File

@ -57,7 +57,7 @@ function barElement:draw()
love.graphics.setColor(1, 1, 1)
--- текст поверх
if self.drawText then
local font = Tree.fonts:getDefaultTheme():getVariant("medium")
local font = Tree.fonts:getDefaultTheme():getVariant("small")
local t = love.graphics.newText(font, tostring(self.value) .. "/" .. tostring(self.maxValue))
love.graphics.draw(t, math.floor(self.bounds.x + self.bounds.width / 2 - t:getWidth() / 2),
math.floor(self.bounds.y + self.bounds.height / 2 - t:getHeight() / 2))

View File

@ -22,7 +22,7 @@ function endTurnButton:update(dt)
end
function endTurnButton:layout()
local font = Tree.fonts:getDefaultTheme():getVariant("headline")
local font = Tree.fonts:getDefaultTheme():getVariant("large")
self.text = love.graphics.newText(font, "Завершить ход")
self.bounds.width = self.text:getWidth() + 32
self.bounds.height = self.text:getHeight() + 16

View File

@ -8,6 +8,7 @@ local UI_SCALE = require "lib.simple_ui.level.scale"
--- @field hovered boolean
--- @field selected boolean
--- @field onClick function?
--- @field getCooldown function?
--- @field icon? string
local skillButton = setmetatable({}, Element)
skillButton.__index = skillButton
@ -18,7 +19,11 @@ function skillButton:update(dt)
if self:hitTest(mx, my) then
self.hovered = true
if Tree.controls:isJustPressed("select") then
if self.onClick then self.onClick() end
local cd = self.getCooldown and self.getCooldown() or 0
if cd == 0 then
if self.onClick then self.onClick() end
end
Tree.controls:consume("select")
end
else
@ -29,6 +34,7 @@ end
function skillButton:draw()
love.graphics.setLineWidth(2)
local cd = self.getCooldown and self.getCooldown() or 0
if not self.icon then
love.graphics.setColor(0.05, 0.05, 0.05)
@ -53,6 +59,25 @@ function skillButton:draw()
love.graphics.setColor(0.7, 1, 0.7, 0.5)
love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height)
end
if cd > 0 then
love.graphics.setColor(0, 0, 0, 0.5)
love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height)
local font = Tree.fonts:getDefaultTheme():getVariant("headline")
love.graphics.setColor(0, 0, 0)
local t = love.graphics.newText(font, tostring(cd))
love.graphics.draw(t, math.floor(self.bounds.x + 2 + self.bounds.width / 2 - t:getWidth() / 2),
math.floor(self.bounds.y + 2 + self.bounds.height / 2 - t:getHeight() / 2))
love.graphics.setColor(1, 1, 1)
love.graphics.draw(t, math.floor(self.bounds.x + self.bounds.width / 2 - t:getWidth() / 2),
math.floor(self.bounds.y + self.bounds.height / 2 - t:getHeight() / 2))
else
end
love.graphics.setColor(1, 1, 1)
end
@ -92,6 +117,9 @@ function skillRow.new(characterId)
behavior.cast = nil
end
end
skb.getCooldown = function()
return behavior.cooldowns[spell.tag] or 0
end
t.children[i] = skb
end
end)

104
lib/spell/spell.lua Normal file
View File

@ -0,0 +1,104 @@
local Query = require "lib.spell.target_query"
local targetTest = require "lib.spell.target_test"
local task = require "lib.utils.task"
--- @alias SpellPreview "default" Подсветка возможных целей
--- | "path" Подсветка пути до цели
--- @class Spell
--- @field tag string
--- @field baseCost integer Базовые затраты маны на каст
--- @field baseCooldown integer Базовый кулдаун в ходах
--- @field targetQuery SpellTargetQuery Селектор возможных целей
--- @field previewType SpellPreview Вид превью во время каста
--- @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.targetQuery = Query(targetTest.any)
spell.previewType = "default"
function spell:update(caster, dt)
if self.previewType == "path" then
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()
if self.targetQuery.test(caster, mpos) then
self.path = require "lib.pathfinder" (charPos, mpos)
else
self.path = nil
end
end
end
function spell:draw()
if self.previewType == "path" then
local path = self.path --[[@as Deque?]]
if not 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 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
end
function spell:cast(caster, target) return task.fromValue() end
--- Конструктор [Spell]
--- @param data {tag: string, baseCost: integer, baseCooldown: integer, targetQuery: SpellTargetQuery?, previewType: SpellPreview?, distance: integer?, onCast: fun(caster: Character, target: Vec3): Task<nil>?}
--- @return Spell
function spell.new(data)
local newSpell = setmetatable({
tag = data.tag,
baseCost = data.baseCost,
baseCooldown = data.baseCooldown,
targetQuery = data.targetQuery,
previewType = data.previewType,
distance = data.distance
}, spell)
newSpell.targetQuery = newSpell.distance
and newSpell.targetQuery:intersect(Query(targetTest.distance(newSpell.distance)))
or newSpell.targetQuery
function newSpell:cast(caster, target)
if caster:try(Tree.behaviors.spellcaster, function(spellcaster) -- проверка на кулдаун
return (spellcaster.cooldowns[self.tag] or 0) > 0
end) then
return
end
if not self.targetQuery.test(caster, 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)
caster:try(Tree.behaviors.spellcaster, function(spellcaster)
spellcaster.cooldowns[self.tag] = self.baseCooldown
end)
return data.onCast(caster, target)
end
return newSpell
end
return spell

View File

@ -0,0 +1,67 @@
--- Тип, отвечающий за выбор и фильтрацию подходящих тайлов как цели спелла
--- теория множеств 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
}, query)
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
}, query)
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
}, query)
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

27
lib/spell/target_test.lua Normal file
View File

@ -0,0 +1,27 @@
--- @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,
-- тайл в пределах окружности в нашей кривой метрике
--- @param radius number
distance = function(radius)
return function(caster, targetPosition)
return caster:try(Tree.behaviors.positioned, function(p)
local dist = math.max(math.abs(p.position.x - targetPosition.x),
math.abs(p.position.y - targetPosition.y))
return dist <= radius
end)
end
end
}

View File

@ -7,157 +7,122 @@
--- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла
--- Да, это Future/Promise/await/async
local task = require 'lib.utils.task'
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 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 Вызывается в момент каста, изменяет мир.
local spell = {}
spell.__index = spell
spell.tag = "base"
local walk = spell.new {
tag = "dev_move",
previewType = "path",
baseCooldown = 1,
baseCost = 2,
targetQuery = Query(targetTest.any):exclude(Query(targetTest.character)),
distance = 3,
onCast = function(caster, target)
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
function spell:update(caster, dt) end
local sprite = caster:has(Tree.behaviors.sprite)
assert(sprite, "[Walk]", "WTF DUDE WHERE'S YOUR SPRITE")
if not sprite then
return
end
function spell:draw() end
function spell:cast(caster, target) return end
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
return caster:has(Tree.behaviors.tiled):followPath(path)
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
local regenerateMana = spell.new {
tag = "dev_mana",
baseCooldown = 2,
baseCost = 0,
targetQuery = Query(targetTest.caster),
distance = 0,
onCast = function(caster, target)
caster:try(Tree.behaviors.stats, function(stats)
stats.mana = 10
stats.initiative = stats.initiative + 10
end)
caster:try(Tree.behaviors.stats, function(stats)
stats.mana = stats.mana - 2
end)
local sprite = caster:has(Tree.behaviors.sprite)
if not sprite then return end
print(caster.id, "has regenerated mana and gained initiative")
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 = setmetatable({}, spell)
regenerateMana.tag = "dev_mana"
function regenerateMana:cast(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 nil 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 = setmetatable({}, spell)
attack.tag = "dev_attack"
function attack:cast(caster, target)
if 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 > 2
end) then
return
end
caster:try(Tree.behaviors.stats, function(stats)
stats.mana = stats.mana - 2
end)
--- @type Character
local targetCharacterId = Tree.level.characterGrid:get(target)
if not targetCharacterId or targetCharacterId == caster.id then return end
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 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)
}
local light = require "lib/character/character".spawn("Light Effect")
light:addBehavior {
Tree.behaviors.light.new { color = Vec3 { 0.3, 0.3, 0.6 }, intensity = 4 },
Tree.behaviors.positioned.new(caster:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }),
}
end
return task.wait {
task.chain(task.tween(light:has(Tree.behaviors.light) --[[@as LightBehavior]],
{ intensity = 1, color = Vec3 {} }, 800, easing.easeInCubic), function()
light:die()
return task.fromValue()
end),
sprite:animate("hurt")
}
end
}
local attack = spell.new {
tag = "dev_attack",
baseCooldown = 1,
baseCost = 2,
targetQuery = Query(targetTest.character):exclude(Query(targetTest.caster)),
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 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(500),
function()
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.positioned.new(targetCharacter:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }),
}
return
task.wait {
task.chain(task.tween(light:has(Tree.behaviors.light) --[[@as LightBehavior]],
{ intensity = 1, color = Vec3 {} }, 1000, easing.easeInCubic), function()
light:die()
return task.fromValue()
end),
targetSprite:animate("hurt")
}
end
),
Tree.audio:play(Tree.assets.files.audio.sounds.hurt)
}
}
end
}
----------------------------------------
local spellbook = {
local spellbook = {
walk = walk,
regenerateMana = regenerateMana,
attack = attack

View File

@ -4,11 +4,11 @@
--- @field private _sizes {[FontVariant]: integer}
local theme = {
_sizes = {
smallest = 10,
small = 12,
medium = 14,
large = 16,
headline = 20,
smallest = 12,
small = 14,
medium = 16,
large = 22,
headline = 32,
}
}
theme.__index = theme

View File

@ -1,5 +1,5 @@
---@class PriorityQueue
---@field private data any[] внутренний массив-куча (индексация с 1)
---@field data any[] внутренний массив-куча (индексация с 1)
---@field private cmp fun(a:any, b:any):boolean компаратор: true, если a выше по приоритету, чем b
local PriorityQueue = {}
PriorityQueue.__index = PriorityQueue

View File

@ -111,7 +111,7 @@ function love.draw()
testLayout:draw()
love.graphics.setColor(1, 1, 1)
love.graphics.setFont(Tree.fonts:getTheme("Roboto_Mono"):getVariant("medium"))
love.graphics.setFont(Tree.fonts:getTheme("Roboto_Mono"):getVariant("small"))
local stats = "fps: " ..
love.timer.getFPS() ..
" lt: " .. lt .. " dt: " .. dt .. " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000)