normalize line endings

This commit is contained in:
Hersi 2022-02-20 17:27:27 +01:00
parent 6b4eac23e2
commit d0c2932c5b
25 changed files with 6327 additions and 6326 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

View File

@ -1,36 +1,36 @@
# ExperimentalGear skin for USC
Project Starter: GSK Bladez
## Coding
- [REDACTED]
- Hersi
- fdigl
- Hoshikara
- GSK Bladez
- Local
## Graphics
- GSK Bladez
- Neardayo
- YellowBird
- Dengekiko
## Translation
- GSK Bladez
- Neardayo
- RealFD
## Misc. Help
- Neardayo
- DDX
- GM*DEO
## Beta Testing
- Gam
- TealStar
- Dengikiko
- Adamyes
- Gio
- Mattadome
# ExperimentalGear skin for USC
Project Starter: GSK Bladez
## Coding
- [REDACTED]
- Hersi
- fdigl
- Hoshikara
- GSK Bladez
- Local
## Graphics
- GSK Bladez
- Neardayo
- YellowBird
- Dengekiko
## Translation
- GSK Bladez
- Neardayo
- RealFD
## Misc. Help
- Neardayo
- DDX
- GM*DEO
## Beta Testing
- Gam
- TealStar
- Dengikiko
- Adamyes
- Gio
- Mattadome

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +1,86 @@
local newFormatTemplate = {
{
Bg={
Base={
Tex={{"",""}},
Tilt=false,
OffsetY=0.17,
Anim=true,
ScaleSoft=false,
ClampTiling=false,
},
Overlay={ -- is meant to be displayed overtop Base
Tex="",
Float=true,
FloatFactor=2.0,
OffsetY=0.12,
FlashEffect=true,
Tilt=true,
},
Layer={
Tex={{"",""}},
ScaleHard=false,
Brighten=0.6,
},
u={
Pivot=0.36
},
},
Center={
Tex={{"",""}},
u={
Scale=2.8,
Pulse=true,
Float=true,
FloatFactor=0.5,
FloatXFactor=0,
FloatRotationFactor=0,
FadeEffect=true,
Tilt=false,
Anim=true,
OffsetY=-0.1,
Glow=false,
Rotate=false,
},
LayerEffect={
Tex="",
Fade=true,
Rotate=true,
RotateSpeed=-2.0,
DodgeBlend=true,
Glow=true,
Alpha=0.7,
Scale=0.5,
},
},
Tunnel={ -- todo: give option to do sth with blend mode
Tex={{"",""}},
u={
Stretch=0.3,
ScaleX=0.8,
ScaleY=0.9,
Fog=10.0,
FlashEffect=true,
VortexEffect=false,
VortexFactor=1.0,
DodgeBlend=false
}
},
Particle={
Tex={{"",""}},
u={
Speed=0.3,
OffsetY=-0.02,
Amount=3,
Scale=1.0,
ExtraRotation=0.125
},
},
luaParticleEffect={
particles={
{"star-particle.png", 32}
}
},
speed=0.6,
}
local newFormatTemplate = {
{
Bg={
Base={
Tex={{"",""}},
Tilt=false,
OffsetY=0.17,
Anim=true,
ScaleSoft=false,
ClampTiling=false,
},
Overlay={ -- is meant to be displayed overtop Base
Tex="",
Float=true,
FloatFactor=2.0,
OffsetY=0.12,
FlashEffect=true,
Tilt=true,
},
Layer={
Tex={{"",""}},
ScaleHard=false,
Brighten=0.6,
},
u={
Pivot=0.36
},
},
Center={
Tex={{"",""}},
u={
Scale=2.8,
Pulse=true,
Float=true,
FloatFactor=0.5,
FloatXFactor=0,
FloatRotationFactor=0,
FadeEffect=true,
Tilt=false,
Anim=true,
OffsetY=-0.1,
Glow=false,
Rotate=false,
},
LayerEffect={
Tex="",
Fade=true,
Rotate=true,
RotateSpeed=-2.0,
DodgeBlend=true,
Glow=true,
Alpha=0.7,
Scale=0.5,
},
},
Tunnel={ -- todo: give option to do sth with blend mode
Tex={{"",""}},
u={
Stretch=0.3,
ScaleX=0.8,
ScaleY=0.9,
Fog=10.0,
FlashEffect=true,
VortexEffect=false,
VortexFactor=1.0,
DodgeBlend=false
}
},
Particle={
Tex={{"",""}},
u={
Speed=0.3,
OffsetY=-0.02,
Amount=3,
Scale=1.0,
ExtraRotation=0.125
},
},
luaParticleEffect={
particles={
{"star-particle.png", 32}
}
},
speed=0.6,
}
}

View File

@ -1,68 +1,68 @@
{
"User information": {"type": "label"},
"username": {
"type": "text",
"label": "Username (max 8 characters)",
"default": "GUEST"
},
"separator_a": {},
"MSG": {
"type": "text",
"label": "Message (max 8 characters)",
"default": "Hellooooooo"
},
"separator_b": {},
"Animations": {"type": "label"},
"animations_affectWithBPM": {
"type": "bool",
"label": "Affect speed of some animations with the current song's BPM",
"default": false
},
"separator_c": {},
"Crew": { "type": "label" },
"single_idol": {
"label": "!!!ALWAYS MATCH THE NAME!!!",
"type": "text",
"default": "nothing"
},
"words": {
"type": "selection",
"label": "Language",
"default": "EN",
"values": ["EN", "DE", "SK", "test2"]
},
"separator_d": {},
"Audio": { "type": "label" },
"audio_systemVoice": {
"label": "Turn on Rasis",
"type": "bool",
"default": false
},
"separator_e": {},
"Gameplay": { "type": "label" },
"gameplay_ucDifferentColor": {
"label": "Use different colors for UC and PUC chain numbers",
"type": "bool",
"default": false
},
"separator_f": {},
"Debug": { "type": "label" },
"debug_showInformation": {
"label": "Show debug information (sometimes in the middle of the screen when you're playing)",
"type": "bool",
"default": false
}
}
{
"User information": {"type": "label"},
"username": {
"type": "text",
"label": "Username (max 8 characters)",
"default": "GUEST"
},
"separator_a": {},
"MSG": {
"type": "text",
"label": "Message (max 8 characters)",
"default": "Hellooooooo"
},
"separator_b": {},
"Animations": {"type": "label"},
"animations_affectWithBPM": {
"type": "bool",
"label": "Affect speed of some animations with the current song's BPM",
"default": false
},
"separator_c": {},
"Crew": { "type": "label" },
"single_idol": {
"label": "!!!ALWAYS MATCH THE NAME!!!",
"type": "text",
"default": "nothing"
},
"words": {
"type": "selection",
"label": "Language",
"default": "EN",
"values": ["EN", "DE", "SK", "test2"]
},
"separator_d": {},
"Audio": { "type": "label" },
"audio_systemVoice": {
"label": "Turn on Rasis",
"type": "bool",
"default": false
},
"separator_e": {},
"Gameplay": { "type": "label" },
"gameplay_ucDifferentColor": {
"label": "Use different colors for UC and PUC chain numbers",
"type": "bool",
"default": false
},
"separator_f": {},
"Debug": { "type": "label" },
"debug_showInformation": {
"label": "Show debug information (sometimes in the middle of the screen when you're playing)",
"type": "bool",
"default": false
}
}

View File

@ -1,302 +1,302 @@
local Easing = require("common.easing");
local Footer = require("components.footer");
local DiffRectangle = require('components.diff_rectangle');
local common = require('common.common');
local Numbers = require('common.numbers')
local VolforceWindow = require("components.volforceWindow")
-- Window variables
local resX, resY
-- Aspect Ratios
local landscapeWidescreenRatio = 16 / 9
local landscapeStandardRatio = 4 / 3
local portraitWidescreenRatio = 9 / 16
-- Portrait sizes
local fullX, fullY
local desw = 1080
local desh = 1920
local function resolutionChange(x, y)
resX = x
resY = y
fullX = portraitWidescreenRatio * y
fullY = y
end
local bgSfxPlayed = false;
local BAR_ALPHA = 191;
local HEADER_HEIGHT = 100
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
local resultBgImage = gfx.CreateSkinImage("challenge_result/bg.png", 0);
local playerInfoOverlayBgImage = gfx.CreateSkinImage("challenge_result/player_info_overlay_bg.png", 0);
local headerTitleImage = gfx.CreateSkinImage("challenge_result/header/title.png", 0);
local username = game.GetSkinSetting("username");
local appealCardImage = gfx.CreateSkinImage("crew/appeal_card.png", 0);
local danBadgeImage = gfx.CreateSkinImage("dan/inf.png", 0);
local crewImage = gfx.CreateSkinImage("crew/portrait.png", 0);
local notchesImage = gfx.CreateSkinImage("challenge_result/notches.png", 0);
local trackBarsImage = gfx.CreateSkinImage("challenge_result/track_bars.png", 0);
local completionFailImage = gfx.CreateSkinImage("challenge_result/pass_states/fail.png", 0);
local completionPassImage = gfx.CreateSkinImage("challenge_result/pass_states/pass.png", 0);
local irHeartbeatRequested = false;
local IRserverName = "";
local badgeImages = {
gfx.CreateSkinImage("song_select/medal/nomedal.png", 1),
gfx.CreateSkinImage("song_select/medal/played.png", 1),
gfx.CreateSkinImage("song_select/medal/clear.png", 1),
gfx.CreateSkinImage("song_select/medal/hard.png", 1),
gfx.CreateSkinImage("song_select/medal/uc.png", 1),
gfx.CreateSkinImage("song_select/medal/puc.png", 1),
}
local gradeImages = {
S = gfx.CreateSkinImage("common/grades/S.png", 0),
AAA_P = gfx.CreateSkinImage("common/grades/AAA+.png", 0),
AAA = gfx.CreateSkinImage("common/grades/AAA.png", 0),
AA_P = gfx.CreateSkinImage("common/grades/AA+.png", 0),
AA = gfx.CreateSkinImage("common/grades/AA.png", 0),
A_P = gfx.CreateSkinImage("common/grades/A+.png", 0),
A = gfx.CreateSkinImage("common/grades/A.png", 0),
B = gfx.CreateSkinImage("common/grades/B.png", 0),
C = gfx.CreateSkinImage("common/grades/C.png", 0),
D = gfx.CreateSkinImage("common/grades/D.png", 0),
none = gfx.CreateSkinImage("common/grades/none.png", 0),
}
local percRequired = 1;
local percGet = 0;
-- AUDIO
game.LoadSkinSample("challenge_result.wav")
function resetLayoutInformation()
resx, resy = game.GetResolution()
desw = 1080
desh = 1920
scale = resx / desw
end
local function handleSfx()
if not bgSfxPlayed then
common.stopMusic();
game.PlaySample("challenge_result.wav", true)
bgSfxPlayed = true
end
if game.GetButton(game.BUTTON_STA) then
game.StopSample("challenge_result.wav")
end
if game.GetButton(game.BUTTON_BCK) then
game.StopSample("challenge_result.wav")
end
end
function drawBackground()
gfx.BeginPath()
gfx.ImageRect(0, 0, desw, desh, resultBgImage, 1, 0);
end
function drawHeader()
gfx.BeginPath();
gfx.FillColor(0, 0, 0, BAR_ALPHA);
gfx.Rect(0, 0, desw, HEADER_HEIGHT);
gfx.Fill();
gfx.ClosePath()
gfx.ImageRect(desw / 2 - 209, HEADER_HEIGHT / 2 - 52, 419, 105, headerTitleImage, 1, 0)
end
function drawPlayerInfo()
-- Draw crew
gfx.BeginPath()
gfx.ImageRect(460, 215, 522, 362, crewImage, 1, 0);
-- Draw the info bg
gfx.BeginPath()
gfx.ImageRect(300, 352, 374 * 0.85, 222 * 0.85, playerInfoOverlayBgImage, 1, 0);
-- Draw appeal card
gfx.BeginPath();
gfx.ImageRect(145, 364, 103 * 1.25, 132 * 1.25, appealCardImage, 1, 0);
-- Draw description
gfx.FontSize(28)
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
gfx.Text("Hellooooooo", 310, 370);
-- Draw username
gfx.FontSize(40)
gfx.Text(username, 310, 413);
-- Draw IR server name
gfx.FontSize(28)
gfx.Text(IRserverName, 310, 453);
-- Draw dan badge
gfx.BeginPath();
gfx.ImageRect(311, 490, 107 * 1.25, 29 * 1.25, danBadgeImage, 1, 0);
end
local scoreNumber = Numbers.load_number_image("score_num");
function drawChartResult(deltaTime, x, y, chartResult)
gfx.Save()
gfx.LoadSkinFont('NotoSans-Regular.ttf')
gfx.FontSize(28)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
gfx.BeginPath()
gfx.GlobalAlpha(1);
gfx.Text(chartResult.title, x+160,y+32);
DiffRectangle.render(deltaTime, x+287.5, y+67, 0.85, chartResult.difficulty, chartResult.level)
local score = chartResult.score or 0;
Numbers.draw_number(x + 500, y+80, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.30, 1.12)
Numbers.draw_number(x + 655, y+85, 1.0, score, 4, scoreNumber, true, 0.22, 1.12)
local gradeImageKey = string.gsub(chartResult.grade, '+', '_P');
local gradeImage = gradeImages[gradeImageKey] or gradeImages.D
gfx.BeginPath()
gfx.ImageRect(x+800, y+12, 79, 79, gradeImage, 1, 0)
if chartResult.badge then
local badgeImage = badgeImages[chartResult.badge+1];
gfx.BeginPath()
gfx.ImageRect(x+900, y+16, 79*1.05, 69*1.05, badgeImage, 1, 0)
end
gfx.Restore()
end
function drawScorePanelContent(deltaTime)
-- game.Log("Drawing scores...", game.LOGGER_INFO) -- debug
for i, chart in ipairs(result.charts) do
-- if chart.score == nil then
-- game.Log("Score does not exist? Quitting loop...", game.LOGGER_WARNING)
-- break
-- end
drawChartResult(deltaTime, 0, 836+(165*(i-1)), chart);
end
end
function drawDecorations()
gfx.BeginPath()
gfx.ImageRect(118, 846.5, 43*0.855, 429*0.855, notchesImage, 1, 0)
gfx.BeginPath()
gfx.ImageRect(400, 807, 367*0.857, 429*0.857, trackBarsImage, 1, 0)
end
function drawCompletion()
local completitionImage = completionFailImage;
if result.passed then
completitionImage = completionPassImage;
end
gfx.BeginPath()
gfx.ImageRect(63, 1331, 766*0.85, 130*0.85, completitionImage, 1, 0)
Numbers.draw_number(925, 1370, 1.0, percGet, 3, scoreNumber, true, 0.3, 1.12)
gfx.BeginPath();
gfx.Rect(741, 1402, 278*math.min(1, percGet / percRequired), 6);
gfx.FillColor(255, 128, 0, 255);
gfx.Fill()
end
function result_set()
local reqTextWords = common.splitString(result.requirement_text, ' ');
for index, word in ipairs(reqTextWords) do
if string.find(word, '%%') ~= nil then -- %% = %, because % is an escape char
local percNumber = tonumber(string.gsub(word, '%%', ''), 10)
percRequired = percNumber;
end
end
game.Log(percRequired, game.LOGGER_ERROR);
local a = 0;
for i, chart in ipairs(result.charts) do
a = a + chart.percent;
game.Log('#' .. i .. ' got ' .. chart.percent .. '% // ACC at ' .. a, game.LOGGER_ERROR);
end
percGet = a / #result.charts;
end
local IR_HeartbeatResponse = function(res)
if res.statusCode == IRData.States.Success then
IRserverName = res.body.serverName .. " " .. res.body.irVersion;
else
game.Log("Can't connect to IR!", game.LOGGER_WARNING)
end
end
local IR_Handle = function()
if not irHeartbeatRequested then
IR.Heartbeat(IR_HeartbeatResponse)
irHeartbeatRequested = true;
end
end
local function drawResultScreen(x, y, w, h, deltaTime)
gfx.BeginPath()
gfx.Translate(x, y);
gfx.Scale(w / 1080, h / 1920);
gfx.Scissor(0, 0, 1080, 1920);
handleSfx()
IR_Handle()
drawBackground()
drawDecorations()
drawPlayerInfo()
drawScorePanelContent(deltaTime)
drawCompletion()
drawHeader()
Footer.draw(deltaTime);
gfx.ResetTransform()
end
function render(deltaTime)
-- detect resolution change
local resx, resy = game.GetResolution()
if resx ~= resX or resy ~= resY then
resolutionChange(resx, resy)
end
gfx.BeginPath()
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
gfx.Rect(0, 0, resX, resY)
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
gfx.Fill()
drawResultScreen((resX - fullX) / 2, 0, fullX, fullY, deltaTime)
end
local Easing = require("common.easing");
local Footer = require("components.footer");
local DiffRectangle = require('components.diff_rectangle');
local common = require('common.common');
local Numbers = require('common.numbers')
local VolforceWindow = require("components.volforceWindow")
-- Window variables
local resX, resY
-- Aspect Ratios
local landscapeWidescreenRatio = 16 / 9
local landscapeStandardRatio = 4 / 3
local portraitWidescreenRatio = 9 / 16
-- Portrait sizes
local fullX, fullY
local desw = 1080
local desh = 1920
local function resolutionChange(x, y)
resX = x
resY = y
fullX = portraitWidescreenRatio * y
fullY = y
end
local bgSfxPlayed = false;
local BAR_ALPHA = 191;
local HEADER_HEIGHT = 100
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
local resultBgImage = gfx.CreateSkinImage("challenge_result/bg.png", 0);
local playerInfoOverlayBgImage = gfx.CreateSkinImage("challenge_result/player_info_overlay_bg.png", 0);
local headerTitleImage = gfx.CreateSkinImage("challenge_result/header/title.png", 0);
local username = game.GetSkinSetting("username");
local appealCardImage = gfx.CreateSkinImage("crew/appeal_card.png", 0);
local danBadgeImage = gfx.CreateSkinImage("dan/inf.png", 0);
local crewImage = gfx.CreateSkinImage("crew/portrait.png", 0);
local notchesImage = gfx.CreateSkinImage("challenge_result/notches.png", 0);
local trackBarsImage = gfx.CreateSkinImage("challenge_result/track_bars.png", 0);
local completionFailImage = gfx.CreateSkinImage("challenge_result/pass_states/fail.png", 0);
local completionPassImage = gfx.CreateSkinImage("challenge_result/pass_states/pass.png", 0);
local irHeartbeatRequested = false;
local IRserverName = "";
local badgeImages = {
gfx.CreateSkinImage("song_select/medal/nomedal.png", 1),
gfx.CreateSkinImage("song_select/medal/played.png", 1),
gfx.CreateSkinImage("song_select/medal/clear.png", 1),
gfx.CreateSkinImage("song_select/medal/hard.png", 1),
gfx.CreateSkinImage("song_select/medal/uc.png", 1),
gfx.CreateSkinImage("song_select/medal/puc.png", 1),
}
local gradeImages = {
S = gfx.CreateSkinImage("common/grades/S.png", 0),
AAA_P = gfx.CreateSkinImage("common/grades/AAA+.png", 0),
AAA = gfx.CreateSkinImage("common/grades/AAA.png", 0),
AA_P = gfx.CreateSkinImage("common/grades/AA+.png", 0),
AA = gfx.CreateSkinImage("common/grades/AA.png", 0),
A_P = gfx.CreateSkinImage("common/grades/A+.png", 0),
A = gfx.CreateSkinImage("common/grades/A.png", 0),
B = gfx.CreateSkinImage("common/grades/B.png", 0),
C = gfx.CreateSkinImage("common/grades/C.png", 0),
D = gfx.CreateSkinImage("common/grades/D.png", 0),
none = gfx.CreateSkinImage("common/grades/none.png", 0),
}
local percRequired = 1;
local percGet = 0;
-- AUDIO
game.LoadSkinSample("challenge_result.wav")
function resetLayoutInformation()
resx, resy = game.GetResolution()
desw = 1080
desh = 1920
scale = resx / desw
end
local function handleSfx()
if not bgSfxPlayed then
common.stopMusic();
game.PlaySample("challenge_result.wav", true)
bgSfxPlayed = true
end
if game.GetButton(game.BUTTON_STA) then
game.StopSample("challenge_result.wav")
end
if game.GetButton(game.BUTTON_BCK) then
game.StopSample("challenge_result.wav")
end
end
function drawBackground()
gfx.BeginPath()
gfx.ImageRect(0, 0, desw, desh, resultBgImage, 1, 0);
end
function drawHeader()
gfx.BeginPath();
gfx.FillColor(0, 0, 0, BAR_ALPHA);
gfx.Rect(0, 0, desw, HEADER_HEIGHT);
gfx.Fill();
gfx.ClosePath()
gfx.ImageRect(desw / 2 - 209, HEADER_HEIGHT / 2 - 52, 419, 105, headerTitleImage, 1, 0)
end
function drawPlayerInfo()
-- Draw crew
gfx.BeginPath()
gfx.ImageRect(460, 215, 522, 362, crewImage, 1, 0);
-- Draw the info bg
gfx.BeginPath()
gfx.ImageRect(300, 352, 374 * 0.85, 222 * 0.85, playerInfoOverlayBgImage, 1, 0);
-- Draw appeal card
gfx.BeginPath();
gfx.ImageRect(145, 364, 103 * 1.25, 132 * 1.25, appealCardImage, 1, 0);
-- Draw description
gfx.FontSize(28)
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
gfx.Text("Hellooooooo", 310, 370);
-- Draw username
gfx.FontSize(40)
gfx.Text(username, 310, 413);
-- Draw IR server name
gfx.FontSize(28)
gfx.Text(IRserverName, 310, 453);
-- Draw dan badge
gfx.BeginPath();
gfx.ImageRect(311, 490, 107 * 1.25, 29 * 1.25, danBadgeImage, 1, 0);
end
local scoreNumber = Numbers.load_number_image("score_num");
function drawChartResult(deltaTime, x, y, chartResult)
gfx.Save()
gfx.LoadSkinFont('NotoSans-Regular.ttf')
gfx.FontSize(28)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
gfx.BeginPath()
gfx.GlobalAlpha(1);
gfx.Text(chartResult.title, x+160,y+32);
DiffRectangle.render(deltaTime, x+287.5, y+67, 0.85, chartResult.difficulty, chartResult.level)
local score = chartResult.score or 0;
Numbers.draw_number(x + 500, y+80, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.30, 1.12)
Numbers.draw_number(x + 655, y+85, 1.0, score, 4, scoreNumber, true, 0.22, 1.12)
local gradeImageKey = string.gsub(chartResult.grade, '+', '_P');
local gradeImage = gradeImages[gradeImageKey] or gradeImages.D
gfx.BeginPath()
gfx.ImageRect(x+800, y+12, 79, 79, gradeImage, 1, 0)
if chartResult.badge then
local badgeImage = badgeImages[chartResult.badge+1];
gfx.BeginPath()
gfx.ImageRect(x+900, y+16, 79*1.05, 69*1.05, badgeImage, 1, 0)
end
gfx.Restore()
end
function drawScorePanelContent(deltaTime)
-- game.Log("Drawing scores...", game.LOGGER_INFO) -- debug
for i, chart in ipairs(result.charts) do
-- if chart.score == nil then
-- game.Log("Score does not exist? Quitting loop...", game.LOGGER_WARNING)
-- break
-- end
drawChartResult(deltaTime, 0, 836+(165*(i-1)), chart);
end
end
function drawDecorations()
gfx.BeginPath()
gfx.ImageRect(118, 846.5, 43*0.855, 429*0.855, notchesImage, 1, 0)
gfx.BeginPath()
gfx.ImageRect(400, 807, 367*0.857, 429*0.857, trackBarsImage, 1, 0)
end
function drawCompletion()
local completitionImage = completionFailImage;
if result.passed then
completitionImage = completionPassImage;
end
gfx.BeginPath()
gfx.ImageRect(63, 1331, 766*0.85, 130*0.85, completitionImage, 1, 0)
Numbers.draw_number(925, 1370, 1.0, percGet, 3, scoreNumber, true, 0.3, 1.12)
gfx.BeginPath();
gfx.Rect(741, 1402, 278*math.min(1, percGet / percRequired), 6);
gfx.FillColor(255, 128, 0, 255);
gfx.Fill()
end
function result_set()
local reqTextWords = common.splitString(result.requirement_text, ' ');
for index, word in ipairs(reqTextWords) do
if string.find(word, '%%') ~= nil then -- %% = %, because % is an escape char
local percNumber = tonumber(string.gsub(word, '%%', ''), 10)
percRequired = percNumber;
end
end
game.Log(percRequired, game.LOGGER_ERROR);
local a = 0;
for i, chart in ipairs(result.charts) do
a = a + chart.percent;
game.Log('#' .. i .. ' got ' .. chart.percent .. '% // ACC at ' .. a, game.LOGGER_ERROR);
end
percGet = a / #result.charts;
end
local IR_HeartbeatResponse = function(res)
if res.statusCode == IRData.States.Success then
IRserverName = res.body.serverName .. " " .. res.body.irVersion;
else
game.Log("Can't connect to IR!", game.LOGGER_WARNING)
end
end
local IR_Handle = function()
if not irHeartbeatRequested then
IR.Heartbeat(IR_HeartbeatResponse)
irHeartbeatRequested = true;
end
end
local function drawResultScreen(x, y, w, h, deltaTime)
gfx.BeginPath()
gfx.Translate(x, y);
gfx.Scale(w / 1080, h / 1920);
gfx.Scissor(0, 0, 1080, 1920);
handleSfx()
IR_Handle()
drawBackground()
drawDecorations()
drawPlayerInfo()
drawScorePanelContent(deltaTime)
drawCompletion()
drawHeader()
Footer.draw(deltaTime);
gfx.ResetTransform()
end
function render(deltaTime)
-- detect resolution change
local resx, resy = game.GetResolution()
if resx ~= resX or resy ~= resY then
resolutionChange(resx, resy)
end
gfx.BeginPath()
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
gfx.Rect(0, 0, resX, resY)
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
gfx.Fill()
drawResultScreen((resX - fullX) / 2, 0, fullX, fullY, deltaTime)
end

View File

@ -1,107 +1,107 @@
options = {}
yscale = 0.0
selectedIndex = 0
resx, resy = game.GetResolution()
width = 500
titleText = ""
scale = 1.0
function make_option(name)
return function()
menu.Confirm(name)
end
end
function open()
yscale = 0.0
options = {}
selectedIndex = 0
resx, resy = game.GetResolution()
index = 1
if #dialog.collections == 0 then
options[index] = {"Favourites", make_option("Favourites"), {255,255,255}}
end
for i,value in ipairs(dialog.collections) do
options[i] = {value.name, make_option(value.name), {255,255,255}}
if value.exists then options[i][3] = {255,0,0} end
end
table.insert(options, {"New Collection", menu.ChangeState, {0, 255, 128}})
table.insert(options, {"Cancel", menu.Cancel, {200,200,200}})
gfx.FontFace("fallback")
gfx.FontSize(50)
titleText = string.format("Add %s to collection:", dialog.title)
xmi,ymi,xma,yma = gfx.TextBounds(0,0, titleText)
width = xma - xmi + 50
width = math.max(500, width)
scale = math.min(resy / 1280, 1.0)
scale = math.min(scale, resx / width)
end
function render(deltaTime)
if dialog.closing then
yscale = math.min(yscale - deltaTime * 4, 1.0)
else
yscale = math.min(yscale + deltaTime * 4, 1.0)
end
gfx.Translate(resx / 2, resy / 2)
gfx.Scale(scale, yscale * scale)
gfx.BeginPath()
gfx.Rect(-width/2, -250, width, 500)
gfx.FillColor(50,50,50)
gfx.Fill()
gfx.BeginPath()
gfx.FillColor(255,255,255)
gfx.FontFace("fallback")
gfx.FontSize(50)
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_CENTER)
gfx.Text(titleText, 0, -240)
if dialog.isTextEntry then
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER)
gfx.BeginPath()
gfx.Rect(-width/2 + 20, -30, width - 40, 60)
gfx.FillColor(25,25,25)
gfx.Fill()
gfx.BeginPath()
gfx.FillColor(255,255,255)
gfx.Text(dialog.newName, 0, 0)
else
local yshift = 20
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT)
for i, option in ipairs(options) do
local y = yshift + 60 * ((i-1) - selectedIndex)
if y > -190 and y < 220 then
gfx.FillColor(option[3][1], option[3][2], option[3][3])
gfx.Text(option[1], 40 - width/2, y)
end
end
gfx.BeginPath()
gfx.MoveTo(20 - width/2, -10 + yshift)
gfx.LineTo(30 - width/2, 0 + yshift)
gfx.LineTo(20 - width/2, 10 + yshift)
gfx.FillColor(255,255,255)
gfx.Fill()
end
if dialog.closing == true and yscale <= 0.0 then
return false
else
return true
end
end
function button_pressed(button)
if button == game.BUTTON_BCK then
menu.Cancel()
elseif button == game.BUTTON_STA then
options[selectedIndex+1][2]()
end
end
function advance_selection(value)
selectedIndex = (selectedIndex + value) % #options
options = {}
yscale = 0.0
selectedIndex = 0
resx, resy = game.GetResolution()
width = 500
titleText = ""
scale = 1.0
function make_option(name)
return function()
menu.Confirm(name)
end
end
function open()
yscale = 0.0
options = {}
selectedIndex = 0
resx, resy = game.GetResolution()
index = 1
if #dialog.collections == 0 then
options[index] = {"Favourites", make_option("Favourites"), {255,255,255}}
end
for i,value in ipairs(dialog.collections) do
options[i] = {value.name, make_option(value.name), {255,255,255}}
if value.exists then options[i][3] = {255,0,0} end
end
table.insert(options, {"New Collection", menu.ChangeState, {0, 255, 128}})
table.insert(options, {"Cancel", menu.Cancel, {200,200,200}})
gfx.FontFace("fallback")
gfx.FontSize(50)
titleText = string.format("Add %s to collection:", dialog.title)
xmi,ymi,xma,yma = gfx.TextBounds(0,0, titleText)
width = xma - xmi + 50
width = math.max(500, width)
scale = math.min(resy / 1280, 1.0)
scale = math.min(scale, resx / width)
end
function render(deltaTime)
if dialog.closing then
yscale = math.min(yscale - deltaTime * 4, 1.0)
else
yscale = math.min(yscale + deltaTime * 4, 1.0)
end
gfx.Translate(resx / 2, resy / 2)
gfx.Scale(scale, yscale * scale)
gfx.BeginPath()
gfx.Rect(-width/2, -250, width, 500)
gfx.FillColor(50,50,50)
gfx.Fill()
gfx.BeginPath()
gfx.FillColor(255,255,255)
gfx.FontFace("fallback")
gfx.FontSize(50)
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_CENTER)
gfx.Text(titleText, 0, -240)
if dialog.isTextEntry then
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER)
gfx.BeginPath()
gfx.Rect(-width/2 + 20, -30, width - 40, 60)
gfx.FillColor(25,25,25)
gfx.Fill()
gfx.BeginPath()
gfx.FillColor(255,255,255)
gfx.Text(dialog.newName, 0, 0)
else
local yshift = 20
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT)
for i, option in ipairs(options) do
local y = yshift + 60 * ((i-1) - selectedIndex)
if y > -190 and y < 220 then
gfx.FillColor(option[3][1], option[3][2], option[3][3])
gfx.Text(option[1], 40 - width/2, y)
end
end
gfx.BeginPath()
gfx.MoveTo(20 - width/2, -10 + yshift)
gfx.LineTo(30 - width/2, 0 + yshift)
gfx.LineTo(20 - width/2, 10 + yshift)
gfx.FillColor(255,255,255)
gfx.Fill()
end
if dialog.closing == true and yscale <= 0.0 then
return false
else
return true
end
end
function button_pressed(button)
if button == game.BUTTON_BCK then
menu.Cancel()
elseif button == game.BUTTON_STA then
options[selectedIndex+1][2]()
end
end
function advance_selection(value)
selectedIndex = (selectedIndex + value) % #options
end

View File

@ -1,429 +1,429 @@
json = require "json"
local header = {}
header["user-agent"] = "unnamed_sdvx_clone"
local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0)
local diffColors = {{50,50,127}, {50,127,50}, {127,50,50}, {127, 50, 127}}
local entryW = 770
local entryH = 320
local resX,resY = game.GetResolution()
local xCount = math.max(1, math.floor(resX / entryW))
local yCount = math.max(1, math.floor(resY / entryH))
local xOffset = (resX - xCount * entryW) / 2
local cursorPos = 0
local cursorPosX = 0
local cursorPosY = 0
local displayCursorPosX = 0
local displayCursorPosY = 0
local nextUrl = "https://ksm.dev/app/songs"
local screenState = 0 --0 = normal, 1 = level, 2 = sorting
local loading = true
local downloaded = {}
local songs = {}
local selectedLevels = {}
local selectedSorting = "Uploaded"
local lastPlaying = nil
for i = 1, 20 do
selectedLevels[i] = false
end
local cachepath = path.Absolute("skins/" .. game.GetSkin() .. "/nautica.json")
local levelcursor = 0
local sortingcursor = 0
local sortingOptions = {"Uploaded", "Oldest"}
local needsReload = false
function addsong(song)
if song.jacket_url ~= nil then
song.jacket = gfx.LoadWebImageJob(song.jacket_url, jacketFallback, 250, 250)
else
song.jacket = jacketFallback
end
if downloaded[song.id] then
song.status = "Downloaded"
end
table.insert(songs, song)
end
local yOffset = 0
local backgroundImage = gfx.CreateSkinImage("bg.png", 1);
dlcache = io.open(cachepath, "r")
if dlcache then
downloaded = json.decode(dlcache:read("*all"))
dlcache:close()
end
function encodeURI(str)
if (str) then
str = string.gsub(str, "\n", "\r\n")
str = string.gsub(str, "([^%w ])",
function (c)
local dontChange = "-/_:."
for i = 1, #dontChange do
if c == dontChange:sub(i,i) then return c end
end
return string.format ("%%%02X", string.byte(c))
end)
str = string.gsub(str, " ", "%%20")
end
return str
end
function gotSongsCallback(response)
if response.status ~= 200 then
error()
return
end
local jsondata = json.decode(response.text)
for i,song in ipairs(jsondata.data) do
addsong(song)
end
nextUrl = jsondata.links.next
loading = false
end
Http.GetAsync(nextUrl, header, gotSongsCallback)
function render_song(song, x,y)
gfx.Save()
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
gfx.Translate(x,y)
gfx.Scissor(0,0,750,300)
gfx.BeginPath()
gfx.FillColor(0,0,0,140)
gfx.Rect(0,0,750,300)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.FontSize(30)
gfx.Text(song.title, 2,2)
gfx.FontSize(24)
gfx.Text(song.artist, 2,26)
if song.jacket_url ~= nil and song.jacket == jacketFallback then
song.jacket = gfx.LoadWebImageJob(song.jacket_url, jacketFallback, 250, 250)
end
gfx.BeginPath()
gfx.ImageRect(0, 50, 250, 250, song.jacket, 1, 0)
gfx.BeginPath()
gfx.Rect(250,50,500,250)
gfx.FillColor(55,55,55,128)
gfx.Fill()
for i, diff in ipairs(song.charts) do
local col = diffColors[diff.difficulty]
local diffY = 50 + 250/4 * (diff.difficulty - 1)
gfx.BeginPath()
gfx.Rect(250,diffY, 500, 250 / 4)
gfx.FillColor(col[1], col[2], col[3])
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.FontSize(40)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
gfx.Text(string.format("%d Effected by %s", diff.level, diff.effector), 255, diffY + 250 / 8)
end
if downloaded[song.id] then
gfx.BeginPath()
gfx.Rect(0,0,750,300)
gfx.FillColor(0,0,0,127)
gfx.Fill()
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.FontSize(60)
gfx.FillColor(255,255,255)
gfx.Text(downloaded[song.id], 375, 150)
elseif song.status then
gfx.BeginPath()
gfx.Rect(0,0,750,300)
gfx.FillColor(0,0,0,127)
gfx.Fill()
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.FontSize(60)
gfx.FillColor(255,255,255)
gfx.Text(song.status, 375, 150)
end
gfx.ResetScissor()
gfx.Restore()
end
function load_more()
if nextUrl ~= nil and not loading then
Http.GetAsync(nextUrl, header, gotSongsCallback)
loading = true
end
end
function render_cursor()
local x = displayCursorPosX * entryW
local y = displayCursorPosY * entryH
gfx.BeginPath()
gfx.Rect(x,y,750,300)
gfx.StrokeColor(255,128,0)
gfx.StrokeWidth(5)
gfx.Stroke()
end
function render_loading()
if not loading then return end
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.MoveTo(resX, resY)
gfx.LineTo(resX - 350, resY)
gfx.LineTo(resX - 300, resY - 50)
gfx.LineTo(resX, resY - 50)
gfx.ClosePath()
gfx.FillColor(33,33,33)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT, gfx.TEXT_ALIGN_BOTTOM)
gfx.FontSize(70)
gfx.Text("LOADING...", resX - 20, resY - 3)
gfx.Restore()
end
function render_hotkeys()
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.FillColor(0,0,0,240)
gfx.Rect(0,resY - 50, resX, 50)
gfx.Fill()
gfx.FontSize(30)
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_BOTTOM)
gfx.Text("FXR: Sorting", resX/2 + 20, resY - 10)
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT, gfx.TEXT_ALIGN_BOTTOM)
gfx.Text("FXL: Levels", resX/2 - 20, resY - 10)
gfx.Restore()
end
function render_info()
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.MoveTo(0, resY)
gfx.LineTo(350, resY)
gfx.LineTo(300, resY - 50)
gfx.LineTo(0, resY - 50)
gfx.ClosePath()
gfx.FillColor(33,33,33)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_BOTTOM)
gfx.FontSize(70)
gfx.Text("Nautica", 3, resY - 3)
local xmin,ymin,xmax,ymax = gfx.TextBounds(3, resY - 3, "Nautica")
gfx.FontSize(20)
gfx.Text("https://ksm.dev/", xmax + 13, resY - 3)
gfx.Restore()
end
function render(deltaTime)
gfx.BeginPath()
gfx.ImageRect(0, 0, resX, resY, backgroundImage, 1, 0);
gfx.LoadSkinFont("NotoSans-Regular.ttf");
displayCursorPosX = displayCursorPosX - (displayCursorPosX - cursorPosX) * deltaTime * 10
displayCursorPosY = displayCursorPosY - (displayCursorPosY - cursorPosY) * deltaTime * 10
if displayCursorPosY - yOffset > yCount - 1 then --scrolling down
yOffset = yOffset - (yOffset - displayCursorPosY) - yCount + 1
elseif displayCursorPosY - yOffset < 0 then
yOffset = yOffset - (yOffset - displayCursorPosY)
end
gfx.Translate(xOffset, 50 - yOffset * entryH)
for i, song in ipairs(songs) do
if math.abs(cursorPos - i) <= xCount * yCount + xCount then
i = i - 1
local x = entryW * (i % xCount)
local y = math.floor(i / xCount) * entryH
render_song(song, x, y)
if math.abs(#songs - i) < 4 then load_more() end
end
end
render_cursor()
if needsReload then reload_songs() end
if screenState == 1 then render_level_filters()
elseif screenState == 2 then render_sorting_selection()
end
render_hotkeys()
render_loading()
render_info()
end
function archive_callback(entries, id)
game.Log("Listing entries for " .. id, 0)
local songsfolder = dlScreen.GetSongsPath()
res = {}
folders = { songsfolder .. "/nautica/" }
local hasFolder = false
for i, entry in ipairs(entries) do
for j = 1, #entry do
if entry:sub(j,j) == '/' then
hasFolder = true
table.insert(folders, songsfolder .. "/nautica/" .. entry:sub(1,j))
end
end
game.Log(entry, 0)
res[entry] = songsfolder .. "/nautica/" .. entry
end
if not hasFolder then
for i, entry in ipairs(entries) do
res[entry] = songsfolder .. "/nautica/" .. id .. "/" .. entry
end
table.insert(folders, songsfolder .. "/nautica/" .. id .. "/")
end
downloaded[id] = "Downloaded"
res[".folders"] = table.concat(folders, "|")
return res
end
function reload_songs()
needsReload = true
if loading then return end
local useLevels = false
local levelarr = {}
for i,value in ipairs(selectedLevels) do
if value then
useLevels = true
table.insert(levelarr, i)
end
end
nextUrl = string.format("https://ksm.dev/app/songs?sort=%s", selectedSorting:lower())
if useLevels then
nextUrl = nextUrl .. "&levels=" .. table.concat(levelarr, ",")
end
songs = {}
cursorPos = 0
cursorPosX = 0
cursorPosY = 0
displayCursorPosX = 0
displayCursorPosY = 0
load_more()
game.Log(nextUrl, 0)
needsReload = false
end
function button_pressed(button)
if button == game.BUTTON_STA then
if screenState == 0 then
local song = songs[cursorPos + 1]
if song == nil then return end
dlScreen.DownloadArchive(encodeURI(song.cdn_download_url), header, song.id, archive_callback)
downloaded[song.id] = "Downloading..."
elseif screenState == 1 then
if selectedLevels[levelcursor + 1] then
selectedLevels[levelcursor + 1] = false
else
selectedLevels[levelcursor + 1] = true
end
reload_songs()
elseif screenState == 2 then
selectedSorting = sortingOptions[sortingcursor + 1]
reload_songs()
end
elseif button == game.BUTTON_BTA then
if screenState == 0 then
local song = songs[cursorPos + 1]
if song == nil then return end
dlScreen.PlayPreview(encodeURI(song.preview_url), header, song.id)
song.status = "Playing"
if lastPlaying ~=nil then
lastPlaying.status = nil
end
lastPlaying = song
end
elseif button == game.BUTTON_FXL then
if screenState ~= 1 then
screenState = 1
else
screenState = 0
end
elseif button == game.BUTTON_FXR then
if screenState ~= 2 then
screenState = 2
else
screenState = 0
end
end
end
function key_pressed(key)
if key == 27 then --escape pressed
dlcache = io.open(cachepath, "w")
dlcache:write(json.encode(downloaded))
dlcache:close()
dlScreen.Exit()
end
end
function advance_selection(steps)
if screenState == 0 and #songs > 0 then
cursorPos = (cursorPos + steps) % #songs
cursorPosX = cursorPos % xCount
cursorPosY = math.floor(cursorPos / xCount)
if cursorPos > #songs - 6 then
load_more()
end
elseif screenState == 1 then
levelcursor = (levelcursor + steps) % 20
elseif screenState == 2 then
sortingcursor = (sortingcursor + steps) % #sortingOptions
end
end
function render_level_filters()
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.Rect(0,0, resX, resY)
gfx.FillColor(0,0,0,200)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
gfx.FontSize(60)
gfx.Text("Level filters:", 10, 10)
gfx.BeginPath()
gfx.Rect(resX/2 - 30, resY/2 - 22, 60, 44)
gfx.StrokeColor(255,128,0)
gfx.StrokeWidth(2)
gfx.Stroke()
gfx.FontSize(40)
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
for i = 1, 20 do
y = (resY/2) + (i - (levelcursor + 1)) * 40
if selectedLevels[i] then gfx.FillColor(255,255,255) else gfx.FillColor(127,127,127) end
gfx.Text(tostring(i), resX/2, y)
end
gfx.Restore()
end
function render_sorting_selection()
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.Rect(0,0, resX, resY)
gfx.FillColor(0,0,0,200)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
gfx.FontSize(60)
gfx.Text("Sorting method:", 10, 10)
gfx.BeginPath()
gfx.Rect(resX/2 - 75, resY/2 - 22, 150, 44)
gfx.StrokeColor(255,128,0)
gfx.StrokeWidth(2)
gfx.Stroke()
gfx.FontSize(40)
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
for i, opt in ipairs(sortingOptions) do
y = (resY/2) + (i - (sortingcursor + 1)) * 40
if selectedSorting == opt then gfx.FillColor(255,255,255) else gfx.FillColor(127,127,127) end
gfx.Text(opt, resX/2, y)
end
gfx.Restore()
json = require "json"
local header = {}
header["user-agent"] = "unnamed_sdvx_clone"
local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0)
local diffColors = {{50,50,127}, {50,127,50}, {127,50,50}, {127, 50, 127}}
local entryW = 770
local entryH = 320
local resX,resY = game.GetResolution()
local xCount = math.max(1, math.floor(resX / entryW))
local yCount = math.max(1, math.floor(resY / entryH))
local xOffset = (resX - xCount * entryW) / 2
local cursorPos = 0
local cursorPosX = 0
local cursorPosY = 0
local displayCursorPosX = 0
local displayCursorPosY = 0
local nextUrl = "https://ksm.dev/app/songs"
local screenState = 0 --0 = normal, 1 = level, 2 = sorting
local loading = true
local downloaded = {}
local songs = {}
local selectedLevels = {}
local selectedSorting = "Uploaded"
local lastPlaying = nil
for i = 1, 20 do
selectedLevels[i] = false
end
local cachepath = path.Absolute("skins/" .. game.GetSkin() .. "/nautica.json")
local levelcursor = 0
local sortingcursor = 0
local sortingOptions = {"Uploaded", "Oldest"}
local needsReload = false
function addsong(song)
if song.jacket_url ~= nil then
song.jacket = gfx.LoadWebImageJob(song.jacket_url, jacketFallback, 250, 250)
else
song.jacket = jacketFallback
end
if downloaded[song.id] then
song.status = "Downloaded"
end
table.insert(songs, song)
end
local yOffset = 0
local backgroundImage = gfx.CreateSkinImage("bg.png", 1);
dlcache = io.open(cachepath, "r")
if dlcache then
downloaded = json.decode(dlcache:read("*all"))
dlcache:close()
end
function encodeURI(str)
if (str) then
str = string.gsub(str, "\n", "\r\n")
str = string.gsub(str, "([^%w ])",
function (c)
local dontChange = "-/_:."
for i = 1, #dontChange do
if c == dontChange:sub(i,i) then return c end
end
return string.format ("%%%02X", string.byte(c))
end)
str = string.gsub(str, " ", "%%20")
end
return str
end
function gotSongsCallback(response)
if response.status ~= 200 then
error()
return
end
local jsondata = json.decode(response.text)
for i,song in ipairs(jsondata.data) do
addsong(song)
end
nextUrl = jsondata.links.next
loading = false
end
Http.GetAsync(nextUrl, header, gotSongsCallback)
function render_song(song, x,y)
gfx.Save()
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
gfx.Translate(x,y)
gfx.Scissor(0,0,750,300)
gfx.BeginPath()
gfx.FillColor(0,0,0,140)
gfx.Rect(0,0,750,300)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.FontSize(30)
gfx.Text(song.title, 2,2)
gfx.FontSize(24)
gfx.Text(song.artist, 2,26)
if song.jacket_url ~= nil and song.jacket == jacketFallback then
song.jacket = gfx.LoadWebImageJob(song.jacket_url, jacketFallback, 250, 250)
end
gfx.BeginPath()
gfx.ImageRect(0, 50, 250, 250, song.jacket, 1, 0)
gfx.BeginPath()
gfx.Rect(250,50,500,250)
gfx.FillColor(55,55,55,128)
gfx.Fill()
for i, diff in ipairs(song.charts) do
local col = diffColors[diff.difficulty]
local diffY = 50 + 250/4 * (diff.difficulty - 1)
gfx.BeginPath()
gfx.Rect(250,diffY, 500, 250 / 4)
gfx.FillColor(col[1], col[2], col[3])
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.FontSize(40)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
gfx.Text(string.format("%d Effected by %s", diff.level, diff.effector), 255, diffY + 250 / 8)
end
if downloaded[song.id] then
gfx.BeginPath()
gfx.Rect(0,0,750,300)
gfx.FillColor(0,0,0,127)
gfx.Fill()
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.FontSize(60)
gfx.FillColor(255,255,255)
gfx.Text(downloaded[song.id], 375, 150)
elseif song.status then
gfx.BeginPath()
gfx.Rect(0,0,750,300)
gfx.FillColor(0,0,0,127)
gfx.Fill()
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.FontSize(60)
gfx.FillColor(255,255,255)
gfx.Text(song.status, 375, 150)
end
gfx.ResetScissor()
gfx.Restore()
end
function load_more()
if nextUrl ~= nil and not loading then
Http.GetAsync(nextUrl, header, gotSongsCallback)
loading = true
end
end
function render_cursor()
local x = displayCursorPosX * entryW
local y = displayCursorPosY * entryH
gfx.BeginPath()
gfx.Rect(x,y,750,300)
gfx.StrokeColor(255,128,0)
gfx.StrokeWidth(5)
gfx.Stroke()
end
function render_loading()
if not loading then return end
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.MoveTo(resX, resY)
gfx.LineTo(resX - 350, resY)
gfx.LineTo(resX - 300, resY - 50)
gfx.LineTo(resX, resY - 50)
gfx.ClosePath()
gfx.FillColor(33,33,33)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT, gfx.TEXT_ALIGN_BOTTOM)
gfx.FontSize(70)
gfx.Text("LOADING...", resX - 20, resY - 3)
gfx.Restore()
end
function render_hotkeys()
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.FillColor(0,0,0,240)
gfx.Rect(0,resY - 50, resX, 50)
gfx.Fill()
gfx.FontSize(30)
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_BOTTOM)
gfx.Text("FXR: Sorting", resX/2 + 20, resY - 10)
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT, gfx.TEXT_ALIGN_BOTTOM)
gfx.Text("FXL: Levels", resX/2 - 20, resY - 10)
gfx.Restore()
end
function render_info()
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.MoveTo(0, resY)
gfx.LineTo(350, resY)
gfx.LineTo(300, resY - 50)
gfx.LineTo(0, resY - 50)
gfx.ClosePath()
gfx.FillColor(33,33,33)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_BOTTOM)
gfx.FontSize(70)
gfx.Text("Nautica", 3, resY - 3)
local xmin,ymin,xmax,ymax = gfx.TextBounds(3, resY - 3, "Nautica")
gfx.FontSize(20)
gfx.Text("https://ksm.dev/", xmax + 13, resY - 3)
gfx.Restore()
end
function render(deltaTime)
gfx.BeginPath()
gfx.ImageRect(0, 0, resX, resY, backgroundImage, 1, 0);
gfx.LoadSkinFont("NotoSans-Regular.ttf");
displayCursorPosX = displayCursorPosX - (displayCursorPosX - cursorPosX) * deltaTime * 10
displayCursorPosY = displayCursorPosY - (displayCursorPosY - cursorPosY) * deltaTime * 10
if displayCursorPosY - yOffset > yCount - 1 then --scrolling down
yOffset = yOffset - (yOffset - displayCursorPosY) - yCount + 1
elseif displayCursorPosY - yOffset < 0 then
yOffset = yOffset - (yOffset - displayCursorPosY)
end
gfx.Translate(xOffset, 50 - yOffset * entryH)
for i, song in ipairs(songs) do
if math.abs(cursorPos - i) <= xCount * yCount + xCount then
i = i - 1
local x = entryW * (i % xCount)
local y = math.floor(i / xCount) * entryH
render_song(song, x, y)
if math.abs(#songs - i) < 4 then load_more() end
end
end
render_cursor()
if needsReload then reload_songs() end
if screenState == 1 then render_level_filters()
elseif screenState == 2 then render_sorting_selection()
end
render_hotkeys()
render_loading()
render_info()
end
function archive_callback(entries, id)
game.Log("Listing entries for " .. id, 0)
local songsfolder = dlScreen.GetSongsPath()
res = {}
folders = { songsfolder .. "/nautica/" }
local hasFolder = false
for i, entry in ipairs(entries) do
for j = 1, #entry do
if entry:sub(j,j) == '/' then
hasFolder = true
table.insert(folders, songsfolder .. "/nautica/" .. entry:sub(1,j))
end
end
game.Log(entry, 0)
res[entry] = songsfolder .. "/nautica/" .. entry
end
if not hasFolder then
for i, entry in ipairs(entries) do
res[entry] = songsfolder .. "/nautica/" .. id .. "/" .. entry
end
table.insert(folders, songsfolder .. "/nautica/" .. id .. "/")
end
downloaded[id] = "Downloaded"
res[".folders"] = table.concat(folders, "|")
return res
end
function reload_songs()
needsReload = true
if loading then return end
local useLevels = false
local levelarr = {}
for i,value in ipairs(selectedLevels) do
if value then
useLevels = true
table.insert(levelarr, i)
end
end
nextUrl = string.format("https://ksm.dev/app/songs?sort=%s", selectedSorting:lower())
if useLevels then
nextUrl = nextUrl .. "&levels=" .. table.concat(levelarr, ",")
end
songs = {}
cursorPos = 0
cursorPosX = 0
cursorPosY = 0
displayCursorPosX = 0
displayCursorPosY = 0
load_more()
game.Log(nextUrl, 0)
needsReload = false
end
function button_pressed(button)
if button == game.BUTTON_STA then
if screenState == 0 then
local song = songs[cursorPos + 1]
if song == nil then return end
dlScreen.DownloadArchive(encodeURI(song.cdn_download_url), header, song.id, archive_callback)
downloaded[song.id] = "Downloading..."
elseif screenState == 1 then
if selectedLevels[levelcursor + 1] then
selectedLevels[levelcursor + 1] = false
else
selectedLevels[levelcursor + 1] = true
end
reload_songs()
elseif screenState == 2 then
selectedSorting = sortingOptions[sortingcursor + 1]
reload_songs()
end
elseif button == game.BUTTON_BTA then
if screenState == 0 then
local song = songs[cursorPos + 1]
if song == nil then return end
dlScreen.PlayPreview(encodeURI(song.preview_url), header, song.id)
song.status = "Playing"
if lastPlaying ~=nil then
lastPlaying.status = nil
end
lastPlaying = song
end
elseif button == game.BUTTON_FXL then
if screenState ~= 1 then
screenState = 1
else
screenState = 0
end
elseif button == game.BUTTON_FXR then
if screenState ~= 2 then
screenState = 2
else
screenState = 0
end
end
end
function key_pressed(key)
if key == 27 then --escape pressed
dlcache = io.open(cachepath, "w")
dlcache:write(json.encode(downloaded))
dlcache:close()
dlScreen.Exit()
end
end
function advance_selection(steps)
if screenState == 0 and #songs > 0 then
cursorPos = (cursorPos + steps) % #songs
cursorPosX = cursorPos % xCount
cursorPosY = math.floor(cursorPos / xCount)
if cursorPos > #songs - 6 then
load_more()
end
elseif screenState == 1 then
levelcursor = (levelcursor + steps) % 20
elseif screenState == 2 then
sortingcursor = (sortingcursor + steps) % #sortingOptions
end
end
function render_level_filters()
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.Rect(0,0, resX, resY)
gfx.FillColor(0,0,0,200)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
gfx.FontSize(60)
gfx.Text("Level filters:", 10, 10)
gfx.BeginPath()
gfx.Rect(resX/2 - 30, resY/2 - 22, 60, 44)
gfx.StrokeColor(255,128,0)
gfx.StrokeWidth(2)
gfx.Stroke()
gfx.FontSize(40)
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
for i = 1, 20 do
y = (resY/2) + (i - (levelcursor + 1)) * 40
if selectedLevels[i] then gfx.FillColor(255,255,255) else gfx.FillColor(127,127,127) end
gfx.Text(tostring(i), resX/2, y)
end
gfx.Restore()
end
function render_sorting_selection()
gfx.Save()
gfx.ResetTransform()
gfx.BeginPath()
gfx.Rect(0,0, resX, resY)
gfx.FillColor(0,0,0,200)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
gfx.FontSize(60)
gfx.Text("Sorting method:", 10, 10)
gfx.BeginPath()
gfx.Rect(resX/2 - 75, resY/2 - 22, 150, 44)
gfx.StrokeColor(255,128,0)
gfx.StrokeWidth(2)
gfx.Stroke()
gfx.FontSize(40)
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
for i, opt in ipairs(sortingOptions) do
y = (resY/2) + (i - (sortingcursor + 1)) * 40
if selectedSorting == opt then gfx.FillColor(255,255,255) else gfx.FillColor(127,127,127) end
gfx.Text(opt, resX/2, y)
end
gfx.Restore()
end

View File

@ -1,247 +1,247 @@
function clamp(x, min, max)
if x < min then
x = min
end
if x > max then
x = max
end
return x
end
function smootherstep(edge0, edge1, x)
-- Scale, and clamp x to 0..1 range
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)
-- Evaluate polynomial
return x * x * x * (x * (x * 6 - 15) + 10)
end
function to_range(val, start, stop)
return start + (stop - start) * val
end
Animation = {
start = 0,
stop = 0,
progress = 0,
duration = 1,
smoothStart = false
}
function Animation:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Animation:restart(start, stop, duration)
self.progress = 0
self.start = start
self.stop = stop
self.duration = duration
end
function Animation:tick(deltaTime)
self.progress = math.min(1, self.progress + deltaTime / self.duration)
if self.progress == 1 then return self.stop end
if self.smoothStart then
return to_range(smootherstep(0, 1, self.progress), self.start, self.stop)
else
return to_range(smootherstep(-1, 1, self.progress) * 2 - 1, self.start, self.stop)
end
end
local yScale = Animation:new()
local diagWidth = 600
local diagHeight = 400
local tabStroke = {start=0, stop=1}
local tabStrokeAnimation = {start=Animation:new(), stop=Animation:new()}
local settingsStrokeAnimation = {x=Animation:new(), y=Animation:new()}
local prevTab = -1
local prevSettingStroke = {x=0, y=0}
local settingStroke = {x=0, y=0}
local prevVis = false
function processSkinSettings()
for ti, tab in ipairs(SettingsDiag.tabs) do
for si, setting in ipairs(tab.settings) do
if (tab.name == 'Game') then
if (setting.name == 'Gauge') then
game.SetSkinSetting('_gaugeType', setting.value);
end
if (setting.name == 'Backup Gauge') then
game.SetSkinSetting('_gaugeARS', setting.value and 1 or 0);
end
end
end
end
end
function render(deltaTime, visible)
if visible and not prevVis then
yScale:restart(0, 1, 0.25)
elseif not visible and prevVis then
yScale:restart(1, 0, 0.25)
end
processSkinSettings()
if not visible and yScale:tick(0) < 0.05 then return end
local posX = SettingsDiag.posX or 0.5
local posY = SettingsDiag.posY or 0.5
local message_1 = "Press both FXs to open/close. Use the Start button to press buttons."
local message_2 = "Use FX keys to navigate tabs. Use arrow keys to navigate and modify settings."
resX, resY = game.GetResolution()
local scale = resY / 1080
gfx.ResetTransform()
gfx.Translate(math.floor(diagWidth/2 + posX*(resX-diagWidth)), math.floor(diagHeight/2 + posY*(resY-diagHeight)))
gfx.Scale(scale, scale)
gfx.Scale(1.0, smootherstep(0, 1, yScale:tick(deltaTime)))
gfx.BeginPath()
gfx.Rect(-diagWidth/2, -diagHeight/2, diagWidth, diagHeight)
gfx.FillColor(50,50,50)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.FontSize(20)
local m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_1)
gfx.Text(message_1, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax - 20)
m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_2)
gfx.Text(message_2, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax)
tabStroke.start = tabStrokeAnimation.start:tick(deltaTime)
tabStroke.stop = tabStrokeAnimation.stop:tick(deltaTime)
settingStroke.x = settingsStrokeAnimation.x:tick(deltaTime)
settingStroke.y = settingsStrokeAnimation.y:tick(deltaTime)
local tabBarHeight = 0
local nextTabX = 5
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
gfx.FontSize(35)
gfx.Save() --draw tab bar
gfx.Translate(-diagWidth / 2, -diagHeight / 2)
for ti, tab in ipairs(SettingsDiag.tabs) do
local xmin,ymin, xmax,ymax = gfx.TextBounds(nextTabX, 5, tab.name)
if ti == SettingsDiag.currentTab and SettingsDiag.currentTab ~= prevTab then
tabStrokeAnimation.start:restart(tabStroke.start, nextTabX, 0.1)
tabStrokeAnimation.stop:restart(tabStroke.stop, xmax, 0.1)
end
tabBarHeight = math.max(tabBarHeight, ymax + 5)
gfx.Text(tab.name, nextTabX, 5)
nextTabX = xmax + 10
end
gfx.BeginPath()
gfx.MoveTo(0, tabBarHeight)
gfx.LineTo(diagWidth, tabBarHeight)
gfx.StrokeWidth(2)
gfx.StrokeColor(0,127,255)
gfx.Stroke()
gfx.BeginPath()
gfx.MoveTo(tabStroke.start, tabBarHeight)
gfx.LineTo(tabStroke.stop, tabBarHeight)
gfx.StrokeColor(255, 127, 0)
gfx.Stroke()
gfx.Restore() --draw tab bar end
gfx.FontSize(30)
gfx.Save() --draw current tab
gfx.Translate(-diagWidth / 2, -diagHeight / 2)
gfx.Translate(5, tabBarHeight + 5)
gfx.BeginPath()
gfx.MoveTo(0, settingStroke.y)
gfx.LineTo(settingStroke.x, settingStroke.y)
gfx.StrokeWidth(2)
gfx.StrokeColor(255, 127, 0)
gfx.Stroke()
local settingHeight = 30
local tab = SettingsDiag.tabs[SettingsDiag.currentTab]
for si, setting in ipairs(tab.settings) do
processSkinSettings(setting.name, setting.value)
local disp = ""
if setting.type == "enum" then
disp = string.format("%s: %s", setting.name, setting.options[setting.value])
elseif setting.type == "int" then
disp = string.format("%s: %d", setting.name, setting.value)
elseif setting.type == "float" then
disp = string.format("%s: %.2f", setting.name, setting.value)
if setting.max == 1 and setting.min == 0 then --draw slider
disp = setting.name .. ": "
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
local width = diagWidth - 20 - xmax
gfx.BeginPath()
gfx.MoveTo(xmax + 5, 20)
gfx.LineTo(xmax + 5 + width, 20)
gfx.StrokeColor(0,127,255)
gfx.StrokeWidth(2)
gfx.Stroke()
gfx.BeginPath()
gfx.MoveTo(xmax + 5, 20)
gfx.LineTo(xmax + 5 + width * setting.value, 20)
gfx.StrokeColor(255,127,0)
gfx.StrokeWidth(2)
gfx.Stroke()
end
elseif setting.type == "button" then
disp = string.format("%s", setting.name)
local xmin, ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
gfx.BeginPath()
gfx.Rect(-2, 3, 4+xmax-xmin, 28)
gfx.FillColor(0, 64, 128)
if si == SettingsDiag.currentSetting then
gfx.StrokeColor(255, 127, 0)
else
gfx.StrokeColor(0,127,255)
end
gfx.StrokeWidth(2)
gfx.Fill()
gfx.Stroke()
gfx.FillColor(255,255,255)
else
disp = string.format("%s:", setting.name)
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
gfx.BeginPath()
gfx.Rect(xmax + 5, 5, 20,20)
gfx.FillColor(255, 127, 0, setting.value and 255 or 0)
gfx.StrokeColor(0,127,255)
gfx.StrokeWidth(2)
gfx.Fill()
gfx.Stroke()
gfx.FillColor(255,255,255)
end
gfx.Text(disp, 0 ,0)
if si == SettingsDiag.currentSetting then
local setting_name = setting.name .. ":"
if setting.type == "button" then
setting_name = setting.name
end
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, setting_name)
ymax = ymax + settingHeight * (si - 1)
if xmax ~= prevSettingStroke.x or ymax ~= prevSettingStroke.y then
settingsStrokeAnimation.x:restart(settingStroke.x, xmax, 0.1)
settingsStrokeAnimation.y:restart(settingStroke.y, ymax, 0.1)
end
prevSettingStroke.x = xmax
prevSettingStroke.y = ymax
end
gfx.Translate(0, settingHeight)
end
gfx.Restore() --draw current tab end
prevTab = SettingsDiag.currentTab
prevVis = visible
function clamp(x, min, max)
if x < min then
x = min
end
if x > max then
x = max
end
return x
end
function smootherstep(edge0, edge1, x)
-- Scale, and clamp x to 0..1 range
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)
-- Evaluate polynomial
return x * x * x * (x * (x * 6 - 15) + 10)
end
function to_range(val, start, stop)
return start + (stop - start) * val
end
Animation = {
start = 0,
stop = 0,
progress = 0,
duration = 1,
smoothStart = false
}
function Animation:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Animation:restart(start, stop, duration)
self.progress = 0
self.start = start
self.stop = stop
self.duration = duration
end
function Animation:tick(deltaTime)
self.progress = math.min(1, self.progress + deltaTime / self.duration)
if self.progress == 1 then return self.stop end
if self.smoothStart then
return to_range(smootherstep(0, 1, self.progress), self.start, self.stop)
else
return to_range(smootherstep(-1, 1, self.progress) * 2 - 1, self.start, self.stop)
end
end
local yScale = Animation:new()
local diagWidth = 600
local diagHeight = 400
local tabStroke = {start=0, stop=1}
local tabStrokeAnimation = {start=Animation:new(), stop=Animation:new()}
local settingsStrokeAnimation = {x=Animation:new(), y=Animation:new()}
local prevTab = -1
local prevSettingStroke = {x=0, y=0}
local settingStroke = {x=0, y=0}
local prevVis = false
function processSkinSettings()
for ti, tab in ipairs(SettingsDiag.tabs) do
for si, setting in ipairs(tab.settings) do
if (tab.name == 'Game') then
if (setting.name == 'Gauge') then
game.SetSkinSetting('_gaugeType', setting.value);
end
if (setting.name == 'Backup Gauge') then
game.SetSkinSetting('_gaugeARS', setting.value and 1 or 0);
end
end
end
end
end
function render(deltaTime, visible)
if visible and not prevVis then
yScale:restart(0, 1, 0.25)
elseif not visible and prevVis then
yScale:restart(1, 0, 0.25)
end
processSkinSettings()
if not visible and yScale:tick(0) < 0.05 then return end
local posX = SettingsDiag.posX or 0.5
local posY = SettingsDiag.posY or 0.5
local message_1 = "Press both FXs to open/close. Use the Start button to press buttons."
local message_2 = "Use FX keys to navigate tabs. Use arrow keys to navigate and modify settings."
resX, resY = game.GetResolution()
local scale = resY / 1080
gfx.ResetTransform()
gfx.Translate(math.floor(diagWidth/2 + posX*(resX-diagWidth)), math.floor(diagHeight/2 + posY*(resY-diagHeight)))
gfx.Scale(scale, scale)
gfx.Scale(1.0, smootherstep(0, 1, yScale:tick(deltaTime)))
gfx.BeginPath()
gfx.Rect(-diagWidth/2, -diagHeight/2, diagWidth, diagHeight)
gfx.FillColor(50,50,50)
gfx.Fill()
gfx.FillColor(255,255,255)
gfx.FontSize(20)
local m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_1)
gfx.Text(message_1, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax - 20)
m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_2)
gfx.Text(message_2, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax)
tabStroke.start = tabStrokeAnimation.start:tick(deltaTime)
tabStroke.stop = tabStrokeAnimation.stop:tick(deltaTime)
settingStroke.x = settingsStrokeAnimation.x:tick(deltaTime)
settingStroke.y = settingsStrokeAnimation.y:tick(deltaTime)
local tabBarHeight = 0
local nextTabX = 5
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
gfx.FontSize(35)
gfx.Save() --draw tab bar
gfx.Translate(-diagWidth / 2, -diagHeight / 2)
for ti, tab in ipairs(SettingsDiag.tabs) do
local xmin,ymin, xmax,ymax = gfx.TextBounds(nextTabX, 5, tab.name)
if ti == SettingsDiag.currentTab and SettingsDiag.currentTab ~= prevTab then
tabStrokeAnimation.start:restart(tabStroke.start, nextTabX, 0.1)
tabStrokeAnimation.stop:restart(tabStroke.stop, xmax, 0.1)
end
tabBarHeight = math.max(tabBarHeight, ymax + 5)
gfx.Text(tab.name, nextTabX, 5)
nextTabX = xmax + 10
end
gfx.BeginPath()
gfx.MoveTo(0, tabBarHeight)
gfx.LineTo(diagWidth, tabBarHeight)
gfx.StrokeWidth(2)
gfx.StrokeColor(0,127,255)
gfx.Stroke()
gfx.BeginPath()
gfx.MoveTo(tabStroke.start, tabBarHeight)
gfx.LineTo(tabStroke.stop, tabBarHeight)
gfx.StrokeColor(255, 127, 0)
gfx.Stroke()
gfx.Restore() --draw tab bar end
gfx.FontSize(30)
gfx.Save() --draw current tab
gfx.Translate(-diagWidth / 2, -diagHeight / 2)
gfx.Translate(5, tabBarHeight + 5)
gfx.BeginPath()
gfx.MoveTo(0, settingStroke.y)
gfx.LineTo(settingStroke.x, settingStroke.y)
gfx.StrokeWidth(2)
gfx.StrokeColor(255, 127, 0)
gfx.Stroke()
local settingHeight = 30
local tab = SettingsDiag.tabs[SettingsDiag.currentTab]
for si, setting in ipairs(tab.settings) do
processSkinSettings(setting.name, setting.value)
local disp = ""
if setting.type == "enum" then
disp = string.format("%s: %s", setting.name, setting.options[setting.value])
elseif setting.type == "int" then
disp = string.format("%s: %d", setting.name, setting.value)
elseif setting.type == "float" then
disp = string.format("%s: %.2f", setting.name, setting.value)
if setting.max == 1 and setting.min == 0 then --draw slider
disp = setting.name .. ": "
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
local width = diagWidth - 20 - xmax
gfx.BeginPath()
gfx.MoveTo(xmax + 5, 20)
gfx.LineTo(xmax + 5 + width, 20)
gfx.StrokeColor(0,127,255)
gfx.StrokeWidth(2)
gfx.Stroke()
gfx.BeginPath()
gfx.MoveTo(xmax + 5, 20)
gfx.LineTo(xmax + 5 + width * setting.value, 20)
gfx.StrokeColor(255,127,0)
gfx.StrokeWidth(2)
gfx.Stroke()
end
elseif setting.type == "button" then
disp = string.format("%s", setting.name)
local xmin, ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
gfx.BeginPath()
gfx.Rect(-2, 3, 4+xmax-xmin, 28)
gfx.FillColor(0, 64, 128)
if si == SettingsDiag.currentSetting then
gfx.StrokeColor(255, 127, 0)
else
gfx.StrokeColor(0,127,255)
end
gfx.StrokeWidth(2)
gfx.Fill()
gfx.Stroke()
gfx.FillColor(255,255,255)
else
disp = string.format("%s:", setting.name)
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
gfx.BeginPath()
gfx.Rect(xmax + 5, 5, 20,20)
gfx.FillColor(255, 127, 0, setting.value and 255 or 0)
gfx.StrokeColor(0,127,255)
gfx.StrokeWidth(2)
gfx.Fill()
gfx.Stroke()
gfx.FillColor(255,255,255)
end
gfx.Text(disp, 0 ,0)
if si == SettingsDiag.currentSetting then
local setting_name = setting.name .. ":"
if setting.type == "button" then
setting_name = setting.name
end
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, setting_name)
ymax = ymax + settingHeight * (si - 1)
if xmax ~= prevSettingStroke.x or ymax ~= prevSettingStroke.y then
settingsStrokeAnimation.x:restart(settingStroke.x, xmax, 0.1)
settingsStrokeAnimation.y:restart(settingStroke.y, ymax, 0.1)
end
prevSettingStroke.x = xmax
prevSettingStroke.y = ymax
end
gfx.Translate(0, settingHeight)
end
gfx.Restore() --draw current tab end
prevTab = SettingsDiag.currentTab
prevVis = visible
end

View File

@ -1,400 +1,400 @@
--
-- json.lua
--
-- Copyright (c) 2019 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = { _version = "0.1.1" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json
--
-- json.lua
--
-- Copyright (c) 2019 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = { _version = "0.1.1" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,149 +1,149 @@
local window = {
isPortrait = false,
resX = 0,
resY = 0,
scale = 1,
w = 0,
h = 0,
set = function(this, doScale)
local resX, resY = game.GetResolution();
if ((this.resX ~= resX) or (this.resY ~= this.resY)) then
this.isPortrait = resY > resX;
this.w = (this.isPortrait and 1080) or 1920;
this.h = this.w * (resY / resX);
this.scale = resX / this.w;
this.resX = resX;
this.resY = resY;
end
if (doScale) then gfx.Scale(this.scale, this.scale); end
end,
};
local wheel = {
cache = { w = 0, h = 0 },
visibleSongs = 11,
margin = 13,
x = 0,
y = 0,
w = 0,
h = {
song = 0,
total = 0,
},
setSizes = function(this)
if ((this.cache.w ~= window.w) or (this.cache.h ~= window.h)) then
local marginTotal = this.margin * (this.visibleSongs - 1);
this.x = window.w / 2;
this.y = 0;
this.w = window.w / 2;
this.h.total = window.h - marginTotal;
this.h.song = this.h.total / this.visibleSongs;
this.cache.w = window.w;
this.cache.h = window.h;
end
end,
};
local displaying = {};
local jacketCache = {};
local currDiff = 1;
local currSong = 1;
local jacketFallback = gfx.CreateSkinImage('song_select/loading.png', 0);
local getJacket = function(diff)
if ((not jacketCache[diff.jacketPath])
or (jacketCache[diff.jacketPath] == jacketFallback)) then
jacketCache[diff.jacketPath] = gfx.LoadImageJob(
diff.jacketPath,
jacketFallback,
500,
500
);
end
return jacketCache[diff.jacketPath];
end
local setDisplaying = function()
local songs = songwheel.songs;
local enoughSongs = #songs >= wheel.visibleSongs;
displaying[5] = songs[currSong] or {};
for i = 1, 4 do
if (enoughSongs) then
displaying[5 - i] = songs[currSong - i] or songs[currSong + #songs - i];
else
displaying[5 - i] = songs[currSong - i] or {};
end
end
for i = 1, 3 do
if (enoughSongs) then
displaying[5 + i] = songs[currSong + i] or songs[currSong - #songs + i];
else
displaying[5 + i] = songs[currSong + i] or {};
end
end
end
local renderWheel = function()
local margin = wheel.margin;
local x = wheel.x;
local y = wheel.y;
local w = wheel.w;
local h = wheel.h.song;
for i, song in ipairs(displaying) do
local isSelected = i == 5;
gfx.BeginPath();
gfx.FillColor(0, 0, 0, (isSelected and 200) or 100);
gfx.Rect(x, y, w, h);
gfx.Fill();
if (song and song.difficulties) then
local jacket = getJacket(song.difficulties[currDiff] or song.difficulties[1]);
if (jacket) then
gfx.BeginPath();
gfx.ImageRect(x, y, h, h, jacket, (isSelected and 1) or 0.5, 0);
end
end
y = y + h + margin;
end
end
render = function(dt)
window:set(true);
wheel:setSizes();
setDisplaying();
renderWheel();
gfx.ForceRender();
end
set_index = function(newSong)
currSong = newSong;
end
set_diff = function(newDiff)
currDiff = newDiff;
end
songs_changed = function(withAll)
if (not withAll) then return; end
end
local window = {
isPortrait = false,
resX = 0,
resY = 0,
scale = 1,
w = 0,
h = 0,
set = function(this, doScale)
local resX, resY = game.GetResolution();
if ((this.resX ~= resX) or (this.resY ~= this.resY)) then
this.isPortrait = resY > resX;
this.w = (this.isPortrait and 1080) or 1920;
this.h = this.w * (resY / resX);
this.scale = resX / this.w;
this.resX = resX;
this.resY = resY;
end
if (doScale) then gfx.Scale(this.scale, this.scale); end
end,
};
local wheel = {
cache = { w = 0, h = 0 },
visibleSongs = 11,
margin = 13,
x = 0,
y = 0,
w = 0,
h = {
song = 0,
total = 0,
},
setSizes = function(this)
if ((this.cache.w ~= window.w) or (this.cache.h ~= window.h)) then
local marginTotal = this.margin * (this.visibleSongs - 1);
this.x = window.w / 2;
this.y = 0;
this.w = window.w / 2;
this.h.total = window.h - marginTotal;
this.h.song = this.h.total / this.visibleSongs;
this.cache.w = window.w;
this.cache.h = window.h;
end
end,
};
local displaying = {};
local jacketCache = {};
local currDiff = 1;
local currSong = 1;
local jacketFallback = gfx.CreateSkinImage('song_select/loading.png', 0);
local getJacket = function(diff)
if ((not jacketCache[diff.jacketPath])
or (jacketCache[diff.jacketPath] == jacketFallback)) then
jacketCache[diff.jacketPath] = gfx.LoadImageJob(
diff.jacketPath,
jacketFallback,
500,
500
);
end
return jacketCache[diff.jacketPath];
end
local setDisplaying = function()
local songs = songwheel.songs;
local enoughSongs = #songs >= wheel.visibleSongs;
displaying[5] = songs[currSong] or {};
for i = 1, 4 do
if (enoughSongs) then
displaying[5 - i] = songs[currSong - i] or songs[currSong + #songs - i];
else
displaying[5 - i] = songs[currSong - i] or {};
end
end
for i = 1, 3 do
if (enoughSongs) then
displaying[5 + i] = songs[currSong + i] or songs[currSong - #songs + i];
else
displaying[5 + i] = songs[currSong + i] or {};
end
end
end
local renderWheel = function()
local margin = wheel.margin;
local x = wheel.x;
local y = wheel.y;
local w = wheel.w;
local h = wheel.h.song;
for i, song in ipairs(displaying) do
local isSelected = i == 5;
gfx.BeginPath();
gfx.FillColor(0, 0, 0, (isSelected and 200) or 100);
gfx.Rect(x, y, w, h);
gfx.Fill();
if (song and song.difficulties) then
local jacket = getJacket(song.difficulties[currDiff] or song.difficulties[1]);
if (jacket) then
gfx.BeginPath();
gfx.ImageRect(x, y, h, h, jacket, (isSelected and 1) or 0.5, 0);
end
end
y = y + h + margin;
end
end
render = function(dt)
window:set(true);
wheel:setSizes();
setDisplaying();
renderWheel();
gfx.ForceRender();
end
set_index = function(newSong)
currSong = newSong;
end
set_diff = function(newDiff)
currDiff = newDiff;
end
songs_changed = function(withAll)
if (not withAll) then return; end
end

File diff suppressed because it is too large Load Diff

View File

@ -1,243 +1,243 @@
require('common')
local Easing = require('common.easing');
local resx, resy = game.GetResolution()
local desw, desh = 1080, 1920
-- AUDIO
game.LoadSkinSample('sort_wheel/enter.wav');
game.LoadSkinSample('sort_wheel/leave.wav');
-- IMAGES
local panelBgImage = gfx.CreateSkinImage('song_select/sort_wheel/bg.png', 0)
local activeItemBgImage = gfx.CreateSkinImage(
'song_select/sort_wheel/active_bg.png', 0)
local titleTextImage =
gfx.CreateSkinImage('song_select/sort_wheel/title.png', 0)
local selection = 1;
local renderedButtonLabels = {}
local FONT_SIZE = 32;
local MARGIN = 16;
local SUB_FONT_SIZE = 26;
local SUB_MARGIN = 8;
local SORT_ORDER_LABEL_TEXTS = {
{
label = 'Title',
asc = '# to A to Z to かな to 漢字',
dsc = '漢字 to かな to Z to A to #'
}, {label = 'Score', asc = 'Worst to best', dsc = 'Best to worst'},
{label = 'Date', asc = 'Oldest to newest', dsc = 'Newest to oldest'},
{label = 'Badge', asc = 'None to D to S', dsc = 'S to D to None'}, {
label = 'Artist',
asc = '# to A to Z to かな to 漢字',
dsc = '漢字 to かな to Z to A to #'
}, {
label = 'Effector',
asc = '# to A to Z to かな to 漢字',
dsc = '漢字 to かな to Z to A to #'
}
}
local transitionEnterReverse = false;
local transitionEnterScale = 0;
local transitionEnterOffsetX = 0;
local previousActiveState = false;
-- Window variables
local resX, resY
-- Aspect Ratios
local landscapeWidescreenRatio = 16 / 9
local landscapeStandardRatio = 4 / 3
local portraitWidescreenRatio = 9 / 16 --+ 0.0035
-- Portrait sizes
local fullX, fullY
local resolutionChange = function(x, y)
resX = x
resY = y
fullX = portraitWidescreenRatio * y
fullY = y
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR);
end
function tableContains(table, value)
for i, v in ipairs(table) do if v == value then return true end end
return false;
end
function drawButton(i, f, x, y)
local spaceAfter = (FONT_SIZE + MARGIN)
local sortOrder = 'asc';
if (string.find(f, 'v')) then sortOrder = 'dsc' end
local label = f:gsub(' ^', '')
label = label:gsub(' v', '')
if (string.find(sorts[selection], label) and sorts[selection] ~= f) then
-- If there is a button active with the same label, but different sort order, don't render this one
return 0;
else
-- If there is no active button with this label, if one with a label was already rendered, don't render this one
if (tableContains(renderedButtonLabels, label)) then return 0; end
table.insert(renderedButtonLabels, label);
end
if (i == selection) then
local ascLabelText = 'Ascending'
local dscLabelText = 'Descending'
for i, obj in ipairs(SORT_ORDER_LABEL_TEXTS) do
if (obj.label == label) then
ascLabelText = obj.asc;
dscLabelText = obj.dsc;
end
end
gfx.BeginPath()
gfx.ImageRect(x - 182, y - 38, 365, 82, activeItemBgImage, 1, 0)
gfx.BeginPath()
if sortOrder == 'asc' then
gfx.ImageRect(x - 150, y + FONT_SIZE + SUB_MARGIN * 2 - 31, 300, 67,
activeItemBgImage, 1, 0)
elseif sortOrder == 'dsc' then
gfx.ImageRect(x - 150, y + FONT_SIZE + SUB_MARGIN * 2 +
SUB_FONT_SIZE + SUB_MARGIN - 31, 300, 67,
activeItemBgImage, 1, 0)
end
gfx.Save()
gfx.FontSize(SUB_FONT_SIZE)
gfx.Text(ascLabelText, x, y + FONT_SIZE + SUB_MARGIN * 2);
gfx.Text(dscLabelText, x,
y + FONT_SIZE + SUB_MARGIN * 2 + SUB_FONT_SIZE + SUB_MARGIN);
gfx.Restore()
spaceAfter = spaceAfter + SUB_FONT_SIZE * 2 + SUB_MARGIN * 4;
end
gfx.BeginPath();
gfx.Text(label, x, y);
return spaceAfter;
end
function tickTransitions(deltaTime)
-- ENTRY TRANSITION
if transitionEnterReverse then
if transitionEnterScale > 0 then
transitionEnterScale = transitionEnterScale - deltaTime / 0.25 -- transition should last for that time in seconds
else
transitionEnterScale = 0
end
else
if transitionEnterScale < 1 then
transitionEnterScale = transitionEnterScale + deltaTime / 0.25 -- transition should last for that time in seconds
else
transitionEnterScale = 1
end
end
transitionEnterOffsetX = Easing.inOutQuad(1 - transitionEnterScale) * 416
end
local drawSortWheel = function (x,y,w,h, deltaTime)
gfx.Scissor(x,y,w,h);
gfx.Translate(x,y);
gfx.Scale(w/1080, h/1920);
gfx.Scissor(0,0,1080,1920);
-- Draw the dark overlay above song wheel
gfx.BeginPath();
gfx.FillColor(0, 0, 0, math.floor(transitionEnterScale * 192));
gfx.Rect(0, 0, 1080, 1920);
gfx.Fill();
-- Draw the panel background
gfx.BeginPath()
gfx.ImageRect(desw - 416 + transitionEnterOffsetX, 0, 416, desh,
panelBgImage, 1, 0)
gfx.LoadSkinFont("Digital-Serial-Bold.ttf");
gfx.FontSize(FONT_SIZE);
gfx.FillColor(255, 255, 255, 255);
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
gfx.GlobalAlpha(transitionEnterScale)
-- Starting position of the first sort option
local x = 889 + transitionEnterOffsetX;
local y = desh / 2 - -- Center point
(#sorts / 2 / 2) * (FONT_SIZE + MARGIN) - -- Space taken up by half the sort options (we remove the duplicate one)
((SUB_FONT_SIZE * 2 + SUB_MARGIN * 4) / 2); -- Space for taken by order options
-- Draw the title image
gfx.BeginPath()
gfx.ImageRect(x - 72, y - 27, 144, 54, titleTextImage, 1, 0)
y = y + (54 + MARGIN)
-- Draw all the sorting options
for i, f in ipairs(sorts) do
local spaceAfter = drawButton(i, f, x, y);
y = y + spaceAfter;
end
end
function setSkinSetting()
for i, f in ipairs(sorts) do
if i == selection then
local label = f:gsub(' ^', '')
label = label:gsub(' v', '')
game.SetSkinSetting('_songWheelActiveSortOptionLabel', label);
end
end
end
function render(deltaTime, shown)
gfx.Save()
gfx.ResetTransform()
renderedButtonLabels = {};
if (shown ~= previousActiveState) then
if (shown) then
game.PlaySample('sort_wheel/enter.wav');
else
game.PlaySample('sort_wheel/leave.wav');
end
previousActiveState = shown;
end
-- detect resolution change
local resx, resy = game.GetResolution();
if resx ~= resX or resy ~= resY then
resolutionChange(resx, resy)
end
gfx.GlobalAlpha(1)
if not shown then
transitionEnterReverse = true
if (transitionEnterScale > 0) then drawSortWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime) end
else
transitionEnterReverse = false
drawSortWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime)
end
tickTransitions(deltaTime)
setSkinSetting();
gfx.Restore()
end
function set_selection(index) selection = index end
require('common')
local Easing = require('common.easing');
local resx, resy = game.GetResolution()
local desw, desh = 1080, 1920
-- AUDIO
game.LoadSkinSample('sort_wheel/enter.wav');
game.LoadSkinSample('sort_wheel/leave.wav');
-- IMAGES
local panelBgImage = gfx.CreateSkinImage('song_select/sort_wheel/bg.png', 0)
local activeItemBgImage = gfx.CreateSkinImage(
'song_select/sort_wheel/active_bg.png', 0)
local titleTextImage =
gfx.CreateSkinImage('song_select/sort_wheel/title.png', 0)
local selection = 1;
local renderedButtonLabels = {}
local FONT_SIZE = 32;
local MARGIN = 16;
local SUB_FONT_SIZE = 26;
local SUB_MARGIN = 8;
local SORT_ORDER_LABEL_TEXTS = {
{
label = 'Title',
asc = '# to A to Z to かな to 漢字',
dsc = '漢字 to かな to Z to A to #'
}, {label = 'Score', asc = 'Worst to best', dsc = 'Best to worst'},
{label = 'Date', asc = 'Oldest to newest', dsc = 'Newest to oldest'},
{label = 'Badge', asc = 'None to D to S', dsc = 'S to D to None'}, {
label = 'Artist',
asc = '# to A to Z to かな to 漢字',
dsc = '漢字 to かな to Z to A to #'
}, {
label = 'Effector',
asc = '# to A to Z to かな to 漢字',
dsc = '漢字 to かな to Z to A to #'
}
}
local transitionEnterReverse = false;
local transitionEnterScale = 0;
local transitionEnterOffsetX = 0;
local previousActiveState = false;
-- Window variables
local resX, resY
-- Aspect Ratios
local landscapeWidescreenRatio = 16 / 9
local landscapeStandardRatio = 4 / 3
local portraitWidescreenRatio = 9 / 16 --+ 0.0035
-- Portrait sizes
local fullX, fullY
local resolutionChange = function(x, y)
resX = x
resY = y
fullX = portraitWidescreenRatio * y
fullY = y
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR);
end
function tableContains(table, value)
for i, v in ipairs(table) do if v == value then return true end end
return false;
end
function drawButton(i, f, x, y)
local spaceAfter = (FONT_SIZE + MARGIN)
local sortOrder = 'asc';
if (string.find(f, 'v')) then sortOrder = 'dsc' end
local label = f:gsub(' ^', '')
label = label:gsub(' v', '')
if (string.find(sorts[selection], label) and sorts[selection] ~= f) then
-- If there is a button active with the same label, but different sort order, don't render this one
return 0;
else
-- If there is no active button with this label, if one with a label was already rendered, don't render this one
if (tableContains(renderedButtonLabels, label)) then return 0; end
table.insert(renderedButtonLabels, label);
end
if (i == selection) then
local ascLabelText = 'Ascending'
local dscLabelText = 'Descending'
for i, obj in ipairs(SORT_ORDER_LABEL_TEXTS) do
if (obj.label == label) then
ascLabelText = obj.asc;
dscLabelText = obj.dsc;
end
end
gfx.BeginPath()
gfx.ImageRect(x - 182, y - 38, 365, 82, activeItemBgImage, 1, 0)
gfx.BeginPath()
if sortOrder == 'asc' then
gfx.ImageRect(x - 150, y + FONT_SIZE + SUB_MARGIN * 2 - 31, 300, 67,
activeItemBgImage, 1, 0)
elseif sortOrder == 'dsc' then
gfx.ImageRect(x - 150, y + FONT_SIZE + SUB_MARGIN * 2 +
SUB_FONT_SIZE + SUB_MARGIN - 31, 300, 67,
activeItemBgImage, 1, 0)
end
gfx.Save()
gfx.FontSize(SUB_FONT_SIZE)
gfx.Text(ascLabelText, x, y + FONT_SIZE + SUB_MARGIN * 2);
gfx.Text(dscLabelText, x,
y + FONT_SIZE + SUB_MARGIN * 2 + SUB_FONT_SIZE + SUB_MARGIN);
gfx.Restore()
spaceAfter = spaceAfter + SUB_FONT_SIZE * 2 + SUB_MARGIN * 4;
end
gfx.BeginPath();
gfx.Text(label, x, y);
return spaceAfter;
end
function tickTransitions(deltaTime)
-- ENTRY TRANSITION
if transitionEnterReverse then
if transitionEnterScale > 0 then
transitionEnterScale = transitionEnterScale - deltaTime / 0.25 -- transition should last for that time in seconds
else
transitionEnterScale = 0
end
else
if transitionEnterScale < 1 then
transitionEnterScale = transitionEnterScale + deltaTime / 0.25 -- transition should last for that time in seconds
else
transitionEnterScale = 1
end
end
transitionEnterOffsetX = Easing.inOutQuad(1 - transitionEnterScale) * 416
end
local drawSortWheel = function (x,y,w,h, deltaTime)
gfx.Scissor(x,y,w,h);
gfx.Translate(x,y);
gfx.Scale(w/1080, h/1920);
gfx.Scissor(0,0,1080,1920);
-- Draw the dark overlay above song wheel
gfx.BeginPath();
gfx.FillColor(0, 0, 0, math.floor(transitionEnterScale * 192));
gfx.Rect(0, 0, 1080, 1920);
gfx.Fill();
-- Draw the panel background
gfx.BeginPath()
gfx.ImageRect(desw - 416 + transitionEnterOffsetX, 0, 416, desh,
panelBgImage, 1, 0)
gfx.LoadSkinFont("Digital-Serial-Bold.ttf");
gfx.FontSize(FONT_SIZE);
gfx.FillColor(255, 255, 255, 255);
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
gfx.GlobalAlpha(transitionEnterScale)
-- Starting position of the first sort option
local x = 889 + transitionEnterOffsetX;
local y = desh / 2 - -- Center point
(#sorts / 2 / 2) * (FONT_SIZE + MARGIN) - -- Space taken up by half the sort options (we remove the duplicate one)
((SUB_FONT_SIZE * 2 + SUB_MARGIN * 4) / 2); -- Space for taken by order options
-- Draw the title image
gfx.BeginPath()
gfx.ImageRect(x - 72, y - 27, 144, 54, titleTextImage, 1, 0)
y = y + (54 + MARGIN)
-- Draw all the sorting options
for i, f in ipairs(sorts) do
local spaceAfter = drawButton(i, f, x, y);
y = y + spaceAfter;
end
end
function setSkinSetting()
for i, f in ipairs(sorts) do
if i == selection then
local label = f:gsub(' ^', '')
label = label:gsub(' v', '')
game.SetSkinSetting('_songWheelActiveSortOptionLabel', label);
end
end
end
function render(deltaTime, shown)
gfx.Save()
gfx.ResetTransform()
renderedButtonLabels = {};
if (shown ~= previousActiveState) then
if (shown) then
game.PlaySample('sort_wheel/enter.wav');
else
game.PlaySample('sort_wheel/leave.wav');
end
previousActiveState = shown;
end
-- detect resolution change
local resx, resy = game.GetResolution();
if resx ~= resX or resy ~= resY then
resolutionChange(resx, resy)
end
gfx.GlobalAlpha(1)
if not shown then
transitionEnterReverse = true
if (transitionEnterScale > 0) then drawSortWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime) end
else
transitionEnterReverse = false
drawSortWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime)
end
tickTransitions(deltaTime)
setSkinSetting();
gfx.Restore()
end
function set_selection(index) selection = index end

View File

@ -1,214 +1,214 @@
local common = require('common.common');
local Numbers = require('common.numbers')
game.LoadSkinSample('song_transition_screen/transition_enter.wav');
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
local bgImage = gfx.CreateSkinImage("songtransition/bg.png", 0)
local glowOverlayImage = gfx.CreateSkinImage("songtransition/glowy.png", 0)
local frameOverlayImage = gfx.CreateSkinImage("songtransition/frames.png", 0)
local albumBgImage = gfx.CreateSkinImage("songtransition/album_crop.png", 0)
local infoOverlayPanel = gfx.CreateSkinImage("songtransition/info_panels_crop.png", 0)
local linkedHexagonsImage = gfx.CreateSkinImage("songtransition/linked_hexagons_crop.png", 0)
local hexagonImages = {
gfx.CreateSkinImage("songtransition/hex1.png", 0),
gfx.CreateSkinImage("songtransition/hex2.png", 0)
}
local difficultyNumbers;
local difficultyLabelImages = {
gfx.CreateSkinImage("songtransition/difficulty_labels/nov.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/adv.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/exh.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/mxm.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/inf.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/grv.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/hvn.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/vvd.png", 0),
}
local timer = 0
local transitionProgress = 0;
local outProgress = 0
local flickerTime = 0.050 --seconds (50ms)
-- Window variables
local resX, resY = game.GetResolution()
-- Aspect Ratios
local landscapeWidescreenRatio = 16 / 9
local landscapeStandardRatio = 4 / 3
local portraitWidescreenRatio = 9 / 16
-- Portrait sizes
local fullX, fullY
local desw = 1080
local desh = 1920
local noJacket = gfx.CreateSkinImage("song_select/loading.png", 0)
local wasEnterSfxPlayed = false;
function resetLayoutInformation()
resx, resy = game.GetResolution()
scale = resx / desw
end
function render(deltaTime)
if not wasEnterSfxPlayed then
common.stopMusic();
game.PlaySample('song_transition_screen/transition_enter.wav');
wasEnterSfxPlayed = true;
end
if not difficultyNumbers then
difficultyNumbers = Numbers.load_number_image('diff_num')
end
local x_offset = (resX - fullX) / 2
local y_offset = 0
gfx.BeginPath()
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
gfx.Rect(0, 0, resX, resY)
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
gfx.Fill()
gfx.Translate(x_offset, y_offset);
gfx.Scale(fullX / 1080, fullY / 1920);
gfx.Scissor(0, 0, 1080, 1920);
render_screen();
transitionProgress = transitionProgress + deltaTime * 0.2
transitionProgress = math.min(transitionProgress,1)
if transitionProgress < 0.25 then
local whiteAlpha = math.max(0, (1-transitionProgress/0.25))
gfx.BeginPath();
gfx.FillColor(255,255,255,math.floor(255*whiteAlpha));
gfx.Rect(0,0,desw,desh);
gfx.Fill();
gfx.ClosePath();
end
if transitionProgress > 0.85 then
local blackAlpha = math.min(1, ((transitionProgress-0.85)/0.15))
gfx.BeginPath();
gfx.FillColor(0,0,0,math.floor(255*blackAlpha));
gfx.Rect(0,0,desw,desh);
gfx.Fill();
gfx.ClosePath();
end
timer = timer + deltaTime
return transitionProgress >= 1
end
function render_out(deltaTime)
outProgress = outProgress + deltaTime * 0.2
outProgress = math.min(outProgress, 1)
timer = timer + deltaTime
return outProgress >= 1;
end
function sign(x)
return x>0 and 1 or x<0 and -1 or 0
end
function render_screen()
gfx.BeginPath()
gfx.ImageRect(0, 0, 1080, 1920, bgImage,1,0);
if transitionProgress < 0.35 then
local hex1alpha = math.max(0, (1-transitionProgress/0.35))
local hex2alpha = math.max(0, (1-transitionProgress/0.3))
gfx.BeginPath()
gfx.ImageRect(0,0, desw, desh, hexagonImages[1], hex1alpha, 0)
gfx.BeginPath()
gfx.ImageRect(0,0, desw, desh, hexagonImages[2], hex2alpha, 0)
end
gfx.BeginPath()
gfx.ImageRect(0,0,1080,1920,frameOverlayImage,1,0);
gfx.BeginPath()
gfx.ImageRect(0, 0, 1080, 1920, glowOverlayImage,1,0);
gfx.BeginPath()
gfx.ImageRect(37.5, 1074, 1180*0.85, 343*0.85, infoOverlayPanel, 1, 0);
if (timer % flickerTime) < (flickerTime / 2) then --flicker with 20Hz (50ms), 50% duty cycle
gfx.BeginPath()
gfx.ImageRect(37.5, 1074, 1180*0.85, 189*0.85, linkedHexagonsImage, 0.1, 0);
end
gfx.BeginPath()
gfx.ImageRect(10, 195.5, 1060, 1015, albumBgImage,1,0);
local jacket = song.jacket == 0 and noJacket or song.jacket
gfx.BeginPath();
gfx.ImageRect(235, 385, 608, 608, jacket, 1, 0)
gfx.ClosePath();
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.FontSize(55)
gfx.Text(song.title,desw/2, 1114)
gfx.FontSize(30)
gfx.Text(song.artist, desw/2 , 1182)
local EFFECTOR_LABEL_Y = 1288
local ILLUSTRATOR_LABEL_Y = 1347
gfx.FontSize(22)
gfx.Text(song.effector, desw/2+70 , EFFECTOR_LABEL_Y-1)
gfx.Text(song.illustrator, desw/2+70 , ILLUSTRATOR_LABEL_Y-3)
-- Draw song diff level
gfx.BeginPath();
Numbers.draw_number(933, 1140, 1.0, song.level, 2, difficultyNumbers, false, 1, 1)
-- Draw song diff label (NOV/ADV/EXH/MXM/etc.)
gfx.BeginPath();
local diffLabelImage = difficultyLabelImages[song.difficulty+1];
local diffLabelW, diffLabelH = gfx.ImageSize(diffLabelImage);
gfx.ImageRect(952-diffLabelW/2, 1154-diffLabelH/2, diffLabelW, diffLabelH, diffLabelImage,1,0);
gfx.ClosePath();
gfx.Save();
gfx.FontSize(24)
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.BeginPath();
gfx.Text('BPM', 127, 1140)
gfx.Text(song.bpm, 127, 1167)
-- temp ref overlay
-- gfx.BeginPath()
-- gfx.ImageRect(0, 0, 1080, 1920, refBgImage,0.5,0);
gfx.ClosePath();
gfx.Restore();
end
function reset()
transitionProgress = 0
resX, resY = game.GetResolution()
fullX = portraitWidescreenRatio * resY
fullY = resY
outProgress = 0
wasEnterSfxPlayed = false;
local common = require('common.common');
local Numbers = require('common.numbers')
game.LoadSkinSample('song_transition_screen/transition_enter.wav');
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
local bgImage = gfx.CreateSkinImage("songtransition/bg.png", 0)
local glowOverlayImage = gfx.CreateSkinImage("songtransition/glowy.png", 0)
local frameOverlayImage = gfx.CreateSkinImage("songtransition/frames.png", 0)
local albumBgImage = gfx.CreateSkinImage("songtransition/album_crop.png", 0)
local infoOverlayPanel = gfx.CreateSkinImage("songtransition/info_panels_crop.png", 0)
local linkedHexagonsImage = gfx.CreateSkinImage("songtransition/linked_hexagons_crop.png", 0)
local hexagonImages = {
gfx.CreateSkinImage("songtransition/hex1.png", 0),
gfx.CreateSkinImage("songtransition/hex2.png", 0)
}
local difficultyNumbers;
local difficultyLabelImages = {
gfx.CreateSkinImage("songtransition/difficulty_labels/nov.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/adv.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/exh.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/mxm.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/inf.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/grv.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/hvn.png", 0),
gfx.CreateSkinImage("songtransition/difficulty_labels/vvd.png", 0),
}
local timer = 0
local transitionProgress = 0;
local outProgress = 0
local flickerTime = 0.050 --seconds (50ms)
-- Window variables
local resX, resY = game.GetResolution()
-- Aspect Ratios
local landscapeWidescreenRatio = 16 / 9
local landscapeStandardRatio = 4 / 3
local portraitWidescreenRatio = 9 / 16
-- Portrait sizes
local fullX, fullY
local desw = 1080
local desh = 1920
local noJacket = gfx.CreateSkinImage("song_select/loading.png", 0)
local wasEnterSfxPlayed = false;
function resetLayoutInformation()
resx, resy = game.GetResolution()
scale = resx / desw
end
function render(deltaTime)
if not wasEnterSfxPlayed then
common.stopMusic();
game.PlaySample('song_transition_screen/transition_enter.wav');
wasEnterSfxPlayed = true;
end
if not difficultyNumbers then
difficultyNumbers = Numbers.load_number_image('diff_num')
end
local x_offset = (resX - fullX) / 2
local y_offset = 0
gfx.BeginPath()
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
gfx.Rect(0, 0, resX, resY)
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
gfx.Fill()
gfx.Translate(x_offset, y_offset);
gfx.Scale(fullX / 1080, fullY / 1920);
gfx.Scissor(0, 0, 1080, 1920);
render_screen();
transitionProgress = transitionProgress + deltaTime * 0.2
transitionProgress = math.min(transitionProgress,1)
if transitionProgress < 0.25 then
local whiteAlpha = math.max(0, (1-transitionProgress/0.25))
gfx.BeginPath();
gfx.FillColor(255,255,255,math.floor(255*whiteAlpha));
gfx.Rect(0,0,desw,desh);
gfx.Fill();
gfx.ClosePath();
end
if transitionProgress > 0.85 then
local blackAlpha = math.min(1, ((transitionProgress-0.85)/0.15))
gfx.BeginPath();
gfx.FillColor(0,0,0,math.floor(255*blackAlpha));
gfx.Rect(0,0,desw,desh);
gfx.Fill();
gfx.ClosePath();
end
timer = timer + deltaTime
return transitionProgress >= 1
end
function render_out(deltaTime)
outProgress = outProgress + deltaTime * 0.2
outProgress = math.min(outProgress, 1)
timer = timer + deltaTime
return outProgress >= 1;
end
function sign(x)
return x>0 and 1 or x<0 and -1 or 0
end
function render_screen()
gfx.BeginPath()
gfx.ImageRect(0, 0, 1080, 1920, bgImage,1,0);
if transitionProgress < 0.35 then
local hex1alpha = math.max(0, (1-transitionProgress/0.35))
local hex2alpha = math.max(0, (1-transitionProgress/0.3))
gfx.BeginPath()
gfx.ImageRect(0,0, desw, desh, hexagonImages[1], hex1alpha, 0)
gfx.BeginPath()
gfx.ImageRect(0,0, desw, desh, hexagonImages[2], hex2alpha, 0)
end
gfx.BeginPath()
gfx.ImageRect(0,0,1080,1920,frameOverlayImage,1,0);
gfx.BeginPath()
gfx.ImageRect(0, 0, 1080, 1920, glowOverlayImage,1,0);
gfx.BeginPath()
gfx.ImageRect(37.5, 1074, 1180*0.85, 343*0.85, infoOverlayPanel, 1, 0);
if (timer % flickerTime) < (flickerTime / 2) then --flicker with 20Hz (50ms), 50% duty cycle
gfx.BeginPath()
gfx.ImageRect(37.5, 1074, 1180*0.85, 189*0.85, linkedHexagonsImage, 0.1, 0);
end
gfx.BeginPath()
gfx.ImageRect(10, 195.5, 1060, 1015, albumBgImage,1,0);
local jacket = song.jacket == 0 and noJacket or song.jacket
gfx.BeginPath();
gfx.ImageRect(235, 385, 608, 608, jacket, 1, 0)
gfx.ClosePath();
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.FontSize(55)
gfx.Text(song.title,desw/2, 1114)
gfx.FontSize(30)
gfx.Text(song.artist, desw/2 , 1182)
local EFFECTOR_LABEL_Y = 1288
local ILLUSTRATOR_LABEL_Y = 1347
gfx.FontSize(22)
gfx.Text(song.effector, desw/2+70 , EFFECTOR_LABEL_Y-1)
gfx.Text(song.illustrator, desw/2+70 , ILLUSTRATOR_LABEL_Y-3)
-- Draw song diff level
gfx.BeginPath();
Numbers.draw_number(933, 1140, 1.0, song.level, 2, difficultyNumbers, false, 1, 1)
-- Draw song diff label (NOV/ADV/EXH/MXM/etc.)
gfx.BeginPath();
local diffLabelImage = difficultyLabelImages[song.difficulty+1];
local diffLabelW, diffLabelH = gfx.ImageSize(diffLabelImage);
gfx.ImageRect(952-diffLabelW/2, 1154-diffLabelH/2, diffLabelW, diffLabelH, diffLabelImage,1,0);
gfx.ClosePath();
gfx.Save();
gfx.FontSize(24)
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.BeginPath();
gfx.Text('BPM', 127, 1140)
gfx.Text(song.bpm, 127, 1167)
-- temp ref overlay
-- gfx.BeginPath()
-- gfx.ImageRect(0, 0, 1080, 1920, refBgImage,0.5,0);
gfx.ClosePath();
gfx.Restore();
end
function reset()
transitionProgress = 0
resX, resY = game.GetResolution()
fullX = portraitWidescreenRatio * resY
fullY = resY
outProgress = 0
wasEnterSfxPlayed = false;
end

View File

@ -1,215 +1,215 @@
local mposx = 0;
local mposy = 0;
local hovered = nil;
local cursorIndex = 1
local buttonWidth = 250;
local buttonHeight = 50;
local buttonBorder = 2;
local label = -1;
local gr_r, gr_g, gr_b, gr_a = game.GetSkinSetting("col_test")
gfx.GradientColors(0,127,255,255,0,128,255,0)
local gradient = gfx.LinearGradient(0,0,0,1)
local bgPattern = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX + gfx.IMAGE_REPEATY)
local bgAngle = 0.5
local bgPaint = gfx.ImagePattern(0,0, 256,256, bgAngle, bgPattern, 1.0)
local bgPatternTimer = 0
local cursorYs = {}
local buttons = nil
local resx, resy = game.GetResolution();
view_update = function()
if package.config:sub(1,1) == '\\' then --windows
updateUrl, updateVersion = game.UpdateAvailable()
os.execute("start " .. updateUrl)
else --unix
--TODO: Mac solution
os.execute("xdg-open " .. updateUrl)
end
end
mouse_clipped = function(x,y,w,h)
return mposx > x and mposy > y and mposx < x+w and mposy < y+h;
end;
draw_button = function(button, x, y)
local name = button[1]
local rx = x - (buttonWidth / 2);
local ty = y - (buttonHeight / 2);
gfx.BeginPath();
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
gfx.FontSize(40);
if mouse_clipped(rx,ty, buttonWidth, buttonHeight) then
hovered = button[2];
r, b_g, b_b, b_a = game.GetSkinSetting("col_test")
gfx.FillColor(0, 125, 255);
gfx.Text(name, x+1, y+1);
gfx.Text(name, x-1, y+1);
gfx.Text(name, x+1, y-1);
gfx.Text(name, x-1, y-1);
end
gfx.FillColor(255,255,255);
gfx.Text(name, x, y);
return buttonHeight + 5
end;
function updateGradient()
gr_r, gr_g, gr_b, gr_a = game.GetSkinSetting("col_test")
if gr_r == nil then return end
gfx.GradientColors(gr_r,gr_g,gr_b,gr_a,0,128,255,0)
--gradient = gfx.LinearGradient(0,0,0,1)
end
function updatePattern(dt)
bgPatternTimer = (bgPatternTimer + dt) % 1.0
local bgx = math.cos(bgAngle) * (bgPatternTimer * 256)
local bgy = math.sin(bgAngle) * (bgPatternTimer * 256)
gfx.UpdateImagePattern(bgPaint, bgx, bgy, 256, 256, bgAngle, 1.0)
end
function setButtons()
if buttons == nil then
buttons = {}
buttons[1] = {"Start", Menu.Start}
buttons[2] = {"Multiplayer", Menu.Multiplayer}
buttons[3] = {"Challenges", Menu.Challenges}
buttons[4] = {"Get Songs", Menu.DLScreen}
buttons[5] = {"Settings", Menu.Settings}
buttons[6] = {"Exit", Menu.Exit}
end
end
local renderY = resy/2
function draw_cursor(x,y,deltaTime)
gfx.Save()
gfx.BeginPath()
local size = 8
renderY = renderY - (renderY - y) * deltaTime * 30
gfx.MoveTo(x-size,renderY-size)
gfx.LineTo(x,renderY)
gfx.LineTo(x-size,renderY+size)
gfx.StrokeWidth(3)
gfx.StrokeColor(255,255,255)
gfx.Stroke()
gfx.Restore()
end
function sign(x)
return x>0 and 1 or x<0 and -1 or 0
end
function roundToZero(x)
if x<0 then return math.ceil(x)
elseif x>0 then return math.floor(x)
else return 0 end
end
function deltaKnob(delta)
if math.abs(delta) > 1.5 * math.pi then
return delta + 2 * math.pi * sign(delta) * -1
end
return delta
end
local lastKnobs = nil
local knobProgress = 0
function handle_controller()
if lastKnobs == nil then
lastKnobs = {game.GetKnob(0), game.GetKnob(1)}
else
local newKnobs = {game.GetKnob(0), game.GetKnob(1)}
knobProgress = knobProgress - deltaKnob(lastKnobs[1] - newKnobs[1]) * 1.2
knobProgress = knobProgress - deltaKnob(lastKnobs[2] - newKnobs[2]) * 1.2
lastKnobs = newKnobs
if math.abs(knobProgress) > 1 then
cursorIndex = (((cursorIndex - 1) + roundToZero(knobProgress)) % #buttons) + 1
knobProgress = knobProgress - roundToZero(knobProgress)
end
end
end
render = function(deltaTime)
setButtons()
updateGradient()
updatePattern(deltaTime)
resx,resy = game.GetResolution();
mposx,mposy = game.GetMousePos();
gfx.Scale(resx, resy / 3)
gfx.Rect(0,0,1,1)
gfx.FillPaint(gradient)
gfx.Fill()
gfx.ResetTransform()
gfx.BeginPath()
gfx.Scale(0.5,0.5)
gfx.Rect(0,0,resx * 2,resy * 2)
gfx.GlobalCompositeOperation(gfx.BLEND_OP_DESTINATION_IN)
gfx.FillPaint(bgPaint)
gfx.Fill()
gfx.ResetTransform()
gfx.BeginPath()
gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER)
cursorGet = 1
buttonY = resy / 2;
hovered = nil;
gfx.LoadSkinFont("NotoSans-Regular.ttf");
for i=1,#buttons do
cursorYs[i] = buttonY
buttonY = buttonY + draw_button(buttons[i], resx / 2, buttonY);
if hovered == buttons[i][2] then
cursorIndex = i
end
end
handle_controller()
draw_cursor(resx/2 - 100, cursorYs[cursorIndex], deltaTime)
gfx.BeginPath();
gfx.FillColor(255,255,255);
gfx.FontSize(120);
if label == -1 then
label = gfx.CreateLabel("ExperimentalGear ALPHA 1.8.7 ''README.TXT''", 120, 0);
end
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
gfx.DrawLabel(label, resx / 2, resy / 2 - 200, resx-40);
updateUrl, updateVersion = game.UpdateAvailable()
if updateUrl then
gfx.BeginPath()
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT)
gfx.FontSize(30)
gfx.Text(string.format("Version %s is now available", updateVersion), 5, resy - buttonHeight - 10)
draw_button({"View", view_update}, buttonWidth / 2 + 5, resy - buttonHeight / 2 - 5);
draw_button({"Update", Menu.Update}, buttonWidth * 1.5 + 15, resy - buttonHeight / 2 - 5)
end
end;
mouse_pressed = function(button)
if hovered then
hovered()
end
return 0
end
function button_pressed(button)
if button == game.BUTTON_STA then
buttons[cursorIndex][2]()
elseif button == game.BUTTON_BCK then
Menu.Exit()
end
end
local mposx = 0;
local mposy = 0;
local hovered = nil;
local cursorIndex = 1
local buttonWidth = 250;
local buttonHeight = 50;
local buttonBorder = 2;
local label = -1;
local gr_r, gr_g, gr_b, gr_a = game.GetSkinSetting("col_test")
gfx.GradientColors(0,127,255,255,0,128,255,0)
local gradient = gfx.LinearGradient(0,0,0,1)
local bgPattern = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX + gfx.IMAGE_REPEATY)
local bgAngle = 0.5
local bgPaint = gfx.ImagePattern(0,0, 256,256, bgAngle, bgPattern, 1.0)
local bgPatternTimer = 0
local cursorYs = {}
local buttons = nil
local resx, resy = game.GetResolution();
view_update = function()
if package.config:sub(1,1) == '\\' then --windows
updateUrl, updateVersion = game.UpdateAvailable()
os.execute("start " .. updateUrl)
else --unix
--TODO: Mac solution
os.execute("xdg-open " .. updateUrl)
end
end
mouse_clipped = function(x,y,w,h)
return mposx > x and mposy > y and mposx < x+w and mposy < y+h;
end;
draw_button = function(button, x, y)
local name = button[1]
local rx = x - (buttonWidth / 2);
local ty = y - (buttonHeight / 2);
gfx.BeginPath();
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
gfx.FontSize(40);
if mouse_clipped(rx,ty, buttonWidth, buttonHeight) then
hovered = button[2];
r, b_g, b_b, b_a = game.GetSkinSetting("col_test")
gfx.FillColor(0, 125, 255);
gfx.Text(name, x+1, y+1);
gfx.Text(name, x-1, y+1);
gfx.Text(name, x+1, y-1);
gfx.Text(name, x-1, y-1);
end
gfx.FillColor(255,255,255);
gfx.Text(name, x, y);
return buttonHeight + 5
end;
function updateGradient()
gr_r, gr_g, gr_b, gr_a = game.GetSkinSetting("col_test")
if gr_r == nil then return end
gfx.GradientColors(gr_r,gr_g,gr_b,gr_a,0,128,255,0)
--gradient = gfx.LinearGradient(0,0,0,1)
end
function updatePattern(dt)
bgPatternTimer = (bgPatternTimer + dt) % 1.0
local bgx = math.cos(bgAngle) * (bgPatternTimer * 256)
local bgy = math.sin(bgAngle) * (bgPatternTimer * 256)
gfx.UpdateImagePattern(bgPaint, bgx, bgy, 256, 256, bgAngle, 1.0)
end
function setButtons()
if buttons == nil then
buttons = {}
buttons[1] = {"Start", Menu.Start}
buttons[2] = {"Multiplayer", Menu.Multiplayer}
buttons[3] = {"Challenges", Menu.Challenges}
buttons[4] = {"Get Songs", Menu.DLScreen}
buttons[5] = {"Settings", Menu.Settings}
buttons[6] = {"Exit", Menu.Exit}
end
end
local renderY = resy/2
function draw_cursor(x,y,deltaTime)
gfx.Save()
gfx.BeginPath()
local size = 8
renderY = renderY - (renderY - y) * deltaTime * 30
gfx.MoveTo(x-size,renderY-size)
gfx.LineTo(x,renderY)
gfx.LineTo(x-size,renderY+size)
gfx.StrokeWidth(3)
gfx.StrokeColor(255,255,255)
gfx.Stroke()
gfx.Restore()
end
function sign(x)
return x>0 and 1 or x<0 and -1 or 0
end
function roundToZero(x)
if x<0 then return math.ceil(x)
elseif x>0 then return math.floor(x)
else return 0 end
end
function deltaKnob(delta)
if math.abs(delta) > 1.5 * math.pi then
return delta + 2 * math.pi * sign(delta) * -1
end
return delta
end
local lastKnobs = nil
local knobProgress = 0
function handle_controller()
if lastKnobs == nil then
lastKnobs = {game.GetKnob(0), game.GetKnob(1)}
else
local newKnobs = {game.GetKnob(0), game.GetKnob(1)}
knobProgress = knobProgress - deltaKnob(lastKnobs[1] - newKnobs[1]) * 1.2
knobProgress = knobProgress - deltaKnob(lastKnobs[2] - newKnobs[2]) * 1.2
lastKnobs = newKnobs
if math.abs(knobProgress) > 1 then
cursorIndex = (((cursorIndex - 1) + roundToZero(knobProgress)) % #buttons) + 1
knobProgress = knobProgress - roundToZero(knobProgress)
end
end
end
render = function(deltaTime)
setButtons()
updateGradient()
updatePattern(deltaTime)
resx,resy = game.GetResolution();
mposx,mposy = game.GetMousePos();
gfx.Scale(resx, resy / 3)
gfx.Rect(0,0,1,1)
gfx.FillPaint(gradient)
gfx.Fill()
gfx.ResetTransform()
gfx.BeginPath()
gfx.Scale(0.5,0.5)
gfx.Rect(0,0,resx * 2,resy * 2)
gfx.GlobalCompositeOperation(gfx.BLEND_OP_DESTINATION_IN)
gfx.FillPaint(bgPaint)
gfx.Fill()
gfx.ResetTransform()
gfx.BeginPath()
gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER)
cursorGet = 1
buttonY = resy / 2;
hovered = nil;
gfx.LoadSkinFont("NotoSans-Regular.ttf");
for i=1,#buttons do
cursorYs[i] = buttonY
buttonY = buttonY + draw_button(buttons[i], resx / 2, buttonY);
if hovered == buttons[i][2] then
cursorIndex = i
end
end
handle_controller()
draw_cursor(resx/2 - 100, cursorYs[cursorIndex], deltaTime)
gfx.BeginPath();
gfx.FillColor(255,255,255);
gfx.FontSize(120);
if label == -1 then
label = gfx.CreateLabel("ExperimentalGear ALPHA 1.8.7 ''README.TXT''", 120, 0);
end
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
gfx.DrawLabel(label, resx / 2, resy / 2 - 200, resx-40);
updateUrl, updateVersion = game.UpdateAvailable()
if updateUrl then
gfx.BeginPath()
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT)
gfx.FontSize(30)
gfx.Text(string.format("Version %s is now available", updateVersion), 5, resy - buttonHeight - 10)
draw_button({"View", view_update}, buttonWidth / 2 + 5, resy - buttonHeight / 2 - 5);
draw_button({"Update", Menu.Update}, buttonWidth * 1.5 + 15, resy - buttonHeight / 2 - 5)
end
end;
mouse_pressed = function(button)
if hovered then
hovered()
end
return 0
end
function button_pressed(button)
if button == game.BUTTON_STA then
buttons[cursorIndex][2]()
elseif button == game.BUTTON_BCK then
Menu.Exit()
end
end

View File

@ -1,21 +1,21 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location=1) in vec2 fsTex;
layout(location=0) out vec4 target;
uniform sampler2D mainTex;
void main()
{
float x = fsTex.x;
if (x < 0.0 || x > 1.0)
{
target = vec4(0);
return;
}
vec4 mainColor = texture(mainTex, vec2(x,fsTex.y));
target = vec4(0.0, 0.0, 0.0, mainColor.a);
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location=1) in vec2 fsTex;
layout(location=0) out vec4 target;
uniform sampler2D mainTex;
void main()
{
float x = fsTex.x;
if (x < 0.0 || x > 1.0)
{
target = vec4(0);
return;
}
vec4 mainColor = texture(mainTex, vec2(x,fsTex.y));
target = vec4(0.0, 0.0, 0.0, mainColor.a);
}

View File

@ -1,20 +1,20 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location=0) in vec2 inPos;
layout(location=1) in vec2 inTex;
out gl_PerVertex
{
vec4 gl_Position;
};
layout(location=1) out vec2 fsTex;
uniform mat4 proj;
uniform mat4 camera;
uniform mat4 world;
void main()
{
fsTex = inTex;
gl_Position = proj * camera * world * vec4(inPos.xy, 0, 1);
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location=0) in vec2 inPos;
layout(location=1) in vec2 inTex;
out gl_PerVertex
{
vec4 gl_Position;
};
layout(location=1) out vec2 fsTex;
uniform mat4 proj;
uniform mat4 camera;
uniform mat4 world;
void main()
{
fsTex = inTex;
gl_Position = proj * camera * world * vec4(inPos.xy, 0, 1);
}

View File

@ -1,26 +1,26 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location=1) in vec2 fsTex;
layout(location=0) out vec4 target;
uniform sampler2D mainTex;
uniform vec4 lCol;
uniform vec4 rCol;
uniform float hidden;
void main()
{
vec4 mainColor = texture(mainTex, fsTex.xy);
vec4 col = mainColor;
if(fsTex.y > hidden * 1.0)
{
}
else
{
col.xyz = vec3(0.);
col.a = col.a > 0.0 ? 0.3 : 0.0;
}
target = col;
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location=1) in vec2 fsTex;
layout(location=0) out vec4 target;
uniform sampler2D mainTex;
uniform vec4 lCol;
uniform vec4 rCol;
uniform float hidden;
void main()
{
vec4 mainColor = texture(mainTex, fsTex.xy);
vec4 col = mainColor;
if(fsTex.y > hidden * 1.0)
{
}
else
{
col.xyz = vec3(0.);
col.a = col.a > 0.0 ? 0.3 : 0.0;
}
target = col;
}

View File

@ -1,20 +1,20 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location=0) in vec2 inPos;
layout(location=1) in vec2 inTex;
out gl_PerVertex
{
vec4 gl_Position;
};
layout(location=1) out vec2 fsTex;
uniform mat4 proj;
uniform mat4 camera;
uniform mat4 world;
void main()
{
fsTex = inTex;
gl_Position = proj * camera * world * vec4(inPos.xy, 0, 1);
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location=0) in vec2 inPos;
layout(location=1) in vec2 inTex;
out gl_PerVertex
{
vec4 gl_Position;
};
layout(location=1) out vec2 fsTex;
uniform mat4 proj;
uniform mat4 camera;
uniform mat4 world;
void main()
{
fsTex = inTex;
gl_Position = proj * camera * world * vec4(inPos.xy, 0, 1);
}

View File

@ -1,53 +1,53 @@
#ifdef EMBEDDED
varying vec2 fsTex;
#else
#extension GL_ARB_separate_shader_objects : enable
layout(location=1) in vec2 fsTex;
layout(location=0) out vec4 target;
#endif
uniform sampler2D mainTex;
uniform float hiddenCutoff;
uniform float hiddenFadeWindow;
uniform float suddenCutoff;
uniform float suddenFadeWindow;
void main()
{
#ifdef EMBEDDED
target = vec4(0.0);
#else
target = texture(mainTex, vec2(fsTex.x, fsTex.y * 2.0));
float off = 1.0 - (fsTex.y * 2.0);
if(hiddenCutoff < suddenCutoff)
{
float hiddenCutoffFade = hiddenCutoff - hiddenFadeWindow;
if (off > hiddenCutoffFade && off < hiddenCutoff) {
target.a = target.a * max(0.0, (hiddenCutoff - off) / hiddenFadeWindow);
}
if (off < suddenCutoff && off > hiddenCutoff) {
target.a = 0.0;
}
float suddenCutoffFade = suddenCutoff + suddenFadeWindow;
if (off < suddenCutoffFade && off > suddenCutoff) {
target.a = target.a * max(0.0, (off - suddenCutoff) / suddenFadeWindow);
}
}
else
{
float hiddenCutoffFade = hiddenCutoff + hiddenFadeWindow;
if (off > hiddenCutoff) {
target.a = target.a * max(0.0, (hiddenCutoffFade - off) / hiddenFadeWindow);
}
float suddenCutoffFade = suddenCutoff - suddenFadeWindow;
if (off < suddenCutoff) {
target.a = target.a * max(0.0, (off - suddenCutoffFade) / suddenFadeWindow);
}
}
#endif
#ifdef EMBEDDED
varying vec2 fsTex;
#else
#extension GL_ARB_separate_shader_objects : enable
layout(location=1) in vec2 fsTex;
layout(location=0) out vec4 target;
#endif
uniform sampler2D mainTex;
uniform float hiddenCutoff;
uniform float hiddenFadeWindow;
uniform float suddenCutoff;
uniform float suddenFadeWindow;
void main()
{
#ifdef EMBEDDED
target = vec4(0.0);
#else
target = texture(mainTex, vec2(fsTex.x, fsTex.y * 2.0));
float off = 1.0 - (fsTex.y * 2.0);
if(hiddenCutoff < suddenCutoff)
{
float hiddenCutoffFade = hiddenCutoff - hiddenFadeWindow;
if (off > hiddenCutoffFade && off < hiddenCutoff) {
target.a = target.a * max(0.0, (hiddenCutoff - off) / hiddenFadeWindow);
}
if (off < suddenCutoff && off > hiddenCutoff) {
target.a = 0.0;
}
float suddenCutoffFade = suddenCutoff + suddenFadeWindow;
if (off < suddenCutoffFade && off > suddenCutoff) {
target.a = target.a * max(0.0, (off - suddenCutoff) / suddenFadeWindow);
}
}
else
{
float hiddenCutoffFade = hiddenCutoff + hiddenFadeWindow;
if (off > hiddenCutoff) {
target.a = target.a * max(0.0, (hiddenCutoffFade - off) / hiddenFadeWindow);
}
float suddenCutoffFade = suddenCutoff - suddenFadeWindow;
if (off < suddenCutoff) {
target.a = target.a * max(0.0, (off - suddenCutoffFade) / suddenFadeWindow);
}
}
#endif
}

View File

@ -1,25 +1,25 @@
#ifdef EMBEDDED
attribute vec2 inPos;
attribute vec2 inTex;
varying vec2 fsTex;
#else
#extension GL_ARB_separate_shader_objects : enable
layout(location=0) in vec2 inPos;
layout(location=1) in vec2 inTex;
out gl_PerVertex
{
vec4 gl_Position;
};
layout(location=1) out vec2 fsTex;
#endif
uniform mat4 proj;
uniform mat4 camera;
uniform mat4 world;
void main()
{
fsTex = inTex;
gl_Position = proj * camera * world * vec4(inPos.xy, 0, 1);
#ifdef EMBEDDED
attribute vec2 inPos;
attribute vec2 inTex;
varying vec2 fsTex;
#else
#extension GL_ARB_separate_shader_objects : enable
layout(location=0) in vec2 inPos;
layout(location=1) in vec2 inTex;
out gl_PerVertex
{
vec4 gl_Position;
};
layout(location=1) out vec2 fsTex;
#endif
uniform mat4 proj;
uniform mat4 camera;
uniform mat4 world;
void main()
{
fsTex = inTex;
gl_Position = proj * camera * world * vec4(inPos.xy, 0, 1);
}

View File

@ -1,15 +1,15 @@
CUSTOM CREW CREATOR
Must have:
Art Program with layers (Paint.NET and GIMP are both FREE)
Basic Knowledge of the program
Custom crew assets
1: Bottom-most layer is frame_glow.png
2: Middle layer is your custom crew member
3: Third layer is frame_metal.png
Clean up so no part of your custom crew is outside of frame_metal.png.
CUSTOM CREW CREATOR
Must have:
Art Program with layers (Paint.NET and GIMP are both FREE)
Basic Knowledge of the program
Custom crew assets
1: Bottom-most layer is frame_glow.png
2: Middle layer is your custom crew member
3: Third layer is frame_metal.png
Clean up so no part of your custom crew is outside of frame_metal.png.
EXPORT AS .PNG FILE!