diff --git a/assets/fonts/Roboto_Mono/OFL.txt b/assets/fonts/Roboto_Mono/OFL.txt new file mode 100644 index 0000000..38d9750 --- /dev/null +++ b/assets/fonts/Roboto_Mono/OFL.txt @@ -0,0 +1,91 @@ +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/Roboto_Mono/font.ttf b/assets/fonts/Roboto_Mono/font.ttf new file mode 100644 index 0000000..3806bfb Binary files /dev/null and b/assets/fonts/Roboto_Mono/font.ttf differ diff --git a/assets/fonts/WDXL_Lubrifont_TC/OFL.txt b/assets/fonts/WDXL_Lubrifont_TC/OFL.txt new file mode 100644 index 0000000..2294d5d --- /dev/null +++ b/assets/fonts/WDXL_Lubrifont_TC/OFL.txt @@ -0,0 +1,94 @@ +Copyright 2025 The WDXL Lubrifont Project Authors (https://github.com/NightFurySL2001/WD-XL-font) +Copyright 2018-2020 The ZCOOL QingKe HuangYou Project Authors (https://www.github.com/googlefonts/zcool-qingke-huangyou) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/WDXL_Lubrifont_TC/font.ttf b/assets/fonts/WDXL_Lubrifont_TC/font.ttf new file mode 100644 index 0000000..a98f355 Binary files /dev/null and b/assets/fonts/WDXL_Lubrifont_TC/font.ttf differ diff --git a/assets/masks/rrect32.png b/assets/masks/rrect32.png new file mode 100644 index 0000000..dff5102 Binary files /dev/null and b/assets/masks/rrect32.png differ diff --git a/assets/masks/squircle.png b/assets/masks/squircle.png new file mode 100644 index 0000000..8bd5a6d Binary files /dev/null and b/assets/masks/squircle.png differ diff --git a/assets/shaders/alpha_mask.glsl b/assets/shaders/alpha_mask.glsl new file mode 100644 index 0000000..5339bf7 --- /dev/null +++ b/assets/shaders/alpha_mask.glsl @@ -0,0 +1,8 @@ +vec4 effect(vec4 color, Image tex, vec2 texCoord, vec2 screenCoord) +{ + vec4 px = Texel(tex, texCoord); + if (px.a == 0.0) { + discard; + } + return vec4(1.0); +} diff --git a/assets/shaders/reveal.glsl b/assets/shaders/reveal.glsl new file mode 100644 index 0000000..dbafdea --- /dev/null +++ b/assets/shaders/reveal.glsl @@ -0,0 +1,24 @@ +extern float t; +extern float blockSize; + +// hash-функция для шума по целочисленным координатам блока +float hash(vec2 p) { + p = vec2( + dot(p, vec2(127.1, 311.7)), + dot(p, vec2(269.5, 183.3)) + ); + return fract(sin(p.x + p.y) * 43758.5453123); +} + +vec4 effect(vec4 color, Image tex, vec2 texCoord, vec2 screenCoord) +{ + float blockSize = 4.0; + + vec2 cell = floor(screenCoord / blockSize); + float n = hash(cell); // [0..1] + float mask = 1.0 - step(t, n); + + vec4 base = Texel(tex, texCoord) * color; + base.a *= mask; + return base; +} diff --git a/assets/shaders/soft_uniform_noise.glsl b/assets/shaders/soft_uniform_noise.glsl new file mode 100644 index 0000000..6f07df4 --- /dev/null +++ b/assets/shaders/soft_uniform_noise.glsl @@ -0,0 +1,17 @@ +#pragma language glsl3 + +vec2 hash(vec2 p) { + p = fract(p * vec2(123.34, 456.21)); + p += dot(p, p + 34.345); + return fract(vec2(p.x * p.y, p.x + p.y)); +} + +vec4 effect(vec4 color, Image tex, vec2 uv, vec2 px) +{ + vec2 cell = floor(px / 2.0); // тут можно размер зерна менять + + float n = hash(cell).x; // 0..1 + float v = 0.9 + n * 0.1; // 0.9..1.0 + + return vec4(v, v, v, 1.0); +} diff --git a/lib/animation_node.lua b/lib/animation_node.lua index 8f560f3..b8309e7 100644 --- a/lib/animation_node.lua +++ b/lib/animation_node.lua @@ -33,7 +33,7 @@ local easing = require "lib.utils.easing" --- @field duration number продолжительность в миллисекундах --- @field easing ease функция смягчения --- @field t number прогресс анимации ---- @field finished boolean +--- @field state "running" | "waiting" | "finished" local animation = {} animation.__index = animation @@ -41,7 +41,7 @@ animation.__index = animation function animation:bubbleUp() self.count = self.count - 1 if self.count > 0 then return end - self.finished = true + self.state = "finished" if self.onEnd then self.onEnd() end if self.parent then self.parent:bubbleUp() end end @@ -63,7 +63,7 @@ function animation:getValue() end function animation:update(dt) - if self.finished then return end + if self.state ~= "running" then return end if self.t < 1 then self.t = self.t + dt * 1000 / self.duration -- в знаменателе продолжительность анимации в секундах @@ -82,14 +82,14 @@ local function new(data) end t.onEnd = data.onEnd t.count = 1 -- своя анимация - t.finished = false t.children = {} t:chain(data.children or {}) t.duration = data.duration or 1000 t.easing = data.easing or easing.linear t.t = 0 + t.state = "running" t.finish = function() - if t.finished then return end + t.state = "waiting" t:bubbleUp() for _, anim in ipairs(t.children) do anim:run() diff --git a/lib/audio.lua b/lib/audio.lua index 226ba30..d3647b0 100644 --- a/lib/audio.lua +++ b/lib/audio.lua @@ -57,7 +57,7 @@ function audio:crossfadeAnim(from, to, ms) from:setVolume(self.musicVolume - node:getValue() * self.musicVolume) to:setVolume(node:getValue() * self.musicVolume) t = node.t - print(node.finished, node.duration, node.t) + print(node.duration, node.t) end, onEnd = function() from:setVolume(0) diff --git a/lib/character/behaviors/map.lua b/lib/character/behaviors/map.lua index b5c8174..d22804d 100644 --- a/lib/character/behaviors/map.lua +++ b/lib/character/behaviors/map.lua @@ -24,6 +24,17 @@ function mapBehavior.new(position, size) }, mapBehavior) end +--- @param position Vec3 +function mapBehavior:lookAt(position) + self.owner:try(Tree.behaviors.sprite, + function(sprite) + if position.x > self.displayedPosition.x then sprite.side = sprite.RIGHT end + -- (sic!) + if position.x < self.displayedPosition.x then sprite.side = sprite.LEFT end + end + ) +end + --- @param path Deque --- @param animationNode AnimationNode function mapBehavior:followPath(path, animationNode) diff --git a/lib/character/behaviors/spellcaster.lua b/lib/character/behaviors/spellcaster.lua index c6cd9af..37c128b 100644 --- a/lib/character/behaviors/spellcaster.lua +++ b/lib/character/behaviors/spellcaster.lua @@ -24,6 +24,10 @@ function behavior:endCast() end function behavior:update(dt) + if Tree.level.selector:deselected() then + self.state = "idle" + self.cast = nil + end if self.cast and self.state == "casting" then self.cast:update(self.owner, dt) end end diff --git a/lib/controls.lua b/lib/controls.lua index 652d26a..6fa7a60 100644 --- a/lib/controls.lua +++ b/lib/controls.lua @@ -1,5 +1,3 @@ -local utils = require "lib.utils.utils" - --- @alias Device "mouse" | "key" | "pad" --- @param device Device @@ -17,6 +15,7 @@ controls.keymap = { cameraMoveRight = control("key", "d"), cameraMoveDown = control("key", "s"), cameraMoveScroll = control("mouse", "3"), + cameraAnimateTo = control('key', 't'), fullMana = control("key", "m"), select = control("mouse", "1"), endTurnTest = control("key", "e"), @@ -26,6 +25,12 @@ controls.keymap = { local currentKeys = {} local cachedKeys = {} +--- @type number +controls.mouseWheelY = 0 +love.wheelmoved = function(x, y) + controls.mouseWheelY = y +end + --- polling controls in O(n) --- should be called at the beginning of every frame function controls:poll() @@ -49,6 +54,7 @@ function controls:cache() for k, v in pairs(currentKeys) do cachedKeys[k] = v end + controls.mouseWheelY = 0 end --- marks a control consumed for the current frame diff --git a/lib/level/camera.lua b/lib/level/camera.lua index 4040203..439330c 100644 --- a/lib/level/camera.lua +++ b/lib/level/camera.lua @@ -9,12 +9,15 @@ local EPSILON = 0.001 --- @field speed number --- @field pixelsPerMeter integer --- @field scale number +--- @field animationNode AnimationNode? +--- @field animationEndPosition Vec3 +--- @field animationBeginPosition Vec3 local camera = { position = Vec3 {}, velocity = Vec3 {}, acceleration = 0.2, speed = 5, - pixelsPerMeter = 24, + pixelsPerMeter = 32, } function camera:getDefaultScale() @@ -27,12 +30,6 @@ camera.scale = camera:getDefaultScale() --------------------------------------------------- -love.wheelmoved = function(x, y) - if camera.scale > camera:getDefaultScale() * 5 and y > 0 then return end; - if camera.scale < camera:getDefaultScale() / 5 and y < 0 then return end; - camera.scale = camera.scale + (camera.scale * 0.1 * y) -end - local controlMap = { cameraMoveUp = Vec3({ 0, -1 }), cameraMoveLeft = Vec3({ -1 }), @@ -41,6 +38,19 @@ local controlMap = { } function camera:update(dt) + if self.animationNode and self.animationNode.state == "running" then + self.animationNode:update(dt) -- тик анимации + self.position = utils.lerp(self.animationBeginPosition, self.animationEndPosition, self.animationNode:getValue()) + return + end + + -------------------- зум на колесо --------------------- + local y = Tree.controls.mouseWheelY + if camera.scale > camera:getDefaultScale() * 5 and y > 0 then return end; + if camera.scale < camera:getDefaultScale() / 5 and y < 0 then return end; + camera.scale = camera.scale + (camera.scale * 0.1 * y) + -------------------------------------------------------- + local ps = Tree.panning if ps.delta:length() > 0 then local worldDelta = ps.delta:scale(1 / (self.pixelsPerMeter * self.scale)):scale(dt):scale(self.speed) @@ -87,6 +97,16 @@ function camera:detach() love.graphics.pop() end +--- @param position Vec3 +--- @param animationNode AnimationNode +function camera:animateTo(position, animationNode) + if self.animationNode and self.animationNode.state ~= "finished" then self.animationNode:finish() end + self.animationNode = animationNode + self.animationEndPosition = position + self.animationBeginPosition = self.position + self.velocity = Vec3 {} +end + --- @return Camera local function new() return setmetatable({ diff --git a/lib/level/level.lua b/lib/level/level.lua index 8a1113a..e12361e 100644 --- a/lib/level/level.lua +++ b/lib/level/level.lua @@ -38,7 +38,6 @@ function level:update(dt) el:update(dt) end) - self.camera:update(dt) self.selector:update(dt) end diff --git a/lib/level/selector.lua b/lib/level/selector.lua index 2f966a1..a5d8f98 100644 --- a/lib/level/selector.lua +++ b/lib/level/selector.lua @@ -34,11 +34,8 @@ function selector:update(dt) char:try(Tree.behaviors.spellcaster, function(b) if not b.cast then - -- тут какая-то страшная дичь, я даже не уверен что оно работает - -- зато я точно уверен, что это надо было писать не так - if not selectedId then self:select(selectedId) end - if selectedId ~= Tree.level.turnOrder.current and Tree.level.turnOrder.isTurnsEnabled then return end - return self:select(selectedId) + if not selectedId then self:select(nil) end + return end if b.cast:cast(char, mousePosition) then self:lock() diff --git a/lib/level/turn_order.lua b/lib/level/turn_order.lua index 6ea46ba..f03c836 100644 --- a/lib/level/turn_order.lua +++ b/lib/level/turn_order.lua @@ -30,7 +30,6 @@ end --- --- Если в очереди на ход больше никого нет, заканчиваем раунд function turnOrder:next() - Tree.level.selector.id = nil self.actedQueue:insert(self.current) local next = self.pendingQueue:peek() if not next then return self:endRound() end diff --git a/lib/simple_ui/color.lua b/lib/simple_ui/color.lua new file mode 100644 index 0000000..f97788d --- /dev/null +++ b/lib/simple_ui/color.lua @@ -0,0 +1,20 @@ +--- @class Color +--- @field r number +--- @field g number +--- @field b number +--- @field a number +local color = { + r = 1, + g = 1, + b = 1, + a = 1 +} +color.__index = color + +--- @param rgba {r?: number, g?: number, b?: number, a?: number} +--- @return Color +function color.new(rgba) + return setmetatable(rgba, color) +end + +return color.new diff --git a/lib/simple_ui/element.lua b/lib/simple_ui/element.lua index ba0f4e3..440f7a6 100644 --- a/lib/simple_ui/element.lua +++ b/lib/simple_ui/element.lua @@ -1,9 +1,22 @@ local Rect = require "lib.simple_ui.rect" +local function makeGradientMesh(w, h, topColor, bottomColor) + local vertices = { + { 0, 0, 0, 0, topColor[1], topColor[2], topColor[3], topColor[4] }, -- левый верх + { w, 0, 1, 0, topColor[1], topColor[2], topColor[3], topColor[4] }, -- правый верх + { w, h, 1, 1, bottomColor[1], bottomColor[2], bottomColor[3], bottomColor[4] }, -- правый низ + { 0, h, 0, 1, bottomColor[1], bottomColor[2], bottomColor[3], bottomColor[4] }, -- левый низ + } + local mesh = love.graphics.newMesh(vertices, "fan", "static") + return mesh +end + --- @class UIElement ---- @field bounds Rect Прямоугольник, в границах которого размещается элемент. Размеры и положение в *локальных* координатах ---- @field transform love.Transform Преобразование из локальных координат элемента (bounds) в экранные координаты +--- @field bounds Rect Прямоугольник, в границах которого размещается элемент. Размеры и положение в экранных координатах +--- @field overlayGradientMesh love.Mesh Общий градиент поверх элемента (интерполированный меш) local uiElement = {} +uiElement.bounds = Rect {} +uiElement.overlayGradientMesh = makeGradientMesh(1, 1, { 0, 0, 0, 0 }, { 0, 0, 0, 0.4 }); uiElement.__index = uiElement function uiElement:update(dt) end @@ -11,10 +24,7 @@ function uiElement:update(dt) end function uiElement:draw() end function uiElement:hitTest(screenX, screenY) - local r, g, b, a = love.graphics.getColor() - if a == 0 then return false end - local lx, ly = self.transform:inverseTransformPoint(screenX, screenY) - return self.bounds:hasPoint(lx, ly) + return self.bounds:hasPoint(screenX, screenY) end --- @generic T : UIElement @@ -23,8 +33,77 @@ end --- @return T function uiElement.new(self, values) values.bounds = values.bounds or Rect {} - values.transform = values.transform or love.math.newTransform() + values.overlayGradientMesh = values.overlayGradientMesh or uiElement.overlayGradientMesh; return setmetatable(values, self) end +--- Рисует границу вокруг элемента (с псевдо-затенением) +--- @param type "outer" | "inner" +--- @param width? number +function uiElement:drawBorder(type, width) + local w = width or 4 + love.graphics.setLineWidth(w) + + if type == "inner" then + love.graphics.setColor(0.2, 0.2, 0.2) + love.graphics.line({ + self.bounds.x, self.bounds.y + self.bounds.height, + self.bounds.x, self.bounds.y, + self.bounds.x + self.bounds.width, self.bounds.y, + }) + + love.graphics.setColor(0.3, 0.3, 0.3) + love.graphics.line({ + self.bounds.x + self.bounds.width, self.bounds.y, + self.bounds.x + self.bounds.width, self.bounds.y + self.bounds.height, + self.bounds.x, self.bounds.y + self.bounds.height, + }) + else + love.graphics.setColor(0.3, 0.3, 0.3) + -- love.graphics.line({ + -- self.bounds.x, self.bounds.y + self.bounds.height, + -- self.bounds.x, self.bounds.y, + -- self.bounds.x + self.bounds.width, self.bounds.y, + -- }) + love.graphics.line({ + self.bounds.x, self.bounds.y + self.bounds.height - w, + self.bounds.x, self.bounds.y + w, + }) + + love.graphics.line({ + self.bounds.x + w, self.bounds.y, + self.bounds.x + self.bounds.width - w, self.bounds.y, + }) + + love.graphics.setColor(0.2, 0.2, 0.2) + -- love.graphics.line({ + -- self.bounds.x + self.bounds.width, self.bounds.y, + -- self.bounds.x + self.bounds.width, self.bounds.y + self.bounds.height, + -- self.bounds.x, self.bounds.y + self.bounds.height, + -- }) + + love.graphics.line({ + self.bounds.x + self.bounds.width, self.bounds.y + w, + self.bounds.x + self.bounds.width, self.bounds.y + self.bounds.height - w, + }) + + love.graphics.line({ + self.bounds.x + self.bounds.width - w, self.bounds.y + self.bounds.height, + self.bounds.x + w, self.bounds.y + self.bounds.height, + }) + end + + love.graphics.setColor(1, 1, 1) +end + +--- рисует градиент поверх элемента +function uiElement:drawGradientOverlay() + love.graphics.push() + love.graphics.translate(self.bounds.x, self.bounds.y) + love.graphics.scale(self.bounds.width, self.bounds.height) + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(self.overlayGradientMesh) + love.graphics.pop() +end + return uiElement diff --git a/lib/simple_ui/level/bar.lua b/lib/simple_ui/level/bar.lua new file mode 100644 index 0000000..9bacb90 --- /dev/null +++ b/lib/simple_ui/level/bar.lua @@ -0,0 +1,71 @@ +local Element = require "lib.simple_ui.element" + + +--- @class BarElement : UIElement +--- @field getter fun() : number +--- @field value number +--- @field maxValue number +--- @field color Color +--- @field useDividers boolean +--- @field drawText boolean +local barElement = setmetatable({}, Element) +barElement.__index = barElement +barElement.useDividers = false +barElement.drawText = false + +function barElement:update(dt) + local val = self.getter() + self.value = val < 0 and 0 or val > self.maxValue and self.maxValue or val +end + +function barElement:draw() + local valueWidth = self.bounds.width * self.value / self.maxValue + local emptyWidth = self.bounds.width - valueWidth + + --- шум + love.graphics.setShader(Tree.assets.files.shaders.soft_uniform_noise) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + love.graphics.setShader() + + --- закраска пустой части + love.graphics.setColor(0.05, 0.05, 0.05) + love.graphics.setBlendMode("multiply", "premultiplied") + love.graphics.rectangle("fill", self.bounds.x + valueWidth, self.bounds.y, emptyWidth, + self.bounds.height) + love.graphics.setBlendMode("alpha") + + --- закраска значимой части её цветом + love.graphics.setColor(self.color.r, self.color.g, self.color.b) + love.graphics.setBlendMode("multiply", "premultiplied") + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, valueWidth, + self.bounds.height) + love.graphics.setBlendMode("alpha") + + --- мерки + love.graphics.setColor(38 / 255, 50 / 255, 56 / 255) + if self.useDividers then + local count = self.maxValue - 1 + local measureWidth = self.bounds.width / self.maxValue + + for i = 1, count, 1 do + love.graphics.line(self.bounds.x + i * measureWidth, self.bounds.y, self.bounds.x + i * measureWidth, + self.bounds.y + self.bounds.height) + end + end + + + love.graphics.setColor(1, 1, 1) + --- текст поверх + if self.drawText then + local font = Tree.fonts:getDefaultTheme():getVariant("medium") + 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)) + end + + self:drawBorder("inner") + + self:drawGradientOverlay() +end + +return function(values) return barElement:new(values) end diff --git a/lib/simple_ui/level/bottom_bars.lua b/lib/simple_ui/level/bottom_bars.lua new file mode 100644 index 0000000..d7e3a91 --- /dev/null +++ b/lib/simple_ui/level/bottom_bars.lua @@ -0,0 +1,86 @@ +local Element = require "lib.simple_ui.element" +local Rect = require "lib.simple_ui.rect" +local Color = require "lib.simple_ui.color" +local Bar = require "lib.simple_ui.level.bar" + +--- @class BottomBars : UIElement +--- @field hpBar BarElement +--- @field manaBar BarElement +local bottomBars = setmetatable({}, Element) +bottomBars.__index = bottomBars; + +--- @param cid Id +function bottomBars.new(cid) + local t = setmetatable({}, bottomBars) + + t.hpBar = + Bar { + getter = function() + local char = Tree.level.characters[cid] + return char:try(Tree.behaviors.stats, function(stats) + return stats.hp or 0 + end) + end, + color = Color { r = 130 / 255, g = 8 / 255, b = 8 / 255 }, + drawText = true, + maxValue = 20 + } + + t.manaBar = + Bar { + getter = function() + local char = Tree.level.characters[cid] + return char:try(Tree.behaviors.stats, function(stats) + return stats.mana or 0 + end) + end, + color = Color { r = 51 / 255, g = 105 / 255, b = 30 / 255 }, + useDividers = true, + maxValue = 10 + } + + + return t +end + +function bottomBars:update(dt) + local height = 16 + local margin = 2 + + self.bounds.height = height + self.bounds.y = self.bounds.y - height + + self.hpBar.bounds = Rect { + width = -2 * margin + self.bounds.width / 2, + height = height - margin, + x = self.bounds.x + margin, + y = self.bounds.y + margin + } + + self.manaBar.bounds = Rect { + width = -2 * margin + self.bounds.width / 2, + height = height - margin, + x = self.bounds.x + margin + self.bounds.width / 2, + y = self.bounds.y + margin + } + + self.hpBar:update(dt) + self.manaBar:update(dt) +end + +function bottomBars:draw() + -- шум + love.graphics.setShader(Tree.assets.files.shaders.soft_uniform_noise) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + love.graphics.setShader() + + love.graphics.setColor(38 / 255, 50 / 255, 56 / 255) + love.graphics.setBlendMode("multiply", "premultiplied") + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + love.graphics.setBlendMode("alpha") + + self.hpBar:draw() + self.manaBar:draw() +end + +return bottomBars.new diff --git a/lib/simple_ui/level/cpanel.lua b/lib/simple_ui/level/cpanel.lua new file mode 100644 index 0000000..c04fe58 --- /dev/null +++ b/lib/simple_ui/level/cpanel.lua @@ -0,0 +1,128 @@ +local easing = require "lib.utils.easing" +local AnimationNode = require "lib.animation_node" +local Element = require "lib.simple_ui.element" +local Rect = require "lib.simple_ui.rect" +local SkillRow = require "lib.simple_ui.level.skill_row" +local Bars = require "lib.simple_ui.level.bottom_bars" +local EndTurnButton = require "lib.simple_ui.level.end_turn" + +--- @class CharacterPanel : UIElement +--- @field animationNode AnimationNode +--- @field state "show" | "idle" | "hide" +--- @field skillRow SkillRow +--- @field bars BottomBars +--- @field endTurnButton EndTurnButton +local characterPanel = setmetatable({}, Element) +characterPanel.__index = characterPanel + +function characterPanel.new(characterId) + local t = {} + t.state = "show" + t.skillRow = SkillRow(characterId) + t.bars = Bars(characterId) + t.endTurnButton = EndTurnButton {} + return setmetatable(t, characterPanel) +end + +function characterPanel:show() + AnimationNode { + function(animationNode) + if self.animationNode then self.animationNode:finish() end + self.animationNode = animationNode + self.state = "show" + end, + duration = 300, + onEnd = function() + self.state = "idle" + end, + easing = easing.easeOutCubic + }:run() +end + +function characterPanel:hide() + AnimationNode { + function(animationNode) + if self.animationNode then self.animationNode:finish() end + self.animationNode = animationNode + self.state = "hide" + end, + duration = 300, + easing = easing.easeOutCubic + }:run() +end + +--- @type love.Canvas +local characterPanelCanvas; + +function characterPanel:update(dt) + if self.animationNode then self.animationNode:update(dt) end + self.skillRow:update(dt) + self.bars.bounds = Rect { + width = self.skillRow.bounds.width, + x = self.skillRow.bounds.x, + y = self.skillRow.bounds.y + } + self.bars:update(dt) + + self.bounds = Rect { + x = self.bars.bounds.x, + y = self.bars.bounds.y, + width = self.bars.bounds.width, + height = self.bars.bounds.height + self.skillRow.bounds.height + } + + self.endTurnButton:layout() + self.endTurnButton.bounds.x = self.bounds.x + self.bounds.width + 32 + self.endTurnButton.bounds.y = self.bounds.y + self.bounds.height / 2 - self.endTurnButton.bounds.height / 2 + + self.endTurnButton:update(dt) + + if not characterPanelCanvas then + characterPanelCanvas = love.graphics.newCanvas(self.bounds.width, self.bounds.height) + end + + --- анимация появления + local alpha = 1 + if self.state == "show" then + alpha = self.animationNode:getValue() + elseif self.state == "hide" then + alpha = 1 - self.animationNode:getValue() + end + local revealShader = Tree.assets.files.shaders.reveal + revealShader:send("t", alpha) +end + +function characterPanel:draw() + self.skillRow:draw() + + --- @TODO: переписать этот ужас с жонглированием координатами, а то слишком хардкод (skillRow рисуется относительно нуля и не закрывает канвас) + love.graphics.push() + local canvas = love.graphics.getCanvas() + love.graphics.translate(0, self.bars.bounds.height) + love.graphics.setCanvas(characterPanelCanvas) + love.graphics.clear() + love.graphics.draw(canvas) + love.graphics.pop() + + love.graphics.push() + love.graphics.translate(-self.bounds.x, -self.bounds.y) + self.bars:draw() + self:drawBorder("outer") + love.graphics.pop() + + --- рисуем текстуру шейдером появления + love.graphics.setCanvas() + love.graphics.setShader(Tree.assets.files.shaders.reveal) + love.graphics.setColor(1, 1, 1, 1) + + self.endTurnButton:draw() + + love.graphics.push() + love.graphics.translate(self.bounds.x, self.bounds.y) + love.graphics.draw(characterPanelCanvas) + love.graphics.setColor(1, 1, 1) + love.graphics.pop() + love.graphics.setShader() +end + +return characterPanel.new diff --git a/lib/simple_ui/level/end_turn.lua b/lib/simple_ui/level/end_turn.lua new file mode 100644 index 0000000..e1cb679 --- /dev/null +++ b/lib/simple_ui/level/end_turn.lua @@ -0,0 +1,66 @@ +local Element = require "lib.simple_ui.element" +local AnimationNode = require "lib.animation_node" +local easing = require "lib.utils.easing" + +--- @class EndTurnButton : UIElement +--- @field hovered boolean +--- @field onClick function? +local endTurnButton = setmetatable({}, Element) +endTurnButton.__index = endTurnButton + +function endTurnButton:update(dt) + local mx, my = love.mouse.getPosition() + if self:hitTest(mx, my) then + self.hovered = true + if Tree.controls:isJustPressed("select") then + if self.onClick then self.onClick() end + Tree.controls:consume("select") + end + else + self.hovered = false + end +end + +function endTurnButton:layout() + local font = Tree.fonts:getDefaultTheme():getVariant("headline") + self.text = love.graphics.newText(font, "Завершить ход") + self.bounds.width = self.text:getWidth() + 32 + self.bounds.height = self.text:getHeight() + 16 +end + +function endTurnButton:draw() + love.graphics.setColor(38 / 255, 50 / 255, 56 / 255, 0.9) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + + if self.hovered then + love.graphics.setColor(0.1, 0.1, 0.1) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + end + + love.graphics.setColor(0.95, 0.95, 0.95) + love.graphics.draw(self.text, self.bounds.x + 16, self.bounds.y + 8) + + self:drawBorder("outer") + love.graphics.setColor(1, 1, 1) +end + +function endTurnButton:onClick() + Tree.level.turnOrder:next() + Tree.level.selector:select(nil) + local cid = Tree.level.turnOrder.current + local playing = Tree.level.characters[cid] + if not playing:has(Tree.behaviors.map) then return end + + AnimationNode { + function(node) + Tree.level.camera:animateTo(playing:has(Tree.behaviors.map).displayedPosition, node) + end, + duration = 1500, + easing = easing.easeInOutCubic, + onEnd = function() Tree.level.selector:select(cid) end + }:run() +end + +return function(values) + return endTurnButton:new(values) +end diff --git a/lib/simple_ui/level/layout.lua b/lib/simple_ui/level/layout.lua index 38c5707..0c9774a 100644 --- a/lib/simple_ui/level/layout.lua +++ b/lib/simple_ui/level/layout.lua @@ -1,24 +1,23 @@ -local easing = require "lib.utils.easing" -local AnimationNode = require "lib.animation_node" -local Element = require "lib.simple_ui.element" -local Rect = require "lib.simple_ui.rect" -local SkillRow = require "lib.simple_ui.level.skill_row" +local CPanel = require "lib.simple_ui.level.cpanel" +local build local layout = {} function layout:update(dt) + if self.characterPanel then self.characterPanel:update(dt) end + local cid = Tree.level.selector:selected() if cid then - self.skillRow = SkillRow(cid) - self.skillRow:show() + self.characterPanel = CPanel(cid) + self.characterPanel:show() + self.characterPanel:update(dt) elseif Tree.level.selector:deselected() then - self.skillRow:hide() + self.characterPanel:hide() end - if self.skillRow then self.skillRow:update(dt) end end function layout:draw() - if self.skillRow then self.skillRow:draw() end + if self.characterPanel then self.characterPanel:draw() end end return layout diff --git a/lib/simple_ui/level/scale.lua b/lib/simple_ui/level/scale.lua new file mode 100644 index 0000000..fb4f658 --- /dev/null +++ b/lib/simple_ui/level/scale.lua @@ -0,0 +1,2 @@ +local UI_SCALE = 0.75 -- выдуманное значение для dependency injection, надо подбирать так, чтобы UI_SCALE * 64 было целым числом +return UI_SCALE diff --git a/lib/simple_ui/level/skill_row.lua b/lib/simple_ui/level/skill_row.lua index d8219f2..6f7ddc7 100644 --- a/lib/simple_ui/level/skill_row.lua +++ b/lib/simple_ui/level/skill_row.lua @@ -1,18 +1,19 @@ local icons = require("lib.utils.sprite_atlas").load(Tree.assets.files.dev_icons) -local easing = require "lib.utils.easing" -local AnimationNode = require "lib.animation_node" local Element = require "lib.simple_ui.element" local Rect = require "lib.simple_ui.rect" +local UI_SCALE = require "lib.simple_ui.level.scale" + --- @class SkillButton : UIElement --- @field hovered boolean --- @field selected boolean --- @field onClick function? ---- @field icon string +--- @field icon? string local skillButton = setmetatable({}, Element) skillButton.__index = skillButton function skillButton:update(dt) + if not self.icon then return end local mx, my = love.mouse.getPosition() if self:hitTest(mx, my) then self.hovered = true @@ -26,22 +27,33 @@ function skillButton:update(dt) end function skillButton:draw() - love.graphics.push() - love.graphics.applyTransform(self.transform) + love.graphics.setLineWidth(2) - local r, g, b, a = love.graphics.getColor() - if self.selected then - love.graphics.setColor(0.3, 1, 0.3, a) - elseif self.hovered then - love.graphics.setColor(0.7, 1, 0.7, a) - else - love.graphics.setColor(1, 1, 1, a) + if not self.icon then + love.graphics.setColor(0.05, 0.05, 0.05) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + self:drawBorder("inner") + return end - love.graphics.translate(0, self.bounds.y) - love.graphics.draw(icons.atlas, icons:pickQuad(self.icon)) + local quad = icons:pickQuad(self.icon) + love.graphics.push() + love.graphics.translate(self.bounds.x, self.bounds.y) + love.graphics.scale(self.bounds.width / icons.tileSize, self.bounds.height / icons.tileSize) + love.graphics.draw(icons.atlas, quad) love.graphics.pop() + + self:drawBorder("inner") + + if self.selected then + love.graphics.setColor(0.3, 1, 0.3, 0.5) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + elseif self.hovered then + 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 + love.graphics.setColor(1, 1, 1) end -------------------------------------------------------------------------------- @@ -49,8 +61,6 @@ end --- @class SkillRow : UIElement --- @field characterId Id --- @field selected SkillButton? ---- @field animationNode AnimationNode ---- @field state "show" | "idle" | "hide" --- @field children SkillButton[] local skillRow = setmetatable({}, Element) skillRow.__index = skillRow @@ -60,7 +70,6 @@ skillRow.__index = skillRow function skillRow.new(characterId) local t = { characterId = characterId, - state = "show", children = {} } @@ -87,75 +96,89 @@ function skillRow.new(characterId) end end) + for i = #t.children + 1, 7, 1 do + t.children[i] = skillButton:new {} + end + return t end -function skillRow:show() - AnimationNode { - function(animationNode) - if self.animationNode then self.animationNode:finish() end - self.animationNode = animationNode - self.state = "show" - end, - duration = 300, - onEnd = function() - self.state = "idle" - end, - easing = easing.easeOutCubic - }:run() -end - -function skillRow:hide() - AnimationNode { - function(animationNode) - if self.animationNode then self.animationNode:finish() end - self.animationNode = animationNode - self.state = "hide" - end, - duration = 300, - easing = easing.easeOutCubic - }:run() -end +--- @type love.Canvas +local c; function skillRow:update(dt) - if self.animationNode then self.animationNode:update(dt) end - - local iconSize = icons.tileSize - local scale = (64 / iconSize) + local iconSize = math.floor(64 * UI_SCALE) local screenW, screenH = love.graphics.getDimensions() - local padding = 8 - local count = #self.children + local padding, margin = 8, 4 + local count = #self.children -- слоты под скиллы self.bounds = Rect { - width = count * icons.tileSize + (count - 1) * padding, - height = iconSize, - y = self.state == "show" and 10 * (1 - self.animationNode:getValue()) or 0 + width = iconSize * count + (count + 1) * margin, + height = iconSize + 2 * margin, } - self.transform = love.math.newTransform():translate(screenW / 2, - screenH - 16):scale(scale, scale):translate(-self.bounds.width / 2, -iconSize) + self.bounds.y = screenH - self.bounds.height - padding -- отступ снизу + self.bounds.x = screenW / 2 - self.bounds.width / 2 for i, skb in ipairs(self.children) do - skb.bounds = Rect { height = iconSize, width = iconSize } - skb.transform = self.transform:clone():translate(self.bounds.x + (i - 1) * iconSize + - (i - 1) * - padding, -- левый край ряда + размер предыдущих иконок + размер предыдущих отступов - self.bounds.y -- высота не меняется - ) + skb.bounds = Rect { x = self.bounds.x + margin + (i - 1) * (iconSize + margin), -- друг за другом, включая первый отступ от границы + y = self.bounds.y + margin, height = iconSize, width = iconSize } skb:update(dt) end + + if not c then + c = love.graphics.newCanvas(self.bounds.width, self.bounds.height) + end end function skillRow:draw() - local alpha = 1 - if self.state == "show" then - alpha = self.animationNode:getValue() - elseif self.state == "hide" then - alpha = 1 - self.animationNode:getValue() - end - love.graphics.setColor(1, 1, 1, alpha) - for _, skb in ipairs(self.children) do - skb:draw() + love.graphics.setCanvas({ c, stencil = true }) + love.graphics.clear() + love.graphics.setColor(1, 1, 1) + + do + --- рисуем в локальных координатах текстурки + love.graphics.push() + love.graphics.translate(-self.bounds.x, -self.bounds.y) + + -- сначала иконки скиллов + for _, skb in ipairs(self.children) do + skb:draw() + end + + -- маска для вырезов под иконки + love.graphics.setShader(Tree.assets.files.shaders.alpha_mask) + love.graphics.stencil(function() + local mask = Tree.assets.files.masks.rrect32 + local maskSize = mask:getWidth() + for _, skb in ipairs(self.children) do + love.graphics.draw(mask, skb.bounds.x, skb.bounds.y, 0, + skb.bounds.width / maskSize, skb.bounds.height / maskSize) + end + end, "replace", 1) + love.graphics.setShader() + + + -- дальше рисуем панель, перекрывая иконки + love.graphics.setStencilTest("less", 1) + -- шум + love.graphics.setShader(Tree.assets.files.shaders.soft_uniform_noise) + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + love.graphics.setShader() + + -- фон + love.graphics.setColor(38 / 255, 50 / 255, 56 / 255) + love.graphics.setBlendMode("multiply", "premultiplied") + love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) + love.graphics.setBlendMode("alpha") + + love.graphics.setStencilTest() + + --затенение + self:drawGradientOverlay() + love.graphics.pop() end + + love.graphics.setColor(1, 1, 1) end return skillRow.new diff --git a/lib/simple_ui/rect.lua b/lib/simple_ui/rect.lua index 3f3931f..932a0ad 100644 --- a/lib/simple_ui/rect.lua +++ b/lib/simple_ui/rect.lua @@ -18,7 +18,7 @@ function rect.new(table) end function rect:hasPoint(x, y) - return x >= self.x and x < self.width and y >= self.y and y < self.height + return x >= self.x and x < self.x + self.width and y >= self.y and y < self.y + self.height end return rect.new diff --git a/lib/spellbook.lua b/lib/spellbook.lua index e21bc58..6283e14 100644 --- a/lib/spellbook.lua +++ b/lib/spellbook.lua @@ -127,9 +127,7 @@ function attack:cast(caster, target) local targetSprite = targetCharacter:has(Tree.behaviors.sprite) if not sprite or not targetSprite then return true end - if sprite.side == targetSprite.side then - sprite.side = -sprite.side - end + caster:try(Tree.behaviors.map, function(map) map:lookAt(target) end) AnimationNode { onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end, diff --git a/lib/tree.lua b/lib/tree.lua index e22374f..0a616b3 100644 --- a/lib/tree.lua +++ b/lib/tree.lua @@ -6,6 +6,7 @@ Tree = { assets = (require "lib.utils.asset_bundle"):load() } +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) diff --git a/lib/utils/asset_bundle.lua b/lib/utils/asset_bundle.lua index 712330d..5db9cde 100644 --- a/lib/utils/asset_bundle.lua +++ b/lib/utils/asset_bundle.lua @@ -55,7 +55,7 @@ function AssetBundle.loadFile(path) elseif (ext == "ogg") and string.find(path, "music") then return love.audio.newSource(path, 'stream') end - return nil + return filedata end function AssetBundle.cutExtension(filename) diff --git a/lib/utils/font_manager.lua b/lib/utils/font_manager.lua new file mode 100644 index 0000000..0bec070 --- /dev/null +++ b/lib/utils/font_manager.lua @@ -0,0 +1,94 @@ +--- @alias FontVariant "smallest" | "small" | "medium" | "large" | "headline" + +--- @class TextTheme +--- @field private _sizes {[FontVariant]: integer} +local theme = { + _sizes = { + smallest = 10, + small = 12, + medium = 14, + large = 16, + headline = 20, + } +} +theme.__index = theme + +--- @param loader fun(size: integer): love.Font? +function theme.new(loader) + local t = {} + for tag, size in pairs(theme._sizes) do + local f = loader(size) + if not f then return nil end + t[tag] = f + end + + return setmetatable(t, theme) +end + +--- @param variant FontVariant +--- @return love.Font +function theme:getVariant(variant) + return self[variant] +end + +---------------------------------------------------------- + +--- A singleton to handle fonts +--- @class FontManager +--- @field private _themes table +--- @field defaultTheme string? +local FontManager = { + _themes = {} +} + +--- @param name string +--- @param size integer +function FontManager.newFont(name, size) + local err = function() + print("[FontManager]: font " .. name .. " not found!") + end + local fontDir = Tree.assets.files.fonts[name] + if not fontDir then return err() end + local font = fontDir.font + if not font then return err() end + return love.graphics.newFont(font, size) +end + +--- @param name string +--- @return FontManager +function FontManager:loadTheme(name) + self._themes[name] = theme.new( + function(size) + return self.newFont(name, size) + end + ) + return self +end + +--- @param name string +--- @return TextTheme? +function FontManager:getTheme(name) + return self._themes[name] +end + +--- @return TextTheme +function FontManager:getDefaultTheme() + return self._themes[self.defaultTheme] +end + +--- initial setup +--- @param defaultFontName string +function FontManager:load(defaultFontName) + local t = self:loadTheme(defaultFontName):getTheme(defaultFontName) + if not t then + print("[FontManager]: default font " .. defaultFontName .. " is missing") + return self + end + self.defaultTheme = defaultFontName + + local f = t:getVariant("medium") + love.graphics.setFont(f) + return self +end + +return FontManager diff --git a/main.lua b/main.lua index 64008c7..256ba45 100644 --- a/main.lua +++ b/main.lua @@ -28,21 +28,11 @@ local lt = "0" function love.update(dt) local t1 = love.timer.getTime() Tree.controls:poll() - testLayout:update(dt) -- логика UI-слоя должна отработать раньше всех, потому что нужно перехватить жесты и не пустить их дальше + Tree.level.camera:update(dt) -- сначала логика камеры, потому что на нее завязан UI + testLayout:update(dt) -- потом UI, потому что нужно перехватить жесты и не пустить их дальше Tree.panning:update(dt) Tree.level:update(dt) - -- для тестов очереди ходов - -- удалить как только появится ui для людей - if Tree.controls:isJustPressed("endTurnTest") then - Tree.level.turnOrder:next() - print("Now playing:", Tree.level.turnOrder.current) - end - if Tree.controls:isJustPressed("toggleTurns") then - print('toggle turns') - Tree.level.turnOrder:toggleTurns() - end - Tree.controls:cache() local t2 = love.timer.getTime() @@ -77,7 +67,10 @@ function love.draw() testLayout:draw() love.graphics.setColor(1, 1, 1) - local stats = "fps: " .. love.timer.getFPS() .. " lt: " .. lt .. " dt: " .. dt + love.graphics.setFont(Tree.fonts:getTheme("Roboto_Mono"):getVariant("medium")) + local stats = "fps: " .. + love.timer.getFPS() .. + " lt: " .. lt .. " dt: " .. dt .. " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000) love.graphics.print(stats, 10, 10) local t2 = love.timer.getTime()