Compare commits

..

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

31 changed files with 116 additions and 924 deletions

View File

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

Binary file not shown.

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,8 +0,0 @@
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);
}

View File

@ -1,24 +0,0 @@
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;
}

View File

@ -1,17 +0,0 @@
#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);
}

View File

@ -33,7 +33,7 @@ local easing = require "lib.utils.easing"
--- @field duration number продолжительность в миллисекундах --- @field duration number продолжительность в миллисекундах
--- @field easing ease функция смягчения --- @field easing ease функция смягчения
--- @field t number прогресс анимации --- @field t number прогресс анимации
--- @field state "running" | "waiting" | "finished" --- @field finished boolean
local animation = {} local animation = {}
animation.__index = animation animation.__index = animation
@ -41,7 +41,7 @@ animation.__index = animation
function animation:bubbleUp() function animation:bubbleUp()
self.count = self.count - 1 self.count = self.count - 1
if self.count > 0 then return end if self.count > 0 then return end
self.state = "finished" self.finished = true
if self.onEnd then self.onEnd() end if self.onEnd then self.onEnd() end
if self.parent then self.parent:bubbleUp() end if self.parent then self.parent:bubbleUp() end
end end
@ -63,7 +63,7 @@ function animation:getValue()
end end
function animation:update(dt) function animation:update(dt)
if self.state ~= "running" then return end if self.finished then return end
if self.t < 1 then if self.t < 1 then
self.t = self.t + dt * 1000 / self.duration -- в знаменателе продолжительность анимации в секундах self.t = self.t + dt * 1000 / self.duration -- в знаменателе продолжительность анимации в секундах
@ -82,14 +82,14 @@ local function new(data)
end end
t.onEnd = data.onEnd t.onEnd = data.onEnd
t.count = 1 -- своя анимация t.count = 1 -- своя анимация
t.finished = false
t.children = {} t.children = {}
t:chain(data.children or {}) t:chain(data.children or {})
t.duration = data.duration or 1000 t.duration = data.duration or 1000
t.easing = data.easing or easing.linear t.easing = data.easing or easing.linear
t.t = 0 t.t = 0
t.state = "running"
t.finish = function() t.finish = function()
t.state = "waiting" if t.finished then return end
t:bubbleUp() t:bubbleUp()
for _, anim in ipairs(t.children) do for _, anim in ipairs(t.children) do
anim:run() anim:run()

View File

@ -24,17 +24,6 @@ function mapBehavior.new(position, size)
}, mapBehavior) }, mapBehavior)
end 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 path Deque
--- @param animationNode AnimationNode --- @param animationNode AnimationNode
function mapBehavior:followPath(path, animationNode) function mapBehavior:followPath(path, animationNode)

View File

@ -24,10 +24,6 @@ function behavior:endCast()
end end
function behavior:update(dt) 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 if self.cast and self.state == "casting" then self.cast:update(self.owner, dt) end
end end

View File

@ -17,7 +17,7 @@ local camera = {
velocity = Vec3 {}, velocity = Vec3 {},
acceleration = 0.2, acceleration = 0.2,
speed = 5, speed = 5,
pixelsPerMeter = 32, pixelsPerMeter = 24,
} }
function camera:getDefaultScale() function camera:getDefaultScale()
@ -38,7 +38,7 @@ local controlMap = {
} }
function camera:update(dt) function camera:update(dt)
if self.animationNode and self.animationNode.state == "running" then if self.animationNode and not (self.animationNode.t >= 1) then
self.animationNode:update(dt) -- тик анимации self.animationNode:update(dt) -- тик анимации
self.position = utils.lerp(self.animationBeginPosition, self.animationEndPosition, self.animationNode:getValue()) self.position = utils.lerp(self.animationBeginPosition, self.animationEndPosition, self.animationNode:getValue())
return return
@ -100,7 +100,7 @@ end
--- @param position Vec3 --- @param position Vec3
--- @param animationNode AnimationNode --- @param animationNode AnimationNode
function camera:animateTo(position, animationNode) function camera:animateTo(position, animationNode)
if self.animationNode and self.animationNode.state ~= "finished" then self.animationNode:finish() end if self.animationNode then self.animationNode:finish() end
self.animationNode = animationNode self.animationNode = animationNode
self.animationEndPosition = position self.animationEndPosition = position
self.animationBeginPosition = self.position self.animationBeginPosition = self.position

View File

@ -35,6 +35,7 @@ function level:update(dt)
el:update(dt) el:update(dt)
end) end)
self.camera:update(dt)
self.selector:update(dt) self.selector:update(dt)
end end

View File

@ -34,8 +34,11 @@ function selector:update(dt)
char:try(Tree.behaviors.spellcaster, function(b) char:try(Tree.behaviors.spellcaster, function(b)
if not b.cast then if not b.cast then
if not selectedId then self:select(nil) end -- тут какая-то страшная дичь, я даже не уверен что оно работает
return -- зато я точно уверен, что это надо было писать не так
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)
end end
if b.cast:cast(char, mousePosition) then if b.cast:cast(char, mousePosition) then
self:lock() self:lock()

View File

@ -30,6 +30,7 @@ end
--- ---
--- Если в очереди на ход больше никого нет, заканчиваем раунд --- Если в очереди на ход больше никого нет, заканчиваем раунд
function turnOrder:next() function turnOrder:next()
Tree.level.selector.id = nil
self.actedQueue:insert(self.current) self.actedQueue:insert(self.current)
local next = self.pendingQueue:peek() local next = self.pendingQueue:peek()
if not next then return self:endRound() end if not next then return self:endRound() end

View File

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

View File

@ -1,22 +1,9 @@
local Rect = require "lib.simple_ui.rect" 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 --- @class UIElement
--- @field bounds Rect Прямоугольник, в границах которого размещается элемент. Размеры и положение в экранных координатах --- @field bounds Rect Прямоугольник, в границах которого размещается элемент. Размеры и положение в *локальных* координатах
--- @field overlayGradientMesh love.Mesh Общий градиент поверх элемента (интерполированный меш) --- @field transform love.Transform Преобразование из локальных координат элемента (bounds) в экранные координаты
local uiElement = {} local uiElement = {}
uiElement.bounds = Rect {}
uiElement.overlayGradientMesh = makeGradientMesh(1, 1, { 0, 0, 0, 0 }, { 0, 0, 0, 0.4 });
uiElement.__index = uiElement uiElement.__index = uiElement
function uiElement:update(dt) end function uiElement:update(dt) end
@ -24,7 +11,10 @@ function uiElement:update(dt) end
function uiElement:draw() end function uiElement:draw() end
function uiElement:hitTest(screenX, screenY) function uiElement:hitTest(screenX, screenY)
return self.bounds:hasPoint(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)
end end
--- @generic T : UIElement --- @generic T : UIElement
@ -33,77 +23,8 @@ end
--- @return T --- @return T
function uiElement.new(self, values) function uiElement.new(self, values)
values.bounds = values.bounds or Rect {} values.bounds = values.bounds or Rect {}
values.overlayGradientMesh = values.overlayGradientMesh or uiElement.overlayGradientMesh; values.transform = values.transform or love.math.newTransform()
return setmetatable(values, self) return setmetatable(values, self)
end 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 return uiElement

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,24 @@
local CPanel = require "lib.simple_ui.level.cpanel" 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 build
local layout = {} local layout = {}
function layout:update(dt) function layout:update(dt)
if self.characterPanel then self.characterPanel:update(dt) end
local cid = Tree.level.selector:selected() local cid = Tree.level.selector:selected()
if cid then if cid then
self.characterPanel = CPanel(cid) self.skillRow = SkillRow(cid)
self.characterPanel:show() self.skillRow:show()
self.characterPanel:update(dt)
elseif Tree.level.selector:deselected() then elseif Tree.level.selector:deselected() then
self.characterPanel:hide() self.skillRow:hide()
end end
if self.skillRow then self.skillRow:update(dt) end
end end
function layout:draw() function layout:draw()
if self.characterPanel then self.characterPanel:draw() end if self.skillRow then self.skillRow:draw() end
end end
return layout return layout

View File

@ -1,2 +0,0 @@
local UI_SCALE = 0.75 -- выдуманное значение для dependency injection, надо подбирать так, чтобы UI_SCALE * 64 было целым числом
return UI_SCALE

View File

@ -1,19 +1,18 @@
local icons = require("lib.utils.sprite_atlas").load(Tree.assets.files.dev_icons) 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 Element = require "lib.simple_ui.element"
local Rect = require "lib.simple_ui.rect" local Rect = require "lib.simple_ui.rect"
local UI_SCALE = require "lib.simple_ui.level.scale"
--- @class SkillButton : UIElement --- @class SkillButton : UIElement
--- @field hovered boolean --- @field hovered boolean
--- @field selected boolean --- @field selected boolean
--- @field onClick function? --- @field onClick function?
--- @field icon? string --- @field icon string
local skillButton = setmetatable({}, Element) local skillButton = setmetatable({}, Element)
skillButton.__index = skillButton skillButton.__index = skillButton
function skillButton:update(dt) function skillButton:update(dt)
if not self.icon then return end
local mx, my = love.mouse.getPosition() local mx, my = love.mouse.getPosition()
if self:hitTest(mx, my) then if self:hitTest(mx, my) then
self.hovered = true self.hovered = true
@ -27,33 +26,22 @@ function skillButton:update(dt)
end end
function skillButton:draw() function skillButton:draw()
love.graphics.setLineWidth(2)
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
local quad = icons:pickQuad(self.icon)
love.graphics.push() love.graphics.push()
love.graphics.translate(self.bounds.x, self.bounds.y) love.graphics.applyTransform(self.transform)
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") local r, g, b, a = love.graphics.getColor()
if self.selected then if self.selected then
love.graphics.setColor(0.3, 1, 0.3, 0.5) love.graphics.setColor(0.3, 1, 0.3, a)
love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height)
elseif self.hovered then elseif self.hovered then
love.graphics.setColor(0.7, 1, 0.7, 0.5) love.graphics.setColor(0.7, 1, 0.7, a)
love.graphics.rectangle("fill", self.bounds.x, self.bounds.y, self.bounds.width, self.bounds.height) else
love.graphics.setColor(1, 1, 1, a)
end end
love.graphics.setColor(1, 1, 1)
love.graphics.translate(0, self.bounds.y)
love.graphics.draw(icons.atlas, icons:pickQuad(self.icon))
love.graphics.pop()
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -61,6 +49,8 @@ end
--- @class SkillRow : UIElement --- @class SkillRow : UIElement
--- @field characterId Id --- @field characterId Id
--- @field selected SkillButton? --- @field selected SkillButton?
--- @field animationNode AnimationNode
--- @field state "show" | "idle" | "hide"
--- @field children SkillButton[] --- @field children SkillButton[]
local skillRow = setmetatable({}, Element) local skillRow = setmetatable({}, Element)
skillRow.__index = skillRow skillRow.__index = skillRow
@ -70,6 +60,7 @@ skillRow.__index = skillRow
function skillRow.new(characterId) function skillRow.new(characterId)
local t = { local t = {
characterId = characterId, characterId = characterId,
state = "show",
children = {} children = {}
} }
@ -96,89 +87,75 @@ function skillRow.new(characterId)
end end
end) end)
for i = #t.children + 1, 7, 1 do
t.children[i] = skillButton:new {}
end
return t return t
end end
--- @type love.Canvas function skillRow:show()
local c; 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
function skillRow:update(dt) function skillRow:update(dt)
local iconSize = math.floor(64 * UI_SCALE) if self.animationNode then self.animationNode:update(dt) end
local iconSize = icons.tileSize
local scale = (64 / iconSize)
local screenW, screenH = love.graphics.getDimensions() local screenW, screenH = love.graphics.getDimensions()
local padding, margin = 8, 4 local padding = 8
local count = #self.children -- слоты под скиллы local count = #self.children
self.bounds = Rect { self.bounds = Rect {
width = iconSize * count + (count + 1) * margin, width = count * icons.tileSize + (count - 1) * padding,
height = iconSize + 2 * margin, height = iconSize,
y = self.state == "show" and 10 * (1 - self.animationNode:getValue()) or 0
} }
self.bounds.y = screenH - self.bounds.height - padding -- отступ снизу self.transform = love.math.newTransform():translate(screenW / 2,
self.bounds.x = screenW / 2 - self.bounds.width / 2 screenH - 16):scale(scale, scale):translate(-self.bounds.width / 2, -iconSize)
for i, skb in ipairs(self.children) do for i, skb in ipairs(self.children) do
skb.bounds = Rect { x = self.bounds.x + margin + (i - 1) * (iconSize + margin), -- друг за другом, включая первый отступ от границы skb.bounds = Rect { height = iconSize, width = iconSize }
y = self.bounds.y + margin, height = iconSize, width = iconSize } skb.transform = self.transform:clone():translate(self.bounds.x + (i - 1) * iconSize +
(i - 1) *
padding, -- левый край ряда + размер предыдущих иконок + размер предыдущих отступов
self.bounds.y -- высота не меняется
)
skb:update(dt) skb:update(dt)
end end
if not c then
c = love.graphics.newCanvas(self.bounds.width, self.bounds.height)
end
end end
function skillRow:draw() function skillRow:draw()
love.graphics.setCanvas({ c, stencil = true }) local alpha = 1
love.graphics.clear() if self.state == "show" then
love.graphics.setColor(1, 1, 1) alpha = self.animationNode:getValue()
elseif self.state == "hide" then
do alpha = 1 - self.animationNode:getValue()
--- рисуем в локальных координатах текстурки end
love.graphics.push() love.graphics.setColor(1, 1, 1, alpha)
love.graphics.translate(-self.bounds.x, -self.bounds.y) for _, skb in ipairs(self.children) do
skb:draw()
-- сначала иконки скиллов
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 end
love.graphics.setColor(1, 1, 1)
end end
return skillRow.new return skillRow.new

View File

@ -18,7 +18,7 @@ function rect.new(table)
end end
function rect:hasPoint(x, y) function rect:hasPoint(x, y)
return x >= self.x and x < self.x + self.width and y >= self.y and y < self.y + self.height return x >= self.x and x < self.width and y >= self.y and y < self.height
end end
return rect.new return rect.new

View File

@ -124,8 +124,6 @@ function attack:cast(caster, target)
local targetSprite = targetCharacter:has(Tree.behaviors.sprite) local targetSprite = targetCharacter:has(Tree.behaviors.sprite)
if not sprite or not targetSprite then return true end if not sprite or not targetSprite then return true end
caster:try(Tree.behaviors.map, function(map) map:lookAt(target) end)
AnimationNode { AnimationNode {
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end, onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end,
children = { children = {

View File

@ -6,7 +6,6 @@
Tree = { Tree = {
assets = (require "lib.utils.asset_bundle"):load() 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.panning = require "lib/panning"
Tree.controls = require "lib.controls" Tree.controls = require "lib.controls"
Tree.level = (require "lib.level.level").new("procedural", "flower_plains") -- для теста у нас только один уровень, который сразу же загружен Tree.level = (require "lib.level.level").new("procedural", "flower_plains") -- для теста у нас только один уровень, который сразу же загружен

View File

@ -51,7 +51,7 @@ function AssetBundle.loadFile(path)
elseif (ext == "lua") then elseif (ext == "lua") then
return require(string.gsub(path, ".lua", "")) return require(string.gsub(path, ".lua", ""))
end end
return filedata return nil
end end
function AssetBundle.cutExtension(filename) function AssetBundle.cutExtension(filename)

View File

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

View File

@ -18,18 +18,28 @@ function love.load()
end end
Tree.level.turnOrder:endRound() Tree.level.turnOrder:endRound()
print("Now playing:", Tree.level.turnOrder.current) print("Now playing:", Tree.level.turnOrder.current)
love.window.setMode(1280, 720, { resizable = true, msaa = 4, vsync = true }) love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true })
end end
local lt = "0" local lt = "0"
function love.update(dt) function love.update(dt)
local t1 = love.timer.getTime() local t1 = love.timer.getTime()
Tree.controls:poll() Tree.controls:poll()
Tree.level.camera:update(dt) -- сначала логика камеры, потому что на нее завязан UI testLayout:update(dt) -- логика UI-слоя должна отработать раньше всех, потому что нужно перехватить жесты и не пустить их дальше
testLayout:update(dt) -- потом UI, потому что нужно перехватить жесты и не пустить их дальше
Tree.panning:update(dt) Tree.panning:update(dt)
Tree.level: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() Tree.controls:cache()
local t2 = love.timer.getTime() local t2 = love.timer.getTime()
@ -64,10 +74,7 @@ function love.draw()
testLayout:draw() testLayout:draw()
love.graphics.setColor(1, 1, 1) love.graphics.setColor(1, 1, 1)
love.graphics.setFont(Tree.fonts:getTheme("Roboto_Mono"):getVariant("medium")) local stats = "fps: " .. love.timer.getFPS() .. " lt: " .. lt .. " dt: " .. dt
local stats = "fps: " ..
love.timer.getFPS() ..
" lt: " .. lt .. " dt: " .. dt .. " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000)
love.graphics.print(stats, 10, 10) love.graphics.print(stats, 10, 10)
local t2 = love.timer.getTime() local t2 = love.timer.getTime()