Compare commits
No commits in common. "main" and "feature/animateTo" have entirely different histories.
main
...
feature/an
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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.
@ -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.
Binary file not shown.
|
Before Width: | Height: | Size: 572 B |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB |
@ -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);
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
extern vec2 direction; // (1.0, 0.0) для X, (0.0, 1.0) для Y
|
|
||||||
extern number radius; // радиус размытия
|
|
||||||
|
|
||||||
vec4 effect(vec4 vcolor, Image tex, vec2 texture_coords, vec2 screen_coords)
|
|
||||||
{
|
|
||||||
vec4 sum = vec4(0.0);
|
|
||||||
float weightTotal = 0.0;
|
|
||||||
|
|
||||||
for (int i = -10; i <= 10; i++) {
|
|
||||||
float offset = float(i);
|
|
||||||
float weight = exp(-offset * offset / (2.0 * radius * radius));
|
|
||||||
vec2 shift = direction * offset / love_ScreenSize.xy;
|
|
||||||
sum += Texel(tex, texture_coords + shift) * weight;
|
|
||||||
weightTotal += weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum / weightTotal;
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
extern vec3 color;
|
|
||||||
extern number time;
|
|
||||||
|
|
||||||
vec4 effect(vec4 vcolor, Image tex, vec2 texture_coords, vec2 screen_coords)
|
|
||||||
{
|
|
||||||
vec4 texColor = Texel(tex, texture_coords);
|
|
||||||
|
|
||||||
float mask = texColor.r;
|
|
||||||
|
|
||||||
vec2 uv = texture_coords - 0.5;
|
|
||||||
float dist = length(uv * 2.0);
|
|
||||||
|
|
||||||
float t = time;
|
|
||||||
|
|
||||||
float wave = sin((uv.x + uv.y) * 6.0 + t * 1.5) * 0.03;
|
|
||||||
float ripple = sin(length(uv) * 20.0 - t * 2.0) * 0.02;
|
|
||||||
float flicker = sin(t * 2.5) * 0.02;
|
|
||||||
|
|
||||||
dist += wave + ripple + flicker;
|
|
||||||
|
|
||||||
float intensity = 1.0 - smoothstep(0.0, 1.0, dist);
|
|
||||||
intensity = pow(intensity, 2.0);
|
|
||||||
|
|
||||||
float colorShift = sin(t * 3.0) * 0.1;
|
|
||||||
vec3 flickerColor = color + vec3(colorShift, colorShift * 0.5, -colorShift * 0.3);
|
|
||||||
|
|
||||||
vec3 finalColor = flickerColor * intensity * mask;
|
|
||||||
|
|
||||||
return vec4(finalColor, mask * intensity);
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
extern Image scene;
|
|
||||||
extern Image light;
|
|
||||||
|
|
||||||
extern vec3 ambient;
|
|
||||||
|
|
||||||
vec4 effect(vec4 vcolor, Image unused, vec2 uv, vec2 px)
|
|
||||||
{
|
|
||||||
vec4 s = Texel(scene, uv);
|
|
||||||
vec3 l = Texel(light, uv).rgb;
|
|
||||||
|
|
||||||
l = clamp(l, 0.0, 1.0);
|
|
||||||
vec3 a = clamp(ambient, 0.0, 1.0);
|
|
||||||
|
|
||||||
// Канальный множитель: от ambient до 1 в зависимости от света
|
|
||||||
vec3 m = a + (vec3(1.0) - a) * l;
|
|
||||||
|
|
||||||
vec3 rgb = s.rgb * m;
|
|
||||||
|
|
||||||
return vec4(rgb, s.a);
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
local easing = require "lib.utils.easing"
|
local easing = require "lib.utils.easing"
|
||||||
|
|
||||||
|
--- @alias voidCallback fun(): nil
|
||||||
--- @alias animationRunner fun(node: AnimationNode)
|
--- @alias animationRunner fun(node: AnimationNode)
|
||||||
|
|
||||||
--- Узел дерева анимаций.
|
--- Узел дерева анимаций.
|
||||||
@ -22,7 +23,6 @@ local easing = require "lib.utils.easing"
|
|||||||
--- }
|
--- }
|
||||||
--- }:run()
|
--- }:run()
|
||||||
--- ```
|
--- ```
|
||||||
--- @deprecated
|
|
||||||
--- @class AnimationNode
|
--- @class AnimationNode
|
||||||
--- @field count integer
|
--- @field count integer
|
||||||
--- @field run animationRunner
|
--- @field run animationRunner
|
||||||
@ -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 -- в знаменателе продолжительность анимации в секундах
|
||||||
@ -73,7 +73,6 @@ function animation:update(dt)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @deprecated
|
|
||||||
--- @param data {[1]: animationRunner?, onEnd?: voidCallback, duration: number?, easing: ease?, children?: AnimationNode[]}
|
--- @param data {[1]: animationRunner?, onEnd?: voidCallback, duration: number?, easing: ease?, children?: AnimationNode[]}
|
||||||
--- @return AnimationNode
|
--- @return AnimationNode
|
||||||
local function new(data)
|
local function new(data)
|
||||||
@ -83,15 +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()
|
||||||
if t.state ~= "running" then return end
|
if t.finished then return end
|
||||||
t.state = "waiting"
|
|
||||||
t:bubbleUp()
|
t:bubbleUp()
|
||||||
for _, anim in ipairs(t.children) do
|
for _, anim in ipairs(t.children) do
|
||||||
anim:run()
|
anim:run()
|
||||||
|
|||||||
@ -1,13 +1,6 @@
|
|||||||
--- @meta _
|
--- @meta _
|
||||||
|
Tree.behaviors.map = require "lib.character.behaviors.map"
|
||||||
Tree.behaviors.spellcaster = require "lib.character.behaviors.spellcaster"
|
Tree.behaviors.spellcaster = require "lib.character.behaviors.spellcaster"
|
||||||
Tree.behaviors.sprite = require "lib.character.behaviors.sprite"
|
Tree.behaviors.sprite = require "lib.character.behaviors.sprite"
|
||||||
Tree.behaviors.stats = require "lib.character.behaviors.stats"
|
Tree.behaviors.stats = require "lib.character.behaviors.stats"
|
||||||
Tree.behaviors.residentsleeper = require "lib.character.behaviors.residentsleeper"
|
Tree.behaviors.residentsleeper = require "lib.character.behaviors.residentsleeper"
|
||||||
Tree.behaviors.shadowcaster = require "lib.character.behaviors.shadowcaster"
|
|
||||||
Tree.behaviors.light = require "character.behaviors.light"
|
|
||||||
Tree.behaviors.positioned = require "character.behaviors.positioned"
|
|
||||||
Tree.behaviors.tiled = require "character.behaviors.tiled"
|
|
||||||
Tree.behaviors.cursor = require "character.behaviors.cursor"
|
|
||||||
Tree.behaviors.ai = require "lib.character.behaviors.ai"
|
|
||||||
|
|
||||||
--- @alias voidCallback fun(): nil
|
|
||||||
|
|||||||
@ -1,91 +0,0 @@
|
|||||||
local ease = require "lib.utils.easing"
|
|
||||||
local AnimationNode = require "lib.animation_node"
|
|
||||||
|
|
||||||
local EFFECTS_SUPPORTED = love.audio.isEffectsSupported()
|
|
||||||
|
|
||||||
--- @alias SourceFilter { type: "bandpass"|"highpass"|"lowpass", volume: number, highgain: number, lowgain: number }
|
|
||||||
|
|
||||||
--- @class Audio
|
|
||||||
--- @field musicVolume number
|
|
||||||
--- @field soundVolume number
|
|
||||||
--- @field looped boolean
|
|
||||||
--- @field animationNode AnimationNode?
|
|
||||||
--- @field from love.Source?
|
|
||||||
--- @field to love.Source?
|
|
||||||
audio = {}
|
|
||||||
audio.__index = audio
|
|
||||||
|
|
||||||
--- здесь мы должны выгружать значения из файлика с сохранением настроек
|
|
||||||
local function new(musicVolume, soundVolume)
|
|
||||||
return setmetatable({
|
|
||||||
musicVolume = musicVolume,
|
|
||||||
soundVolume = soundVolume,
|
|
||||||
looped = true
|
|
||||||
}, audio)
|
|
||||||
end
|
|
||||||
|
|
||||||
function audio:update(dt)
|
|
||||||
if not self.animationNode then return end
|
|
||||||
self.from:setVolume(self.musicVolume - self.animationNode:getValue() * self.musicVolume)
|
|
||||||
self.to:setVolume(self.animationNode:getValue() * self.musicVolume)
|
|
||||||
self.animationNode:update(dt)
|
|
||||||
-- print(self.animationNode.t)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- if from is nil, than we have fade in to;
|
|
||||||
--- if to is nil, than we have fade out from
|
|
||||||
---
|
|
||||||
--- also we should guarantee, that from and to have the same volume
|
|
||||||
--- @param from love.Source
|
|
||||||
--- @param to love.Source
|
|
||||||
--- @param ms number? in milliseconds
|
|
||||||
function audio:crossfade(from, to, ms)
|
|
||||||
print("[Audio]: Triggered crossfade")
|
|
||||||
self:play(to)
|
|
||||||
to:setVolume(0)
|
|
||||||
self.from = from
|
|
||||||
self.to = to
|
|
||||||
self.animationNode = AnimationNode {
|
|
||||||
function(node) end,
|
|
||||||
onEnd = function()
|
|
||||||
self.from:setVolume(0)
|
|
||||||
self.to:setVolume(self.musicVolume)
|
|
||||||
self.from:stop()
|
|
||||||
self.animationNode = nil
|
|
||||||
print("[Audio]: Crossfade done")
|
|
||||||
end,
|
|
||||||
duration = ms or 1000,
|
|
||||||
easing = ease.easeOutCubic,
|
|
||||||
}
|
|
||||||
self.animationNode:run()
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param source love.Source
|
|
||||||
--- @param settings SourceFilter?
|
|
||||||
--- @param effectName string?
|
|
||||||
function audio:play(source, settings, effectName)
|
|
||||||
if source:getType() == "stream" then
|
|
||||||
source:setLooping(self.looped)
|
|
||||||
source:setVolume(self.musicVolume)
|
|
||||||
source:play()
|
|
||||||
else
|
|
||||||
source:setVolume(self.soundVolume)
|
|
||||||
source:play()
|
|
||||||
end
|
|
||||||
if settings and EFFECTS_SUPPORTED then
|
|
||||||
source.setFilter(source, settings)
|
|
||||||
end
|
|
||||||
if effectName and EFFECTS_SUPPORTED then
|
|
||||||
source:setEffect(effectName, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function audio:setMusicVolume(volume)
|
|
||||||
self.musicVolume = volume
|
|
||||||
end
|
|
||||||
|
|
||||||
function audio:setSoundVolume(volume)
|
|
||||||
self.soundVolume = volume
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
local AnimationNode = require "lib.animation_node"
|
|
||||||
local easing = require "lib.utils.easing"
|
|
||||||
|
|
||||||
local function closestCharacter(char)
|
|
||||||
local caster = Vec3 {}
|
|
||||||
char:try(Tree.behaviors.positioned, function(b)
|
|
||||||
caster = b.position
|
|
||||||
end)
|
|
||||||
local charTarget
|
|
||||||
local minDist = 88005553535 -- spooky magic number
|
|
||||||
for k, v in pairs(Tree.level.characters) do
|
|
||||||
v:try(Tree.behaviors.positioned, function(b)
|
|
||||||
local dist = ((caster.x - b.position.x) ^ 2 + (caster.y - b.position.y) ^ 2) ^ 0.5
|
|
||||||
if dist < minDist and dist ~= 0 then
|
|
||||||
minDist = dist
|
|
||||||
charTarget = v
|
|
||||||
end
|
|
||||||
-- print(k, b.position)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
return charTarget
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @class AIBehavior : Behavior
|
|
||||||
--- @field animationNode AnimationNode?
|
|
||||||
--- @field target Vec3?
|
|
||||||
local behavior = {}
|
|
||||||
behavior.__index = behavior
|
|
||||||
behavior.id = "ai"
|
|
||||||
|
|
||||||
function behavior.new()
|
|
||||||
return setmetatable({}, behavior)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @return Task<nil>
|
|
||||||
function behavior:makeTurn()
|
|
||||||
return function(callback) -- почему так, описано в Task
|
|
||||||
self.owner:try(Tree.behaviors.spellcaster, function(spellB)
|
|
||||||
local charTarget = closestCharacter(self.owner)
|
|
||||||
charTarget:try(Tree.behaviors.positioned, function(b)
|
|
||||||
self.target = Vec3 { b.position.x, b.position.y + 1 } --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти
|
|
||||||
end)
|
|
||||||
|
|
||||||
spellB.spellbook[1]:cast(self.owner, self.target)(function()
|
|
||||||
-- здесь мы оказываемся после того, как сходили в первый раз
|
|
||||||
print("[AI]: finished move 1")
|
|
||||||
local newTarget = Vec3 { 1, 1 }
|
|
||||||
-- поэтому позиция персонажа для нового каста пересчитается динамически
|
|
||||||
spellB.spellbook[1]:cast(self.owner, newTarget)(function()
|
|
||||||
print("[AI]: finished move 2")
|
|
||||||
-- дергаем функцию после завершения хода
|
|
||||||
callback()
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return behavior
|
|
||||||
@ -11,11 +11,6 @@ behavior.id = "behavior"
|
|||||||
|
|
||||||
function behavior.new() return setmetatable({}, behavior) end
|
function behavior.new() return setmetatable({}, behavior) end
|
||||||
|
|
||||||
--- это деструктор с крутым названием
|
|
||||||
function behavior:die()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function behavior:update(dt) end
|
function behavior:update(dt) end
|
||||||
|
|
||||||
function behavior:draw() end
|
function behavior:draw() end
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
--- Добавляет следование за курсором мыши
|
|
||||||
--- @class CursorBehavior : Behavior
|
|
||||||
local behavior = {}
|
|
||||||
behavior.__index = behavior
|
|
||||||
behavior.id = "cursor"
|
|
||||||
|
|
||||||
---@return CursorBehavior
|
|
||||||
function behavior.new()
|
|
||||||
return setmetatable({}, behavior)
|
|
||||||
end
|
|
||||||
|
|
||||||
function behavior:update()
|
|
||||||
self.owner:try(Tree.behaviors.positioned, function(b)
|
|
||||||
local mx, my = love.mouse.getX(), love.mouse.getY()
|
|
||||||
b.position = Tree.level.camera:toWorldPosition(Vec3 { mx, my })
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return behavior
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
local AnimationNode = require "lib.animation_node"
|
|
||||||
local easing = require "lib.utils.easing"
|
|
||||||
|
|
||||||
--- @class LightBehavior : Behavior
|
|
||||||
--- @field intensity number
|
|
||||||
--- @field color Vec3
|
|
||||||
--- @field seed integer
|
|
||||||
--- @field colorAnimationNode? AnimationNode
|
|
||||||
--- @field private animateColorCallback? fun(): nil
|
|
||||||
--- @field targetColor? Vec3
|
|
||||||
--- @field sourceColor? Vec3
|
|
||||||
local behavior = {}
|
|
||||||
behavior.__index = behavior
|
|
||||||
behavior.id = "light"
|
|
||||||
|
|
||||||
---@param values {intensity: number?, color: Vec3?, seed: integer?}
|
|
||||||
---@return LightBehavior
|
|
||||||
function behavior.new(values)
|
|
||||||
return setmetatable({
|
|
||||||
intensity = values.intensity or 1,
|
|
||||||
color = values.color or Vec3 { 1, 1, 1 },
|
|
||||||
seed = values.seed or math.random(math.pow(2, 16))
|
|
||||||
}, behavior)
|
|
||||||
end
|
|
||||||
|
|
||||||
function behavior:update(dt)
|
|
||||||
if not self.colorAnimationNode then return end
|
|
||||||
local delta = self.targetColor - self.sourceColor
|
|
||||||
self.color = self.sourceColor + delta * self.colorAnimationNode:getValue()
|
|
||||||
self.colorAnimationNode:update(dt)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @TODO: refactor
|
|
||||||
function behavior:animateColor(targetColor)
|
|
||||||
if self.colorAnimationNode then self.colorAnimationNode:finish() end
|
|
||||||
self.colorAnimationNode = AnimationNode {
|
|
||||||
function(_) end,
|
|
||||||
easing = easing.easeInQuad,
|
|
||||||
duration = 800,
|
|
||||||
onEnd = function()
|
|
||||||
if self.animateColorCallback then self.animateColorCallback() end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
self.colorAnimationNode:run()
|
|
||||||
self.sourceColor = self.color
|
|
||||||
self.targetColor = targetColor
|
|
||||||
|
|
||||||
return function(callback)
|
|
||||||
self.animateColorCallback = callback
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function behavior:draw()
|
|
||||||
local positioned = self.owner:has(Tree.behaviors.positioned)
|
|
||||||
if not positioned then return end
|
|
||||||
|
|
||||||
Tree.level.camera:attach()
|
|
||||||
love.graphics.setCanvas(Tree.level.render.textures.lightLayer)
|
|
||||||
local shader = Tree.assets.files.shaders.light
|
|
||||||
shader:send("color", { self.color.x, self.color.y, self.color.z })
|
|
||||||
shader:send("time", love.timer.getTime() + self.seed)
|
|
||||||
love.graphics.setShader(shader)
|
|
||||||
love.graphics.draw(Tree.assets.files.masks.circle128, positioned.position.x - self.intensity / 2,
|
|
||||||
positioned.position.y - self.intensity / 2, 0, self.intensity / 128,
|
|
||||||
self.intensity / 128)
|
|
||||||
|
|
||||||
love.graphics.setBlendMode("alpha")
|
|
||||||
|
|
||||||
love.graphics.setShader()
|
|
||||||
love.graphics.setCanvas()
|
|
||||||
Tree.level.camera:detach()
|
|
||||||
end
|
|
||||||
|
|
||||||
return behavior
|
|
||||||
80
lib/character/behaviors/map.lua
Normal file
80
lib/character/behaviors/map.lua
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
local utils = require "lib.utils.utils"
|
||||||
|
|
||||||
|
--- Отвечает за размещение и перемещение по локации
|
||||||
|
--- @class MapBehavior : Behavior
|
||||||
|
--- @field position Vec3
|
||||||
|
--- @field runTarget Vec3 точка, в которую в данный момент бежит персонаж
|
||||||
|
--- @field displayedPosition Vec3 точка, в которой персонаж отображается
|
||||||
|
--- @field t0 number время начала движения для анимациии
|
||||||
|
--- @field path Deque путь, по которому сейчас бежит персонаж
|
||||||
|
--- @field animationNode? AnimationNode AnimationNode, с которым связана анимация перемещения
|
||||||
|
--- @field size Vec3
|
||||||
|
local mapBehavior = {}
|
||||||
|
mapBehavior.__index = mapBehavior
|
||||||
|
mapBehavior.id = "map"
|
||||||
|
|
||||||
|
|
||||||
|
--- @param position? Vec3
|
||||||
|
--- @param size? Vec3
|
||||||
|
function mapBehavior.new(position, size)
|
||||||
|
return setmetatable({
|
||||||
|
position = position or Vec3({}),
|
||||||
|
displayedPosition = position or Vec3({}),
|
||||||
|
size = size or Vec3({ 1, 1 }),
|
||||||
|
}, mapBehavior)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param path Deque
|
||||||
|
--- @param animationNode AnimationNode
|
||||||
|
function mapBehavior:followPath(path, animationNode)
|
||||||
|
if path:is_empty() then return animationNode:finish() end
|
||||||
|
self.animationNode = animationNode
|
||||||
|
self.position = self.displayedPosition
|
||||||
|
self.owner:try(Tree.behaviors.sprite, function(sprite)
|
||||||
|
sprite:loop("run")
|
||||||
|
end)
|
||||||
|
self.path = path;
|
||||||
|
---@type Vec3
|
||||||
|
local nextCell = path:peek_front()
|
||||||
|
self:runTo(nextCell)
|
||||||
|
path:pop_front()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param target Vec3
|
||||||
|
function mapBehavior:runTo(target)
|
||||||
|
self.t0 = love.timer.getTime()
|
||||||
|
self.runTarget = target
|
||||||
|
self.owner:try(Tree.behaviors.sprite,
|
||||||
|
function(sprite)
|
||||||
|
if target.x < self.position.x then
|
||||||
|
sprite.side = Tree.behaviors.sprite.LEFT
|
||||||
|
elseif target.x > self.position.x then
|
||||||
|
sprite.side = Tree.behaviors.sprite.RIGHT
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mapBehavior:update(dt)
|
||||||
|
if self.runTarget then
|
||||||
|
local delta = love.timer.getTime() - self.t0 or love.timer.getTime()
|
||||||
|
local fraction = delta /
|
||||||
|
(0.5 * self.runTarget:subtract(self.position):length()) -- бежим одну клетку за 500 мс, по диагонали больше
|
||||||
|
if fraction >= 1 then -- анимация перемещена завершена
|
||||||
|
self.position = self.runTarget
|
||||||
|
if not self.path:is_empty() then -- еще есть, куда бежать
|
||||||
|
self:runTo(self.path:pop_front())
|
||||||
|
else -- мы добежали до финальной цели
|
||||||
|
self.owner:try(Tree.behaviors.sprite, function(sprite)
|
||||||
|
sprite:loop("idle")
|
||||||
|
end)
|
||||||
|
self.runTarget = nil
|
||||||
|
if self.animationNode then self.animationNode:finish() end
|
||||||
|
end
|
||||||
|
else -- анимация перемещения не завершена
|
||||||
|
self.displayedPosition = utils.lerp(self.position, self.runTarget, fraction) -- линейный интерполятор
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return mapBehavior
|
||||||
@ -1,25 +0,0 @@
|
|||||||
--- Отвечает за размещение на уровне
|
|
||||||
--- @class PositionedBehavior : Behavior
|
|
||||||
--- @field position Vec3
|
|
||||||
local behavior = {}
|
|
||||||
behavior.__index = behavior
|
|
||||||
behavior.id = "positioned"
|
|
||||||
|
|
||||||
--- @param position? Vec3
|
|
||||||
function behavior.new(position)
|
|
||||||
return setmetatable({
|
|
||||||
position = position or Vec3({}),
|
|
||||||
}, behavior)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param position Vec3
|
|
||||||
function behavior:lookAt(position)
|
|
||||||
self.owner:try(Tree.behaviors.sprite,
|
|
||||||
function(sprite)
|
|
||||||
if position.x > self.position.x then sprite.side = sprite.RIGHT end
|
|
||||||
if position.x < self.position.x then sprite.side = sprite.LEFT end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
return behavior
|
|
||||||
@ -1,37 +1,28 @@
|
|||||||
--- Умеет асинхронно ждать какое-то время (для анимаций)
|
--- Умеет асинхронно ждать какое-то время (для анимаций)
|
||||||
--- @class ResidentSleeperBehavior : Behavior
|
--- @class ResidentSleeperBehavior : Behavior
|
||||||
--- @field private t0 number?
|
--- @field animationNode? AnimationNode
|
||||||
--- @field private sleepTime number?
|
--- @field endsAt? number
|
||||||
--- @field private callback voidCallback?
|
|
||||||
--- @field private state 'running' | 'finished'
|
|
||||||
local behavior = {}
|
local behavior = {}
|
||||||
behavior.__index = behavior
|
behavior.__index = behavior
|
||||||
behavior.id = "residentsleeper"
|
behavior.id = "residentsleeper"
|
||||||
|
|
||||||
function behavior.new() return setmetatable({}, behavior) end
|
function behavior.new() return setmetatable({}, behavior) end
|
||||||
|
|
||||||
function behavior:update(_)
|
function behavior:update(dt)
|
||||||
if self.state ~= 'running' then return end
|
if not self.animationNode then return end
|
||||||
|
if love.timer.getTime() >= self.endsAt then
|
||||||
local t = love.timer.getTime()
|
self.animationNode:finish()
|
||||||
if t >= self.t0 + self.sleepTime then
|
self.animationNode = nil
|
||||||
self.state = 'finished'
|
self.endsAt = nil
|
||||||
self.callback()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @return Task<nil>
|
--- @param ms number time to wait in milliseconds
|
||||||
function behavior:sleep(ms)
|
--- @param node AnimationNode
|
||||||
self.sleepTime = ms / 1000
|
function behavior:sleep(ms, node)
|
||||||
return function(callback)
|
if self.animationNode then node:finish() end
|
||||||
if self.state == 'running' then
|
self.animationNode = node
|
||||||
self.callback()
|
self.endsAt = love.timer.getTime() + ms / 1000
|
||||||
end
|
|
||||||
|
|
||||||
self.t0 = love.timer.getTime()
|
|
||||||
self.callback = callback
|
|
||||||
self.state = 'running'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return behavior
|
return behavior
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
local easing = require "lib.utils.easing"
|
|
||||||
--- @class ShadowcasterBehavior : Behavior
|
|
||||||
local behavior = {}
|
|
||||||
behavior.id = "shadowcaster"
|
|
||||||
behavior.__index = behavior
|
|
||||||
|
|
||||||
function behavior.new() return setmetatable({}, behavior) end
|
|
||||||
|
|
||||||
function behavior:draw()
|
|
||||||
local sprite = self.owner:has(Tree.behaviors.sprite)
|
|
||||||
local positioned = self.owner:has(Tree.behaviors.positioned)
|
|
||||||
if not positioned then return end
|
|
||||||
|
|
||||||
local ppm = Tree.level.camera.pixelsPerMeter
|
|
||||||
local position = positioned.position + Vec3 { 0.5, 0.5 }
|
|
||||||
|
|
||||||
local lightIds = Tree.level.lightGrid:query(position, 5)
|
|
||||||
--- @type Character[]
|
|
||||||
local lights = {}
|
|
||||||
for _, id in ipairs(lightIds) do
|
|
||||||
table.insert(lights, Tree.level.characters[id])
|
|
||||||
end
|
|
||||||
|
|
||||||
Tree.level.camera:attach()
|
|
||||||
love.graphics.setCanvas(Tree.level.render.textures.shadowLayer)
|
|
||||||
love.graphics.push()
|
|
||||||
love.graphics.setColor(0, 0, 0, 1)
|
|
||||||
love.graphics.translate(position.x, position.y)
|
|
||||||
love.graphics.ellipse("fill", 0, 0, 0.2, 0.2 * math.cos(math.pi / 4))
|
|
||||||
love.graphics.pop()
|
|
||||||
|
|
||||||
if not sprite then
|
|
||||||
love.graphics.setCanvas()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
love.graphics.setCanvas(Tree.level.render.textures.spriteLightLayer)
|
|
||||||
love.graphics.setBlendMode("add")
|
|
||||||
for _, light in ipairs(lights) do
|
|
||||||
local lightPos = light:has(Tree.behaviors.positioned).position
|
|
||||||
local lightVec = lightPos - position
|
|
||||||
|
|
||||||
local lightColor = light:has(Tree.behaviors.light).color
|
|
||||||
if lightPos.y > position.y then
|
|
||||||
love.graphics.setColor(lightColor.x, lightColor.y, lightColor.z,
|
|
||||||
1 - 0.3 * lightVec:length())
|
|
||||||
elseif position.y - lightPos.y < 3 then
|
|
||||||
love.graphics.setColor(lightColor.x, lightColor.y, lightColor.z,
|
|
||||||
(1 - easing.easeInSine((position.y - lightPos.y))) - 0.3 * lightVec:length())
|
|
||||||
end
|
|
||||||
|
|
||||||
sprite.animationTable[sprite.state]:draw(Tree.assets.files.sprites.character[sprite.state],
|
|
||||||
position.x,
|
|
||||||
position.y, nil, 1 / ppm * sprite.side, 1 / ppm, 38, 47)
|
|
||||||
end
|
|
||||||
love.graphics.setBlendMode("alpha")
|
|
||||||
|
|
||||||
Tree.level.camera:detach()
|
|
||||||
love.graphics.setColor(1, 1, 1)
|
|
||||||
love.graphics.setCanvas()
|
|
||||||
end
|
|
||||||
|
|
||||||
return behavior
|
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -41,15 +41,10 @@ end
|
|||||||
function sprite:draw()
|
function sprite:draw()
|
||||||
if not self.animationTable[self.state] or not Tree.assets.files.sprites.character[self.state] then return end
|
if not self.animationTable[self.state] or not Tree.assets.files.sprites.character[self.state] then return end
|
||||||
|
|
||||||
self.owner:try(Tree.behaviors.positioned,
|
self.owner:try(Tree.behaviors.map,
|
||||||
function(pos)
|
function(map)
|
||||||
local ppm = Tree.level.camera.pixelsPerMeter
|
local ppm = Tree.level.camera.pixelsPerMeter
|
||||||
local position = pos.position + Vec3 { 0.5, 0.5 }
|
local position = map.displayedPosition
|
||||||
|
|
||||||
love.graphics.setCanvas(Tree.level.render.textures.spriteLayer)
|
|
||||||
Tree.level.camera:attach()
|
|
||||||
|
|
||||||
love.graphics.setColor(1, 1, 1)
|
|
||||||
if Tree.level.selector.id == self.owner.id then
|
if Tree.level.selector.id == self.owner.id then
|
||||||
local texW, texH = Tree.assets.files.sprites.character[self.state]:getWidth(),
|
local texW, texH = Tree.assets.files.sprites.character[self.state]:getWidth(),
|
||||||
Tree.assets.files.sprites.character[self.state]:getHeight()
|
Tree.assets.files.sprites.character[self.state]:getHeight()
|
||||||
@ -58,32 +53,28 @@ function sprite:draw()
|
|||||||
shader:send("time", love.timer:getTime())
|
shader:send("time", love.timer:getTime())
|
||||||
love.graphics.setShader(shader)
|
love.graphics.setShader(shader)
|
||||||
end
|
end
|
||||||
self.animationTable[self.state]:draw(Tree.assets.files.sprites.character[self.state],
|
|
||||||
position.x,
|
|
||||||
position.y, nil, 1 / ppm * self.side, 1 / ppm, 38, 47)
|
|
||||||
|
|
||||||
|
self.animationTable[self.state]:draw(Tree.assets.files.sprites.character[self.state],
|
||||||
|
position.x + 0.5,
|
||||||
|
position.y + 0.5, nil, 1 / ppm * self.side, 1 / ppm, 38, 47)
|
||||||
|
love.graphics.setColor(1, 1, 1)
|
||||||
love.graphics.setShader()
|
love.graphics.setShader()
|
||||||
Tree.level.camera:detach()
|
|
||||||
love.graphics.setCanvas()
|
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @return Task<nil>
|
--- @param node AnimationNode
|
||||||
function sprite:animate(state)
|
function sprite:animate(state, node)
|
||||||
return function(callback)
|
|
||||||
if not self.animationGrid[state] then
|
if not self.animationGrid[state] then
|
||||||
print("[SpriteBehavior]: no animation for '" .. state .. "'")
|
return print("[SpriteBehavior]: no animation for '" .. state .. "'")
|
||||||
callback()
|
|
||||||
end
|
end
|
||||||
self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED,
|
self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED,
|
||||||
function()
|
function()
|
||||||
self:loop("idle")
|
self:loop("idle")
|
||||||
callback()
|
node:finish()
|
||||||
end)
|
end)
|
||||||
self.state = state
|
self.state = state
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
function sprite:loop(state)
|
function sprite:loop(state)
|
||||||
if not self.animationGrid[state] then
|
if not self.animationGrid[state] then
|
||||||
|
|||||||
@ -1,88 +0,0 @@
|
|||||||
local utils = require "lib.utils.utils"
|
|
||||||
|
|
||||||
--- Отвечает за перемещение по тайлам
|
|
||||||
--- @class TiledBehavior : Behavior
|
|
||||||
--- @field private runSource? Vec3 точка, из которой бежит персонаж
|
|
||||||
--- @field private runTarget? Vec3 точка, в которую в данный момент бежит персонаж
|
|
||||||
--- @field private path? Deque путь, по которому сейчас бежит персонаж
|
|
||||||
--- @field private followPathCallback? fun()
|
|
||||||
--- @field private t0 number время начала движения
|
|
||||||
--- @field size Vec3
|
|
||||||
local behavior = {}
|
|
||||||
behavior.__index = behavior
|
|
||||||
behavior.id = "tiled"
|
|
||||||
|
|
||||||
--- @param size? Vec3
|
|
||||||
function behavior.new(size)
|
|
||||||
return setmetatable({
|
|
||||||
size = size or Vec3({ 1, 1 }),
|
|
||||||
}, behavior)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param path Deque
|
|
||||||
--- @return Task<nil>
|
|
||||||
function behavior:followPath(path)
|
|
||||||
self.owner:try(Tree.behaviors.sprite, function(sprite)
|
|
||||||
sprite:loop("run")
|
|
||||||
end)
|
|
||||||
self.path = path;
|
|
||||||
---@type Vec3
|
|
||||||
local nextCell = path:peek_front()
|
|
||||||
self:runTo(nextCell)
|
|
||||||
path:pop_front()
|
|
||||||
|
|
||||||
return function(callback)
|
|
||||||
self.followPathCallback = callback
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param target Vec3
|
|
||||||
function behavior:runTo(target)
|
|
||||||
local positioned = self.owner:has(Tree.behaviors.positioned)
|
|
||||||
if not positioned then return end
|
|
||||||
|
|
||||||
self.t0 = love.timer.getTime()
|
|
||||||
self.runTarget = target
|
|
||||||
|
|
||||||
self.runSource = positioned.position
|
|
||||||
|
|
||||||
self.owner:try(Tree.behaviors.sprite,
|
|
||||||
function(sprite)
|
|
||||||
if target.x < positioned.position.x then
|
|
||||||
sprite.side = Tree.behaviors.sprite.LEFT
|
|
||||||
elseif target.x > positioned.position.x then
|
|
||||||
sprite.side = Tree.behaviors.sprite.RIGHT
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
function behavior:update(dt)
|
|
||||||
if self.runTarget then
|
|
||||||
local positioned = self.owner:has(Tree.behaviors.positioned)
|
|
||||||
if not positioned then return end
|
|
||||||
|
|
||||||
local delta = love.timer.getTime() - self.t0 or love.timer.getTime()
|
|
||||||
local fraction = delta /
|
|
||||||
(0.5 * self.runTarget:subtract(self.runSource):length()) -- бежим одну клетку за 500 мс, по диагонали больше
|
|
||||||
if fraction >= 1 then -- анимация перемещена завершена
|
|
||||||
positioned.position = self.runTarget
|
|
||||||
if not self.path:is_empty() then -- еще есть, куда бежать
|
|
||||||
self:runTo(self.path:pop_front())
|
|
||||||
else -- мы добежали до финальной цели
|
|
||||||
self.owner:try(Tree.behaviors.sprite, function(sprite)
|
|
||||||
sprite:loop("idle")
|
|
||||||
end)
|
|
||||||
self.runTarget = nil
|
|
||||||
|
|
||||||
if self.followPathCallback then
|
|
||||||
self.followPathCallback()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else -- анимация перемещения не завершена
|
|
||||||
positioned.position = utils.lerp(self.runSource, self.runTarget, fraction) -- линейный интерполятор
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return behavior
|
|
||||||
@ -13,7 +13,11 @@ character.__index = character
|
|||||||
|
|
||||||
--- Создаёт персонажа, которым будет управлять или игрок или компьютер
|
--- Создаёт персонажа, которым будет управлять или игрок или компьютер
|
||||||
--- @param name string
|
--- @param name string
|
||||||
local function spawn(name)
|
--- @param spriteDir table
|
||||||
|
--- @param position? Vec3
|
||||||
|
--- @param size? Vec3
|
||||||
|
--- @param initiative? integer
|
||||||
|
local function spawn(name, spriteDir, position, size, initiative)
|
||||||
local char = {}
|
local char = {}
|
||||||
|
|
||||||
char = setmetatable(char, character)
|
char = setmetatable(char, character)
|
||||||
@ -22,6 +26,14 @@ local function spawn(name)
|
|||||||
char.behaviors = {}
|
char.behaviors = {}
|
||||||
char._behaviorsIdx = {}
|
char._behaviorsIdx = {}
|
||||||
|
|
||||||
|
char:addBehavior {
|
||||||
|
Tree.behaviors.residentsleeper.new(),
|
||||||
|
Tree.behaviors.stats.new(nil, nil, initiative),
|
||||||
|
Tree.behaviors.map.new(position, size),
|
||||||
|
Tree.behaviors.sprite.new(spriteDir),
|
||||||
|
Tree.behaviors.spellcaster.new()
|
||||||
|
}
|
||||||
|
|
||||||
Tree.level.characters[char.id] = char
|
Tree.level.characters[char.id] = char
|
||||||
return char
|
return char
|
||||||
end
|
end
|
||||||
@ -80,18 +92,6 @@ function character:addBehavior(behaviors)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Добавляет персонажа в очередь на удаление.
|
|
||||||
--- В конце фрейма он умирает. Ужасной смертью.
|
|
||||||
---
|
|
||||||
--- Ещё этот метод должен освобождать ресурсы в поведениях. Мы против утечек памяти!
|
|
||||||
function character:die()
|
|
||||||
for _, b in ipairs(self.behaviors) do
|
|
||||||
if b.die then b:die() end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(Tree.level.deadIds, self.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
function character:update(dt)
|
function character:update(dt)
|
||||||
for _, b in ipairs(self.behaviors) do
|
for _, b in ipairs(self.behaviors) do
|
||||||
if b.update then b:update(dt) end
|
if b.update then b:update(dt) end
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -8,7 +8,7 @@ grid.__index = grid
|
|||||||
--- adds a value to the grid
|
--- adds a value to the grid
|
||||||
--- @param value any
|
--- @param value any
|
||||||
function grid:add(value)
|
function grid:add(value)
|
||||||
self.__grid[tostring(value.position)] = value
|
grid[tostring(value.position)] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param position Vec3
|
--- @param position Vec3
|
||||||
|
|||||||
@ -12,15 +12,12 @@ function grid:add(id)
|
|||||||
local character = Tree.level.characters[id]
|
local character = Tree.level.characters[id]
|
||||||
if not character then return end
|
if not character then return end
|
||||||
|
|
||||||
local positioned = character:has(Tree.behaviors.positioned)
|
local mapB = character:has(Tree.behaviors.map)
|
||||||
if not positioned then return end
|
if not mapB then return end
|
||||||
|
|
||||||
local tiled = character:has(Tree.behaviors.tiled)
|
local centerX, centerY = math.floor(mapB.displayedPosition.x + 0.5),
|
||||||
if not tiled then return end
|
math.floor(mapB.displayedPosition.y + 0.5)
|
||||||
|
local sizeX, sizeY = mapB.size.x, mapB.size.y
|
||||||
local centerX, centerY = math.floor(positioned.position.x + 0.5),
|
|
||||||
math.floor(positioned.position.y + 0.5)
|
|
||||||
local sizeX, sizeY = tiled.size.x, tiled.size.y
|
|
||||||
|
|
||||||
for y = centerY, centerY + sizeY - 1 do
|
for y = centerY, centerY + sizeY - 1 do
|
||||||
for x = centerX, centerX + sizeX - 1 do
|
for x = centerX, centerX + sizeX - 1 do
|
||||||
@ -32,8 +29,10 @@ end
|
|||||||
--- @param a Character
|
--- @param a Character
|
||||||
--- @param b Character
|
--- @param b Character
|
||||||
local function drawCmp(a, b)
|
local function drawCmp(a, b)
|
||||||
--- @TODO: это захардкожено, надо разделить поведения
|
-- здесь персонажи гарантированно имеют нужное поведение
|
||||||
return a:has(Tree.behaviors.positioned).position.y < b:has(Tree.behaviors.positioned).position.y
|
return a:has(Tree.behaviors.map).displayedPosition.y
|
||||||
|
<
|
||||||
|
b:has(Tree.behaviors.map).displayedPosition.y
|
||||||
end
|
end
|
||||||
|
|
||||||
--- fills the grid with the actual data
|
--- fills the grid with the actual data
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
local utils = require "lib.utils.utils"
|
|
||||||
--- Пометровая сетка источников света, чтобы быстро искать ближайшие для некоторого объекта
|
|
||||||
--- @class LightGrid : Grid
|
|
||||||
--- @field __grid {string: [Id]}
|
|
||||||
local grid = setmetatable({}, require "lib.level.grid.base")
|
|
||||||
grid.__index = grid
|
|
||||||
|
|
||||||
--- Adds a character id to the grid
|
|
||||||
--- @private
|
|
||||||
--- @param id Id
|
|
||||||
function grid:add(id)
|
|
||||||
local character = Tree.level.characters[id]
|
|
||||||
if not character then return end
|
|
||||||
|
|
||||||
local lightB = character:has(Tree.behaviors.light)
|
|
||||||
if not lightB then return end
|
|
||||||
|
|
||||||
local positioned = character:has(Tree.behaviors.positioned)
|
|
||||||
if not positioned then return end
|
|
||||||
|
|
||||||
local key = tostring(Vec3 { positioned.position.x, positioned.position.y }:floor())
|
|
||||||
if not self.__grid[key] then self.__grid[key] = {} end
|
|
||||||
table.insert(self.__grid[key], character.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- fills the grid with the actual data
|
|
||||||
---
|
|
||||||
--- should be called as early as possible during every tick
|
|
||||||
function grid:reload()
|
|
||||||
self:reset()
|
|
||||||
utils.each(Tree.level.characters, function(c)
|
|
||||||
self:add(c.id)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Возвращает все источники света, которые находятся в пределах круга с диаметром [distance] в [метрике Чебышёва](https://ru.wikipedia.org/wiki/Расстояние_Чебышёва)
|
|
||||||
--- @param position Vec3
|
|
||||||
--- @param distance integer
|
|
||||||
function grid:query(position, distance)
|
|
||||||
--- @type Id[]
|
|
||||||
local res = {}
|
|
||||||
local topLeft = position:subtract(Vec3 { distance / 2, distance / 2 }):floor()
|
|
||||||
for i = 0, distance, 1 do
|
|
||||||
for j = 0, distance, 1 do
|
|
||||||
--- @type Id[]?
|
|
||||||
local lights = self:get(topLeft:add(Vec3 { i, j }))
|
|
||||||
if lights then
|
|
||||||
for _, lightChar in ipairs(lights) do
|
|
||||||
table.insert(res, lightChar)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Generates an empty grid
|
|
||||||
--- @return LightGrid
|
|
||||||
local function new()
|
|
||||||
return setmetatable({
|
|
||||||
__grid = {}
|
|
||||||
}, grid)
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
|
||||||
@ -13,13 +13,9 @@ local function new(type, template, size)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function map:draw()
|
function map:draw()
|
||||||
love.graphics.setCanvas(Tree.level.render.textures.floorLayer)
|
|
||||||
Tree.level.camera:attach()
|
|
||||||
utils.each(self.__grid, function(el)
|
utils.each(self.__grid, function(el)
|
||||||
el:draw()
|
el:draw()
|
||||||
end)
|
end)
|
||||||
Tree.level.camera:detach()
|
|
||||||
love.graphics.setCanvas()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return { new = new }
|
return { new = new }
|
||||||
|
|||||||
@ -3,64 +3,47 @@ local utils = require "lib.utils.utils"
|
|||||||
--- @class Level
|
--- @class Level
|
||||||
--- @field size Vec3
|
--- @field size Vec3
|
||||||
--- @field characters Character[]
|
--- @field characters Character[]
|
||||||
--- @field deadIds Id[]
|
|
||||||
--- @field characterGrid CharacterGrid
|
--- @field characterGrid CharacterGrid
|
||||||
--- @field lightGrid LightGrid
|
|
||||||
--- @field selector Selector
|
--- @field selector Selector
|
||||||
--- @field camera Camera
|
--- @field camera Camera
|
||||||
--- @field tileGrid TileGrid
|
--- @field tileGrid TileGrid
|
||||||
--- @field turnOrder TurnOrder
|
--- @field turnOrder TurnOrder
|
||||||
--- @field render Render
|
|
||||||
local level = {}
|
local level = {}
|
||||||
level.__index = level
|
level.__index = level
|
||||||
|
|
||||||
|
local path = nil
|
||||||
|
|
||||||
--- @param type "procedural"|"handmaded"
|
--- @param type "procedural"|"handmaded"
|
||||||
--- @param template Procedural|Handmaded
|
--- @param template Procedural|Handmaded
|
||||||
local function new(type, template)
|
local function new(type, template)
|
||||||
local size = Vec3 { 30, 30 } -- magic numbers for testing purposes only
|
local size = Vec3 { 30, 30 } -- magic numbers for testing purposes only
|
||||||
print(type, template, size)
|
print(type, template, size)
|
||||||
|
|
||||||
Tree.audio:play(Tree.assets.files.audio.music.level1.battle)
|
|
||||||
|
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
size = size,
|
size = size,
|
||||||
characters = {},
|
characters = {},
|
||||||
deadIds = {},
|
|
||||||
characterGrid = (require "lib.level.grid.character_grid").new(),
|
characterGrid = (require "lib.level.grid.character_grid").new(),
|
||||||
lightGrid = (require "lib.level.grid.light_grid").new(),
|
|
||||||
tileGrid = (require "lib.level.grid.tile_grid").new(type, template, size),
|
tileGrid = (require "lib.level.grid.tile_grid").new(type, template, size),
|
||||||
selector = (require "lib.level.selector").new(),
|
selector = (require "lib.level.selector").new(),
|
||||||
camera = (require "lib.level.camera").new(),
|
camera = (require "lib.level.camera").new(),
|
||||||
turnOrder = (require "lib.level.turn_order").new(),
|
turnOrder = (require "lib.level.turn_order").new(),
|
||||||
render = (require "lib.level.render").new {},
|
|
||||||
weather = (require "lib.level.weather").new { ambientLight = Vec3 { 0.36, 0.42, 0.6 }, skyLight = Vec3 {} }
|
|
||||||
}, level)
|
}, level)
|
||||||
end
|
end
|
||||||
|
|
||||||
function level:update(dt)
|
function level:update(dt)
|
||||||
utils.each(self.deadIds, function(id)
|
|
||||||
self.characters[id] = nil
|
|
||||||
self.turnOrder:remove(id)
|
|
||||||
end)
|
|
||||||
self.deadIds = {}
|
|
||||||
|
|
||||||
self.characterGrid:reload()
|
self.characterGrid:reload()
|
||||||
self.lightGrid:reload()
|
|
||||||
utils.each(self.characters, function(el)
|
utils.each(self.characters, function(el)
|
||||||
el:update(dt)
|
el:update(dt)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
self.camera:update(dt)
|
||||||
self.selector:update(dt)
|
self.selector:update(dt)
|
||||||
end
|
end
|
||||||
|
|
||||||
function level:draw()
|
function level:draw()
|
||||||
self.render:clear()
|
|
||||||
self.tileGrid:draw()
|
self.tileGrid:draw()
|
||||||
while not self.characterGrid.yOrderQueue:is_empty() do -- по сути это сортировка кучей за n log n
|
while not self.characterGrid.yOrderQueue:is_empty() do -- по сути это сортировка кучей за n log n
|
||||||
self.characterGrid.yOrderQueue:pop():draw()
|
self.characterGrid.yOrderQueue:pop():draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
self.render:draw()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,106 +0,0 @@
|
|||||||
--- @class Render
|
|
||||||
--- @field textures table<string, love.Canvas>
|
|
||||||
local render = {
|
|
||||||
textures = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function render:clear()
|
|
||||||
local weather = Tree.level.weather
|
|
||||||
local txs = self.textures
|
|
||||||
love.graphics.setCanvas(txs.shadowLayer)
|
|
||||||
love.graphics.clear()
|
|
||||||
love.graphics.setCanvas(txs.spriteLayer)
|
|
||||||
love.graphics.clear()
|
|
||||||
love.graphics.setCanvas(txs.spriteLightLayer)
|
|
||||||
love.graphics.clear(weather.skyLight.x, weather.skyLight.y, weather.skyLight.z)
|
|
||||||
love.graphics.setCanvas(txs.floorLayer)
|
|
||||||
love.graphics.clear()
|
|
||||||
love.graphics.setCanvas(txs.lightLayer)
|
|
||||||
love.graphics.clear(weather.skyLight.x, weather.skyLight.y, weather.skyLight.z)
|
|
||||||
love.graphics.setCanvas(txs.overlayLayer)
|
|
||||||
love.graphics.clear()
|
|
||||||
end
|
|
||||||
|
|
||||||
function render:free()
|
|
||||||
for _, tx in pairs(self.textures) do
|
|
||||||
tx:release()
|
|
||||||
end
|
|
||||||
self.textures = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- TODO: это используется для блюра, должно кэшироваться и поддерживать ресайз
|
|
||||||
|
|
||||||
function render:applyBlur(input, radius)
|
|
||||||
local blurShader = Tree.assets.files.shaders.blur
|
|
||||||
|
|
||||||
-- Горизонтальный проход
|
|
||||||
blurShader:send("direction", { 1.0, 0.0 })
|
|
||||||
blurShader:send("radius", radius)
|
|
||||||
|
|
||||||
self.textures.tmp1:renderTo(function()
|
|
||||||
love.graphics.clear()
|
|
||||||
love.graphics.setShader(blurShader)
|
|
||||||
love.graphics.draw(input)
|
|
||||||
love.graphics.setShader()
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Вертикальный проход
|
|
||||||
self.textures.tmp2:renderTo(
|
|
||||||
function()
|
|
||||||
love.graphics.clear()
|
|
||||||
love.graphics.setShader(blurShader)
|
|
||||||
blurShader:send("direction", { 0.0, 1.0 })
|
|
||||||
love.graphics.draw(self.textures.tmp1)
|
|
||||||
love.graphics.setShader()
|
|
||||||
end
|
|
||||||
)
|
|
||||||
return self.textures.tmp2
|
|
||||||
end
|
|
||||||
|
|
||||||
function render:draw()
|
|
||||||
-- пол -> тени -> спрайты -> свет -> оверлей
|
|
||||||
local weather = Tree.level.weather
|
|
||||||
local txs = self.textures
|
|
||||||
love.graphics.setCanvas(txs.lightLayer)
|
|
||||||
love.graphics.draw(self:applyBlur(txs.shadowLayer, 4 * Tree.level.camera.scale))
|
|
||||||
love.graphics.setCanvas()
|
|
||||||
|
|
||||||
-- self.lightLayer:newImageData():encode("png", "lightLayer.png")
|
|
||||||
-- os.exit(0)
|
|
||||||
|
|
||||||
local lightShader = Tree.assets.files.shaders.light_postprocess
|
|
||||||
lightShader:send("scene", txs.floorLayer)
|
|
||||||
lightShader:send("light", self:applyBlur(txs.lightLayer, 2))
|
|
||||||
lightShader:send("ambient", { weather.ambientLight.x, weather.ambientLight.y, weather.ambientLight.z })
|
|
||||||
love.graphics.setShader(lightShader)
|
|
||||||
love.graphics.draw(txs.floorLayer)
|
|
||||||
|
|
||||||
|
|
||||||
lightShader:send("scene", txs.spriteLayer)
|
|
||||||
lightShader:send("light", txs.spriteLightLayer)
|
|
||||||
love.graphics.draw(txs.spriteLayer)
|
|
||||||
love.graphics.setShader()
|
|
||||||
|
|
||||||
love.graphics.draw(txs.overlayLayer)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param params {w: number?, h: number?}
|
|
||||||
---@return table|Render
|
|
||||||
local function new(params)
|
|
||||||
local w = params.w or love.graphics.getWidth()
|
|
||||||
local h = params.h or love.graphics.getHeight()
|
|
||||||
return setmetatable({
|
|
||||||
textures = {
|
|
||||||
shadowLayer = love.graphics.newCanvas(w, h),
|
|
||||||
spriteLayer = love.graphics.newCanvas(w, h),
|
|
||||||
spriteLightLayer = love.graphics.newCanvas(w, h),
|
|
||||||
floorLayer = love.graphics.newCanvas(w, h),
|
|
||||||
overlayLayer = love.graphics.newCanvas(w, h),
|
|
||||||
lightLayer = love.graphics.newCanvas(w, h),
|
|
||||||
tmp1 = love.graphics.newCanvas(w, h),
|
|
||||||
tmp2 = love.graphics.newCanvas(w, h),
|
|
||||||
}
|
|
||||||
}, { __index = render })
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
|
||||||
@ -34,19 +34,15 @@ 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
|
||||||
local task = b.cast:cast(char, mousePosition) -- в task функция, которая запускает анимацию спелла
|
if b.cast:cast(char, mousePosition) then
|
||||||
if task then
|
|
||||||
self:lock()
|
self:lock()
|
||||||
b.state = "running"
|
b.state = "running"
|
||||||
|
|
||||||
task(
|
|
||||||
function(_) -- это коллбэк, который сработает по окончании анимации спелла
|
|
||||||
b:endCast()
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -30,19 +30,11 @@ 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
|
||||||
self.current = self.pendingQueue:pop()
|
self.current = self.pendingQueue:pop()
|
||||||
|
|
||||||
local char = Tree.level.characters[self.current]
|
|
||||||
char:try(Tree.behaviors.ai, function(ai)
|
|
||||||
Tree.level.selector:lock()
|
|
||||||
ai:makeTurn()(function()
|
|
||||||
Tree.level.selector:unlock()
|
|
||||||
self:next()
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Меняем местами очередь сходивших и не сходивших (пустую)
|
--- Меняем местами очередь сходивших и не сходивших (пустую)
|
||||||
@ -117,29 +109,4 @@ function turnOrder:add(id)
|
|||||||
self.actedQueue:insert(id) -- новые персонажи по умолчанию попадают в очередь следующего хода
|
self.actedQueue:insert(id) -- новые персонажи по умолчанию попадают в очередь следующего хода
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Удалить персонажа из очереди хода (например, при смерти)
|
|
||||||
--- @param id Id
|
|
||||||
function turnOrder:remove(id)
|
|
||||||
if self.current == id then
|
|
||||||
self.current = self.pendingQueue:pop()
|
|
||||||
if not self.current then
|
|
||||||
self:endRound()
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local function filterQueue(q, targetId)
|
|
||||||
local newQ = PriorityQueue.new(initiativeComparator)
|
|
||||||
for _, val in ipairs(q.data) do
|
|
||||||
if val ~= targetId then
|
|
||||||
newQ:insert(val)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return newQ
|
|
||||||
end
|
|
||||||
|
|
||||||
self.actedQueue = filterQueue(self.actedQueue, id)
|
|
||||||
self.pendingQueue = filterQueue(self.pendingQueue, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
return { new = new }
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
--- @class Weather
|
|
||||||
--- @field skyLight Vec3
|
|
||||||
--- @field ambientLight Vec3
|
|
||||||
local weather = {}
|
|
||||||
|
|
||||||
--- @param proto Weather
|
|
||||||
--- @return Weather
|
|
||||||
local function new(proto)
|
|
||||||
return setmetatable(proto, { __index = weather })
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
-- --- @class Music
|
|
||||||
-- --- @field source table<string, love.Source> audio streams, that supports multitrack (kind of)
|
|
||||||
-- --- @field offset number
|
|
||||||
-- music = {}
|
|
||||||
-- music.__index = music
|
|
||||||
|
|
||||||
-- --- @param path string accepts path to dir with some music files (example: "main_ambient"; "player/theme1" and etc etc)
|
|
||||||
-- local function new(path)
|
|
||||||
-- local dir = Tree.assets.files.audio.music[path]
|
|
||||||
-- --- @type table<string, love.Source>
|
|
||||||
-- local source = {}
|
|
||||||
-- print(dir)
|
|
||||||
|
|
||||||
-- for _, v in pairs(dir) do
|
|
||||||
-- print(v.filename)
|
|
||||||
-- source[v.filename] = v.source
|
|
||||||
-- print(v.filename)
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- print('[music]: new source: ', table.concat(source, ' '))
|
|
||||||
|
|
||||||
-- return setmetatable({ source = source, offset = 0 }, music)
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- function music:update()
|
|
||||||
-- for _, v in ipairs(self.source) do
|
|
||||||
-- v:seek()
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- --- pause stemfile or music at all
|
|
||||||
-- --- @param filename? string
|
|
||||||
-- function music:pause(filename)
|
|
||||||
-- if filename then
|
|
||||||
-- self.source[filename]:pause()
|
|
||||||
-- else
|
|
||||||
-- for _, v in pairs(self.source) do
|
|
||||||
-- v:pause()
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- --- play music stemfile by his name
|
|
||||||
-- --- @param filename string
|
|
||||||
-- --- @return boolean
|
|
||||||
-- function music:play(filename)
|
|
||||||
-- print('[music]: ', table.concat(self.source, ' '))
|
|
||||||
-- self.source[filename]:seek(self.offset, "seconds")
|
|
||||||
-- return self.source[filename]:play()
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- return { new = new }
|
|
||||||
@ -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
|
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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.positioned) then return end
|
|
||||||
|
|
||||||
AnimationNode {
|
|
||||||
function(node)
|
|
||||||
Tree.level.camera:animateTo(playing:has(Tree.behaviors.positioned).position, node)
|
|
||||||
end,
|
|
||||||
duration = 1500,
|
|
||||||
easing = easing.easeInOutCubic,
|
|
||||||
onEnd = function() if not playing:has(Tree.behaviors.ai) then Tree.level.selector:select(cid) end end
|
|
||||||
}:run()
|
|
||||||
end
|
|
||||||
|
|
||||||
return function(values)
|
|
||||||
return endTurnButton:new(values)
|
|
||||||
end
|
|
||||||
@ -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
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
local UI_SCALE = 0.75 -- выдуманное значение для dependency injection, надо подбирать так, чтобы UI_SCALE * 64 было целым числом
|
|
||||||
return UI_SCALE
|
|
||||||
@ -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)
|
||||||
function skillRow:update(dt)
|
if self.animationNode then self.animationNode:finish() end
|
||||||
local iconSize = math.floor(64 * UI_SCALE)
|
self.animationNode = animationNode
|
||||||
local screenW, screenH = love.graphics.getDimensions()
|
self.state = "show"
|
||||||
local padding, margin = 8, 4
|
end,
|
||||||
local count = #self.children -- слоты под скиллы
|
duration = 300,
|
||||||
|
onEnd = function()
|
||||||
self.bounds = Rect {
|
self.state = "idle"
|
||||||
width = iconSize * count + (count + 1) * margin,
|
end,
|
||||||
height = iconSize + 2 * margin,
|
easing = easing.easeOutCubic
|
||||||
}
|
}:run()
|
||||||
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 { x = self.bounds.x + margin + (i - 1) * (iconSize + margin), -- друг за другом, включая первый отступ от границы
|
|
||||||
y = self.bounds.y + margin, height = iconSize, width = iconSize }
|
|
||||||
skb:update(dt)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if not c then
|
function skillRow:hide()
|
||||||
c = love.graphics.newCanvas(self.bounds.width, self.bounds.height)
|
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)
|
||||||
|
if self.animationNode then self.animationNode:update(dt) end
|
||||||
|
|
||||||
|
local iconSize = icons.tileSize
|
||||||
|
local scale = (64 / iconSize)
|
||||||
|
local screenW, screenH = love.graphics.getDimensions()
|
||||||
|
local padding = 8
|
||||||
|
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
|
||||||
|
}
|
||||||
|
self.transform = love.math.newTransform():translate(screenW / 2,
|
||||||
|
screenH - 16):scale(scale, scale):translate(-self.bounds.width / 2, -iconSize)
|
||||||
|
|
||||||
|
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:update(dt)
|
||||||
end
|
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
|
for _, skb in ipairs(self.children) do
|
||||||
skb:draw()
|
skb:draw()
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
return skillRow.new
|
return skillRow.new
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
-- --- @class Sound
|
|
||||||
-- --- @field source love.Source just a sound
|
|
||||||
-- sound = {}
|
|
||||||
|
|
||||||
-- local function new()
|
|
||||||
-- return setmetatable({}, sound)
|
|
||||||
-- end
|
|
||||||
|
|
||||||
-- return { new }
|
|
||||||
@ -7,13 +7,13 @@
|
|||||||
--- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла
|
--- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла
|
||||||
--- Да, это Future/Promise/await/async
|
--- Да, это Future/Promise/await/async
|
||||||
|
|
||||||
local task = require 'lib.utils.task'
|
local AnimationNode = require "lib.animation_node"
|
||||||
|
|
||||||
--- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell
|
--- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell
|
||||||
--- @field tag string
|
--- @field tag string
|
||||||
--- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла
|
--- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла
|
||||||
--- @field draw fun(self: Spell): nil Рисует превью каста, ничего не должна изменять в идеальном мире
|
--- @field draw fun(self: Spell): nil Рисует превью каста, ничего не должна изменять в идеальном мире
|
||||||
--- @field cast fun(self: Spell, caster: Character, target: Vec3): Task<nil> | nil Вызывается в момент каста, изменяет мир.
|
--- @field cast fun(self: Spell, caster: Character, target: Vec3): boolean Вызывается в момент каста, изменяет мир. Возвращает bool в зависимости от того, получилось ли скастовать
|
||||||
local spell = {}
|
local spell = {}
|
||||||
spell.__index = spell
|
spell.__index = spell
|
||||||
spell.tag = "base"
|
spell.tag = "base"
|
||||||
@ -22,7 +22,7 @@ function spell:update(caster, dt) end
|
|||||||
|
|
||||||
function spell:draw() end
|
function spell:draw() end
|
||||||
|
|
||||||
function spell:cast(caster, target) return end
|
function spell:cast(caster, target) return true end
|
||||||
|
|
||||||
local walk = setmetatable({
|
local walk = setmetatable({
|
||||||
--- @type Deque
|
--- @type Deque
|
||||||
@ -34,32 +34,32 @@ function walk:cast(caster, target)
|
|||||||
if not caster:try(Tree.behaviors.stats, function(stats)
|
if not caster:try(Tree.behaviors.stats, function(stats)
|
||||||
return stats.mana >= 2
|
return stats.mana >= 2
|
||||||
end) then
|
end) then
|
||||||
return
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local initialPos = caster:has(Tree.behaviors.positioned).position:floor()
|
local path = self.path
|
||||||
local path = require "lib.pathfinder" (initialPos, target)
|
|
||||||
path:pop_front()
|
path:pop_front()
|
||||||
if path:is_empty() then
|
if path:is_empty() then return false end
|
||||||
print("[Walk]: the path is empty", initialPos, target)
|
|
||||||
return
|
for p in path:values() do print(p) end
|
||||||
end
|
|
||||||
|
|
||||||
caster:try(Tree.behaviors.stats, function(stats)
|
caster:try(Tree.behaviors.stats, function(stats)
|
||||||
stats.mana = stats.mana - 2
|
stats.mana = stats.mana - 2
|
||||||
|
print(stats.mana)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local sprite = caster:has(Tree.behaviors.sprite)
|
local sprite = caster:has(Tree.behaviors.sprite)
|
||||||
assert(sprite, "[Walk]", "WTF DUDE WHERE'S YOUR SPRITE")
|
if not sprite then return true end
|
||||||
if not sprite then
|
AnimationNode {
|
||||||
return
|
function(node) caster:has(Tree.behaviors.map):followPath(path, node) end,
|
||||||
end
|
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end,
|
||||||
|
}:run()
|
||||||
|
|
||||||
return caster:has(Tree.behaviors.tiled):followPath(path)
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function walk:update(caster, dt)
|
function walk:update(caster, dt)
|
||||||
local charPos = caster:has(Tree.behaviors.positioned).position:floor()
|
local charPos = caster:has(Tree.behaviors.map).position:floor()
|
||||||
--- @type Vec3
|
--- @type Vec3
|
||||||
local mpos = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor()
|
local mpos = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor()
|
||||||
self.path = require "lib.pathfinder" (charPos, mpos)
|
self.path = require "lib.pathfinder" (charPos, mpos)
|
||||||
@ -68,14 +68,10 @@ end
|
|||||||
function walk:draw()
|
function walk:draw()
|
||||||
if not self.path then return end
|
if not self.path then return end
|
||||||
--- Это отрисовка пути персонажа к мышке
|
--- Это отрисовка пути персонажа к мышке
|
||||||
Tree.level.camera:attach()
|
|
||||||
love.graphics.setCanvas(Tree.level.render.textures.overlayLayer)
|
|
||||||
love.graphics.setColor(0.6, 0.75, 0.5)
|
love.graphics.setColor(0.6, 0.75, 0.5)
|
||||||
for p in self.path:values() do
|
for p in self.path:values() do
|
||||||
love.graphics.circle("fill", p.x + 0.45, p.y + 0.45, 0.1)
|
love.graphics.circle("fill", p.x + 0.45, p.y + 0.45, 0.1)
|
||||||
end
|
end
|
||||||
love.graphics.setCanvas()
|
|
||||||
Tree.level.camera:detach()
|
|
||||||
love.graphics.setColor(1, 1, 1)
|
love.graphics.setColor(1, 1, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -87,34 +83,29 @@ function regenerateMana:cast(caster, target)
|
|||||||
stats.mana = 10
|
stats.mana = 10
|
||||||
stats.initiative = stats.initiative + 10
|
stats.initiative = stats.initiative + 10
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local sprite = caster:has(Tree.behaviors.sprite)
|
|
||||||
if not sprite then return nil end
|
|
||||||
print(caster.id, "has regenerated mana and gained initiative")
|
print(caster.id, "has regenerated mana and gained initiative")
|
||||||
|
local sprite = caster:has(Tree.behaviors.sprite)
|
||||||
|
if not sprite then return true end
|
||||||
|
AnimationNode {
|
||||||
|
function(node)
|
||||||
|
sprite:animate("hurt", node)
|
||||||
|
end,
|
||||||
|
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end
|
||||||
|
}:run()
|
||||||
|
|
||||||
local light = require "lib/character/character".spawn("Light Effect")
|
return true
|
||||||
light:addBehavior {
|
|
||||||
Tree.behaviors.light.new { color = Vec3 { 0.6, 0.3, 0.3 }, intensity = 4 },
|
|
||||||
Tree.behaviors.residentsleeper.new(),
|
|
||||||
Tree.behaviors.positioned.new(caster:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }),
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.wait {
|
|
||||||
light:has(Tree.behaviors.light):animateColor(Vec3 {}),
|
|
||||||
sprite:animate("hurt")
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local attack = setmetatable({}, spell)
|
local attack = setmetatable({}, spell)
|
||||||
attack.tag = "dev_attack"
|
attack.tag = "dev_attack"
|
||||||
|
|
||||||
function attack:cast(caster, target)
|
function attack:cast(caster, target)
|
||||||
if caster:try(Tree.behaviors.positioned, function(p)
|
if caster:try(Tree.behaviors.map, function(map)
|
||||||
local dist = math.max(math.abs(p.position.x - target.x), math.abs(p.position.y - target.y))
|
local dist = math.max(math.abs(map.position.x - target.x), math.abs(map.position.y - target.y))
|
||||||
print("dist:", dist)
|
print("dist:", dist)
|
||||||
return dist > 2
|
return dist > 2
|
||||||
end) then
|
end) then
|
||||||
return
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
caster:try(Tree.behaviors.stats, function(stats)
|
caster:try(Tree.behaviors.stats, function(stats)
|
||||||
@ -123,7 +114,7 @@ function attack:cast(caster, target)
|
|||||||
|
|
||||||
--- @type Character
|
--- @type Character
|
||||||
local targetCharacterId = Tree.level.characterGrid:get(target)
|
local targetCharacterId = Tree.level.characterGrid:get(target)
|
||||||
if not targetCharacterId or targetCharacterId == caster.id then return end
|
if not targetCharacterId or targetCharacterId == caster.id then return false end
|
||||||
local targetCharacter = Tree.level.characters[targetCharacterId]
|
local targetCharacter = Tree.level.characters[targetCharacterId]
|
||||||
targetCharacter:try(Tree.behaviors.stats, function(stats)
|
targetCharacter:try(Tree.behaviors.stats, function(stats)
|
||||||
stats.hp = stats.hp - 4
|
stats.hp = stats.hp - 4
|
||||||
@ -131,20 +122,32 @@ function attack:cast(caster, target)
|
|||||||
|
|
||||||
local sprite = caster:has(Tree.behaviors.sprite)
|
local sprite = caster:has(Tree.behaviors.sprite)
|
||||||
local targetSprite = targetCharacter:has(Tree.behaviors.sprite)
|
local targetSprite = targetCharacter:has(Tree.behaviors.sprite)
|
||||||
if not sprite or not targetSprite then return end
|
if not sprite or not targetSprite then return true end
|
||||||
|
|
||||||
caster:try(Tree.behaviors.positioned, function(b) b:lookAt(target) end)
|
AnimationNode {
|
||||||
|
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end,
|
||||||
return
|
children = {
|
||||||
task.wait {
|
AnimationNode {
|
||||||
sprite:animate("attack"),
|
function(node)
|
||||||
task.wait {
|
sprite:animate("attack", node)
|
||||||
task.chain(targetCharacter:has(Tree.behaviors.residentsleeper):sleep(200),
|
end
|
||||||
function() return targetSprite:animate("hurt") end
|
},
|
||||||
),
|
AnimationNode {
|
||||||
Tree.audio:play(Tree.assets.files.audio.sounds.hurt)
|
function(node)
|
||||||
|
targetCharacter:has(Tree.behaviors.residentsleeper):sleep(200, node)
|
||||||
|
end,
|
||||||
|
children = {
|
||||||
|
AnimationNode {
|
||||||
|
function(node)
|
||||||
|
targetSprite:animate("hurt", node)
|
||||||
|
end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}:run()
|
||||||
|
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@ -159,7 +162,6 @@ local spellbook = {
|
|||||||
function spellbook.of(list)
|
function spellbook.of(list)
|
||||||
local spb = {}
|
local spb = {}
|
||||||
for i, sp in ipairs(list) do
|
for i, sp in ipairs(list) do
|
||||||
print(i)
|
|
||||||
spb[i] = setmetatable({}, { __index = sp })
|
spb[i] = setmetatable({}, { __index = sp })
|
||||||
end
|
end
|
||||||
return spb
|
return spb
|
||||||
|
|||||||
@ -6,14 +6,11 @@
|
|||||||
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.audio = (require "lib.audio").new(1, 1)
|
|
||||||
Tree.level = (require "lib.level.level").new("procedural", "flower_plains") -- для теста у нас только один уровень, который сразу же загружен
|
Tree.level = (require "lib.level.level").new("procedural", "flower_plains") -- для теста у нас только один уровень, который сразу же загружен
|
||||||
|
|
||||||
Tree.behaviors = (require "lib.utils.behavior_loader")("lib/character/behaviors") --- @todo написать нормальную загрузку поведений
|
Tree.behaviors = (require "lib.utils.behavior_loader")("lib/character/behaviors") --- @todo написать нормальную загрузку поведений
|
||||||
-- Tree.audio = (require "lib.audio").new(1, 1)
|
|
||||||
-- Tree.behaviors.map = require "lib.character.behaviors.map"
|
-- Tree.behaviors.map = require "lib.character.behaviors.map"
|
||||||
-- Tree.behaviors.spellcaster = require "lib.character.behaviors.spellcaster"
|
-- Tree.behaviors.spellcaster = require "lib.character.behaviors.spellcaster"
|
||||||
-- Tree.behaviors.sprite = require "lib.character.behaviors.sprite"
|
-- Tree.behaviors.sprite = require "lib.character.behaviors.sprite"
|
||||||
|
|||||||
@ -50,12 +50,8 @@ function AssetBundle.loadFile(path)
|
|||||||
return love.graphics.newShader(path);
|
return love.graphics.newShader(path);
|
||||||
elseif (ext == "lua") then
|
elseif (ext == "lua") then
|
||||||
return require(string.gsub(path, ".lua", ""))
|
return require(string.gsub(path, ".lua", ""))
|
||||||
elseif (ext == "ogg") and string.find(path, "sounds") then
|
|
||||||
return love.audio.newSource(path, 'static')
|
|
||||||
elseif (ext == "ogg") and string.find(path, "music") then
|
|
||||||
return love.audio.newSource(path, 'stream')
|
|
||||||
end
|
end
|
||||||
return filedata
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function AssetBundle.cutExtension(filename)
|
function AssetBundle.cutExtension(filename)
|
||||||
|
|||||||
@ -1,40 +0,0 @@
|
|||||||
--- @class Counter
|
|
||||||
--- @field private count integer
|
|
||||||
--- @field private onFinish fun(): nil
|
|
||||||
--- @field private isAlive boolean
|
|
||||||
--- @field push fun():nil добавить 1 к счетчику
|
|
||||||
--- @field pop fun():nil убавить 1 у счетчика
|
|
||||||
--- @field set fun(count: integer): nil установить значение на счетчике
|
|
||||||
local counter = {}
|
|
||||||
counter.__index = counter
|
|
||||||
|
|
||||||
|
|
||||||
--- @private
|
|
||||||
function counter:_push()
|
|
||||||
self.count = self.count + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @private
|
|
||||||
function counter:_pop()
|
|
||||||
self.count = self.count - 1
|
|
||||||
if self.count == 0 and self.isAlive then
|
|
||||||
self.isAlive = false
|
|
||||||
self.onFinish()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param onFinish fun(): nil
|
|
||||||
local function new(onFinish)
|
|
||||||
local t = {
|
|
||||||
count = 0,
|
|
||||||
onFinish = onFinish,
|
|
||||||
isAlive = true,
|
|
||||||
}
|
|
||||||
t.push = function() counter._push(t) end
|
|
||||||
t.pop = function() counter._pop(t) end
|
|
||||||
t.set = function(count) t.count = count end
|
|
||||||
|
|
||||||
return setmetatable(t, counter)
|
|
||||||
end
|
|
||||||
|
|
||||||
return new
|
|
||||||
@ -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
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
--- Обобщенная асинхронная функция
|
|
||||||
---
|
|
||||||
--- Использование в общих чертах выглядит так:
|
|
||||||
--- ```lua
|
|
||||||
--- local multiplyByTwoCallback = nil
|
|
||||||
--- local n = nil
|
|
||||||
--- local function multiplyByTwoAsync(number)
|
|
||||||
--- -- императивно сохраняем/обрабатываем параметр
|
|
||||||
--- n = number
|
|
||||||
--- return function(callback) -- это функция, которая запускает задачу
|
|
||||||
--- multiplyByTwoCallback = callback
|
|
||||||
--- end
|
|
||||||
--- end
|
|
||||||
---
|
|
||||||
--- local function update(dt)
|
|
||||||
--- --- ждем нужного момента времени...
|
|
||||||
---
|
|
||||||
--- if multiplyByTwoCallback then -- завершаем вычисление
|
|
||||||
--- local result = n * 2
|
|
||||||
--- multiplyByTwoCallback(result) -- результат асинхронного вычисления идет в параметр коллбека!
|
|
||||||
--- multiplyByTwoCallback = nil
|
|
||||||
--- end
|
|
||||||
--- end
|
|
||||||
---
|
|
||||||
---
|
|
||||||
--- --- потом это можно вызывать так:
|
|
||||||
--- local task = multiplyByTwoAsync(21)
|
|
||||||
--- -- это ленивое вычисление, так что в этот момент ничего не произойдет
|
|
||||||
--- -- запускаем
|
|
||||||
--- task(
|
|
||||||
--- function(result) print(result) end -- выведет 42 после завершения вычисления, т.е. аналогично `task.then((res) => print(res))` на JS
|
|
||||||
--- )
|
|
||||||
---
|
|
||||||
--- ```
|
|
||||||
--- @generic T
|
|
||||||
--- @alias Task<T> fun(callback: fun(value: T): nil): nil
|
|
||||||
|
|
||||||
--- Возвращает новый Task, который завершится после завершения всех переданных `tasks`.
|
|
||||||
---
|
|
||||||
--- Значение созданного Task будет содержать список значений `tasks` в том же порядке.
|
|
||||||
---
|
|
||||||
--- См. также https://api.dart.dev/dart-async/Future/wait.html
|
|
||||||
--- @generic T
|
|
||||||
--- @param tasks Task[]
|
|
||||||
--- @return Task<T[]>
|
|
||||||
local function wait(tasks)
|
|
||||||
local count = #tasks
|
|
||||||
local results = {}
|
|
||||||
|
|
||||||
return function(callback)
|
|
||||||
for i, task in ipairs(tasks) do
|
|
||||||
task(
|
|
||||||
function(result)
|
|
||||||
results[i] = result
|
|
||||||
|
|
||||||
count = count - 1
|
|
||||||
if count == 0 then callback(results) end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
--- Последовательно объединяет два `Task` в один.
|
|
||||||
--- @generic T
|
|
||||||
--- @generic R
|
|
||||||
--- @param task Task<T> `Task`, который выполнится первым
|
|
||||||
--- @param onCompleted fun(value: T): Task<R> Конструктор второго `Task`. Принимает результат выполнения первого `Task`
|
|
||||||
--- @return Task<R>
|
|
||||||
local function chain(task, onCompleted)
|
|
||||||
return function(callback)
|
|
||||||
task(function(value)
|
|
||||||
local task2 = onCompleted(value)
|
|
||||||
task2(callback)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
wait = wait,
|
|
||||||
chain = chain
|
|
||||||
}
|
|
||||||
94
main.lua
94
main.lua
@ -1,83 +1,44 @@
|
|||||||
-- CameraLoader = require 'lib/camera'
|
-- CameraLoader = require 'lib/camera'
|
||||||
|
|
||||||
local character = require "lib/character/character"
|
local character = require "lib/character/character"
|
||||||
local testLayout
|
require "lib/tree"
|
||||||
local TestRunner = require "test.runner"
|
local testLayout = require "lib.simple_ui.level.layout"
|
||||||
TestRunner:register(require "test.task")
|
|
||||||
|
|
||||||
function love.conf(t)
|
function love.conf(t)
|
||||||
t.console = true
|
t.console = true
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.load()
|
function love.load()
|
||||||
love.window.setMode(1280, 720, { resizable = true, msaa = 4, vsync = true })
|
character.spawn("Foodor", Tree.assets.files.sprites.character, nil, nil, 1)
|
||||||
require "lib/tree" -- важно это сделать после настройки окна
|
character.spawn("Baris", Tree.assets.files.sprites.character, Vec3 { 3, 3 }, nil, 2)
|
||||||
testLayout = require "lib.simple_ui.level.layout"
|
character.spawn("Foodor Jr", Tree.assets.files.sprites.character, Vec3 { 0, 3 }, nil, 3)
|
||||||
|
character.spawn("Baris Jr", Tree.assets.files.sprites.character, Vec3 { 0, 6 }, nil, 4)
|
||||||
local chars = {
|
for id, _ in pairs(Tree.level.characters) do
|
||||||
character.spawn("Foodor")
|
|
||||||
:addBehavior {
|
|
||||||
Tree.behaviors.residentsleeper.new(),
|
|
||||||
Tree.behaviors.stats.new(nil, nil, 1),
|
|
||||||
Tree.behaviors.positioned.new(Vec3 { 3, 3 }),
|
|
||||||
Tree.behaviors.tiled.new(),
|
|
||||||
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
|
|
||||||
Tree.behaviors.shadowcaster.new(),
|
|
||||||
Tree.behaviors.spellcaster.new()
|
|
||||||
},
|
|
||||||
character.spawn("Foodor")
|
|
||||||
:addBehavior {
|
|
||||||
Tree.behaviors.residentsleeper.new(),
|
|
||||||
Tree.behaviors.stats.new(nil, nil, 1),
|
|
||||||
Tree.behaviors.positioned.new(Vec3 { 4, 3 }),
|
|
||||||
Tree.behaviors.tiled.new(),
|
|
||||||
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
|
|
||||||
Tree.behaviors.shadowcaster.new(),
|
|
||||||
Tree.behaviors.spellcaster.new()
|
|
||||||
},
|
|
||||||
character.spawn("Foodor")
|
|
||||||
:addBehavior {
|
|
||||||
Tree.behaviors.residentsleeper.new(),
|
|
||||||
Tree.behaviors.stats.new(nil, nil, 3),
|
|
||||||
Tree.behaviors.positioned.new(Vec3 { 5, 3 }),
|
|
||||||
Tree.behaviors.tiled.new(),
|
|
||||||
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
|
|
||||||
Tree.behaviors.shadowcaster.new(),
|
|
||||||
Tree.behaviors.spellcaster.new()
|
|
||||||
},
|
|
||||||
character.spawn("Baris")
|
|
||||||
:addBehavior {
|
|
||||||
Tree.behaviors.residentsleeper.new(),
|
|
||||||
Tree.behaviors.stats.new(nil, nil, 2),
|
|
||||||
Tree.behaviors.positioned.new(Vec3 { 5, 5 }),
|
|
||||||
Tree.behaviors.tiled.new(),
|
|
||||||
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
|
|
||||||
Tree.behaviors.shadowcaster.new(),
|
|
||||||
Tree.behaviors.spellcaster.new(),
|
|
||||||
Tree.behaviors.ai.new()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, _ in pairs(chars) do
|
|
||||||
Tree.level.turnOrder:add(id)
|
Tree.level.turnOrder:add(id)
|
||||||
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(1080, 720, { resizable = true, msaa = 4, vsync = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
local lt = "0"
|
local lt = "0"
|
||||||
function love.update(dt)
|
function love.update(dt)
|
||||||
TestRunner:update(dt) -- закомментировать для отключения тестов
|
|
||||||
|
|
||||||
local t1 = love.timer.getTime()
|
local t1 = love.timer.getTime()
|
||||||
Tree.controls:poll()
|
Tree.controls:poll()
|
||||||
Tree.level.camera:update(dt) -- сначала логика камеры, потому что на нее завязан UI
|
testLayout:update(dt) -- логика UI-слоя должна отработать раньше всех, потому что нужно перехватить жесты и не пустить их дальше
|
||||||
testLayout:update(dt) -- потом UI, потому что нужно перехватить жесты и не пустить их дальше
|
|
||||||
Tree.panning:update(dt)
|
Tree.panning:update(dt)
|
||||||
Tree.level:update(dt)
|
Tree.level:update(dt)
|
||||||
Tree.audio: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()
|
||||||
|
|
||||||
@ -105,24 +66,17 @@ function love.draw()
|
|||||||
love.graphics.draw(Tree.assets.files.cats, 0, 0)
|
love.graphics.draw(Tree.assets.files.cats, 0, 0)
|
||||||
love.graphics.pop()
|
love.graphics.pop()
|
||||||
|
|
||||||
|
Tree.level.camera:attach()
|
||||||
|
|
||||||
Tree.level:draw()
|
Tree.level:draw()
|
||||||
|
|
||||||
|
Tree.level.camera:detach()
|
||||||
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()
|
||||||
dt = string.format("%.3f", (t2 - t1) * 1000)
|
dt = string.format("%.3f", (t2 - t1) * 1000)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.resize(w, h)
|
|
||||||
local render = Tree.level.render
|
|
||||||
if not render then return end
|
|
||||||
render:free()
|
|
||||||
Tree.level.render = (require "lib.level.render").new { w, h }
|
|
||||||
end
|
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
--- @class Test
|
|
||||||
local test = {}
|
|
||||||
function test:run(complete) end
|
|
||||||
|
|
||||||
function test:update(dt) end
|
|
||||||
|
|
||||||
--- @class TestRunner
|
|
||||||
--- @field private tests Test[]
|
|
||||||
--- @field private state "loading" | "running" | "completed"
|
|
||||||
--- @field private completedCount integer
|
|
||||||
local runner = {}
|
|
||||||
runner.tests = {}
|
|
||||||
runner.state = "loading"
|
|
||||||
runner.completedCount = 0
|
|
||||||
|
|
||||||
--- глобальный update для тестов, нужен для тестирования фич, зависимых от времени
|
|
||||||
function runner:update(dt)
|
|
||||||
if self.state == "loading" then
|
|
||||||
print("[TestRunner]: running " .. #self.tests .. " tests")
|
|
||||||
|
|
||||||
for _, t in ipairs(self.tests) do
|
|
||||||
t:run(
|
|
||||||
function()
|
|
||||||
self.completedCount = self.completedCount + 1
|
|
||||||
if self.completedCount == #self.tests then
|
|
||||||
self.state = "completed"
|
|
||||||
print("[TestRunner]: tests completed")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
self.state = "running"
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, t in ipairs(self.tests) do
|
|
||||||
if t.update then t:update(dt) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- добавляет тест для прохождения
|
|
||||||
--- @param t Test
|
|
||||||
function runner:register(t)
|
|
||||||
table.insert(self.tests, t)
|
|
||||||
end
|
|
||||||
|
|
||||||
return runner
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
local task = require "lib.utils.task"
|
|
||||||
|
|
||||||
local test = {}
|
|
||||||
|
|
||||||
local t0
|
|
||||||
local task1Start, task2Start
|
|
||||||
local task1Callback, task2Callback
|
|
||||||
|
|
||||||
--- @return Task<number>
|
|
||||||
local function task1()
|
|
||||||
return function(callback)
|
|
||||||
task1Start = love.timer.getTime()
|
|
||||||
task1Callback = callback
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @return Task<number>
|
|
||||||
local function task2()
|
|
||||||
return function(callback)
|
|
||||||
task2Start = love.timer.getTime()
|
|
||||||
task2Callback = callback
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function test:run(complete)
|
|
||||||
t0 = love.timer.getTime()
|
|
||||||
|
|
||||||
task.wait {
|
|
||||||
task1(),
|
|
||||||
task2()
|
|
||||||
} (function(values)
|
|
||||||
local tWait = love.timer.getTime()
|
|
||||||
local dt = tWait - t0
|
|
||||||
|
|
||||||
local t1 = values[1]
|
|
||||||
local t2 = values[2]
|
|
||||||
|
|
||||||
assert(type(t1) == "number" and type(t2) == "number")
|
|
||||||
assert(t2 > t1)
|
|
||||||
assert(dt >= 2, "dt = " .. dt)
|
|
||||||
|
|
||||||
print("task.wait completed in " .. dt .. " sec", "t1 = " .. t1 - t0, "t2 = " .. t2 - t0)
|
|
||||||
|
|
||||||
|
|
||||||
t0 = love.timer.getTime()
|
|
||||||
task.chain(task1(), function(value)
|
|
||||||
t1 = value
|
|
||||||
assert(t1 - t0 >= 1)
|
|
||||||
return task2()
|
|
||||||
end)(
|
|
||||||
function(value)
|
|
||||||
t2 = value
|
|
||||||
assert(t2 - t0 >= 2)
|
|
||||||
print("task.chain completed in " .. t2 - t0 .. " sec")
|
|
||||||
|
|
||||||
complete()
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function test:update(dt)
|
|
||||||
local t = love.timer.getTime()
|
|
||||||
if task1Start and t - task1Start >= 1 then
|
|
||||||
task1Callback(t)
|
|
||||||
task1Start = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if task2Start and t - task2Start >= 2 then
|
|
||||||
task2Callback(t)
|
|
||||||
task2Start = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return test
|
|
||||||
Loading…
x
Reference in New Issue
Block a user