From d0c2932c5be35aeb445376c5cd8752c092e864bb Mon Sep 17 00:00:00 2001 From: Hersi Date: Sun, 20 Feb 2022 17:27:27 +0100 Subject: [PATCH] normalize line endings --- .gitattributes | 1 + README.md | 72 +- backgrounds/fallback/bg.fs | 1090 +++++------ backgrounds/fallback/bg.lua | 1226 ++++++------ backgrounds/fallback/bg_template.lua | 170 +- config-definitions.json | 136 +- scripts/challengeresult.lua | 604 +++--- scripts/collectiondialog.lua | 212 +-- scripts/downloadscreen.lua | 856 ++++----- scripts/gamesettingsdialog.lua | 492 ++--- scripts/json.lua | 800 ++++---- scripts/multiplayerscreen.lua | 2002 ++++++++++---------- scripts/songselect/chalwheel.lua | 1212 ++++++------ scripts/songselect/songwheel new.lua | 298 +-- scripts/songselect/songwheel3.lua | 1794 +++++++++--------- scripts/songselect/sortwheel.lua | 486 ++--- scripts/songtransition.lua | 426 ++--- scripts/titlescreen OLD.lua | 430 ++--- shaders/blackLaser.fs | 40 +- shaders/blackLaser.vs | 38 +- shaders/track.fs | 50 +- shaders/track.vs | 38 +- shaders/trackCover.fs | 104 +- shaders/trackCover.vs | 48 +- textures/crew/make-a-crew/instructions.txt | 28 +- 25 files changed, 6327 insertions(+), 6326 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/README.md b/README.md index d679a97..0663613 100644 --- a/README.md +++ b/README.md @@ -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 + diff --git a/backgrounds/fallback/bg.fs b/backgrounds/fallback/bg.fs index 6b529b6..dbb995d 100644 --- a/backgrounds/fallback/bg.fs +++ b/backgrounds/fallback/bg.fs @@ -1,546 +1,546 @@ -#version 330 -#extension GL_ARB_separate_shader_objects : enable - -layout(location=1) in vec2 texVp; -layout(location=0) out vec4 target; - -uniform ivec2 screenCenter; -// x = bar time -// y = off-sync but smooth bpm based timing -// z = real time since song start -uniform vec3 timing; -uniform ivec2 viewport; -uniform float objectGlow; -// bg_texture.png -uniform vec2 tilt; -uniform float clearTransition; - -#define HALF_PI 1.570796326794 -#define PI 3.14159265359 -#define TWO_PI 6.28318530718 - -// Background -uniform int Bg = 0; - -uniform float BgPivot = .27; - -uniform int BgBase = 0; -uniform sampler2D BgBaseTex; -uniform int BgBaseClearVersion = 0; -uniform sampler2D BgBaseClearTex; -uniform float BgBaseOffsetY = 0.; -uniform int BgBaseTilt = 1; -uniform float BgBaseAR = 1.125; -uniform int BgBaseAnim = 0; -uniform float BgBaseAnimFramesCount; // should be int -uniform int BgBaseScaleSoft = 0; -uniform int BgBaseClampTiling = 0; - -uniform int BgOverlay = 0; -uniform sampler2D BgOverlayTex; -uniform int BgOverlayClearVersion = 0; -uniform sampler2D BgOverlayClearTex; -uniform int BgOverlayFloat = 0; -uniform float BgOverlayFloatFactor = 1.; -uniform float BgOverlayOffsetY = 0.; -uniform int BgOverlayFlashEffect = 0; -uniform int BgOverlayTilt = 1; - -uniform int BgLayer = 0; -uniform sampler2D BgLayerTex; -uniform int BgLayerClearVersion = 0; -uniform sampler2D BgLayerClearTex; -uniform float BgLayerAnimFramesCount; // should be int -uniform float BgLayerBrighten = 0.; -uniform int BgLayerScaleHard = 0; - -// Center -uniform int Center = 0; -uniform int CenterNormalVersion = 0; -uniform sampler2D CenterTex; -uniform int CenterClearVersion = 0; -uniform sampler2D CenterClearTex; -uniform float CenterScale = 3.; // todo: change to smaller -uniform float CenterFloatFactor = 1.; -uniform float CenterFloatXFactor = 0.; -uniform float CenterFloatRotationFactor = 0.; -uniform int CenterPulse = 0; -uniform int CenterFloat = 0; -uniform int CenterFadeEffect = 0; -uniform int CenterTilt = 1; -uniform float CenterOffsetY = 0.; -uniform int CenterGlow = 0; -uniform int CenterSnapToTrack = 1; -uniform int CenterRotate = 0; - -uniform int CenterLayerEffect = 0; -uniform sampler2D CenterLayerEffectTex; -uniform int CenterLayerEffectFade = 0; -uniform int CenterLayerEffectRotate = 0; -uniform float CenterLayerEffectRotateSpeed = 1.; -uniform int CenterLayerEffectGlow = 0; -uniform float CenterLayerEffectScale = 1.; -uniform int CenterLayerEffectDodgeBlend = 0; -uniform float CenterLayerEffectAlpha = 1.; - -uniform int CenterAnim = 0; -uniform float CenterAnimFramesCount; // should be int - -// Tunnel -uniform int Tunnel = 0; -uniform sampler2D TunnelTex; -uniform int TunnelClearVersion = 0; -uniform sampler2D TunnelClearTex; -uniform float TunnelSides = float(8); // should be int -uniform float TunnelStretch = .15; // lower = "Stretchier" -uniform float TunnelScaleX = 1.; // for scale: lower is longer i believe -uniform float TunnelScaleY = 1.; -uniform float TunnelFog = 10.; -uniform int TunnelFlashEffect = 0; -uniform float TunnelExtraRotation = 0.; // 1. == 2*PI radians -uniform int TunnelVortexEffect = 0; -uniform float TunnelVortexFactor = 1.; -uniform int TunnelDodgeBlend = 0; - -// Particle -uniform int Particle = 0; -uniform sampler2D ParticleTex; -uniform int ParticleClearVersion = 0; -uniform sampler2D ParticleClearTex; -uniform float ParticleSpeed = 1.; -uniform float ParticleScale = 1.; -uniform float ParticleOffsetY = 0.; -uniform float ParticleAmount = float(2); // should be int - - - -// MISC CONSTANTS -float TunnelSpeed = 1.; -float TunnelBaseRotation = 0.0; // Default rotation in radians -float TunnelBaseTexRotation = 0.5 * HALF_PI; // Rotation of texture for alignment, in radians -vec2 TunnelScale = vec2(TunnelScaleX, TunnelScaleY); -float bgLayerAR = 1.25; -vec2 bgPivot = vec2(.5, BgPivot); -vec2 hardScale = vec2(.7, .4); // Base scale (lower is more scaled: 1/x) <> how much is subtracted on rotation -vec2 softScale = vec2(.9, .1); -float rotateScaleSmoothnessFactor = 1.3; // Higher is smoother - -float portrait(float a, float b) { - if (viewport.y > viewport.x) { - return a; - } else { - return b; - } -} - -vec3 rgb2hsv(vec3 c) { - vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); - vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); - - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); -} - -float blendOverlay(float base, float blend) { - return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); -} -vec3 blendOverlay(vec3 base, vec3 blend) { - return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); -} -vec3 blendOverlay(vec3 base, vec3 blend, float opacity) { - return (blendOverlay(base, blend) * opacity + base * (1.0 - opacity)); -} - -float blendScreen(float base, float blend) { - return 1.0-((1.0-base)*(1.0-blend)); -} -vec3 blendScreen(vec3 base, vec3 blend) { - return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b)); -} -vec3 blendScreen(vec3 base, vec3 blend, float opacity) { - return (blendScreen(base, blend) * opacity + base * (1.0 - opacity)); -} - -float blendLinearDodge(float base, float blend) { - // Note : Same implementation as BlendAddf - return min(base+blend,1.0); -} -vec3 blendLinearDodge(vec3 base, vec3 blend) { - // Note : Same implementation as BlendAdd - return min(base+blend,vec3(1.0)); -} -vec3 blendLinearDodge(vec3 base, vec3 blend, float opacity) { - return (blendLinearDodge(base, blend) * opacity + base * (1.0 - opacity)); -} - - -vec2 ScaleUV(vec2 uv,float scale,vec2 pivot) { - return (uv - pivot) * scale + pivot; -} -vec2 ScaleUV(vec2 uv,vec2 scale,vec2 pivot) { - return (uv - pivot) * scale + pivot; -} - -vec2 rotatePoint(vec2 cen,float angle,vec2 p) { - float s = sin(angle); - float c = cos(angle); - - // translate point back to origin: - p.x -= cen.x; - p.y -= cen.y; - - // rotate point - float xnew = p.x * c - p.y * s; - float ynew = p.x * s + p.y * c; - - // translate point back: - p.x = xnew + cen.x; - p.y = ynew + cen.y; - return p; -} - -float mirrorTile(float val, float lower, float upper) { - if (val < lower) return lower * 2 - val; - if (val > upper) return upper * 2. - val; - return val; -} -vec2 mirrorTile(vec2 val, float lower, float upper) { - val.x = mirrorTile(val.x, lower, upper); - val.y = mirrorTile(val.y, lower, upper); - return val; -} - -float getRotateScaleModifier(float rotation) { - return smoothstep(0., HALF_PI*.5*rotateScaleSmoothnessFactor, 2 * abs(asin(sin(0.5*rotation*TWO_PI))*1.)); - // return min(2. * abs( asin(sin(0.5*layerRotation*TWO_PI))*2. ), .5*HALF_PI ) / (.5*HALF_PI); -} - -/////////////////// -// END RENDER GIF BG -/////////////////// - -vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -// Reference to -// https://github.com/Ikeiwa/USC-SDVX-IV-Skin (Ikeiwa) -// http://thndl.com/square-shaped-shaders.html -// https://thebookofshaders.com/07/ - -float GetDistanceShape(vec2 st, /*int*/float N){ - vec3 color = vec3(0.0); - float d = 0.0; - - // Angle and radius from the current pixel - float a = atan(st.x,st.y)+PI; - float r = TWO_PI/N; - - // Shaping function that modulate the distance - d = cos(floor(.5+a/r)*r-a)*length(st); - - return d; - -} - -float mirrored(float v) { - float m = mod(v, 2.0); - return mix(m, 2.0 - m, step(1.0, m)); -} - -float rand(vec2 co){ - return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); -} - -vec2 rotate2d(vec2 uv,float _angle, vec2 pivot){ - return (uv - pivot) * mat2(cos(_angle),-sin(_angle), - sin(_angle),cos(_angle)) + pivot; -} - -void main() { - target.rgba = vec4(vec3(0.),1.); - - // MAIN SETUP - float beatTime = mod(timing.y,1.0); - - float ar = float(viewport.x) / float(viewport.y); - float var = float(viewport.y) / float(viewport.x); - - vec2 screenUV = vec2(texVp.x / viewport.x, texVp.y / viewport.y); - - vec2 uv = screenUV; - uv.x *= ar; - - vec2 center = vec2(screenCenter) / vec2(viewport); - center.x *= ar; - - vec2 point = uv; - float bgrot = dot(tilt, vec2(0.5, 1.0)); - point = rotatePoint(center, TunnelBaseRotation + (bgrot * TWO_PI), point); - - // BACKGROUND - vec4 backgroundTexture = vec4(0.,0.,0.,1.); - vec4 backgroundTexture2 = vec4(0.); - vec4 layerTexture = vec4(0.); - if (Bg == 1) { - // Bg => BgOverlay => BgLayer - for (int i=0;i<3;++i) { - if ((i == 0 && BgBase == 0) || (i == 1 && BgOverlay == 0) || (i == 2 && BgLayer == 0)) - continue; - - bool isAnim = (i < 2 && BgBaseAnim == 1) || i == 2; - float frameCount; - if (isAnim) { - frameCount = i < 2 ? BgBaseAnimFramesCount : BgLayerAnimFramesCount; - } - - vec2 backgroundUV = screenUV; - // backgroundUV.x -= 0.5; - if (BgOverlay == 1 && i == 1) { - backgroundUV.y -= portrait(BgOverlayOffsetY,-0.5); // todo: figure out landscape - if (BgOverlayFloat == 1) - backgroundUV.y += sin(timing.z) * 0.003 * BgOverlayFloatFactor; - } else { - backgroundUV.y -= portrait(BgBaseOffsetY,-0.5); // todo: figure out landscape - } - // backgroundUV.x *= ar; - // backgroundUV /= 1.2; - // backgroundUV /= ar; - // backgroundUV.x += 0.5; - backgroundUV.y -= portrait(0.15,0.05); - backgroundUV.y /= ar; - - if ((i < 2 && BgBaseTilt == 1) || (i == 1 && BgOverlayTilt == 1) || i == 2) { - float rotation = i < 2 && !(i == 1 && BgBaseTilt == 0) // todo: tilt to maximum point? - ? dot(tilt, vec2(0.5, 1.0)) - : dot(tilt, vec2(1.0)); - float scaleModifier = getRotateScaleModifier(rotation); - vec2 scaleConst = ((i == 2 && BgLayerScaleHard == 0) || (i < 2 && BgBaseScaleSoft == 1)) ? softScale : hardScale; - if (BgBaseTilt == 1) - backgroundUV = ScaleUV( backgroundUV, scaleConst.x - (scaleModifier*scaleConst.y), bgPivot ); - backgroundUV = rotatePoint(bgPivot, rotation*TWO_PI, backgroundUV); - } - - float bgAr = i < 2 && BgBaseAnim == 0 ? BgBaseAR : bgLayerAR; - if (bgAr > 1) - backgroundUV = ScaleUV(backgroundUV, vec2(1/bgAr, 1.), bgPivot); - else - backgroundUV = ScaleUV(backgroundUV, vec2(1., bgAr), bgPivot); - - vec2 animOffset; - animOffset = vec2(0.); - if (isAnim) { - float frameFraction = 1. / frameCount; - float currentFrame = floor(timing.y * frameCount); - - animOffset = vec2(currentFrame*frameFraction, 0.); - backgroundUV *= vec2(frameFraction, 1.); - } - backgroundUV = BgBaseClampTiling == 0 - ? mirrorTile(backgroundUV, .001, .999) - : clamp(backgroundUV, .001, .999); - - - if (isAnim) - backgroundUV += animOffset; - - if (i == 0) { - backgroundTexture = texture(BgBaseTex, backgroundUV); - if (BgBaseClearVersion == 1) { - vec4 backgroundClearTexture = texture(BgBaseClearTex, backgroundUV); - backgroundTexture = mix(backgroundTexture,backgroundClearTexture,clearTransition); - } - } else if (i == 1) { - backgroundTexture2 = texture(BgOverlayTex, backgroundUV); - if (BgOverlayClearVersion == 1) { - vec4 backgroundOverlayClearTexture = texture(BgOverlayClearTex, backgroundUV); - backgroundTexture2 = mix(backgroundTexture2,backgroundOverlayClearTexture,clearTransition); - } - if (BgOverlayFlashEffect == 1) { - backgroundTexture2.a *= .9 + timing.y * .1; - } - } else if (i == 2) { - layerTexture = texture(BgLayerTex, backgroundUV); - if (BgLayerClearVersion == 1) { - vec4 layerClearTexture = texture(BgLayerClearTex, backgroundUV); - layerTexture = mix(layerTexture, layerClearTexture, clearTransition); - } - layerTexture.rgb = blendOverlay(layerTexture.rgb, vec3(1.), BgLayerBrighten); - } - } - } - target.rgb = mix(target.rgb, backgroundTexture.rgb, backgroundTexture.a); - if (BgLayer == 1) - target.rgb = blendLinearDodge(target.rgb, layerTexture.rgb, layerTexture.a); - // END BACKGROUND - - // TUNNEL -- gaat nog niet goed denkik - if (Tunnel == 1) { - vec2 pointFromCenter = center - point; - pointFromCenter /= TunnelScale; - float diff = GetDistanceShape(pointFromCenter,TunnelSides); - float fog = -1. / (diff * TunnelFog * TunnelScale.x) + 1.; - fog = clamp(fog, 0, 1); - float tunnelTexY = TunnelStretch / diff; - tunnelTexY += timing.y * TunnelSpeed; - float rot = (atan(pointFromCenter.x,pointFromCenter.y) + TunnelBaseTexRotation + TunnelExtraRotation*TWO_PI) / TWO_PI; - if (TunnelVortexEffect == 1) - rot += fract(timing.z*0.1*TunnelVortexFactor); - vec4 tunnelTexture = texture(TunnelTex, vec2(rot,mod(tunnelTexY,1))); - if (TunnelClearVersion == 1) { - vec4 clearTunnelTexture = texture(TunnelClearTex, vec2(rot,mod(tunnelTexY,1))); - tunnelTexture = mix(tunnelTexture,clearTunnelTexture,clearTransition); - } - - if (TunnelFlashEffect == 1) { - float brightness = timing.y * 1.; - tunnelTexture.rgb = blendOverlay(tunnelTexture.rgb, vec3(1.), brightness); - } - - if (TunnelDodgeBlend == 1) - target.rgb = blendLinearDodge(target.rgb, tunnelTexture.rgb*2, tunnelTexture.a*2*fog); - else - target.rgb = mix(target.rgb, tunnelTexture.rgb*2., tunnelTexture.a*fog); - - // target.rgb = backgroundTexture.rgb * (1-target.a) + target.rgb * target.a; - // target.rgb = tunnelTexture.rgb * 2.0; - // target.a = tunnelTexture.a * fog; - } - // END TUNNEL - - // CENTER TEXTURE - if (Center == 1) { - vec2 centerUV = screenUV; - //centerUV = center - centerUV; // this would be 'centered uv calculation' (?) - //centerUV *= -1.0; - //centerUV += 0.5; - centerUV.x -= 0.5; - centerUV.y -= CenterOffsetY; - if (CenterSnapToTrack == 1) - centerUV.y += (0.5-center.y); - else - centerUV.y += portrait(0.19,0.25); - - if (CenterFloat == 1) { - centerUV -= vec2( - sin(timing.z * 0.6) * 0.003 * CenterFloatXFactor, - cos(timing.z) * 0.007 * CenterFloatFactor - ); - } - centerUV.x *= ar; - centerUV.x += 0.5; - centerUV = clamp(centerUV,0.0,1.0); - - if (CenterTilt == 1) - centerUV = rotatePoint(vec2(0.5, 0.5-CenterOffsetY), clamp(TunnelBaseRotation + (bgrot * TWO_PI),-360,360), centerUV); - - if (CenterFloat == 1 && CenterFloatRotationFactor > 0.) { - centerUV = rotatePoint(vec2(0.5), (sin(timing.z*0.3))*0.05*TWO_PI * CenterFloatRotationFactor, centerUV); - } - - centerUV = ScaleUV(centerUV,CenterScale,vec2(0.5)); - - float GlowTimingProgress = sin(timing.z*6.5)*.5+.5; - - if (CenterGlow == 1) - centerUV = ScaleUV(centerUV,GlowTimingProgress*.3+1.,vec2(0.5)); - - if (CenterRotate == 1) - centerUV = rotatePoint(vec2(0.5), fract(timing.z*0.02)*TWO_PI, centerUV); - - // TODO: center anim - - vec4 centerTexture = vec4(0.); - if (CenterNormalVersion == 1) { - centerTexture = texture(CenterTex, clamp(centerUV,0.,1.)); - } - if (CenterClearVersion == 1) { - vec4 centerTextureClear = texture(CenterClearTex, clamp(centerUV,0.,1.)); - centerTexture = mix(centerTexture,centerTextureClear,clearTransition); - } - - float opacity = CenterFadeEffect == 1 ? (0.2+abs(cos(timing.z)*0.2)) : 1.; - target.rgb = mix(target.rgb,centerTexture.rgb,centerTexture.a * opacity); - - if (BgOverlay == 1) - target.rgb = mix(target.rgb,backgroundTexture2.rgb,backgroundTexture2.a); - - if (CenterLayerEffect == 1) { - float a = CenterLayerEffectAlpha; - float sc = CenterLayerEffectScale; - if (CenterLayerEffectFade == 1) - a *= (0.3+(cos(timing.z)*0.3)); - if (CenterLayerEffectGlow == 1) { - a *= (GlowTimingProgress*.2+.4); - sc *= (GlowTimingProgress*.05+.95); - } - - vec2 centerLayerUV = ScaleUV(centerUV,sc,vec2(.5,.5)); - if (CenterLayerEffectRotate == 1) - centerLayerUV = rotatePoint(vec2(0.5), fract(timing.z*0.01*CenterLayerEffectRotateSpeed)*TWO_PI, centerLayerUV); - vec4 centerTexture2 = texture(CenterLayerEffectTex, clamp(centerLayerUV,0.,1.)); - - if (CenterLayerEffectDodgeBlend == 1) - target.rgb = blendLinearDodge(target.rgb, centerTexture2.rgb, centerTexture2.a*a); - else - target.rgb = mix(target.rgb,centerTexture2.rgb,centerTexture2.a * a); - } - // todo: have pulsing speed factor uniform - if (CenterPulse == 1) { // also has to account for clear version - vec4 centerTextureef = texture(CenterTex, clamp(ScaleUV(centerUV,1.-fract(timing.z*1.5)*0.15,vec2(0.5)),0.0,1.0)); - target.rgb = mix(target.rgb,centerTextureef.rgb + target.rgb,centerTextureef.a *(fract(-timing.z*1.5))*0.2); - } - } - // END CENTER TEXTURE - - // If Center == 1 this will be drawn in the Center block - if (Center == 0 && BgOverlay == 1) - target.rgb = mix(target.rgb,backgroundTexture2.rgb,backgroundTexture2.a); - - //PARTICLES - if (Particle == 1) { - vec2 particlesUV = point; - particlesUV.x = center.x - particlesUV.x; - particlesUV.x = mirrored(clamp(particlesUV.x,-1.0,1.0)); - // particlesUV.y += 0.1; - - vec4 particles = vec4(vec3(1.),0.); - float particlesTime = timing.z * ParticleSpeed; - - for (int i=0;i how much is subtracted on rotation +vec2 softScale = vec2(.9, .1); +float rotateScaleSmoothnessFactor = 1.3; // Higher is smoother + +float portrait(float a, float b) { + if (viewport.y > viewport.x) { + return a; + } else { + return b; + } +} + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +float blendOverlay(float base, float blend) { + return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); +} +vec3 blendOverlay(vec3 base, vec3 blend) { + return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); +} +vec3 blendOverlay(vec3 base, vec3 blend, float opacity) { + return (blendOverlay(base, blend) * opacity + base * (1.0 - opacity)); +} + +float blendScreen(float base, float blend) { + return 1.0-((1.0-base)*(1.0-blend)); +} +vec3 blendScreen(vec3 base, vec3 blend) { + return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b)); +} +vec3 blendScreen(vec3 base, vec3 blend, float opacity) { + return (blendScreen(base, blend) * opacity + base * (1.0 - opacity)); +} + +float blendLinearDodge(float base, float blend) { + // Note : Same implementation as BlendAddf + return min(base+blend,1.0); +} +vec3 blendLinearDodge(vec3 base, vec3 blend) { + // Note : Same implementation as BlendAdd + return min(base+blend,vec3(1.0)); +} +vec3 blendLinearDodge(vec3 base, vec3 blend, float opacity) { + return (blendLinearDodge(base, blend) * opacity + base * (1.0 - opacity)); +} + + +vec2 ScaleUV(vec2 uv,float scale,vec2 pivot) { + return (uv - pivot) * scale + pivot; +} +vec2 ScaleUV(vec2 uv,vec2 scale,vec2 pivot) { + return (uv - pivot) * scale + pivot; +} + +vec2 rotatePoint(vec2 cen,float angle,vec2 p) { + float s = sin(angle); + float c = cos(angle); + + // translate point back to origin: + p.x -= cen.x; + p.y -= cen.y; + + // rotate point + float xnew = p.x * c - p.y * s; + float ynew = p.x * s + p.y * c; + + // translate point back: + p.x = xnew + cen.x; + p.y = ynew + cen.y; + return p; +} + +float mirrorTile(float val, float lower, float upper) { + if (val < lower) return lower * 2 - val; + if (val > upper) return upper * 2. - val; + return val; +} +vec2 mirrorTile(vec2 val, float lower, float upper) { + val.x = mirrorTile(val.x, lower, upper); + val.y = mirrorTile(val.y, lower, upper); + return val; +} + +float getRotateScaleModifier(float rotation) { + return smoothstep(0., HALF_PI*.5*rotateScaleSmoothnessFactor, 2 * abs(asin(sin(0.5*rotation*TWO_PI))*1.)); + // return min(2. * abs( asin(sin(0.5*layerRotation*TWO_PI))*2. ), .5*HALF_PI ) / (.5*HALF_PI); +} + +/////////////////// +// END RENDER GIF BG +/////////////////// + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +// Reference to +// https://github.com/Ikeiwa/USC-SDVX-IV-Skin (Ikeiwa) +// http://thndl.com/square-shaped-shaders.html +// https://thebookofshaders.com/07/ + +float GetDistanceShape(vec2 st, /*int*/float N){ + vec3 color = vec3(0.0); + float d = 0.0; + + // Angle and radius from the current pixel + float a = atan(st.x,st.y)+PI; + float r = TWO_PI/N; + + // Shaping function that modulate the distance + d = cos(floor(.5+a/r)*r-a)*length(st); + + return d; + +} + +float mirrored(float v) { + float m = mod(v, 2.0); + return mix(m, 2.0 - m, step(1.0, m)); +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec2 rotate2d(vec2 uv,float _angle, vec2 pivot){ + return (uv - pivot) * mat2(cos(_angle),-sin(_angle), + sin(_angle),cos(_angle)) + pivot; +} + +void main() { + target.rgba = vec4(vec3(0.),1.); + + // MAIN SETUP + float beatTime = mod(timing.y,1.0); + + float ar = float(viewport.x) / float(viewport.y); + float var = float(viewport.y) / float(viewport.x); + + vec2 screenUV = vec2(texVp.x / viewport.x, texVp.y / viewport.y); + + vec2 uv = screenUV; + uv.x *= ar; + + vec2 center = vec2(screenCenter) / vec2(viewport); + center.x *= ar; + + vec2 point = uv; + float bgrot = dot(tilt, vec2(0.5, 1.0)); + point = rotatePoint(center, TunnelBaseRotation + (bgrot * TWO_PI), point); + + // BACKGROUND + vec4 backgroundTexture = vec4(0.,0.,0.,1.); + vec4 backgroundTexture2 = vec4(0.); + vec4 layerTexture = vec4(0.); + if (Bg == 1) { + // Bg => BgOverlay => BgLayer + for (int i=0;i<3;++i) { + if ((i == 0 && BgBase == 0) || (i == 1 && BgOverlay == 0) || (i == 2 && BgLayer == 0)) + continue; + + bool isAnim = (i < 2 && BgBaseAnim == 1) || i == 2; + float frameCount; + if (isAnim) { + frameCount = i < 2 ? BgBaseAnimFramesCount : BgLayerAnimFramesCount; + } + + vec2 backgroundUV = screenUV; + // backgroundUV.x -= 0.5; + if (BgOverlay == 1 && i == 1) { + backgroundUV.y -= portrait(BgOverlayOffsetY,-0.5); // todo: figure out landscape + if (BgOverlayFloat == 1) + backgroundUV.y += sin(timing.z) * 0.003 * BgOverlayFloatFactor; + } else { + backgroundUV.y -= portrait(BgBaseOffsetY,-0.5); // todo: figure out landscape + } + // backgroundUV.x *= ar; + // backgroundUV /= 1.2; + // backgroundUV /= ar; + // backgroundUV.x += 0.5; + backgroundUV.y -= portrait(0.15,0.05); + backgroundUV.y /= ar; + + if ((i < 2 && BgBaseTilt == 1) || (i == 1 && BgOverlayTilt == 1) || i == 2) { + float rotation = i < 2 && !(i == 1 && BgBaseTilt == 0) // todo: tilt to maximum point? + ? dot(tilt, vec2(0.5, 1.0)) + : dot(tilt, vec2(1.0)); + float scaleModifier = getRotateScaleModifier(rotation); + vec2 scaleConst = ((i == 2 && BgLayerScaleHard == 0) || (i < 2 && BgBaseScaleSoft == 1)) ? softScale : hardScale; + if (BgBaseTilt == 1) + backgroundUV = ScaleUV( backgroundUV, scaleConst.x - (scaleModifier*scaleConst.y), bgPivot ); + backgroundUV = rotatePoint(bgPivot, rotation*TWO_PI, backgroundUV); + } + + float bgAr = i < 2 && BgBaseAnim == 0 ? BgBaseAR : bgLayerAR; + if (bgAr > 1) + backgroundUV = ScaleUV(backgroundUV, vec2(1/bgAr, 1.), bgPivot); + else + backgroundUV = ScaleUV(backgroundUV, vec2(1., bgAr), bgPivot); + + vec2 animOffset; + animOffset = vec2(0.); + if (isAnim) { + float frameFraction = 1. / frameCount; + float currentFrame = floor(timing.y * frameCount); + + animOffset = vec2(currentFrame*frameFraction, 0.); + backgroundUV *= vec2(frameFraction, 1.); + } + backgroundUV = BgBaseClampTiling == 0 + ? mirrorTile(backgroundUV, .001, .999) + : clamp(backgroundUV, .001, .999); + + + if (isAnim) + backgroundUV += animOffset; + + if (i == 0) { + backgroundTexture = texture(BgBaseTex, backgroundUV); + if (BgBaseClearVersion == 1) { + vec4 backgroundClearTexture = texture(BgBaseClearTex, backgroundUV); + backgroundTexture = mix(backgroundTexture,backgroundClearTexture,clearTransition); + } + } else if (i == 1) { + backgroundTexture2 = texture(BgOverlayTex, backgroundUV); + if (BgOverlayClearVersion == 1) { + vec4 backgroundOverlayClearTexture = texture(BgOverlayClearTex, backgroundUV); + backgroundTexture2 = mix(backgroundTexture2,backgroundOverlayClearTexture,clearTransition); + } + if (BgOverlayFlashEffect == 1) { + backgroundTexture2.a *= .9 + timing.y * .1; + } + } else if (i == 2) { + layerTexture = texture(BgLayerTex, backgroundUV); + if (BgLayerClearVersion == 1) { + vec4 layerClearTexture = texture(BgLayerClearTex, backgroundUV); + layerTexture = mix(layerTexture, layerClearTexture, clearTransition); + } + layerTexture.rgb = blendOverlay(layerTexture.rgb, vec3(1.), BgLayerBrighten); + } + } + } + target.rgb = mix(target.rgb, backgroundTexture.rgb, backgroundTexture.a); + if (BgLayer == 1) + target.rgb = blendLinearDodge(target.rgb, layerTexture.rgb, layerTexture.a); + // END BACKGROUND + + // TUNNEL -- gaat nog niet goed denkik + if (Tunnel == 1) { + vec2 pointFromCenter = center - point; + pointFromCenter /= TunnelScale; + float diff = GetDistanceShape(pointFromCenter,TunnelSides); + float fog = -1. / (diff * TunnelFog * TunnelScale.x) + 1.; + fog = clamp(fog, 0, 1); + float tunnelTexY = TunnelStretch / diff; + tunnelTexY += timing.y * TunnelSpeed; + float rot = (atan(pointFromCenter.x,pointFromCenter.y) + TunnelBaseTexRotation + TunnelExtraRotation*TWO_PI) / TWO_PI; + if (TunnelVortexEffect == 1) + rot += fract(timing.z*0.1*TunnelVortexFactor); + vec4 tunnelTexture = texture(TunnelTex, vec2(rot,mod(tunnelTexY,1))); + if (TunnelClearVersion == 1) { + vec4 clearTunnelTexture = texture(TunnelClearTex, vec2(rot,mod(tunnelTexY,1))); + tunnelTexture = mix(tunnelTexture,clearTunnelTexture,clearTransition); + } + + if (TunnelFlashEffect == 1) { + float brightness = timing.y * 1.; + tunnelTexture.rgb = blendOverlay(tunnelTexture.rgb, vec3(1.), brightness); + } + + if (TunnelDodgeBlend == 1) + target.rgb = blendLinearDodge(target.rgb, tunnelTexture.rgb*2, tunnelTexture.a*2*fog); + else + target.rgb = mix(target.rgb, tunnelTexture.rgb*2., tunnelTexture.a*fog); + + // target.rgb = backgroundTexture.rgb * (1-target.a) + target.rgb * target.a; + // target.rgb = tunnelTexture.rgb * 2.0; + // target.a = tunnelTexture.a * fog; + } + // END TUNNEL + + // CENTER TEXTURE + if (Center == 1) { + vec2 centerUV = screenUV; + //centerUV = center - centerUV; // this would be 'centered uv calculation' (?) + //centerUV *= -1.0; + //centerUV += 0.5; + centerUV.x -= 0.5; + centerUV.y -= CenterOffsetY; + if (CenterSnapToTrack == 1) + centerUV.y += (0.5-center.y); + else + centerUV.y += portrait(0.19,0.25); + + if (CenterFloat == 1) { + centerUV -= vec2( + sin(timing.z * 0.6) * 0.003 * CenterFloatXFactor, + cos(timing.z) * 0.007 * CenterFloatFactor + ); + } + centerUV.x *= ar; + centerUV.x += 0.5; + centerUV = clamp(centerUV,0.0,1.0); + + if (CenterTilt == 1) + centerUV = rotatePoint(vec2(0.5, 0.5-CenterOffsetY), clamp(TunnelBaseRotation + (bgrot * TWO_PI),-360,360), centerUV); + + if (CenterFloat == 1 && CenterFloatRotationFactor > 0.) { + centerUV = rotatePoint(vec2(0.5), (sin(timing.z*0.3))*0.05*TWO_PI * CenterFloatRotationFactor, centerUV); + } + + centerUV = ScaleUV(centerUV,CenterScale,vec2(0.5)); + + float GlowTimingProgress = sin(timing.z*6.5)*.5+.5; + + if (CenterGlow == 1) + centerUV = ScaleUV(centerUV,GlowTimingProgress*.3+1.,vec2(0.5)); + + if (CenterRotate == 1) + centerUV = rotatePoint(vec2(0.5), fract(timing.z*0.02)*TWO_PI, centerUV); + + // TODO: center anim + + vec4 centerTexture = vec4(0.); + if (CenterNormalVersion == 1) { + centerTexture = texture(CenterTex, clamp(centerUV,0.,1.)); + } + if (CenterClearVersion == 1) { + vec4 centerTextureClear = texture(CenterClearTex, clamp(centerUV,0.,1.)); + centerTexture = mix(centerTexture,centerTextureClear,clearTransition); + } + + float opacity = CenterFadeEffect == 1 ? (0.2+abs(cos(timing.z)*0.2)) : 1.; + target.rgb = mix(target.rgb,centerTexture.rgb,centerTexture.a * opacity); + + if (BgOverlay == 1) + target.rgb = mix(target.rgb,backgroundTexture2.rgb,backgroundTexture2.a); + + if (CenterLayerEffect == 1) { + float a = CenterLayerEffectAlpha; + float sc = CenterLayerEffectScale; + if (CenterLayerEffectFade == 1) + a *= (0.3+(cos(timing.z)*0.3)); + if (CenterLayerEffectGlow == 1) { + a *= (GlowTimingProgress*.2+.4); + sc *= (GlowTimingProgress*.05+.95); + } + + vec2 centerLayerUV = ScaleUV(centerUV,sc,vec2(.5,.5)); + if (CenterLayerEffectRotate == 1) + centerLayerUV = rotatePoint(vec2(0.5), fract(timing.z*0.01*CenterLayerEffectRotateSpeed)*TWO_PI, centerLayerUV); + vec4 centerTexture2 = texture(CenterLayerEffectTex, clamp(centerLayerUV,0.,1.)); + + if (CenterLayerEffectDodgeBlend == 1) + target.rgb = blendLinearDodge(target.rgb, centerTexture2.rgb, centerTexture2.a*a); + else + target.rgb = mix(target.rgb,centerTexture2.rgb,centerTexture2.a * a); + } + // todo: have pulsing speed factor uniform + if (CenterPulse == 1) { // also has to account for clear version + vec4 centerTextureef = texture(CenterTex, clamp(ScaleUV(centerUV,1.-fract(timing.z*1.5)*0.15,vec2(0.5)),0.0,1.0)); + target.rgb = mix(target.rgb,centerTextureef.rgb + target.rgb,centerTextureef.a *(fract(-timing.z*1.5))*0.2); + } + } + // END CENTER TEXTURE + + // If Center == 1 this will be drawn in the Center block + if (Center == 0 && BgOverlay == 1) + target.rgb = mix(target.rgb,backgroundTexture2.rgb,backgroundTexture2.a); + + //PARTICLES + if (Particle == 1) { + vec2 particlesUV = point; + particlesUV.x = center.x - particlesUV.x; + particlesUV.x = mirrored(clamp(particlesUV.x,-1.0,1.0)); + // particlesUV.y += 0.1; + + vec4 particles = vec4(vec3(1.),0.); + float particlesTime = timing.z * ParticleSpeed; + + for (int i=0;i edited by Shirijii \ No newline at end of file diff --git a/backgrounds/fallback/bg.lua b/backgrounds/fallback/bg.lua index de76dbf..9672ce8 100644 --- a/backgrounds/fallback/bg.lua +++ b/backgrounds/fallback/bg.lua @@ -1,613 +1,613 @@ -local delayedOperations = { - u={}, - speed=nil -} -local dark = game.GetSkinSetting("dark_mode") or game.GetSkinSetting("dark_bgs") - --- backgroundTextures -local bt = { - _={"_.png"}, - cyberspace={"cyberspace.jpg", "cyberspace-c.jpg"}, - watervault={"watervault.jpg", "watervault-c.jpg"}, - underwater={"underwater.jpg", "underwater-c.jpg", bright=true}, - ocean={"ocean.jpg", "ocean-c.jpg",bright=true}, - grass={"grass.jpg", "grass-c.jpg"}, - deepsea={"deepsea.jpg", "deepsea-c.jpg"}, - cyber={"cyber.jpg", "cyber-c.jpg"}, - desert={"desert.jpg", "desert-c.jpg"}, - desertYellowClear={"desert.jpg", "desert-c2.jpg",bright=true}, - sky={"sky.jpg", "sky-c.jpg",bright=true}, - skyIv={"sky-iv.jpg", "sky-iv-c.jpg",bright=true}, - skyIv2={"sky_iv_2.png", "sky_iv_2-c.png",bright=true}, - skyIvDark={"sky-iv-dark.jpg"}, - sunset={"sunset.jpg", "sunset-c.jpg",bright=true}, - redgradient={"redgradient.jpg", "redblur.jpg"}, - mars={"mars.jpg", "mars-c.jpg"}, - cloudy={"cloudy.jpg", "cloudy-c.jpg"}, - redblur={"redblur.jpg", "redblur-c.jpg"}, - galaxy={"galaxy.jpg", "galaxy-c.jpg"}, - fantasy={"fantasy.jpg", "fantasy-c.jpg",bright=true}, - bedroom={"bedroom.jpg", "bedroom-c.jpg",bright=true}, - flame={"flame.jpg", "flame-c.jpg"}, - game={"game.png",bright=true}, - beach={"beach.png"}, - night={"night.jpg", "night-c.jpg"}, - prettygalaxy={"prettygalaxy.jpg", "prettygalaxy-c.jpg"}, - sakura={"sakura.jpg", "sakura-c.jpg"}, - cyberspaceNight={"cyberspace_night.png", "cyberspace_night_starburst.png", bright=true}, - moonBlue={"moon_blue.jpg", "moon_blue-c.jpg"}, - moonPurple={"moon_purple.jpg", "moon_purple-c.jpg",bright=true}, - redDusk={"red_dusk.jpg", "red_dusk-c.jpg"}, - star={"star.png", "star-c.png",bright=true}, - twilight={"twilight.png", "twilight-c.png"}, - undersea={"undersea.png", "undersea-c.png"}, -} - --- backgroundComposition -local function bc(bt, opts) - local out = {} - for k,v in pairs(bt) do out[k] = v end - for k,v in pairs(opts) do out[k] = v end - return out -end - -local btCollections = { - blue={bt.watervault,bc(bt.underwater,{u={TunnelDodgeBlend=true}}),bt.cyberspace,bt.ocean,bt.grass,bt.deepsea,bt.cyber,bt.desert,bt.sky,bt.skyIv,bt.skyIv2,bt.moonBlue,{"city.png"},bt.cyberspaceNight,bt.undersea,bc(bt._,{weight=6})}, - red={bc(bt.flame,{u={TunnelDodgeBlend=true}}),bt.sunset,bt.redgradient,bt.mars,bt.cloudy,bt.redDusk,bt.moonPurple,bc(bt._,{weight=4})}, -} - -local pt = { - lights={"lights_default.png", "lights_default-c.png"}, - lightsMoonblue={"lights_moonblue.png", "lights_moonblue-c.png"}, - lightsPurplish={"lights_purplish.png"}, - lightsOrangePink={"lights_orangepink.png"}, - lightsPink={"lights_pink.png", "lights_pink-c.png"}, - lightsSea={"lights_sea.png", "lights_sea-c.png"}, - lightsYellow={"lights_yellow.png"}, - lightsYellowPurple={"lights_yellow.png", "lights_purplish.png"}, - lightsYellowGreen={"lights_yellowgreen.png"}, - twilight={"twilight.png", "twilight-c.png"}, - streetLanterns={"street_lanterns.png", "street_lanterns-c.png"}, - starParticles={"star_particles.png", "star_particles-c.png"}, - squares={"squares.png", "squares-c.png"}, -} - - - -local bgs = { - arrows={ - Tunnel={ - Tex={{"arrows-large.png", "arrows-large-c.png"},{"arrows-small.png", "arrows-small-c.png",speed=0.7}}, - u={ Sides=4, Stretch=0.2, ScaleX=0.8, ScaleY=0.8, Fog=15.0 }, - }, - Bg={ Base={Tex={bt.redgradient,bt._}} }, - Particle={ - Tex={"icons.png", "icons-c.png"}, - u={ Scale=0.2, OffsetY=-0.2 } - }, - speed=0.8,weight=2 - }, - technoEye={ - Bg={ Base={Tex=bt.cyberspace} }, - Center={ - Tex="techno-eye.png", - u={Pulse=true, Float=true, Scale=2.8}, - LayerEffect={Tex="glowshine.png", Fade=true} - }, - Tunnel={ - Tex={"electro-blue.png", "electro-c.png"}, - u={Sides=8, Stretch=0.3, ScaleY=0.9, Fog=10.0, ExtraRotation=-0.125} - }, - }, - waveBlue={ - Bg={ Base={Tex={bc(bt.watervault,{u={Center=false}}),bc(bt.underwater,{u={TunnelDodgeBlend=true,Center=false}}),bc(bt.cyberspace,{u={Center=false}}),bc(bt.ocean,{u={Center=false}}),bt.grass,bt.deepsea,bt.cyber,bc(bt.desert,{u={Center=false}}),bt.sky,bt.moonBlue,{"city.png"},bc(bt.cyberspaceNight,{u={Center=false}}),bc(bt.undersea,{u={Center=false,TunnelDodgeBlend=true}}),bc(bt._,{weight=5})}, ScaleSoft=true}}, -- Customised version of bt.blue - Tunnel={ - Tex={{"wave-blue.png", "wave-blue-c.png"},{"wave-green.png", "wave-green-c.png"}}, - u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=20.0} - }, - Center={ Tex={{"moon2.png","moon2-c.png"},{0}}, u={Scale=9.0, OffsetY=-0.05}, LayerEffect={Tex="moon2_shine.png", Glow=true, Scale=0.8, DodgeBlend=true} }, - Particle={ Tex=pt.lightsMoonblue, u={OffsetY=-0.02, Amount=4, Speed=2.0} }, - weight=2 - }, - waveRed={ - Bg={ Base={Tex=btCollections.red, ScaleSoft=true} }, - Tunnel={ - Tex={"wave-red.png","wave-red-c.png"}, - u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=20.0} - }, - Center={ Tex={{"moon_pink.png","moon_pink-c.png"},{0}}, u={Scale=8.0, OffsetY=-0.05}, LayerEffect={Tex="moon_pink_shine.png", Glow=true, DodgeBlend=true, Scale=0.8} }, - Particle={ Tex=pt.lightsPink, u={OffsetY=-0.02, Amount=4, Speed=2.0} }, - }, - waveOrange={ - Bg={ Base={Tex={bc(bt.twilight,{u={Center=false}}),bc(bt.grass,{u={Center=false}}),bt.redblur,bt.redgradient,bt.redDusk,bc(bt.star,{u={Center=false}}),bt.cyberspace,bt.sunset,bc(bt.cyber,{u={Center=false}}),bc(bt.galaxy,{u={Center=false}}),bc(bt.desertYellowClear,{u={Center=false}}),bc(bt.fantasy,{u={Center=false}}),bt.moonPurple,bc(bt.undersea,{u={Center=false}}),{"city.png"},bc(bt._,{weight=6})}, ScaleSoft=true} }, - Tunnel={ - Tex={"wave-orange.png","wave-orange-c.png"}, - u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=20.0} - }, - Center={ Tex={{"moon_orange.png","moon_orange-c.png"},{0}}, u={Scale=8.0, OffsetY=-0.05}, LayerEffect={Tex="moon_orange_shine.png", Glow=true, DodgeBlend=true, Scale=0.8} }, - Particle={ Tex=pt.lightsOrangePink, u={OffsetY=-0.02, Amount=4, Speed=2.0} }, - }, - waveRedNoise={ - Bg={ - Layer={Tex={{"wave4.jpg", "wave4-c.jpg"}}}, - Pivot={0.35} - } - }, - waveGalaxy={Bg={ - Layer={Tex={{"wave6.jpg", "wave6-c.jpg"}}}, - Pivot={0.35} - }}, - game={ - Bg={Base={Tex="game.png", Tilt=false}, Overlay={Tex="game-f.png", Float=true, FlashEffect=true, FloatFactor=2.0}}, - Center={Tex={"logo.png", "logo-c.png"}, u={Scale=3.8, Tilt=true}}, - Tunnel={Tex="game.png", u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=15.0, ExtraRotation=0.375}}, - Particle={Tex={bc(pt.squares,{u={ParticleSpeed=1}}),{"triangles.png",weight=2}}, - u={OffsetY=-0.1, Amount=5, Speed=2.0, Scale=0.8} - } - }, - seaNight={ -- todo: add bg landscapeoffset - Bg={Base={Tex="sea-night.png", OffsetY=-0.19, ScaleSoft=true}, Overlay={Tex="sea-night-f.png", Float=true, OffsetY=-0.17}}, - Center={ Tex="ship-night.png", u={Scale=2.5, Float=true, FloatFactor=0.5, SnapToTrack=false}, LayerEffect={Tex="kac-hikari-2.png"}}, - Particle={ Tex="shines2.png", u={Speed=1.8, Amount=9} }, - -- Tunnel={}, - luaParticleEffect = { particles = { {"star-particle.png", 32} } } - }, - seaStorm={ - Bg={Base={Tex="sea-storm.png", OffsetY=-0.13, ScaleSoft=true}, Overlay={Tex="sea-storm-f.png", Float=true, OffsetY=-0.17}}, - Center={ Tex="ship-storm.png", u={Scale=2.5, Float=true, FloatFactor=0.5, SnapToTrack=false}, LayerEffect={Tex="kac-hikari.png"}}, - Particle={ Tex="shines2.png", u={Speed=1.8, Amount=9} }, - -- Tunnel={}, - }, - seaIce={ - Bg={Base={Tex="sea-ice.png", OffsetY=-0.1, ScaleSoft=true}, Overlay={Tex="sea-ice-f.png", Float=true, OffsetY=-0.15}}, - Center={ Tex="ship-ice.png", u={Scale=2.5, Float=true, FloatFactor=0.5, SnapToTrack=false}, LayerEffect={Tex="kac-hikari-2.png"}}, - Particle={ Tex="shines1.png", u={Scale=0.3, OffsetY=-0.3, Speed=1.8, Amount=9} }, - -- Tunnel={}, - luaParticleEffect = { particles = { {"star-particle.png", 32} } } - }, - seaThunder={ - Bg={Base={Tex="sea-thunder.png", OffsetY=-0.15, ScaleSoft=true}, Overlay={Tex="sea-thunder-f.png", Float=true, OffsetY=-0.16}}, - Center={ Tex="ship-storm.png", u={Scale=2.5, Float=true, FloatFactor=0.5, SnapToTrack=false}, LayerEffect={Tex="kac-hikari.png"}}, - Particle={ Tex="shines2.png", u={Speed=1.8, Amount=9} }, - }, - seaDay={ -- todo: fixlandscape, fix rotation - Bg={Base={Tex="sea-day.png", ScaleSoft=true}, Overlay={Tex="sea-day-f.png", Float=true, OffsetY=-0.1}}, - Center={Tex="ship-day.png", u={Scale=2, Float=true, FloatFactor=0.5, OffsetY=0.02, SnapToTrack=false}, LayerEffect={Tex="kac-hikari.png"}}, - Particle={ Tex="shines2.png", u={Speed=1.8, Amount=9} }, - bright=true, - -- Tunnel={}, - }, - sakuraRainbow={ - Bg={Base={Tex=bt.sakura, ScaleSoft=true}}, - Center={ - Tex="rainbow.png", u={Scale=1.5, FadeEffect=true}, - LayerEffect={Tex="kac_hikari_sakura.png"} - }, - -- Tunnel: rainbow rings?! probably not xd - Particle={Tex="shines1.png", u={Speed=1.6, OffsetY=-0.3, Amount=6, Scale=0.35}}, - luaParticleEffect={particles={{"petal1.png", 140},{"petal2.png", 40},{"petal3.png", 140}}}, - bright=true - }, - -- colorBokeh={Bg={Layer={Tex={"colorbokeh.jpg", "colorbokeh-c.jpg"} }, u={Pivot=0.35}}}, -- todo: find working bgs (?) - smoke1={Bg={ - Base={Tex={bt.fantasy,bt.cyberspaceNight,bt.undersea,bt.watervault,bt.underwater,bt.cyberspace,bt.ocean,bt.grass,bt.deepsea,bt.cyber,bt.desertYellowClear,bt.sky,bc(bt._,{weight=5})},ScaleSoft=true}, - Layer={Tex={"smoke.jpg", "smoke-c.jpg"}}, - u={Pivot=0.35} - }}, - -- sparkles1={Bg={Layer={Tex={"sparkles1.jpg", "sparkles1-c.jpg"}, ScaleHard=true }}}, - -- domeLayer={Bg={ - -- Base={Tex={bt.redblur,bt._}}, - -- Layer={Tex={{"spider1.jpg", "spider1-c.jpg"},{"spider2.jpg", "spider2-c.jpg"},{"spider3.jpg", "spider3-c.jpg", speed=0.75, u={BgPivot=0.27}}}}, - -- u={Pivot=0.36} - -- },weight=2}, - -- electro1={Bg={ - -- Base={Tex={bt.redblur,bt.cyber,bt._}}, - -- Layer={Tex={"electro1.jpg", "electro1-c.jpg"}, brightenLayer=0.6}, u={Pivot=0.35}} - -- }, - -- electro2={Bg={Layer={Tex={"electro2.jpg", "electro2-c.jpg"} }, u={Pivot=0.3}}}, - -- plasmaTunnel={Bg={Layer={Tex={"plasmatunnel.jpg", "plasmatunnel-c.jpg"}, }, u={Pivot=0.37}}, speed=0.75, bright=true}, -- my eyes are burning - -- xcalibur={Bg={Base={Tex={"anim/xcalibur.jpg", "anim/xcalibur-c.jpg"}}, u={Pivot=0.36}}, speed=0.3}, - goldleaves={ - Bg={Base={Tex={"anim/goldleaves.jpg", "anim/goldleaves-c.jpg"}}}, - Particle={ Tex=pt.lightsYellow, u={Speed=1.8, OffsetY=-0.1, Amount=9} }, - speed=0.6 - }, - technocircle={ - Bg={Base={Tex={"anim/technocircle.jpg", "anim/technocircle-c.jpg"}, ScaleSoft=true}, u={Pivot=0.37}}, - Particle={ Tex="shines2.png", u={Speed=1.8, OffsetY=-0.15, Amount=7} }, - speed=0.6, bright=true - }, - snow={ - Bg={Base={Tex={"anim/snow.jpg", "anim/snow-c.jpg"}}, u={Pivot=0.3}}, - Particle={Tex="shines1.png", u={Speed=1.9, OffsetY=-0.3, Amount=8, Scale=0.4}}, - }, - sky={ - Bg={Base={Tex={bt.skyIv,bt.skyIv2}}}, - Tunnel={Tex="clouds.png", u={Sides=16, Fog=15, Stretch=0.07, VortexEffect=true}}, - Center={ - Tex={{speed=0.9, u={TunnelFog=100, TunnelVortexFactor=5}},{"sdvx_iv.png",u={CenterScale=10,CenterOffsetY=0}}}, - u={Float=true, Scale=5, FloatXFactor=2, OffsetY=-0.02} - }, - Particle={Tex="shines1.png", u={Amount=8, OffsetY=-0.3, Speed=1.9, Scale=0.5}}, - speed=0.5, bright=true - }, - -- skyDark={ - -- Bg={Base={Tex={"sky-iv-dark.jpg"}}}, - -- Tunnel={Tex={"clouds-dark.png","clouds-dark-c.png"}, u={Sides=16, Fog=100, Stretch=0.07, VortexEffect=true, VortexFactor=5}}, - -- speed=0.9, - -- }, - hexagons={ - Tunnel={Tex={{"hexagons.png", "hexagons-c.png"},{"hexagons-gray.png","hexagons.png"}}, u={ExtraRotation=-0.125, Fog=30}}, - Particle={Tex="hexes.png", u={Amount=3, OffsetY=-0.2, Speed=1.9, Scale=0.7}}, - speed=1.2,weight=2 - }, - dome={ -- todo: fix rotation - Bg={Base={Tex={bt.redblur,bt.redgradient,bt._}}}, - Tunnel={Tex={"dome.png","dome-c.png"}, u={ExtraRotation=-0.125/2, Fog=30, Stretch=0.09}}, - Center={ - Tex={{"glowshine_green.png",weight=2},{0}}, u={Scale=13, Pulse=true, Glow=true}, - LayerEffect={Tex="glowshine_sun.png", Glow=true, Scale=0.8} - }, - Particle={Tex=pt.lightsYellowGreen, u={Amount=4, OffsetY=0, Speed=1.9}}, - speed=0.6, - }, - domeRed={ - Bg={Base={Tex={bc(bt.redblur,{u={CenterScale=9}}),bt.redgradient,bt._}}}, - Tunnel={Tex={"dome-red.png","dome-red-c.png"}, u={ExtraRotation=-0.125/2, Fog=30, Stretch=0.09}}, - Center={ - Tex={{"glowshine_pink.png",weight=2},{0}}, u={Scale=13, Pulse=true, Glow=true}, - LayerEffect={Tex="glowshine_orange.png", Glow=true, Scale=0.8} - }, - Particle={Tex=pt.lightsOrangePink, u={Amount=4, OffsetY=0, Speed=1.9}}, - speed=0.6 - }, - iseki={ - Tunnel={ - Tex={{"iseki.png","iseki-c.png",u={TunnelExtraRotation=-0.125/2}}}, - u={Sides=16, Stretch=0.1, FlashEffect=true, Fog=25.0} - }, - Center={Tex={{"kac_maxima_gold.png"},{0,weight=4}}, u={Float=true, Scale=5, OffsetY=0.04}, LayerEffect={Tex="kac_hikari_iseki.png", Scale=0.7}}, - Particle={Tex=pt.lightsYellow, u={Amount=6, OffsetY=0, Speed=1.9}}, - speed=0.5, - }, - idofront={ - Tunnel={ - Tex={{"idofront.png","idofront-c.png",u={TunnelExtraRotation=-0.125/2}}}, - u={Sides=16, Stretch=0.1, Fog=25.0} - }, - Particle={Tex=pt.lightsPurplish, u={Amount=5, Scale=1.3, Speed=1.9}}, - }, - genom={ - Tunnel={ - Tex={{"genom.png","genom-c.png",u={TunnelExtraRotation=-0.125/2*0}}}, - u={Sides=16, Stretch=0.1, Fog=25.0} - }, - Particle={Tex=pt.lightsOrangePink, u={Amount=5, Scale=1.3, Speed=1.9}}, - speed=0.7 - }, - twilight={ - Bg={ Base={Tex=bt.twilight, ScaleSoft=true}}, - Tunnel={Tex={"wave-orange.png","wave-orange-c.png"}, u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=14.0}}, - Center={ Tex="moon_twilight.png", u={Scale=8.0, OffsetY=-0.05}, LayerEffect={Tex="moon_twilight_shine.png", Glow=true} }, - Particle={ Tex=pt.twilight, u={OffsetY=-0.3, Amount=5, Speed=1.5, Scale=0.5} }, - }, - shinwa={ -- todo: put clouds in extra layer of center, remove weird 'bg is static but overlay can tilt' logic - Bg={ Base={Tex="cyberspace_sunrise.png", OffsetY=-0.23, Tilt=false, ClampTiling=true}, Overlay={Tex="cyberspace_sunrise-f.png", Tilt=true, OffsetY=-0.1}}, - Center={ Tex={"shinwa.png"}, u={Scale=9.0, OffsetY=0, Float=true, Pulse=true}, LayerEffect={Tex="cyberspace_shine.png", Glow=true, Alpha=0.8, Scale=1, Rotate=true, RotateSpeed=-5} }, - Particle={ Tex=pt.lightsYellow, u={OffsetY=-0.3, Amount=9, Speed=2, Scale=0.5} }, - }, - beach={ - Bg={ Base={Tex="beach.png", ScaleSoft=true}}, - Center={ Tex={"glowshine_orange.png","glowshine_sun.png"}, u={Scale=8.0, OffsetY=-0.05, Float=true}, LayerEffect={Tex="glowshine_sun.png", Glow=true, Scale=0.9} }, - Particle={ Tex=pt.lightsYellow, u={OffsetY=-0.3, Amount=9, Speed=2, Scale=0.5} }, - bright=true - }, - sonar={ - Bg={ Base={Tex={bt.undersea,bt.watervault,bt.underwater}}}, - Center={ Tex={"magic_circle.png","magic_circle-c.png"}, u={Scale=2.6, Rotate=true}, LayerEffect={Tex="sonar.png", Rotate=true, RotateSpeed=-10, Scale=3} }, - Particle={ Tex=pt.lightsSea, u={OffsetY=-0.1, Amount=9, Speed=1.7} }, - }, - star={ - Bg={ Base={Tex=bt.star, ScaleSoft=true, Tilt=false}}, - Center={ Tex="star_core.png", u={Scale=2.15, Tilt=false, SnapToTrack=false, OffsetY=-0.04}, LayerEffect={Tex="star_core.png", Glow=true, DodgeBlend=true } }, - Particle={ Tex=pt.starParticles, u={OffsetY=-0.1, Amount=9, Speed=1.7} }, - Tunnel={ Tex={"electro.png", "electro-c.png"}, u={Sides=12, Stretch=0.12, ScaleY=0.9, Fog=18.0, ExtraRotation=-0.125}}, - }, - -- beams={ - -- Tunnel={ - -- Tex={{"beams.png","beams-c.png",u={TunnelExtraRotation=-0.125/2}}}, - -- u={Sides=16, Stretch=0.1, Fog=25.0} - -- }, - -- }, -} - --- local bgTrumpcard=bgs.shinwa --- local bgNestedIndices = { --- -- BgBase=1, --- -- Tunnel=1, --- Center=1 --- } - -local function stringToNumber(str) - local number = 0 - for i = 1, string.len(str) do - number = number + string.byte(str, i) - end - return number -end - -local function sign(x) - return x>0 and 1 or x<0 and -1 or 0 -end - -local function setUniform(key, value) - local valueType = type(value) - if valueType == "number" then - background.SetParamf(key, value) - elseif valueType == "boolean" then - background.SetParami(key, value and 1 or 0) - else - game.Log("Weird param type was passed to setUniform", 1) - end -end - ------------------ --- PARTICLE STUFF ------------------ - -local resx, resy = game.GetResolution() -local portrait = resy > resx -local desw = portrait and 720 or 1280 -local desh = desw * (resy / resx) -local scale = resx / desw - -local shouldRenderParticles = false -local particleTextures = {} -local particles = {} -local psizes = {} - -local particleCount = 30 -local particleSizeSpread = 0.5 - -local function initializeParticle(initial) - local particle = {} - particle.x = math.random() - particle.y = math.random() * 1.2 - 0.1 - if not initial then particle.y = -0.1 end - particle.r = math.random() - particle.s = (math.random() - 0.5) * particleSizeSpread + 1.0 - particle.xv = 0 - particle.yv = 0.1 - particle.rv = math.random() * 2.0 - 1.0 - particle.p = math.random() * math.pi * 2 - particle.t = math.random(1, #psizes) - return particle -end - -local function renderParticles(deltaTime) - local alpha = 0.3 + 0.5 * background.GetClearTransition() - for i,p in ipairs(particles) do - p.x = p.x + p.xv * deltaTime - p.y = p.y + p.yv * deltaTime - p.r = p.r + p.rv * deltaTime - p.p = (p.p + deltaTime) % (math.pi * 2) - - p.xv = 0.5 - ((p.x * 2) % 1) + (0.5 * sign(p.x - 0.5)) - p.xv = math.max(math.abs(p.xv * 2) - 1, 0) * sign(p.xv) - p.xv = p.xv * p.y - p.xv = p.xv + math.sin(p.p) * 0.01 - - gfx.Save() - gfx.ResetTransform() - gfx.Translate(p.x * resx, p.y * resy) - gfx.Rotate(p.r) - gfx.Scale(p.s * scale, p.s * scale) - gfx.BeginPath() - gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER) - gfx.ImageRect(-psizes[p.t]/2, -psizes[p.t]/2, psizes[p.t], psizes[p.t], particleTextures[p.t], alpha, 0) - gfx.Restore() - if p.y > 1.1 then - particles[i] = initializeParticle(false) - end - end - gfx.ForceRender() -end - -local function getRandomItemFromArray(array, seed) - if #array == 1 then return array[1] end - - local weightedArray = {} - for i, value in ipairs(array) do - local weight = value.weight or 1 - for j = 1, weight do - weightedArray[#weightedArray+1] = value - end - end - - math.randomseed(stringToNumber(seed)) - return weightedArray[math.random(#weightedArray)] -end - -local function getImageDimensions(imagePath) - return gfx.ImageSize( gfx.CreateImage(background.GetPath().."textures/"..imagePath, 0) ) -end - -local function filterArray(array, propertyToRemove) - local newArray = {} - for i, v in ipairs(array) do - if not v[propertyToRemove] then newArray[#newArray+1] = v end - end - return newArray -end - -local function filterTable(table, propertyToRemove) - local newTable = {} - for k, v in pairs(table) do - if not v[propertyToRemove] then newTable[k] = v end - end - return newTable -end - -local function loadTextures(prefix, tex, subFolder, checkAnim, noAnimCallback, setNormalVersion) - local texture - if type(tex) == "string" then - texture = {tex} - elseif type(tex[1]) == "string" then - texture = tex - else - if dark then tex = filterArray(tex, "bright") end - if bgNestedIndices and bgNestedIndices[prefix] then - texture = tex[bgNestedIndices[prefix]] - else - texture = getRandomItemFromArray(tex, prefix..gameplay.title..gameplay.artist) -- todo: improve seeding - end - end - - if texture[1] == 0 then - return false - end - - if texture[1] then - if setNormalVersion then - setUniform(prefix.."NormalVersion", true) - end - background.LoadTexture(prefix.."Tex", "textures/"..subFolder.."/"..texture[1]) - end - if texture[2] then - setUniform(prefix.."ClearVersion", true) - background.LoadTexture(prefix.."ClearTex", "textures/"..subFolder.."/"..texture[2]) - end - if checkAnim and texture[1] then - local w, h = getImageDimensions(subFolder.."/"..texture[1]) - if w / h > 2 then - setUniform(prefix.."Anim", true) - setUniform(prefix.."AnimFramesCount", math.floor(w / 600)) - elseif noAnimCallback then - noAnimCallback(w, h) - end - end - - if texture.u then - for k,v in pairs(texture.u) do - delayedOperations.u[k] = v - end - end - if texture.speed then delayedOperations.speed = texture.speed end - return true -end - -local function setUniformsRaw(uniforms) - for k,v in pairs(uniforms) do setUniform(k, v) end -end - -local function setUniforms(prefix, uniforms, subFolder, checkAnim, noAnimCallback) - for k, v in pairs(uniforms) do - if k == "Tex" then - loadTextures(prefix, v, subFolder, checkAnim, noAnimCallback) - else - setUniform(prefix..k, v) - end - end -end - -local function loadPart(prefix, part, subFolder, checkAnim, setNormalVersion) - local loaded - if part.Tex then - loaded = loadTextures(prefix, part.Tex, subFolder, checkAnim, nil, setNormalVersion) - else - loaded = true - end - if loaded then - setUniform(prefix, true) - end - if part.u then - setUniforms(prefix, part.u, subFolder) - end -end - -local function randomItemFromTable(table) - local keys = {} - for key, value in pairs(table) do - local weight = value.weight or 1 - for i = 1, weight do - keys[#keys+1] = key - end - end - local index = keys[math.random(#keys)] - return table[index], index -end - -local function processDelayedOperations() - setUniformsRaw(delayedOperations.u) - if delayedOperations.speed then background.SetSpeedMult(delayedOperations.speed) end -end - -local function loadBackground(bg) - if bg.Bg then - local part = bg.Bg - local prefix = "Bg" - loadPart(prefix, part, "background") - if part.Base then - setUniform(prefix.."Base", true) - setUniforms(prefix.."Base", part.Base, "background", true, function(w,h) setUniform(prefix.."Base".."AR", w / h) end) - end - if part.Overlay then - setUniform(prefix.."Overlay", true) - setUniforms(prefix.."Overlay", part.Overlay, "background") - end - if part.Layer then - setUniform(prefix.."Layer", true) - setUniforms(prefix.."Layer", part.Layer, "layer", true) - end - end - if bg.Center then - local part = bg.Center - local prefix = "Center" - loadPart(prefix, part, "center", true, true) - if part.LayerEffect then - setUniform(prefix.."LayerEffect", true) - setUniforms(prefix.."LayerEffect", part.LayerEffect, "center") - end - end - if bg.Tunnel then - local part = bg.Tunnel - local prefix = "Tunnel" - loadPart(prefix, part, "tunnel") - end - if bg.Particle then - local part = bg.Particle - local prefix = "Particle" - loadPart(prefix, part, "particle") - end - - background.SetSpeedMult(bg.speed or 1.0) - - processDelayedOperations() - - if bg.luaParticleEffect then - shouldRenderParticles = true - for i, p in ipairs(bg.luaParticleEffect.particles) do - particleTextures[i] = gfx.CreateImage(background.GetPath().."textures/luaparticle/" .. p[1], 0) - psizes[i] = p[2] - end - for i=1,particleCount do - particles[i] = initializeParticle(true) - end - end -end - -if dark then - bgs = filterTable(bgs, "bright") -end - -math.randomseed(stringToNumber(gameplay.title)) -local bg, k = randomItemFromTable(bgs) -game.Log("bg:", 0) -game.Log(tostring(k), 0) -if bgTrumpcard then bg = bgTrumpcard end -loadBackground(bg) - -function render_bg(deltaTime) - background.DrawShader() - if shouldRenderParticles then renderParticles(deltaTime) end -end +local delayedOperations = { + u={}, + speed=nil +} +local dark = game.GetSkinSetting("dark_mode") or game.GetSkinSetting("dark_bgs") + +-- backgroundTextures +local bt = { + _={"_.png"}, + cyberspace={"cyberspace.jpg", "cyberspace-c.jpg"}, + watervault={"watervault.jpg", "watervault-c.jpg"}, + underwater={"underwater.jpg", "underwater-c.jpg", bright=true}, + ocean={"ocean.jpg", "ocean-c.jpg",bright=true}, + grass={"grass.jpg", "grass-c.jpg"}, + deepsea={"deepsea.jpg", "deepsea-c.jpg"}, + cyber={"cyber.jpg", "cyber-c.jpg"}, + desert={"desert.jpg", "desert-c.jpg"}, + desertYellowClear={"desert.jpg", "desert-c2.jpg",bright=true}, + sky={"sky.jpg", "sky-c.jpg",bright=true}, + skyIv={"sky-iv.jpg", "sky-iv-c.jpg",bright=true}, + skyIv2={"sky_iv_2.png", "sky_iv_2-c.png",bright=true}, + skyIvDark={"sky-iv-dark.jpg"}, + sunset={"sunset.jpg", "sunset-c.jpg",bright=true}, + redgradient={"redgradient.jpg", "redblur.jpg"}, + mars={"mars.jpg", "mars-c.jpg"}, + cloudy={"cloudy.jpg", "cloudy-c.jpg"}, + redblur={"redblur.jpg", "redblur-c.jpg"}, + galaxy={"galaxy.jpg", "galaxy-c.jpg"}, + fantasy={"fantasy.jpg", "fantasy-c.jpg",bright=true}, + bedroom={"bedroom.jpg", "bedroom-c.jpg",bright=true}, + flame={"flame.jpg", "flame-c.jpg"}, + game={"game.png",bright=true}, + beach={"beach.png"}, + night={"night.jpg", "night-c.jpg"}, + prettygalaxy={"prettygalaxy.jpg", "prettygalaxy-c.jpg"}, + sakura={"sakura.jpg", "sakura-c.jpg"}, + cyberspaceNight={"cyberspace_night.png", "cyberspace_night_starburst.png", bright=true}, + moonBlue={"moon_blue.jpg", "moon_blue-c.jpg"}, + moonPurple={"moon_purple.jpg", "moon_purple-c.jpg",bright=true}, + redDusk={"red_dusk.jpg", "red_dusk-c.jpg"}, + star={"star.png", "star-c.png",bright=true}, + twilight={"twilight.png", "twilight-c.png"}, + undersea={"undersea.png", "undersea-c.png"}, +} + +-- backgroundComposition +local function bc(bt, opts) + local out = {} + for k,v in pairs(bt) do out[k] = v end + for k,v in pairs(opts) do out[k] = v end + return out +end + +local btCollections = { + blue={bt.watervault,bc(bt.underwater,{u={TunnelDodgeBlend=true}}),bt.cyberspace,bt.ocean,bt.grass,bt.deepsea,bt.cyber,bt.desert,bt.sky,bt.skyIv,bt.skyIv2,bt.moonBlue,{"city.png"},bt.cyberspaceNight,bt.undersea,bc(bt._,{weight=6})}, + red={bc(bt.flame,{u={TunnelDodgeBlend=true}}),bt.sunset,bt.redgradient,bt.mars,bt.cloudy,bt.redDusk,bt.moonPurple,bc(bt._,{weight=4})}, +} + +local pt = { + lights={"lights_default.png", "lights_default-c.png"}, + lightsMoonblue={"lights_moonblue.png", "lights_moonblue-c.png"}, + lightsPurplish={"lights_purplish.png"}, + lightsOrangePink={"lights_orangepink.png"}, + lightsPink={"lights_pink.png", "lights_pink-c.png"}, + lightsSea={"lights_sea.png", "lights_sea-c.png"}, + lightsYellow={"lights_yellow.png"}, + lightsYellowPurple={"lights_yellow.png", "lights_purplish.png"}, + lightsYellowGreen={"lights_yellowgreen.png"}, + twilight={"twilight.png", "twilight-c.png"}, + streetLanterns={"street_lanterns.png", "street_lanterns-c.png"}, + starParticles={"star_particles.png", "star_particles-c.png"}, + squares={"squares.png", "squares-c.png"}, +} + + + +local bgs = { + arrows={ + Tunnel={ + Tex={{"arrows-large.png", "arrows-large-c.png"},{"arrows-small.png", "arrows-small-c.png",speed=0.7}}, + u={ Sides=4, Stretch=0.2, ScaleX=0.8, ScaleY=0.8, Fog=15.0 }, + }, + Bg={ Base={Tex={bt.redgradient,bt._}} }, + Particle={ + Tex={"icons.png", "icons-c.png"}, + u={ Scale=0.2, OffsetY=-0.2 } + }, + speed=0.8,weight=2 + }, + technoEye={ + Bg={ Base={Tex=bt.cyberspace} }, + Center={ + Tex="techno-eye.png", + u={Pulse=true, Float=true, Scale=2.8}, + LayerEffect={Tex="glowshine.png", Fade=true} + }, + Tunnel={ + Tex={"electro-blue.png", "electro-c.png"}, + u={Sides=8, Stretch=0.3, ScaleY=0.9, Fog=10.0, ExtraRotation=-0.125} + }, + }, + waveBlue={ + Bg={ Base={Tex={bc(bt.watervault,{u={Center=false}}),bc(bt.underwater,{u={TunnelDodgeBlend=true,Center=false}}),bc(bt.cyberspace,{u={Center=false}}),bc(bt.ocean,{u={Center=false}}),bt.grass,bt.deepsea,bt.cyber,bc(bt.desert,{u={Center=false}}),bt.sky,bt.moonBlue,{"city.png"},bc(bt.cyberspaceNight,{u={Center=false}}),bc(bt.undersea,{u={Center=false,TunnelDodgeBlend=true}}),bc(bt._,{weight=5})}, ScaleSoft=true}}, -- Customised version of bt.blue + Tunnel={ + Tex={{"wave-blue.png", "wave-blue-c.png"},{"wave-green.png", "wave-green-c.png"}}, + u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=20.0} + }, + Center={ Tex={{"moon2.png","moon2-c.png"},{0}}, u={Scale=9.0, OffsetY=-0.05}, LayerEffect={Tex="moon2_shine.png", Glow=true, Scale=0.8, DodgeBlend=true} }, + Particle={ Tex=pt.lightsMoonblue, u={OffsetY=-0.02, Amount=4, Speed=2.0} }, + weight=2 + }, + waveRed={ + Bg={ Base={Tex=btCollections.red, ScaleSoft=true} }, + Tunnel={ + Tex={"wave-red.png","wave-red-c.png"}, + u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=20.0} + }, + Center={ Tex={{"moon_pink.png","moon_pink-c.png"},{0}}, u={Scale=8.0, OffsetY=-0.05}, LayerEffect={Tex="moon_pink_shine.png", Glow=true, DodgeBlend=true, Scale=0.8} }, + Particle={ Tex=pt.lightsPink, u={OffsetY=-0.02, Amount=4, Speed=2.0} }, + }, + waveOrange={ + Bg={ Base={Tex={bc(bt.twilight,{u={Center=false}}),bc(bt.grass,{u={Center=false}}),bt.redblur,bt.redgradient,bt.redDusk,bc(bt.star,{u={Center=false}}),bt.cyberspace,bt.sunset,bc(bt.cyber,{u={Center=false}}),bc(bt.galaxy,{u={Center=false}}),bc(bt.desertYellowClear,{u={Center=false}}),bc(bt.fantasy,{u={Center=false}}),bt.moonPurple,bc(bt.undersea,{u={Center=false}}),{"city.png"},bc(bt._,{weight=6})}, ScaleSoft=true} }, + Tunnel={ + Tex={"wave-orange.png","wave-orange-c.png"}, + u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=20.0} + }, + Center={ Tex={{"moon_orange.png","moon_orange-c.png"},{0}}, u={Scale=8.0, OffsetY=-0.05}, LayerEffect={Tex="moon_orange_shine.png", Glow=true, DodgeBlend=true, Scale=0.8} }, + Particle={ Tex=pt.lightsOrangePink, u={OffsetY=-0.02, Amount=4, Speed=2.0} }, + }, + waveRedNoise={ + Bg={ + Layer={Tex={{"wave4.jpg", "wave4-c.jpg"}}}, + Pivot={0.35} + } + }, + waveGalaxy={Bg={ + Layer={Tex={{"wave6.jpg", "wave6-c.jpg"}}}, + Pivot={0.35} + }}, + game={ + Bg={Base={Tex="game.png", Tilt=false}, Overlay={Tex="game-f.png", Float=true, FlashEffect=true, FloatFactor=2.0}}, + Center={Tex={"logo.png", "logo-c.png"}, u={Scale=3.8, Tilt=true}}, + Tunnel={Tex="game.png", u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=15.0, ExtraRotation=0.375}}, + Particle={Tex={bc(pt.squares,{u={ParticleSpeed=1}}),{"triangles.png",weight=2}}, + u={OffsetY=-0.1, Amount=5, Speed=2.0, Scale=0.8} + } + }, + seaNight={ -- todo: add bg landscapeoffset + Bg={Base={Tex="sea-night.png", OffsetY=-0.19, ScaleSoft=true}, Overlay={Tex="sea-night-f.png", Float=true, OffsetY=-0.17}}, + Center={ Tex="ship-night.png", u={Scale=2.5, Float=true, FloatFactor=0.5, SnapToTrack=false}, LayerEffect={Tex="kac-hikari-2.png"}}, + Particle={ Tex="shines2.png", u={Speed=1.8, Amount=9} }, + -- Tunnel={}, + luaParticleEffect = { particles = { {"star-particle.png", 32} } } + }, + seaStorm={ + Bg={Base={Tex="sea-storm.png", OffsetY=-0.13, ScaleSoft=true}, Overlay={Tex="sea-storm-f.png", Float=true, OffsetY=-0.17}}, + Center={ Tex="ship-storm.png", u={Scale=2.5, Float=true, FloatFactor=0.5, SnapToTrack=false}, LayerEffect={Tex="kac-hikari.png"}}, + Particle={ Tex="shines2.png", u={Speed=1.8, Amount=9} }, + -- Tunnel={}, + }, + seaIce={ + Bg={Base={Tex="sea-ice.png", OffsetY=-0.1, ScaleSoft=true}, Overlay={Tex="sea-ice-f.png", Float=true, OffsetY=-0.15}}, + Center={ Tex="ship-ice.png", u={Scale=2.5, Float=true, FloatFactor=0.5, SnapToTrack=false}, LayerEffect={Tex="kac-hikari-2.png"}}, + Particle={ Tex="shines1.png", u={Scale=0.3, OffsetY=-0.3, Speed=1.8, Amount=9} }, + -- Tunnel={}, + luaParticleEffect = { particles = { {"star-particle.png", 32} } } + }, + seaThunder={ + Bg={Base={Tex="sea-thunder.png", OffsetY=-0.15, ScaleSoft=true}, Overlay={Tex="sea-thunder-f.png", Float=true, OffsetY=-0.16}}, + Center={ Tex="ship-storm.png", u={Scale=2.5, Float=true, FloatFactor=0.5, SnapToTrack=false}, LayerEffect={Tex="kac-hikari.png"}}, + Particle={ Tex="shines2.png", u={Speed=1.8, Amount=9} }, + }, + seaDay={ -- todo: fixlandscape, fix rotation + Bg={Base={Tex="sea-day.png", ScaleSoft=true}, Overlay={Tex="sea-day-f.png", Float=true, OffsetY=-0.1}}, + Center={Tex="ship-day.png", u={Scale=2, Float=true, FloatFactor=0.5, OffsetY=0.02, SnapToTrack=false}, LayerEffect={Tex="kac-hikari.png"}}, + Particle={ Tex="shines2.png", u={Speed=1.8, Amount=9} }, + bright=true, + -- Tunnel={}, + }, + sakuraRainbow={ + Bg={Base={Tex=bt.sakura, ScaleSoft=true}}, + Center={ + Tex="rainbow.png", u={Scale=1.5, FadeEffect=true}, + LayerEffect={Tex="kac_hikari_sakura.png"} + }, + -- Tunnel: rainbow rings?! probably not xd + Particle={Tex="shines1.png", u={Speed=1.6, OffsetY=-0.3, Amount=6, Scale=0.35}}, + luaParticleEffect={particles={{"petal1.png", 140},{"petal2.png", 40},{"petal3.png", 140}}}, + bright=true + }, + -- colorBokeh={Bg={Layer={Tex={"colorbokeh.jpg", "colorbokeh-c.jpg"} }, u={Pivot=0.35}}}, -- todo: find working bgs (?) + smoke1={Bg={ + Base={Tex={bt.fantasy,bt.cyberspaceNight,bt.undersea,bt.watervault,bt.underwater,bt.cyberspace,bt.ocean,bt.grass,bt.deepsea,bt.cyber,bt.desertYellowClear,bt.sky,bc(bt._,{weight=5})},ScaleSoft=true}, + Layer={Tex={"smoke.jpg", "smoke-c.jpg"}}, + u={Pivot=0.35} + }}, + -- sparkles1={Bg={Layer={Tex={"sparkles1.jpg", "sparkles1-c.jpg"}, ScaleHard=true }}}, + -- domeLayer={Bg={ + -- Base={Tex={bt.redblur,bt._}}, + -- Layer={Tex={{"spider1.jpg", "spider1-c.jpg"},{"spider2.jpg", "spider2-c.jpg"},{"spider3.jpg", "spider3-c.jpg", speed=0.75, u={BgPivot=0.27}}}}, + -- u={Pivot=0.36} + -- },weight=2}, + -- electro1={Bg={ + -- Base={Tex={bt.redblur,bt.cyber,bt._}}, + -- Layer={Tex={"electro1.jpg", "electro1-c.jpg"}, brightenLayer=0.6}, u={Pivot=0.35}} + -- }, + -- electro2={Bg={Layer={Tex={"electro2.jpg", "electro2-c.jpg"} }, u={Pivot=0.3}}}, + -- plasmaTunnel={Bg={Layer={Tex={"plasmatunnel.jpg", "plasmatunnel-c.jpg"}, }, u={Pivot=0.37}}, speed=0.75, bright=true}, -- my eyes are burning + -- xcalibur={Bg={Base={Tex={"anim/xcalibur.jpg", "anim/xcalibur-c.jpg"}}, u={Pivot=0.36}}, speed=0.3}, + goldleaves={ + Bg={Base={Tex={"anim/goldleaves.jpg", "anim/goldleaves-c.jpg"}}}, + Particle={ Tex=pt.lightsYellow, u={Speed=1.8, OffsetY=-0.1, Amount=9} }, + speed=0.6 + }, + technocircle={ + Bg={Base={Tex={"anim/technocircle.jpg", "anim/technocircle-c.jpg"}, ScaleSoft=true}, u={Pivot=0.37}}, + Particle={ Tex="shines2.png", u={Speed=1.8, OffsetY=-0.15, Amount=7} }, + speed=0.6, bright=true + }, + snow={ + Bg={Base={Tex={"anim/snow.jpg", "anim/snow-c.jpg"}}, u={Pivot=0.3}}, + Particle={Tex="shines1.png", u={Speed=1.9, OffsetY=-0.3, Amount=8, Scale=0.4}}, + }, + sky={ + Bg={Base={Tex={bt.skyIv,bt.skyIv2}}}, + Tunnel={Tex="clouds.png", u={Sides=16, Fog=15, Stretch=0.07, VortexEffect=true}}, + Center={ + Tex={{speed=0.9, u={TunnelFog=100, TunnelVortexFactor=5}},{"sdvx_iv.png",u={CenterScale=10,CenterOffsetY=0}}}, + u={Float=true, Scale=5, FloatXFactor=2, OffsetY=-0.02} + }, + Particle={Tex="shines1.png", u={Amount=8, OffsetY=-0.3, Speed=1.9, Scale=0.5}}, + speed=0.5, bright=true + }, + -- skyDark={ + -- Bg={Base={Tex={"sky-iv-dark.jpg"}}}, + -- Tunnel={Tex={"clouds-dark.png","clouds-dark-c.png"}, u={Sides=16, Fog=100, Stretch=0.07, VortexEffect=true, VortexFactor=5}}, + -- speed=0.9, + -- }, + hexagons={ + Tunnel={Tex={{"hexagons.png", "hexagons-c.png"},{"hexagons-gray.png","hexagons.png"}}, u={ExtraRotation=-0.125, Fog=30}}, + Particle={Tex="hexes.png", u={Amount=3, OffsetY=-0.2, Speed=1.9, Scale=0.7}}, + speed=1.2,weight=2 + }, + dome={ -- todo: fix rotation + Bg={Base={Tex={bt.redblur,bt.redgradient,bt._}}}, + Tunnel={Tex={"dome.png","dome-c.png"}, u={ExtraRotation=-0.125/2, Fog=30, Stretch=0.09}}, + Center={ + Tex={{"glowshine_green.png",weight=2},{0}}, u={Scale=13, Pulse=true, Glow=true}, + LayerEffect={Tex="glowshine_sun.png", Glow=true, Scale=0.8} + }, + Particle={Tex=pt.lightsYellowGreen, u={Amount=4, OffsetY=0, Speed=1.9}}, + speed=0.6, + }, + domeRed={ + Bg={Base={Tex={bc(bt.redblur,{u={CenterScale=9}}),bt.redgradient,bt._}}}, + Tunnel={Tex={"dome-red.png","dome-red-c.png"}, u={ExtraRotation=-0.125/2, Fog=30, Stretch=0.09}}, + Center={ + Tex={{"glowshine_pink.png",weight=2},{0}}, u={Scale=13, Pulse=true, Glow=true}, + LayerEffect={Tex="glowshine_orange.png", Glow=true, Scale=0.8} + }, + Particle={Tex=pt.lightsOrangePink, u={Amount=4, OffsetY=0, Speed=1.9}}, + speed=0.6 + }, + iseki={ + Tunnel={ + Tex={{"iseki.png","iseki-c.png",u={TunnelExtraRotation=-0.125/2}}}, + u={Sides=16, Stretch=0.1, FlashEffect=true, Fog=25.0} + }, + Center={Tex={{"kac_maxima_gold.png"},{0,weight=4}}, u={Float=true, Scale=5, OffsetY=0.04}, LayerEffect={Tex="kac_hikari_iseki.png", Scale=0.7}}, + Particle={Tex=pt.lightsYellow, u={Amount=6, OffsetY=0, Speed=1.9}}, + speed=0.5, + }, + idofront={ + Tunnel={ + Tex={{"idofront.png","idofront-c.png",u={TunnelExtraRotation=-0.125/2}}}, + u={Sides=16, Stretch=0.1, Fog=25.0} + }, + Particle={Tex=pt.lightsPurplish, u={Amount=5, Scale=1.3, Speed=1.9}}, + }, + genom={ + Tunnel={ + Tex={{"genom.png","genom-c.png",u={TunnelExtraRotation=-0.125/2*0}}}, + u={Sides=16, Stretch=0.1, Fog=25.0} + }, + Particle={Tex=pt.lightsOrangePink, u={Amount=5, Scale=1.3, Speed=1.9}}, + speed=0.7 + }, + twilight={ + Bg={ Base={Tex=bt.twilight, ScaleSoft=true}}, + Tunnel={Tex={"wave-orange.png","wave-orange-c.png"}, u={Sides=4, Stretch=0.15, ScaleX=0.8, ScaleY=0.8, FlashEffect=true, Fog=14.0}}, + Center={ Tex="moon_twilight.png", u={Scale=8.0, OffsetY=-0.05}, LayerEffect={Tex="moon_twilight_shine.png", Glow=true} }, + Particle={ Tex=pt.twilight, u={OffsetY=-0.3, Amount=5, Speed=1.5, Scale=0.5} }, + }, + shinwa={ -- todo: put clouds in extra layer of center, remove weird 'bg is static but overlay can tilt' logic + Bg={ Base={Tex="cyberspace_sunrise.png", OffsetY=-0.23, Tilt=false, ClampTiling=true}, Overlay={Tex="cyberspace_sunrise-f.png", Tilt=true, OffsetY=-0.1}}, + Center={ Tex={"shinwa.png"}, u={Scale=9.0, OffsetY=0, Float=true, Pulse=true}, LayerEffect={Tex="cyberspace_shine.png", Glow=true, Alpha=0.8, Scale=1, Rotate=true, RotateSpeed=-5} }, + Particle={ Tex=pt.lightsYellow, u={OffsetY=-0.3, Amount=9, Speed=2, Scale=0.5} }, + }, + beach={ + Bg={ Base={Tex="beach.png", ScaleSoft=true}}, + Center={ Tex={"glowshine_orange.png","glowshine_sun.png"}, u={Scale=8.0, OffsetY=-0.05, Float=true}, LayerEffect={Tex="glowshine_sun.png", Glow=true, Scale=0.9} }, + Particle={ Tex=pt.lightsYellow, u={OffsetY=-0.3, Amount=9, Speed=2, Scale=0.5} }, + bright=true + }, + sonar={ + Bg={ Base={Tex={bt.undersea,bt.watervault,bt.underwater}}}, + Center={ Tex={"magic_circle.png","magic_circle-c.png"}, u={Scale=2.6, Rotate=true}, LayerEffect={Tex="sonar.png", Rotate=true, RotateSpeed=-10, Scale=3} }, + Particle={ Tex=pt.lightsSea, u={OffsetY=-0.1, Amount=9, Speed=1.7} }, + }, + star={ + Bg={ Base={Tex=bt.star, ScaleSoft=true, Tilt=false}}, + Center={ Tex="star_core.png", u={Scale=2.15, Tilt=false, SnapToTrack=false, OffsetY=-0.04}, LayerEffect={Tex="star_core.png", Glow=true, DodgeBlend=true } }, + Particle={ Tex=pt.starParticles, u={OffsetY=-0.1, Amount=9, Speed=1.7} }, + Tunnel={ Tex={"electro.png", "electro-c.png"}, u={Sides=12, Stretch=0.12, ScaleY=0.9, Fog=18.0, ExtraRotation=-0.125}}, + }, + -- beams={ + -- Tunnel={ + -- Tex={{"beams.png","beams-c.png",u={TunnelExtraRotation=-0.125/2}}}, + -- u={Sides=16, Stretch=0.1, Fog=25.0} + -- }, + -- }, +} + +-- local bgTrumpcard=bgs.shinwa +-- local bgNestedIndices = { +-- -- BgBase=1, +-- -- Tunnel=1, +-- Center=1 +-- } + +local function stringToNumber(str) + local number = 0 + for i = 1, string.len(str) do + number = number + string.byte(str, i) + end + return number +end + +local function sign(x) + return x>0 and 1 or x<0 and -1 or 0 +end + +local function setUniform(key, value) + local valueType = type(value) + if valueType == "number" then + background.SetParamf(key, value) + elseif valueType == "boolean" then + background.SetParami(key, value and 1 or 0) + else + game.Log("Weird param type was passed to setUniform", 1) + end +end + +----------------- +-- PARTICLE STUFF +----------------- + +local resx, resy = game.GetResolution() +local portrait = resy > resx +local desw = portrait and 720 or 1280 +local desh = desw * (resy / resx) +local scale = resx / desw + +local shouldRenderParticles = false +local particleTextures = {} +local particles = {} +local psizes = {} + +local particleCount = 30 +local particleSizeSpread = 0.5 + +local function initializeParticle(initial) + local particle = {} + particle.x = math.random() + particle.y = math.random() * 1.2 - 0.1 + if not initial then particle.y = -0.1 end + particle.r = math.random() + particle.s = (math.random() - 0.5) * particleSizeSpread + 1.0 + particle.xv = 0 + particle.yv = 0.1 + particle.rv = math.random() * 2.0 - 1.0 + particle.p = math.random() * math.pi * 2 + particle.t = math.random(1, #psizes) + return particle +end + +local function renderParticles(deltaTime) + local alpha = 0.3 + 0.5 * background.GetClearTransition() + for i,p in ipairs(particles) do + p.x = p.x + p.xv * deltaTime + p.y = p.y + p.yv * deltaTime + p.r = p.r + p.rv * deltaTime + p.p = (p.p + deltaTime) % (math.pi * 2) + + p.xv = 0.5 - ((p.x * 2) % 1) + (0.5 * sign(p.x - 0.5)) + p.xv = math.max(math.abs(p.xv * 2) - 1, 0) * sign(p.xv) + p.xv = p.xv * p.y + p.xv = p.xv + math.sin(p.p) * 0.01 + + gfx.Save() + gfx.ResetTransform() + gfx.Translate(p.x * resx, p.y * resy) + gfx.Rotate(p.r) + gfx.Scale(p.s * scale, p.s * scale) + gfx.BeginPath() + gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER) + gfx.ImageRect(-psizes[p.t]/2, -psizes[p.t]/2, psizes[p.t], psizes[p.t], particleTextures[p.t], alpha, 0) + gfx.Restore() + if p.y > 1.1 then + particles[i] = initializeParticle(false) + end + end + gfx.ForceRender() +end + +local function getRandomItemFromArray(array, seed) + if #array == 1 then return array[1] end + + local weightedArray = {} + for i, value in ipairs(array) do + local weight = value.weight or 1 + for j = 1, weight do + weightedArray[#weightedArray+1] = value + end + end + + math.randomseed(stringToNumber(seed)) + return weightedArray[math.random(#weightedArray)] +end + +local function getImageDimensions(imagePath) + return gfx.ImageSize( gfx.CreateImage(background.GetPath().."textures/"..imagePath, 0) ) +end + +local function filterArray(array, propertyToRemove) + local newArray = {} + for i, v in ipairs(array) do + if not v[propertyToRemove] then newArray[#newArray+1] = v end + end + return newArray +end + +local function filterTable(table, propertyToRemove) + local newTable = {} + for k, v in pairs(table) do + if not v[propertyToRemove] then newTable[k] = v end + end + return newTable +end + +local function loadTextures(prefix, tex, subFolder, checkAnim, noAnimCallback, setNormalVersion) + local texture + if type(tex) == "string" then + texture = {tex} + elseif type(tex[1]) == "string" then + texture = tex + else + if dark then tex = filterArray(tex, "bright") end + if bgNestedIndices and bgNestedIndices[prefix] then + texture = tex[bgNestedIndices[prefix]] + else + texture = getRandomItemFromArray(tex, prefix..gameplay.title..gameplay.artist) -- todo: improve seeding + end + end + + if texture[1] == 0 then + return false + end + + if texture[1] then + if setNormalVersion then + setUniform(prefix.."NormalVersion", true) + end + background.LoadTexture(prefix.."Tex", "textures/"..subFolder.."/"..texture[1]) + end + if texture[2] then + setUniform(prefix.."ClearVersion", true) + background.LoadTexture(prefix.."ClearTex", "textures/"..subFolder.."/"..texture[2]) + end + if checkAnim and texture[1] then + local w, h = getImageDimensions(subFolder.."/"..texture[1]) + if w / h > 2 then + setUniform(prefix.."Anim", true) + setUniform(prefix.."AnimFramesCount", math.floor(w / 600)) + elseif noAnimCallback then + noAnimCallback(w, h) + end + end + + if texture.u then + for k,v in pairs(texture.u) do + delayedOperations.u[k] = v + end + end + if texture.speed then delayedOperations.speed = texture.speed end + return true +end + +local function setUniformsRaw(uniforms) + for k,v in pairs(uniforms) do setUniform(k, v) end +end + +local function setUniforms(prefix, uniforms, subFolder, checkAnim, noAnimCallback) + for k, v in pairs(uniforms) do + if k == "Tex" then + loadTextures(prefix, v, subFolder, checkAnim, noAnimCallback) + else + setUniform(prefix..k, v) + end + end +end + +local function loadPart(prefix, part, subFolder, checkAnim, setNormalVersion) + local loaded + if part.Tex then + loaded = loadTextures(prefix, part.Tex, subFolder, checkAnim, nil, setNormalVersion) + else + loaded = true + end + if loaded then + setUniform(prefix, true) + end + if part.u then + setUniforms(prefix, part.u, subFolder) + end +end + +local function randomItemFromTable(table) + local keys = {} + for key, value in pairs(table) do + local weight = value.weight or 1 + for i = 1, weight do + keys[#keys+1] = key + end + end + local index = keys[math.random(#keys)] + return table[index], index +end + +local function processDelayedOperations() + setUniformsRaw(delayedOperations.u) + if delayedOperations.speed then background.SetSpeedMult(delayedOperations.speed) end +end + +local function loadBackground(bg) + if bg.Bg then + local part = bg.Bg + local prefix = "Bg" + loadPart(prefix, part, "background") + if part.Base then + setUniform(prefix.."Base", true) + setUniforms(prefix.."Base", part.Base, "background", true, function(w,h) setUniform(prefix.."Base".."AR", w / h) end) + end + if part.Overlay then + setUniform(prefix.."Overlay", true) + setUniforms(prefix.."Overlay", part.Overlay, "background") + end + if part.Layer then + setUniform(prefix.."Layer", true) + setUniforms(prefix.."Layer", part.Layer, "layer", true) + end + end + if bg.Center then + local part = bg.Center + local prefix = "Center" + loadPart(prefix, part, "center", true, true) + if part.LayerEffect then + setUniform(prefix.."LayerEffect", true) + setUniforms(prefix.."LayerEffect", part.LayerEffect, "center") + end + end + if bg.Tunnel then + local part = bg.Tunnel + local prefix = "Tunnel" + loadPart(prefix, part, "tunnel") + end + if bg.Particle then + local part = bg.Particle + local prefix = "Particle" + loadPart(prefix, part, "particle") + end + + background.SetSpeedMult(bg.speed or 1.0) + + processDelayedOperations() + + if bg.luaParticleEffect then + shouldRenderParticles = true + for i, p in ipairs(bg.luaParticleEffect.particles) do + particleTextures[i] = gfx.CreateImage(background.GetPath().."textures/luaparticle/" .. p[1], 0) + psizes[i] = p[2] + end + for i=1,particleCount do + particles[i] = initializeParticle(true) + end + end +end + +if dark then + bgs = filterTable(bgs, "bright") +end + +math.randomseed(stringToNumber(gameplay.title)) +local bg, k = randomItemFromTable(bgs) +game.Log("bg:", 0) +game.Log(tostring(k), 0) +if bgTrumpcard then bg = bgTrumpcard end +loadBackground(bg) + +function render_bg(deltaTime) + background.DrawShader() + if shouldRenderParticles then renderParticles(deltaTime) end +end diff --git a/backgrounds/fallback/bg_template.lua b/backgrounds/fallback/bg_template.lua index 0cd5c50..7ad0953 100644 --- a/backgrounds/fallback/bg_template.lua +++ b/backgrounds/fallback/bg_template.lua @@ -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, + } } \ No newline at end of file diff --git a/config-definitions.json b/config-definitions.json index 717f57d..2c451f0 100644 --- a/config-definitions.json +++ b/config-definitions.json @@ -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 + } +} diff --git a/scripts/challengeresult.lua b/scripts/challengeresult.lua index 8e86d64..b788fc9 100644 --- a/scripts/challengeresult.lua +++ b/scripts/challengeresult.lua @@ -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 diff --git a/scripts/collectiondialog.lua b/scripts/collectiondialog.lua index d00d735..bd73d02 100644 --- a/scripts/collectiondialog.lua +++ b/scripts/collectiondialog.lua @@ -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 \ No newline at end of file diff --git a/scripts/downloadscreen.lua b/scripts/downloadscreen.lua index 8accd7b..dabd001 100644 --- a/scripts/downloadscreen.lua +++ b/scripts/downloadscreen.lua @@ -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 \ No newline at end of file diff --git a/scripts/gamesettingsdialog.lua b/scripts/gamesettingsdialog.lua index 8e53861..a933494 100644 --- a/scripts/gamesettingsdialog.lua +++ b/scripts/gamesettingsdialog.lua @@ -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 \ No newline at end of file diff --git a/scripts/json.lua b/scripts/json.lua index 2d37c58..d008bf7 100644 --- a/scripts/json.lua +++ b/scripts/json.lua @@ -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 diff --git a/scripts/multiplayerscreen.lua b/scripts/multiplayerscreen.lua index 3df4f8a..0cab640 100644 --- a/scripts/multiplayerscreen.lua +++ b/scripts/multiplayerscreen.lua @@ -1,1001 +1,1001 @@ -json = require "json" - -local common = require('common.common'); - -local resX,resY = game.GetResolution() - -local mposx = 0; -local mposy = 0; -local hovered = nil; -local buttonWidth = resX*(3/4); -local buttonHeight = 75; -local buttonBorder = 2; -local portrait - -game.LoadSkinSample("click-02") -game.LoadSkinSample("click-01") -game.LoadSkinSample("menu_click") - -local loading = true; -local rooms = {}; -local lobby_users = {}; -local selected_room = nil; -local user_id = nil; -local jacket = 0; -local all_ready; -local user_ready; -local hard_mode = false; -local rotate_host = false; -local start_game_soon = false; -local host = nil; -local owner = nil; -local missing_song = false; -local placeholderJacket = gfx.CreateSkinImage("song_select/loading.png", 0) -local did_exit = false; - -local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}} - -local grades = { - {["max"] = 6999999, ["image"] = gfx.CreateSkinImage("common/grades/D.png", 0)}, - {["max"] = 7999999, ["image"] = gfx.CreateSkinImage("common/grades/C.png", 0)}, - {["max"] = 8699999, ["image"] = gfx.CreateSkinImage("common/grades/B.png", 0)}, - {["max"] = 8999999, ["image"] = gfx.CreateSkinImage("common/grades/A.png", 0)}, - {["max"] = 9299999, ["image"] = gfx.CreateSkinImage("common/grades/A+.png", 0)}, - {["max"] = 9499999, ["image"] = gfx.CreateSkinImage("common/grades/AA.png", 0)}, - {["max"] = 9699999, ["image"] = gfx.CreateSkinImage("common/grades/AA+.png", 0)}, - {["max"] = 9799999, ["image"] = gfx.CreateSkinImage("common/grades/AAA.png", 0)}, - {["max"] = 9899999, ["image"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0)}, - {["max"] = 99999999, ["image"] = gfx.CreateSkinImage("common/grades/S.png", 0)} - } - -local badges = { - gfx.CreateSkinImage("badges/played.png", 0), - gfx.CreateSkinImage("badges/clear.png", 0), - gfx.CreateSkinImage("badges/hard-clear.png", 0), - gfx.CreateSkinImage("badges/full-combo.png", 0), - gfx.CreateSkinImage("badges/perfect.png", 0) -} - -local user_name_key = game.GetSkinSetting('multi.user_name_key') -if user_name_key == nil then - user_name_key = 'nick' -end -local name = game.GetSkinSetting(user_name_key) -if name == nil or name == '' then - name = 'Guest' -end - -local normal_font = game.GetSkinSetting('multi.normal_font') -if normal_font == nil then - normal_font = 'NotoSans-Regular.ttf' -end -local mono_font = game.GetSkinSetting('multi.mono_font') -if mono_font == nil then - mono_font = 'NovaMono.ttf' -end - -local SERVER = game.GetSkinSetting("multi.server") - - - - - -mouse_clipped = function(x,y,w,h) - return mposx > x and mposy > y and mposx < x+w and mposy < y+h; -end; - -draw_room = function(name, x, y, selected, hoverindex) - local buttonWidth = resX*(3/4); - local rx = x - (buttonWidth / 2); - local ty = y - (buttonHeight / 2); - local roomButtonBorder = buttonBorder; - gfx.BeginPath(); - gfx.FillColor(0,128,255); - if selected then - gfx.FillColor(0,255,0); - roomButtonBorder = 4; - end - if mouse_clipped(rx,ty, buttonWidth, buttonHeight) then - hovered = hoverindex; - gfx.FillColor(255,128,0); - end - gfx.Rect(rx - roomButtonBorder, - ty - roomButtonBorder, - buttonWidth + (roomButtonBorder * 2), - buttonHeight + (roomButtonBorder * 2)); - gfx.Fill(); - gfx.BeginPath(); - gfx.FillColor(40,40,40); - gfx.Rect(rx, ty, buttonWidth, buttonHeight); - gfx.Fill(); - gfx.BeginPath(); - gfx.FillColor(255,255,255); - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); - gfx.FontSize(40); - gfx.Text(name, x, y); -end; - -draw_button = function(name, x, y, buttonWidth, hoverindex) - draw_button_color(name, x, y, buttonWidth, hoverindex, 40,40,40, 0,128,255) -end - -draw_button_color = function(name, x, y, buttonWidth, hoverindex,r,g,b, olr,olg,olb) - local rx = x - (buttonWidth / 2); - local ty = y - (buttonHeight / 2); - gfx.BeginPath(); - gfx.FillColor(olr, olg, olb); - if mouse_clipped(rx,ty, buttonWidth, buttonHeight) then - hovered = hoverindex; - gfx.FillColor(255,128,0); - end - gfx.Rect(rx - buttonBorder, - ty - buttonBorder, - buttonWidth + (buttonBorder * 2), - buttonHeight + (buttonBorder * 2)); - gfx.Fill(); - gfx.BeginPath(); - gfx.FillColor(r,g,b); - gfx.Rect(rx, ty, buttonWidth, buttonHeight); - gfx.Fill(); - gfx.BeginPath(); - gfx.FillColor(255,255,255); - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); - gfx.FontSize(40); - gfx.Text(name, x, y); -end; - -draw_checkbox = function(text, x, y, hoverindex, current, can_click) - local rx = x - (buttonWidth / 2); - local ty = y - (buttonHeight / 2); - gfx.BeginPath(); - - if can_click then - gfx.FillColor(255,255,255); - else - gfx.FillColor(150,100,100); - end - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); - gfx.FontSize(35); - gfx.Text(text, x, y) - - local xmin,ymin,xmax,ymax = gfx.TextBounds(x, y, text); - - local sx = xmin - 40; - local sy = y - 15; - - gfx.StrokeColor(0,128,255); - if can_click and (mouse_clipped(sx, sy, 31, 30) or mouse_clipped(xmin-10, ymin, xmax-xmin, ymax-ymin)) then - hovered = hoverindex; - gfx.StrokeColor(255,128,0); - end - - gfx.Rect(sx, y - 15, 30, 30) - gfx.StrokeWidth(2) - gfx.Stroke() - - if current then - -- Draw checkmark - gfx.BeginPath(); - gfx.MoveTo(sx+5, sy+10); - gfx.LineTo(sx+15, y+5); - gfx.LineTo(sx+35, y-15); - gfx.StrokeWidth(5) - gfx.StrokeColor(0,255,0); - gfx.Stroke() - - end -end; - -local userHeight = 100 - -draw_user = function(user, x, y, buttonWidth, rank) - local buttonHeight = userHeight; - local rx = x - (buttonWidth / 2); - local ty = y - (buttonHeight / 2); - gfx.BeginPath(); - gfx.FillColor(256,128,255); - - gfx.Rect(rx - buttonBorder, - ty - buttonBorder, - buttonWidth + (buttonBorder * 2), - buttonHeight + (buttonBorder * 2)); - gfx.Fill(); - gfx.BeginPath(); - if host == user.id then - gfx.FillColor(80,0,0); - else - gfx.FillColor(0,0,40); - end - gfx.Rect(rx, ty, buttonWidth, buttonHeight); - gfx.Fill(); - gfx.BeginPath(); - gfx.FillColor(255,255,255); - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); - gfx.FontSize(40); - local name = user.name; - if user.id == user_id then - name = name - end - if user.id == host then - name = name..' (host)' - elseif user.missing_map then - name = name..' (NO CHART)' - elseif user.ready then - name = name..' (ready)' - end - if user.score ~= nil then - name = '#'..rank..' '..name - end - first_y = y - 28 - second_y = y + 28 - gfx.Text(name, x - buttonWidth/2 + 5, first_y); - if user.score ~= nil then - gfx.FillColor(255,255,0) - gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE); - local combo_text = ' '..user.combo..'x' - gfx.Text(combo_text, x+buttonWidth/2 - 5, second_y-5); - local xmin,ymin,xmax,ymax = gfx.TextBounds(x+buttonWidth/2 - 5, second_y-5, combo_text); - - - local score_text = ' '..string.format("%08d",user.score); - gfx.FillColor(255,255,255) - gfx.Text(score_text, xmin, second_y-5); - xmin,ymin,xmax,ymax = gfx.TextBounds(xmin, second_y-5, score_text); - - if user.grade == nil then - for i,v in ipairs(grades) do - if v.max > user.score then - user.grade = v.image - break - end - end - end - if user.badge == nil and user.clear > 1 then - user.badge = badges[user.clear]; - end - - gfx.BeginPath() - local iw, ih = gfx.ImageSize(user.grade) - local iar = iw/ih - local grade_height = buttonHeight/2 - 10 - gfx.ImageRect(xmin - iar * grade_height, second_y - buttonHeight/4 , iar * grade_height, grade_height, user.grade, 1, 0) - end - if user.level ~= 0 then - gfx.FillColor(255,255,0) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); - local level_text = 'Lvl '..user.level..' ' - gfx.Text(level_text, x-buttonWidth/2 + 5, second_y-5) - local xmin,ymin,xmax,ymax = gfx.TextBounds(x-buttonWidth/2 + 5, second_y-5, level_text); - - - if user.badge then - gfx.BeginPath() - local iw, ih = gfx.ImageSize(user.badge) - local iar = iw/ih; - local badge_height = buttonHeight/2 - 10 - gfx.ImageRect(xmax+5, second_y - buttonHeight/4 , iar * badge_height, badge_height, user.badge, 1, 0) - - - end - end -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_info() - gfx.Save() - gfx.ResetTransform() - gfx.BeginPath() - gfx.MoveTo(0, resY) - gfx.LineTo(550, resY) - gfx.LineTo(500, resY - 65) - gfx.LineTo(0, resY - 65) - 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("Multiplayer", 3, resY - 15) - local xmin,ymin,xmax,ymax = gfx.TextBounds(3, resY - 3, "Multiplayer") - gfx.FontSize(20) - gfx.Text(MULTIPLAYER_VERSION, xmax + 13, resY - 15) - --gfx.Text('Server: '..'', xmax + 13, resY - 15) - draw_button_color("Settings", 500-60, resY-25, 120, function() - mpScreen.OpenSettings(); - end, 33,33,33, 33,33,33) - gfx.Restore() - - if searchStatus then - gfx.BeginPath() - gfx.FillColor(255,255,255) - gfx.FontSize(20); - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(searchStatus, 3, 3) - end - - - - -end - -draw_diff_icon = function(diff, x, y, w, h, selected) - local shrinkX = w/4 - local shrinkY = h/4 - if selected then - gfx.FontSize(h/2) - shrinkX = w/6 - shrinkY = h/6 - else - gfx.FontSize(math.floor(h / 3)) - end - gfx.BeginPath() - gfx.RoundedRectVarying(x+shrinkX,y+shrinkY,w-shrinkX*2,h-shrinkY*2,0,0,0,0) - gfx.FillColor(15,15,15) - gfx.StrokeColor(table.unpack(diffColors[diff.difficulty + 1])) - gfx.StrokeWidth(2) - gfx.Fill() - gfx.Stroke() - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER) - gfx.FastText(tostring(diff.level), x+(w/2),y+(h/2)) -end - -local doffset = 0; -local timer = 0; - -draw_cursor = function(x,y,rotation,width) - gfx.Save() - gfx.BeginPath(); - gfx.Translate(x,y) - gfx.Rotate(rotation) - gfx.StrokeColor(255,128,0) - gfx.StrokeWidth(4) - gfx.Rect(-width/2, -width/2, width, width) - gfx.Stroke() - gfx.Restore() -end - -draw_diffs = function(diffs, x, y, w, h, selectedDiff) - local diffWidth = w/2.5 - local diffHeight = w/2.5 - local diffCount = #diffs - gfx.Scissor(x,y,w,h) - for i = math.max(selectedDiff - 2, 1), math.max(selectedDiff - 1,1) do - local diff = diffs[i] - local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) - if i ~= selectedDiff then - draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) - end - end - - --after selected - for i = math.min(selectedDiff + 2, diffCount), selectedDiff + 1,-1 do - local diff = diffs[i] - local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) - if i ~= selectedDiff then - draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) - end - end - local diff = diffs[selectedDiff] - local xpos = x + ((w/2 - diffWidth/2) + (doffset)*(-0.8*diffWidth)) - draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, true) - gfx.BeginPath() - gfx.FillColor(0,128,255) - gfx.Rect(x,y+10,2,diffHeight-h/6) - gfx.Fill() - gfx.BeginPath() - gfx.Rect(x+w-2,y+10,2,diffHeight-h/6) - gfx.Fill() - gfx.ResetScissor() - draw_cursor(x + w/2, y +diffHeight/2, timer * math.pi, diffHeight / 1.5) -end - -set_diff = function(oldDiff, newDiff) - game.PlaySample("click-02") - doffset = doffset + oldDiff - newDiff -end; - -local selected_room_index = 1; -local ioffset = 0; - -function draw_rooms(y, h) - if #rooms == 0 then - return - end - local num_rooms_visible = math.floor(h / (buttonHeight + 10)) - - local first_half_rooms = math.floor(num_rooms_visible/2) - local second_half_rooms = math.ceil(num_rooms_visible/2) - 1 - - local start_offset = math.max(selected_room_index - first_half_rooms, 1); - local end_offset = math.min(selected_room_index + second_half_rooms + 2, #rooms); - - local start_index_offset = 1; - - -- If our selection is near the start or end we have to offset - if selected_room_index <= first_half_rooms then - start_index_offset = 0; - end_offset = math.min(#rooms, num_rooms_visible + 1) - end - if selected_room_index >= #rooms - second_half_rooms then - start_offset = math.max(1, #rooms - num_rooms_visible) - end_offset = #rooms - end - - for i = start_offset, end_offset do - local room = rooms[i]; - -- if selected room < halfvis then we start at 1 - -- if sel > #rooms - halfvis then we start at -halfvis - local offset_index = (start_offset + first_half_rooms) - i + start_index_offset - - local offsetY = (offset_index + ioffset) * (buttonHeight + 10); - local ypos = y + (h/2) - offsetY; - local status = room.current..'/'..room.max - if room.ingame then - status = status..' (In Game)' - end - if room.password then - status = status..'

' - end - draw_room(room.name .. ': '.. status, resX / 2, ypos, i == selected_room_index, function() - join_room(room) - end) - end -end - -change_selected_room = function(off) - - local new_index = selected_room_index + off; - --selected_room_index = 2; - if new_index < 1 or new_index > #rooms then - return; - end - - local h = resY - 290; - - local num_rooms_visible = math.floor(h / (buttonHeight + 10)) - - local first_half_rooms = math.floor(num_rooms_visible/2) - local second_half_rooms = math.ceil(num_rooms_visible/2) - 1 - - if off > 0 and (selected_room_index < first_half_rooms or selected_room_index >= #rooms - second_half_rooms - 1) then - elseif off < 0 and (selected_room_index <= first_half_rooms or selected_room_index >= #rooms - second_half_rooms) then - else - ioffset = ioffset - new_index + selected_room_index; - end - - game.PlaySample("menu_click") - - selected_room_index = new_index; -end - -function render_lobby(deltaTime) - - local jacket_size; - - -- split is how the screen is split or not - if portrait then - split = resX - jacket_size = math.min(resX/3, resY/3); - user_y_off = 375+jacket_size + 70 - song_x_off = 0; - else - split = resX/2 - jacket_size = math.min(resX/2, resY/2); - user_y_off = 0 - song_x_off = 1/2*resX; - end - - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) - gfx.FontSize(70) - gfx.Text(selected_room.name, resX/2, 50) - - -- === Users === - - gfx.Text("Users", split/2, user_y_off+100) - - buttonY = user_y_off + 125 + userHeight/2 - for i, user in ipairs(lobby_users) do - draw_user(user, split/2, buttonY, split*3/4, i) - local side_button_off = 0 - if owner == user_id and user.id ~= user_id then - draw_button("K",split/2 + split*3/8+10+25, buttonY, 50, function() - kick_user(user); - end) - side_button_off = 60; - end - if (owner == user_id or host == user_id) and user.id ~= host then - draw_button("H",split/2 + split*3/8+10+25+side_button_off, buttonY, 50, function() - change_host(user); - end) - end - buttonY = buttonY + userHeight - end - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); - gfx.FillColor(255,255,255) - - - -- === song select === - - gfx.FontSize(60) - gfx.Text("Selected Song:", split/2 + song_x_off, 100) - gfx.FontSize(40) - if selected_song == nil then - if host == user_id then - gfx.Text("Select song:", split/2 + song_x_off, 175) - else - if missing_song then - gfx.Text("Missing song!!!!", split/2 + song_x_off, 175) - else - gfx.Text("Host is selecting song", split/2 + song_x_off, 175) - end - end - if jacket == 0 then - jacket = placeholderJacket - end - else - gfx.Text(selected_song.title, split/2 + song_x_off, 175) - draw_diffs(selected_song.all_difficulties, split/2 + song_x_off - 150, 200, 300, 100, selected_song.diff_index+1) - - if selected_song.jacket == nil or selected_song.jacket == placeholderJacket then - selected_song.jacket = gfx.LoadImageJob(selected_song.jacketPath, placeholderJacket) - jacket = selected_song.jacket - end - end - gfx.Save() - gfx.BeginPath() - gfx.Translate(split/2 + song_x_off, 325+jacket_size/2) - gfx.ImageRect(-jacket_size/2,-jacket_size/2,jacket_size,jacket_size,jacket,1,0) - - if mouse_clipped(split/2 + song_x_off-jacket_size/2, 325, jacket_size,jacket_size) and host == user_id then - hovered = function() - missing_song = false - mpScreen.SelectSong() - end - end - gfx.Restore() - if start_game_soon then - draw_button("Game starting...", split/2 + song_x_off, 375+jacket_size, 600, function() end); - else - if host == user_id then - if selected_song == nil or not selected_song.self_picked then - draw_button_color("Select song", split/2 + song_x_off, 375+jacket_size, 600, function() - missing_song = false - mpScreen.SelectSong() - end, 0, math.min(255, 128 + math.floor(32 * math.cos(timer * math.pi))), 0, 0,128,255); - elseif user_ready and all_ready then - draw_button("Start game", split/2 + song_x_off, 375+jacket_size, 600, start_game) - elseif user_ready and not all_ready then - draw_button("Waiting for others", split/2 + song_x_off, 375+jacket_size, 600, function() - missing_song = false - mpScreen.SelectSong() - end) - else - draw_button("Ready", split/2 + song_x_off, 375+jacket_size, 600, ready_up); - end - elseif host == nil then - draw_button("Waiting for game to end", split/2 + song_x_off, 375+jacket_size, 600, function() end); - elseif missing_song then - draw_button("Missing Song!", split/2 + song_x_off, 375+jacket_size, 600, function() end); - elseif selected_song ~= nil then - if user_ready then - draw_button("Cancel", split/2 + song_x_off, 375+jacket_size, 600, ready_up); - else - draw_button("Ready", split/2 + song_x_off, 375+jacket_size, 600, ready_up); - end - else - draw_button("Waiting for host", split/2 + song_x_off, 375+jacket_size, 600, function() end); - end - end - - draw_checkbox("Excessive", split/2 + song_x_off - 150, 375+jacket_size + 70, toggle_hard, hard_mode, not start_game_soon) - draw_checkbox("Mirror", split/2 + song_x_off, 375+jacket_size + 70, toggle_mirror, mirror_mode, not start_game_soon) - - draw_checkbox("Rotate Host", split/2 + song_x_off + 150 + 20, 375+jacket_size + 70, toggle_rotate, do_rotate, - (owner == user_id or host == user_id) and not start_game_soon) - - if selected_song ~= nil then - gfx.FillColor(255,255,255) - gfx.FontSize(20); - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP) - if selected_song.min_bpm ~= selected_song.max_bpm then - gfx.Text(string.format("BPM: %.0f-%.0f, Start BPM: %.0f, Hispeed: %.0f x %.1f = %.0f", - selected_song.min_bpm, selected_song.max_bpm, selected_song.start_bpm, - selected_song.speed_bpm, selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed), - split/2 + song_x_off, 375+jacket_size + 70 + 30) - else - gfx.Text(string.format("BPM: %.0f, Hispeed: %.0f x %.1f = %.0f", - selected_song.min_bpm, - selected_song.speed_bpm, selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed), - split/2 + song_x_off, 375+jacket_size + 70 + 30) - end - end -end - -function render_room_list(deltaTime) - draw_rooms(175, resY - 290); - - -- Draw cover for rooms out of view - gfx.BeginPath() - gfx.FillColor(20, 20, 20) - gfx.Rect(0, 0, resX, 145) - gfx.Rect(0, resY-170, resX, 170) - gfx.Fill() - - gfx.BeginPath() - gfx.FillColor(60, 60, 60) - gfx.Rect(0, 145, resX, 2) - gfx.Rect(0, resY-170-2, resX, 2) - gfx.Fill() - - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) - gfx.FontSize(70) - gfx.Text("Multiplayer Rooms", resX/2, 100) - - - if not loading then - draw_button("Create new room", resX/2, resY-40-buttonHeight, resX*(3/4), new_room); - end -end - - -passwordErrorOffset = 0; -function render_password_screen(deltaTime) - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) - gfx.FontSize(70) - gfx.Text("Joining "..selected_room.name.."...", resX/2, resY/4) - - gfx.FillColor(50,50,50) - gfx.BeginPath() - gfx.Rect(0, resY/2-10, resX, 40) - gfx.Fill(); - - gfx.FillColor(255,255,255) - gfx.Text("Please enter room password:", resX/2, resY/2-40) - gfx.Text(string.rep("*",#textInput.text), resX/2, resY/2+40) - if passwordError then - - gfx.FillColor(255,50,50) - gfx.FontSize(60 + math.floor(passwordErrorOffset*20)) - gfx.Text("Invalid password", resX/2, resY/2+80) - end - draw_button("Join", resX/2, resY*3/4, resX/2, mpScreen.JoinWithPassword); -end - -function render_new_room_password(delta_time) - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) - gfx.FontSize(70) - gfx.Text("Create New Room", resX/2, resY/4) - - gfx.FillColor(50,50,50) - gfx.BeginPath() - gfx.Rect(0, resY/2-10, resX, 40) - gfx.Fill(); - - gfx.FillColor(255,255,255) - gfx.Text("Enter room password:", resX/2, resY/2-40) - gfx.Text(string.rep("*",#textInput.text), resX/2, resY/2+40) - draw_button("Create Room", resX/2, resY*3/4, resX/2, mpScreen.NewRoomStep); -end - -function render_new_room_name(deltaTime) - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) - gfx.FontSize(70) - gfx.Text("Create New Room", resX/2, resY/4) - - gfx.FillColor(50,50,50) - gfx.BeginPath() - gfx.Rect(0, resY/2-10, resX, 60) - gfx.Fill(); - - gfx.FillColor(255,255,255) - gfx.Text("Please enter room name:", resX/2, resY/2-40) - gfx.Text(textInput.text, resX/2, resY/2+40) - draw_button("Next", resX/2, resY*3/4, resX/2, mpScreen.NewRoomStep); -end - -function render_set_username(deltaTime) - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) - gfx.FontSize(70) - gfx.Text("First things first...", resX/2, resY/4) - - gfx.FillColor(50,50,50) - gfx.BeginPath() - gfx.Rect(0, resY/2-10, resX, 60) - gfx.Fill(); - - gfx.FillColor(255,255,255) - gfx.Text("Enter a display name:", resX/2, resY/2-40) - gfx.Text(textInput.text, resX/2, resY/2+40) - draw_button("Join Multiplayer", resX/2, resY*3/4, resX/2, function() - loading = true; - mpScreen.SaveUsername() - end); - -end - -render = function(deltaTime) - resX,resY = game.GetResolution(); - buttonWidth = resX*(3/4); - mposx,mposy = game.GetMousePos(); - portrait = resY > resX - - common.stopMusic(); - - doffset = doffset * 0.9 - ioffset = ioffset * 0.9 - passwordErrorOffset = passwordErrorOffset * 0.9 - timer = (timer + deltaTime) - timer = timer % 2 - - hovered = nil; - - gfx.LoadSkinFont(normal_font); - - do_sounds(deltaTime); - - -- Room Listing View - if screenState == "inRoom" then - render_lobby(deltaTime); - elseif screenState == "roomList" then - render_room_list(deltaTime); - elseif screenState == "passwordScreen" then - render_password_screen(deltaTime); - elseif screenState == "newRoomName" then - render_new_room_name() - elseif screenState == "newRoomPassword" then - render_new_room_password() - elseif screenState == "setUsername" then - loading = false; - render_set_username() - end - render_loading(); - render_info(); - -end - --- Ready up to play -function ready_up() - Tcp.SendLine(json.encode({topic="user.ready.toggle"})) -end - --- Toggle hard gauage -function toggle_hard() - Tcp.SendLine(json.encode({topic="user.hard.toggle"})) -end - --- Toggle hard gauage -function toggle_mirror() - Tcp.SendLine(json.encode({topic="user.mirror.toggle"})) -end - -function new_room() - host = user_id - owner = user_id - mpScreen.NewRoomStep() -end - --- Toggle host rotation -function toggle_rotate() - Tcp.SendLine(json.encode({topic="room.option.rotation.toggle"})) -end - --- Change lobby host -function change_host(user) - Tcp.SendLine(json.encode({topic="room.host.set", host=user.id})) -end - --- Kick user -function kick_user(user) - Tcp.SendLine(json.encode({topic="room.kick", id=user.id})) -end - --- Tell the server to start the game -function start_game() - selected_song.self_picked = false - if (selected_song == nil) then - return - end - if (start_game_soon) then - return - end - - Tcp.SendLine(json.encode({topic="room.game.start"})) -end - --- Join a given room -function join_room(room) - host = user_id - selected_room = room; - if room.password then - mpScreen.JoinWithPassword(room.id) - else - mpScreen.JoinWithoutPassword(room.id) - end -end - --- Handle button presses to advance the UI -button_pressed = function(button) - if button == game.BUTTON_STA then - if start_game_soon then - return - end - if screenState == "roomList" then - if #rooms == 0 then - new_room() - else - -- TODO navigate room selection - join_room(rooms[selected_room_index]) - end - elseif screenState == "inRoom" then - if host == user_id then - if selected_song and selected_song.self_picked then - if all_ready then - start_game() - else - missing_song = false - mpScreen.SelectSong() - end - else - missing_song = false - mpScreen.SelectSong() - end - else - ready_up() - end - end - end - - if button == game.BUTTON_FXL then - toggle_hard(); - end - if button == game.BUTTON_FXR then - toggle_mirror(); - end -end - --- Handle the escape key around the UI -function key_pressed(key) - if key == 27 then --escape pressed - if screenState == "roomList" then - did_exit = true; - mpScreen.Exit(); - return - end - - -- Reset room data - screenState = "roomList" -- have to update here - selected_room = nil; - rooms = {}; - selected_song = nil - selected_song_index = 1; - jacket = 0; - end - -end - --- Handle mouse clicks in the UI -mouse_pressed = function(button) - if hovered then - hovered() - end - return 0 -end - -function init_tcp() -Tcp.SetTopicHandler("server.info", function(data) - loading = false - user_id = data.userid -end) --- Update the list of rooms as well as get user_id for the client -Tcp.SetTopicHandler("server.rooms", function(data) - - rooms = {} - for i, room in ipairs(data.rooms) do - table.insert(rooms, room) - end -end) - -Tcp.SetTopicHandler("server.room.joined", function(data) - selected_room = data.room -end) - -local sound_time = 0; -local sound_clip = nil; -local sounds_left = 0; -local sound_interval = 0; - -function repeat_sound(clip, times, interval) - sound_clip = clip; - sound_time = 0; - sounds_left = times - 1; - sound_interval = interval; - game.PlaySample(clip) -end - -function do_sounds(deltaTime) - if sound_clip == nil then - return - end - - sound_time = sound_time + deltaTime; - if sound_time > sound_interval then - sound_time = sound_time - sound_interval; - game.PlaySample(sound_clip); - sounds_left = sounds_left - 1 - if sounds_left <= 0 then - sound_clip = nil - end - end -end - -local last_song = nil - --- Update the current lobby -Tcp.SetTopicHandler("room.update", function(data) - -- Update the users in the lobby - lobby_users = {} - local prev_all_ready = all_ready; - all_ready = true - for i, user in ipairs(data.users) do - table.insert(lobby_users, user) - if user.id == user_id then - user_ready = user.ready - end - if not user.ready then - all_ready = false - end - end - - if user_id == host and #data.users > 1 and all_ready and not prev_all_ready then - repeat_sound("click-02", 3, .1) - end - - if data.host == user_id and host ~= user_id then - repeat_sound("click-02", 3, .1) - end - - if data.song ~=nil and last_song ~=data.song then - game.PlaySample("menu_click") - last_song = data.song - end - host = data.host - if data.owner then - owner = data.owner - else - owner = host - end - hard_mode = data.hard_mode - mirror_mode = data.mirror_mode - do_rotate = data.do_rotate - if data.start_soon and not start_game_soon then - repeat_sound("click-01", 5, 1) - end - start_game_soon = data.start_soon - -end) -end +json = require "json" + +local common = require('common.common'); + +local resX,resY = game.GetResolution() + +local mposx = 0; +local mposy = 0; +local hovered = nil; +local buttonWidth = resX*(3/4); +local buttonHeight = 75; +local buttonBorder = 2; +local portrait + +game.LoadSkinSample("click-02") +game.LoadSkinSample("click-01") +game.LoadSkinSample("menu_click") + +local loading = true; +local rooms = {}; +local lobby_users = {}; +local selected_room = nil; +local user_id = nil; +local jacket = 0; +local all_ready; +local user_ready; +local hard_mode = false; +local rotate_host = false; +local start_game_soon = false; +local host = nil; +local owner = nil; +local missing_song = false; +local placeholderJacket = gfx.CreateSkinImage("song_select/loading.png", 0) +local did_exit = false; + +local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}} + +local grades = { + {["max"] = 6999999, ["image"] = gfx.CreateSkinImage("common/grades/D.png", 0)}, + {["max"] = 7999999, ["image"] = gfx.CreateSkinImage("common/grades/C.png", 0)}, + {["max"] = 8699999, ["image"] = gfx.CreateSkinImage("common/grades/B.png", 0)}, + {["max"] = 8999999, ["image"] = gfx.CreateSkinImage("common/grades/A.png", 0)}, + {["max"] = 9299999, ["image"] = gfx.CreateSkinImage("common/grades/A+.png", 0)}, + {["max"] = 9499999, ["image"] = gfx.CreateSkinImage("common/grades/AA.png", 0)}, + {["max"] = 9699999, ["image"] = gfx.CreateSkinImage("common/grades/AA+.png", 0)}, + {["max"] = 9799999, ["image"] = gfx.CreateSkinImage("common/grades/AAA.png", 0)}, + {["max"] = 9899999, ["image"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0)}, + {["max"] = 99999999, ["image"] = gfx.CreateSkinImage("common/grades/S.png", 0)} + } + +local badges = { + gfx.CreateSkinImage("badges/played.png", 0), + gfx.CreateSkinImage("badges/clear.png", 0), + gfx.CreateSkinImage("badges/hard-clear.png", 0), + gfx.CreateSkinImage("badges/full-combo.png", 0), + gfx.CreateSkinImage("badges/perfect.png", 0) +} + +local user_name_key = game.GetSkinSetting('multi.user_name_key') +if user_name_key == nil then + user_name_key = 'nick' +end +local name = game.GetSkinSetting(user_name_key) +if name == nil or name == '' then + name = 'Guest' +end + +local normal_font = game.GetSkinSetting('multi.normal_font') +if normal_font == nil then + normal_font = 'NotoSans-Regular.ttf' +end +local mono_font = game.GetSkinSetting('multi.mono_font') +if mono_font == nil then + mono_font = 'NovaMono.ttf' +end + +local SERVER = game.GetSkinSetting("multi.server") + + + + + +mouse_clipped = function(x,y,w,h) + return mposx > x and mposy > y and mposx < x+w and mposy < y+h; +end; + +draw_room = function(name, x, y, selected, hoverindex) + local buttonWidth = resX*(3/4); + local rx = x - (buttonWidth / 2); + local ty = y - (buttonHeight / 2); + local roomButtonBorder = buttonBorder; + gfx.BeginPath(); + gfx.FillColor(0,128,255); + if selected then + gfx.FillColor(0,255,0); + roomButtonBorder = 4; + end + if mouse_clipped(rx,ty, buttonWidth, buttonHeight) then + hovered = hoverindex; + gfx.FillColor(255,128,0); + end + gfx.Rect(rx - roomButtonBorder, + ty - roomButtonBorder, + buttonWidth + (roomButtonBorder * 2), + buttonHeight + (roomButtonBorder * 2)); + gfx.Fill(); + gfx.BeginPath(); + gfx.FillColor(40,40,40); + gfx.Rect(rx, ty, buttonWidth, buttonHeight); + gfx.Fill(); + gfx.BeginPath(); + gfx.FillColor(255,255,255); + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); + gfx.FontSize(40); + gfx.Text(name, x, y); +end; + +draw_button = function(name, x, y, buttonWidth, hoverindex) + draw_button_color(name, x, y, buttonWidth, hoverindex, 40,40,40, 0,128,255) +end + +draw_button_color = function(name, x, y, buttonWidth, hoverindex,r,g,b, olr,olg,olb) + local rx = x - (buttonWidth / 2); + local ty = y - (buttonHeight / 2); + gfx.BeginPath(); + gfx.FillColor(olr, olg, olb); + if mouse_clipped(rx,ty, buttonWidth, buttonHeight) then + hovered = hoverindex; + gfx.FillColor(255,128,0); + end + gfx.Rect(rx - buttonBorder, + ty - buttonBorder, + buttonWidth + (buttonBorder * 2), + buttonHeight + (buttonBorder * 2)); + gfx.Fill(); + gfx.BeginPath(); + gfx.FillColor(r,g,b); + gfx.Rect(rx, ty, buttonWidth, buttonHeight); + gfx.Fill(); + gfx.BeginPath(); + gfx.FillColor(255,255,255); + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); + gfx.FontSize(40); + gfx.Text(name, x, y); +end; + +draw_checkbox = function(text, x, y, hoverindex, current, can_click) + local rx = x - (buttonWidth / 2); + local ty = y - (buttonHeight / 2); + gfx.BeginPath(); + + if can_click then + gfx.FillColor(255,255,255); + else + gfx.FillColor(150,100,100); + end + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); + gfx.FontSize(35); + gfx.Text(text, x, y) + + local xmin,ymin,xmax,ymax = gfx.TextBounds(x, y, text); + + local sx = xmin - 40; + local sy = y - 15; + + gfx.StrokeColor(0,128,255); + if can_click and (mouse_clipped(sx, sy, 31, 30) or mouse_clipped(xmin-10, ymin, xmax-xmin, ymax-ymin)) then + hovered = hoverindex; + gfx.StrokeColor(255,128,0); + end + + gfx.Rect(sx, y - 15, 30, 30) + gfx.StrokeWidth(2) + gfx.Stroke() + + if current then + -- Draw checkmark + gfx.BeginPath(); + gfx.MoveTo(sx+5, sy+10); + gfx.LineTo(sx+15, y+5); + gfx.LineTo(sx+35, y-15); + gfx.StrokeWidth(5) + gfx.StrokeColor(0,255,0); + gfx.Stroke() + + end +end; + +local userHeight = 100 + +draw_user = function(user, x, y, buttonWidth, rank) + local buttonHeight = userHeight; + local rx = x - (buttonWidth / 2); + local ty = y - (buttonHeight / 2); + gfx.BeginPath(); + gfx.FillColor(256,128,255); + + gfx.Rect(rx - buttonBorder, + ty - buttonBorder, + buttonWidth + (buttonBorder * 2), + buttonHeight + (buttonBorder * 2)); + gfx.Fill(); + gfx.BeginPath(); + if host == user.id then + gfx.FillColor(80,0,0); + else + gfx.FillColor(0,0,40); + end + gfx.Rect(rx, ty, buttonWidth, buttonHeight); + gfx.Fill(); + gfx.BeginPath(); + gfx.FillColor(255,255,255); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); + gfx.FontSize(40); + local name = user.name; + if user.id == user_id then + name = name + end + if user.id == host then + name = name..' (host)' + elseif user.missing_map then + name = name..' (NO CHART)' + elseif user.ready then + name = name..' (ready)' + end + if user.score ~= nil then + name = '#'..rank..' '..name + end + first_y = y - 28 + second_y = y + 28 + gfx.Text(name, x - buttonWidth/2 + 5, first_y); + if user.score ~= nil then + gfx.FillColor(255,255,0) + gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE); + local combo_text = ' '..user.combo..'x' + gfx.Text(combo_text, x+buttonWidth/2 - 5, second_y-5); + local xmin,ymin,xmax,ymax = gfx.TextBounds(x+buttonWidth/2 - 5, second_y-5, combo_text); + + + local score_text = ' '..string.format("%08d",user.score); + gfx.FillColor(255,255,255) + gfx.Text(score_text, xmin, second_y-5); + xmin,ymin,xmax,ymax = gfx.TextBounds(xmin, second_y-5, score_text); + + if user.grade == nil then + for i,v in ipairs(grades) do + if v.max > user.score then + user.grade = v.image + break + end + end + end + if user.badge == nil and user.clear > 1 then + user.badge = badges[user.clear]; + end + + gfx.BeginPath() + local iw, ih = gfx.ImageSize(user.grade) + local iar = iw/ih + local grade_height = buttonHeight/2 - 10 + gfx.ImageRect(xmin - iar * grade_height, second_y - buttonHeight/4 , iar * grade_height, grade_height, user.grade, 1, 0) + end + if user.level ~= 0 then + gfx.FillColor(255,255,0) + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); + local level_text = 'Lvl '..user.level..' ' + gfx.Text(level_text, x-buttonWidth/2 + 5, second_y-5) + local xmin,ymin,xmax,ymax = gfx.TextBounds(x-buttonWidth/2 + 5, second_y-5, level_text); + + + if user.badge then + gfx.BeginPath() + local iw, ih = gfx.ImageSize(user.badge) + local iar = iw/ih; + local badge_height = buttonHeight/2 - 10 + gfx.ImageRect(xmax+5, second_y - buttonHeight/4 , iar * badge_height, badge_height, user.badge, 1, 0) + + + end + end +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_info() + gfx.Save() + gfx.ResetTransform() + gfx.BeginPath() + gfx.MoveTo(0, resY) + gfx.LineTo(550, resY) + gfx.LineTo(500, resY - 65) + gfx.LineTo(0, resY - 65) + 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("Multiplayer", 3, resY - 15) + local xmin,ymin,xmax,ymax = gfx.TextBounds(3, resY - 3, "Multiplayer") + gfx.FontSize(20) + gfx.Text(MULTIPLAYER_VERSION, xmax + 13, resY - 15) + --gfx.Text('Server: '..'', xmax + 13, resY - 15) + draw_button_color("Settings", 500-60, resY-25, 120, function() + mpScreen.OpenSettings(); + end, 33,33,33, 33,33,33) + gfx.Restore() + + if searchStatus then + gfx.BeginPath() + gfx.FillColor(255,255,255) + gfx.FontSize(20); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.Text(searchStatus, 3, 3) + end + + + + +end + +draw_diff_icon = function(diff, x, y, w, h, selected) + local shrinkX = w/4 + local shrinkY = h/4 + if selected then + gfx.FontSize(h/2) + shrinkX = w/6 + shrinkY = h/6 + else + gfx.FontSize(math.floor(h / 3)) + end + gfx.BeginPath() + gfx.RoundedRectVarying(x+shrinkX,y+shrinkY,w-shrinkX*2,h-shrinkY*2,0,0,0,0) + gfx.FillColor(15,15,15) + gfx.StrokeColor(table.unpack(diffColors[diff.difficulty + 1])) + gfx.StrokeWidth(2) + gfx.Fill() + gfx.Stroke() + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER) + gfx.FastText(tostring(diff.level), x+(w/2),y+(h/2)) +end + +local doffset = 0; +local timer = 0; + +draw_cursor = function(x,y,rotation,width) + gfx.Save() + gfx.BeginPath(); + gfx.Translate(x,y) + gfx.Rotate(rotation) + gfx.StrokeColor(255,128,0) + gfx.StrokeWidth(4) + gfx.Rect(-width/2, -width/2, width, width) + gfx.Stroke() + gfx.Restore() +end + +draw_diffs = function(diffs, x, y, w, h, selectedDiff) + local diffWidth = w/2.5 + local diffHeight = w/2.5 + local diffCount = #diffs + gfx.Scissor(x,y,w,h) + for i = math.max(selectedDiff - 2, 1), math.max(selectedDiff - 1,1) do + local diff = diffs[i] + local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) + if i ~= selectedDiff then + draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) + end + end + + --after selected + for i = math.min(selectedDiff + 2, diffCount), selectedDiff + 1,-1 do + local diff = diffs[i] + local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) + if i ~= selectedDiff then + draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) + end + end + local diff = diffs[selectedDiff] + local xpos = x + ((w/2 - diffWidth/2) + (doffset)*(-0.8*diffWidth)) + draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, true) + gfx.BeginPath() + gfx.FillColor(0,128,255) + gfx.Rect(x,y+10,2,diffHeight-h/6) + gfx.Fill() + gfx.BeginPath() + gfx.Rect(x+w-2,y+10,2,diffHeight-h/6) + gfx.Fill() + gfx.ResetScissor() + draw_cursor(x + w/2, y +diffHeight/2, timer * math.pi, diffHeight / 1.5) +end + +set_diff = function(oldDiff, newDiff) + game.PlaySample("click-02") + doffset = doffset + oldDiff - newDiff +end; + +local selected_room_index = 1; +local ioffset = 0; + +function draw_rooms(y, h) + if #rooms == 0 then + return + end + local num_rooms_visible = math.floor(h / (buttonHeight + 10)) + + local first_half_rooms = math.floor(num_rooms_visible/2) + local second_half_rooms = math.ceil(num_rooms_visible/2) - 1 + + local start_offset = math.max(selected_room_index - first_half_rooms, 1); + local end_offset = math.min(selected_room_index + second_half_rooms + 2, #rooms); + + local start_index_offset = 1; + + -- If our selection is near the start or end we have to offset + if selected_room_index <= first_half_rooms then + start_index_offset = 0; + end_offset = math.min(#rooms, num_rooms_visible + 1) + end + if selected_room_index >= #rooms - second_half_rooms then + start_offset = math.max(1, #rooms - num_rooms_visible) + end_offset = #rooms + end + + for i = start_offset, end_offset do + local room = rooms[i]; + -- if selected room < halfvis then we start at 1 + -- if sel > #rooms - halfvis then we start at -halfvis + local offset_index = (start_offset + first_half_rooms) - i + start_index_offset + + local offsetY = (offset_index + ioffset) * (buttonHeight + 10); + local ypos = y + (h/2) - offsetY; + local status = room.current..'/'..room.max + if room.ingame then + status = status..' (In Game)' + end + if room.password then + status = status..'

' + end + draw_room(room.name .. ': '.. status, resX / 2, ypos, i == selected_room_index, function() + join_room(room) + end) + end +end + +change_selected_room = function(off) + + local new_index = selected_room_index + off; + --selected_room_index = 2; + if new_index < 1 or new_index > #rooms then + return; + end + + local h = resY - 290; + + local num_rooms_visible = math.floor(h / (buttonHeight + 10)) + + local first_half_rooms = math.floor(num_rooms_visible/2) + local second_half_rooms = math.ceil(num_rooms_visible/2) - 1 + + if off > 0 and (selected_room_index < first_half_rooms or selected_room_index >= #rooms - second_half_rooms - 1) then + elseif off < 0 and (selected_room_index <= first_half_rooms or selected_room_index >= #rooms - second_half_rooms) then + else + ioffset = ioffset - new_index + selected_room_index; + end + + game.PlaySample("menu_click") + + selected_room_index = new_index; +end + +function render_lobby(deltaTime) + + local jacket_size; + + -- split is how the screen is split or not + if portrait then + split = resX + jacket_size = math.min(resX/3, resY/3); + user_y_off = 375+jacket_size + 70 + song_x_off = 0; + else + split = resX/2 + jacket_size = math.min(resX/2, resY/2); + user_y_off = 0 + song_x_off = 1/2*resX; + end + + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) + gfx.FontSize(70) + gfx.Text(selected_room.name, resX/2, 50) + + -- === Users === + + gfx.Text("Users", split/2, user_y_off+100) + + buttonY = user_y_off + 125 + userHeight/2 + for i, user in ipairs(lobby_users) do + draw_user(user, split/2, buttonY, split*3/4, i) + local side_button_off = 0 + if owner == user_id and user.id ~= user_id then + draw_button("K",split/2 + split*3/8+10+25, buttonY, 50, function() + kick_user(user); + end) + side_button_off = 60; + end + if (owner == user_id or host == user_id) and user.id ~= host then + draw_button("H",split/2 + split*3/8+10+25+side_button_off, buttonY, 50, function() + change_host(user); + end) + end + buttonY = buttonY + userHeight + end + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); + gfx.FillColor(255,255,255) + + + -- === song select === + + gfx.FontSize(60) + gfx.Text("Selected Song:", split/2 + song_x_off, 100) + gfx.FontSize(40) + if selected_song == nil then + if host == user_id then + gfx.Text("Select song:", split/2 + song_x_off, 175) + else + if missing_song then + gfx.Text("Missing song!!!!", split/2 + song_x_off, 175) + else + gfx.Text("Host is selecting song", split/2 + song_x_off, 175) + end + end + if jacket == 0 then + jacket = placeholderJacket + end + else + gfx.Text(selected_song.title, split/2 + song_x_off, 175) + draw_diffs(selected_song.all_difficulties, split/2 + song_x_off - 150, 200, 300, 100, selected_song.diff_index+1) + + if selected_song.jacket == nil or selected_song.jacket == placeholderJacket then + selected_song.jacket = gfx.LoadImageJob(selected_song.jacketPath, placeholderJacket) + jacket = selected_song.jacket + end + end + gfx.Save() + gfx.BeginPath() + gfx.Translate(split/2 + song_x_off, 325+jacket_size/2) + gfx.ImageRect(-jacket_size/2,-jacket_size/2,jacket_size,jacket_size,jacket,1,0) + + if mouse_clipped(split/2 + song_x_off-jacket_size/2, 325, jacket_size,jacket_size) and host == user_id then + hovered = function() + missing_song = false + mpScreen.SelectSong() + end + end + gfx.Restore() + if start_game_soon then + draw_button("Game starting...", split/2 + song_x_off, 375+jacket_size, 600, function() end); + else + if host == user_id then + if selected_song == nil or not selected_song.self_picked then + draw_button_color("Select song", split/2 + song_x_off, 375+jacket_size, 600, function() + missing_song = false + mpScreen.SelectSong() + end, 0, math.min(255, 128 + math.floor(32 * math.cos(timer * math.pi))), 0, 0,128,255); + elseif user_ready and all_ready then + draw_button("Start game", split/2 + song_x_off, 375+jacket_size, 600, start_game) + elseif user_ready and not all_ready then + draw_button("Waiting for others", split/2 + song_x_off, 375+jacket_size, 600, function() + missing_song = false + mpScreen.SelectSong() + end) + else + draw_button("Ready", split/2 + song_x_off, 375+jacket_size, 600, ready_up); + end + elseif host == nil then + draw_button("Waiting for game to end", split/2 + song_x_off, 375+jacket_size, 600, function() end); + elseif missing_song then + draw_button("Missing Song!", split/2 + song_x_off, 375+jacket_size, 600, function() end); + elseif selected_song ~= nil then + if user_ready then + draw_button("Cancel", split/2 + song_x_off, 375+jacket_size, 600, ready_up); + else + draw_button("Ready", split/2 + song_x_off, 375+jacket_size, 600, ready_up); + end + else + draw_button("Waiting for host", split/2 + song_x_off, 375+jacket_size, 600, function() end); + end + end + + draw_checkbox("Excessive", split/2 + song_x_off - 150, 375+jacket_size + 70, toggle_hard, hard_mode, not start_game_soon) + draw_checkbox("Mirror", split/2 + song_x_off, 375+jacket_size + 70, toggle_mirror, mirror_mode, not start_game_soon) + + draw_checkbox("Rotate Host", split/2 + song_x_off + 150 + 20, 375+jacket_size + 70, toggle_rotate, do_rotate, + (owner == user_id or host == user_id) and not start_game_soon) + + if selected_song ~= nil then + gfx.FillColor(255,255,255) + gfx.FontSize(20); + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP) + if selected_song.min_bpm ~= selected_song.max_bpm then + gfx.Text(string.format("BPM: %.0f-%.0f, Start BPM: %.0f, Hispeed: %.0f x %.1f = %.0f", + selected_song.min_bpm, selected_song.max_bpm, selected_song.start_bpm, + selected_song.speed_bpm, selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed), + split/2 + song_x_off, 375+jacket_size + 70 + 30) + else + gfx.Text(string.format("BPM: %.0f, Hispeed: %.0f x %.1f = %.0f", + selected_song.min_bpm, + selected_song.speed_bpm, selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed), + split/2 + song_x_off, 375+jacket_size + 70 + 30) + end + end +end + +function render_room_list(deltaTime) + draw_rooms(175, resY - 290); + + -- Draw cover for rooms out of view + gfx.BeginPath() + gfx.FillColor(20, 20, 20) + gfx.Rect(0, 0, resX, 145) + gfx.Rect(0, resY-170, resX, 170) + gfx.Fill() + + gfx.BeginPath() + gfx.FillColor(60, 60, 60) + gfx.Rect(0, 145, resX, 2) + gfx.Rect(0, resY-170-2, resX, 2) + gfx.Fill() + + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) + gfx.FontSize(70) + gfx.Text("Multiplayer Rooms", resX/2, 100) + + + if not loading then + draw_button("Create new room", resX/2, resY-40-buttonHeight, resX*(3/4), new_room); + end +end + + +passwordErrorOffset = 0; +function render_password_screen(deltaTime) + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) + gfx.FontSize(70) + gfx.Text("Joining "..selected_room.name.."...", resX/2, resY/4) + + gfx.FillColor(50,50,50) + gfx.BeginPath() + gfx.Rect(0, resY/2-10, resX, 40) + gfx.Fill(); + + gfx.FillColor(255,255,255) + gfx.Text("Please enter room password:", resX/2, resY/2-40) + gfx.Text(string.rep("*",#textInput.text), resX/2, resY/2+40) + if passwordError then + + gfx.FillColor(255,50,50) + gfx.FontSize(60 + math.floor(passwordErrorOffset*20)) + gfx.Text("Invalid password", resX/2, resY/2+80) + end + draw_button("Join", resX/2, resY*3/4, resX/2, mpScreen.JoinWithPassword); +end + +function render_new_room_password(delta_time) + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) + gfx.FontSize(70) + gfx.Text("Create New Room", resX/2, resY/4) + + gfx.FillColor(50,50,50) + gfx.BeginPath() + gfx.Rect(0, resY/2-10, resX, 40) + gfx.Fill(); + + gfx.FillColor(255,255,255) + gfx.Text("Enter room password:", resX/2, resY/2-40) + gfx.Text(string.rep("*",#textInput.text), resX/2, resY/2+40) + draw_button("Create Room", resX/2, resY*3/4, resX/2, mpScreen.NewRoomStep); +end + +function render_new_room_name(deltaTime) + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) + gfx.FontSize(70) + gfx.Text("Create New Room", resX/2, resY/4) + + gfx.FillColor(50,50,50) + gfx.BeginPath() + gfx.Rect(0, resY/2-10, resX, 60) + gfx.Fill(); + + gfx.FillColor(255,255,255) + gfx.Text("Please enter room name:", resX/2, resY/2-40) + gfx.Text(textInput.text, resX/2, resY/2+40) + draw_button("Next", resX/2, resY*3/4, resX/2, mpScreen.NewRoomStep); +end + +function render_set_username(deltaTime) + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM) + gfx.FontSize(70) + gfx.Text("First things first...", resX/2, resY/4) + + gfx.FillColor(50,50,50) + gfx.BeginPath() + gfx.Rect(0, resY/2-10, resX, 60) + gfx.Fill(); + + gfx.FillColor(255,255,255) + gfx.Text("Enter a display name:", resX/2, resY/2-40) + gfx.Text(textInput.text, resX/2, resY/2+40) + draw_button("Join Multiplayer", resX/2, resY*3/4, resX/2, function() + loading = true; + mpScreen.SaveUsername() + end); + +end + +render = function(deltaTime) + resX,resY = game.GetResolution(); + buttonWidth = resX*(3/4); + mposx,mposy = game.GetMousePos(); + portrait = resY > resX + + common.stopMusic(); + + doffset = doffset * 0.9 + ioffset = ioffset * 0.9 + passwordErrorOffset = passwordErrorOffset * 0.9 + timer = (timer + deltaTime) + timer = timer % 2 + + hovered = nil; + + gfx.LoadSkinFont(normal_font); + + do_sounds(deltaTime); + + -- Room Listing View + if screenState == "inRoom" then + render_lobby(deltaTime); + elseif screenState == "roomList" then + render_room_list(deltaTime); + elseif screenState == "passwordScreen" then + render_password_screen(deltaTime); + elseif screenState == "newRoomName" then + render_new_room_name() + elseif screenState == "newRoomPassword" then + render_new_room_password() + elseif screenState == "setUsername" then + loading = false; + render_set_username() + end + render_loading(); + render_info(); + +end + +-- Ready up to play +function ready_up() + Tcp.SendLine(json.encode({topic="user.ready.toggle"})) +end + +-- Toggle hard gauage +function toggle_hard() + Tcp.SendLine(json.encode({topic="user.hard.toggle"})) +end + +-- Toggle hard gauage +function toggle_mirror() + Tcp.SendLine(json.encode({topic="user.mirror.toggle"})) +end + +function new_room() + host = user_id + owner = user_id + mpScreen.NewRoomStep() +end + +-- Toggle host rotation +function toggle_rotate() + Tcp.SendLine(json.encode({topic="room.option.rotation.toggle"})) +end + +-- Change lobby host +function change_host(user) + Tcp.SendLine(json.encode({topic="room.host.set", host=user.id})) +end + +-- Kick user +function kick_user(user) + Tcp.SendLine(json.encode({topic="room.kick", id=user.id})) +end + +-- Tell the server to start the game +function start_game() + selected_song.self_picked = false + if (selected_song == nil) then + return + end + if (start_game_soon) then + return + end + + Tcp.SendLine(json.encode({topic="room.game.start"})) +end + +-- Join a given room +function join_room(room) + host = user_id + selected_room = room; + if room.password then + mpScreen.JoinWithPassword(room.id) + else + mpScreen.JoinWithoutPassword(room.id) + end +end + +-- Handle button presses to advance the UI +button_pressed = function(button) + if button == game.BUTTON_STA then + if start_game_soon then + return + end + if screenState == "roomList" then + if #rooms == 0 then + new_room() + else + -- TODO navigate room selection + join_room(rooms[selected_room_index]) + end + elseif screenState == "inRoom" then + if host == user_id then + if selected_song and selected_song.self_picked then + if all_ready then + start_game() + else + missing_song = false + mpScreen.SelectSong() + end + else + missing_song = false + mpScreen.SelectSong() + end + else + ready_up() + end + end + end + + if button == game.BUTTON_FXL then + toggle_hard(); + end + if button == game.BUTTON_FXR then + toggle_mirror(); + end +end + +-- Handle the escape key around the UI +function key_pressed(key) + if key == 27 then --escape pressed + if screenState == "roomList" then + did_exit = true; + mpScreen.Exit(); + return + end + + -- Reset room data + screenState = "roomList" -- have to update here + selected_room = nil; + rooms = {}; + selected_song = nil + selected_song_index = 1; + jacket = 0; + end + +end + +-- Handle mouse clicks in the UI +mouse_pressed = function(button) + if hovered then + hovered() + end + return 0 +end + +function init_tcp() +Tcp.SetTopicHandler("server.info", function(data) + loading = false + user_id = data.userid +end) +-- Update the list of rooms as well as get user_id for the client +Tcp.SetTopicHandler("server.rooms", function(data) + + rooms = {} + for i, room in ipairs(data.rooms) do + table.insert(rooms, room) + end +end) + +Tcp.SetTopicHandler("server.room.joined", function(data) + selected_room = data.room +end) + +local sound_time = 0; +local sound_clip = nil; +local sounds_left = 0; +local sound_interval = 0; + +function repeat_sound(clip, times, interval) + sound_clip = clip; + sound_time = 0; + sounds_left = times - 1; + sound_interval = interval; + game.PlaySample(clip) +end + +function do_sounds(deltaTime) + if sound_clip == nil then + return + end + + sound_time = sound_time + deltaTime; + if sound_time > sound_interval then + sound_time = sound_time - sound_interval; + game.PlaySample(sound_clip); + sounds_left = sounds_left - 1 + if sounds_left <= 0 then + sound_clip = nil + end + end +end + +local last_song = nil + +-- Update the current lobby +Tcp.SetTopicHandler("room.update", function(data) + -- Update the users in the lobby + lobby_users = {} + local prev_all_ready = all_ready; + all_ready = true + for i, user in ipairs(data.users) do + table.insert(lobby_users, user) + if user.id == user_id then + user_ready = user.ready + end + if not user.ready then + all_ready = false + end + end + + if user_id == host and #data.users > 1 and all_ready and not prev_all_ready then + repeat_sound("click-02", 3, .1) + end + + if data.host == user_id and host ~= user_id then + repeat_sound("click-02", 3, .1) + end + + if data.song ~=nil and last_song ~=data.song then + game.PlaySample("menu_click") + last_song = data.song + end + host = data.host + if data.owner then + owner = data.owner + else + owner = host + end + hard_mode = data.hard_mode + mirror_mode = data.mirror_mode + do_rotate = data.do_rotate + if data.start_soon and not start_game_soon then + repeat_sound("click-01", 5, 1) + end + start_game_soon = data.start_soon + +end) +end diff --git a/scripts/songselect/chalwheel.lua b/scripts/songselect/chalwheel.lua index 91da25b..f91f32e 100644 --- a/scripts/songselect/chalwheel.lua +++ b/scripts/songselect/chalwheel.lua @@ -1,606 +1,606 @@ -local Common = require("common.common") -local Numbers = require("common.numbers") -local DiffRectangle = require("components.diff_rectangle") -local Header = require("components.headers.challengeSelectHeader") -local Footer = require("components.footer") - -local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY) -local challengeBGImage = gfx.CreateSkinImage("challenge_select/bg.png", 0) -local challengeCardBGImage = gfx.CreateSkinImage("challenge_select/small_box.png", 0) -local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) - ---[[ will be reimplemented sometime later -local soffset = 0 -local searchText = gfx.CreateLabel("", 5, 0) -local searchIndex = 1 - -local showGuide = game.GetSkinSetting("show_guide") -local legendTable = { - { - ["labelSingleLine"] = gfx.CreateLabel("SCROLL INFO", 16, 0), - ["labelMultiLine"] = gfx.CreateLabel("SCROLL\nINFO", 16, 0), - ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0) - }, - { - ["labelSingleLine"] = gfx.CreateLabel("CHALL SELECT", 16, 0), - ["labelMultiLine"] = gfx.CreateLabel("CHALLENGE\nSELECT", 16, 0), - ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0) - }, - { - ["labelSingleLine"] = gfx.CreateLabel("FILTER CHALLS", 16, 0), - ["labelMultiLine"] = gfx.CreateLabel("FILTER\nCHALLENGES", 16, 0), - ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0) - }, - { - ["labelSingleLine"] = gfx.CreateLabel("SORT CHALLS", 16, 0), - ["labelMultiLine"] = gfx.CreateLabel("SORT\nCHALLENGES", 16, 0), - ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0) - }, - { - ["labelSingleLine"] = gfx.CreateLabel("GAME SETTINGS", 16, 0), - ["labelMultiLine"] = gfx.CreateLabel("GAME\nSETTINGS", 16, 0), - ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0) - }, - { - ["labelSingleLine"] = gfx.CreateLabel("PLAY", 16, 0), - ["labelMultiLine"] = gfx.CreateLabel("PLAY", 16, 0), - ["image"] = gfx.CreateSkinImage("legend/start.png", 0) - } -} ---]] - -local grades = { - ["D"] = gfx.CreateSkinImage("common/grades/D.png", 0), - ["C"] = gfx.CreateSkinImage("common/grades/C.png", 0), - ["B"] = gfx.CreateSkinImage("common/grades/B.png", 0), - ["A"] = gfx.CreateSkinImage("common/grades/A.png", 0), - ["A+"] = gfx.CreateSkinImage("common/grades/A+.png", 0), - ["AA"] = gfx.CreateSkinImage("common/grades/AA.png", 0), - ["AA+"] = gfx.CreateSkinImage("common/grades/AA+.png", 0), - ["AAA"] = gfx.CreateSkinImage("common/grades/AAA.png", 0), - ["AAA+"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0), - ["S"] = gfx.CreateSkinImage("common/grades/S.png", 0) -} - -local badges = { - gfx.CreateSkinImage("song_select/medal/played.png", gfx.IMAGE_GENERATE_MIPMAPS), - gfx.CreateSkinImage("song_select/medal/clear.png", gfx.IMAGE_GENERATE_MIPMAPS), - gfx.CreateSkinImage("song_select/medal/hard.png", gfx.IMAGE_GENERATE_MIPMAPS), - gfx.CreateSkinImage("song_select/medal/uc.png", gfx.IMAGE_GENERATE_MIPMAPS), - gfx.CreateSkinImage("song_select/medal/puc.png", gfx.IMAGE_GENERATE_MIPMAPS) -} - -local passStates = { - gfx.CreateSkinImage("challenge_select/pass_states/not_played.png", 0), - gfx.CreateSkinImage("challenge_select/pass_states/failed.png", 0), - gfx.CreateSkinImage("challenge_select/pass_states/cleared.png", 0) -} - -local scoreNumbers = Numbers.load_number_image("score_num") -local percentImage = gfx.CreateSkinImage("score_num/percent.png", 0) - -gfx.LoadSkinFont("dfmarugoth.ttf"); - -game.LoadSkinSample("menu_click") -game.LoadSkinSample("woosh") - --- Wheel variables -local wheelSize = 5 - -local selectedIndex = 1 -local challengeCache = {} -local timer = 0 - --- Window variables -local desw = 1080 -local desh = 1920 -local resX, resY - --- Aspect Ratios -local landscapeWidescreenRatio = 16 / 9 -local landscapeStandardRatio = 4 / 3 -local portraitWidescreenRatio = 9 / 16 - --- Portrait sizes -local fullX, fullY - -local resolutionChange = function(x, y) - resX = x - resY = y - fullX = portraitWidescreenRatio * y - fullY = y -end - -local update_cache_labels = function(challenge, titleFontSize) - if challengeCache[challenge.id] then - local _, fontsize = gfx.LabelSize(challengeCache[challenge.id]["title"]) - if fontsize ~= titleFontSize then - gfx.UpdateLabel(challengeCache[challenge.id]["title"], challengeCache[challenge.id]["title_raw"], titleFontSize) - for _, chart in ipairs(challengeCache[challenge.id]["charts"]) do - gfx.UpdateLabel(chart["title"], chart["title_raw"], titleFontSize) - end - end - end -end - -local check_or_create_cache = function(challenge) - local defaultLabelSize = 16 - - if not challengeCache[challenge.id] then - challengeCache[challenge.id] = {} - end - - if not challengeCache[challenge.id]["title"] then - challengeCache[challenge.id]["title"] = gfx.CreateLabel(challenge.title, defaultLabelSize, 0) - challengeCache[challenge.id]["title_raw"] = challenge.title - end - - if not challengeCache[challenge.id]["charts"] then - if challenge.missing_chart then - local missing_text = "*COULD NOT FIND ALL CHARTS!*" - challengeCache[challenge.id]["charts"] = { - { - ["title"] = gfx.CreateLabel(missing_text, defaultLabelSize, 0), - ["title_raw"] = missing_text, - ["level"] = 0, - ["difficulty"] = 0 - } - } - else -- if not challenge.missing_chart then - local charts = {} - for _, chart in ipairs(challenge.charts) do - table.insert(charts, { - ["title"] = gfx.CreateLabel(chart.title, defaultLabelSize, 0), - ["title_raw"] = chart.title, - ["level"] = chart.level, - ["difficulty"] = chart.difficulty - }) - end - challengeCache[challenge.id]["charts"] = charts - end - end - - if not challengeCache[challenge.id]["percent_required"] then - local percentRequired = 100 - local reqTextWords = Common.splitString(challenge.requirement_text, ' '); - for _, word in ipairs(reqTextWords) do - if string.find(word, '%%') ~= nil then -- %% = %, because % is an escape char - local percentNumber = tonumber(string.gsub(word, '%%', ''), 10) - percentRequired = percentNumber; - break - end - end - challengeCache[challenge.id]["percent_required"] = percentRequired - end - - if (not challengeCache[challenge.id]["percent"] or not challengeCache[challenge.id]["total_score"] - or challengeCache[challenge.id]["total_score"] ~= challenge.bestScore) then - challengeCache[challenge.id]["percent"] = math.max(0, (challenge.bestScore - 8000000) // 10000) - challengeCache[challenge.id]["total_score"] = challenge.bestScore - end - - local passState = math.min(challenge.topBadge, 2) + 1 -- challenge.topBadge -> [1, 3] - if (not challengeCache[challenge.id]["pass_state"] or not challengeCache[challenge.id]["pass_state_idx"] - or challengeCache[challenge.id]["pass_state_idx"] ~= passState) then - challengeCache[challenge.id]["pass_state_idx"] = passState - challengeCache[challenge.id]["pass_state"] = passStates[passState] - end - - local lastChart = challenge.charts[#challenge.charts] - if not challengeCache[challenge.id]["jacket"] then - if challenge.missing_chart then - challengeCache[challenge.id]["jacket"] = jacketFallback - else - challengeCache[challenge.id]["jacket"] = gfx.LoadImageJob(lastChart.jacketPath, jacketFallback, 200, 200) - end - elseif not challenge.missing_chart and challengeCache[challenge.id]["jacket"] == jacketFallback then - challengeCache[challenge.id]["jacket"] = gfx.LoadImageJob(lastChart.jacketPath, jacketFallback, 200, 200) - end -end - -draw_challenge = function(challenge, x, y, w, h, selected) - if not challenge then - return - end - - check_or_create_cache(challenge) - - ---------------------------------------------------------- - -- draw card bg section - ---------------------------------------------------------- - if not selected then - gfx.BeginPath() - gfx.ImageRect(x, y, w, h, challengeCardBGImage, 1, 0) - end - - ---------------------------------------------------------- - -- draw info section - ---------------------------------------------------------- - local stateLabel = challengeCache[challenge.id]["pass_state"] - local stateLabelWidth, stateLabelHeight = gfx.ImageSize(stateLabel) - local stateLabelAspect = stateLabelWidth / stateLabelHeight - - local stateWidth = w / 5 - local stateHeight = stateWidth / stateLabelAspect - local stateOffsetX = x + w / 32 - local stateOffsetY = y + h / 32 - - local titleMargin = 6 - local titleFontSize = math.floor(0.11 * h) -- must be an integer - local titleMaxWidth = 6 / 9 * w - local titleCenterX = x + w - titleMargin - titleMaxWidth / 2 -- align right - local titleBaselineY = y + 0.025 * h -- align baseline - - if selected then - stateWidth = stateWidth / 0.9 - stateHeight = stateHeight / 0.9 - stateOffsetY = y + h / 16 - - titleFontSize = math.floor(0.075 * h) -- must be an integer - titleMaxWidth = 3 / 5 * w - titleCenterX = x + w - titleMargin - titleMaxWidth / 2 -- align right - titleBaselineY = y + 0.09 * h -- align baseline - end - - update_cache_labels(challenge, titleFontSize) - - gfx.BeginPath() - gfx.ImageRect(stateOffsetX, stateOffsetY, stateWidth, stateHeight, stateLabel, 1, 0) - - gfx.FontFace("divlit_custom.ttf") - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER | gfx.TEXT_ALIGN_BASELINE) - gfx.DrawLabel(challengeCache[challenge.id]["title"], titleCenterX, titleBaselineY, titleMaxWidth) - - ---------------------------------------------------------- - -- draw jacket section - ---------------------------------------------------------- - local size = h * 0.68 - local offsetX = x + w / 32 - local offsetY = y + h - size - h * 0.05 - - if not selected then - size = h * 0.66 - offsetX = x + w * 0.058 - offsetY = y + h - size - h * 0.066 - end - - gfx.BeginPath() - gfx.ImageRect(offsetX, offsetY, size, size, challengeCache[challenge.id]["jacket"], 1, 0) - - ---------------------------------------------------------- - -- draw stats section - ---------------------------------------------------------- - local textSizeCorrection = h / gfx.ImageSize(scoreNumbers[1]) - - local percentOffsetX = x + 0.5 * w - local percentOffsetY = y + 0.87 * h - local percentSize = 0.17 * textSizeCorrection - local percentImageWidth, percentImageHeight = gfx.ImageSize(percentImage) - local percentImageOffsetX = percentOffsetX + 0.08 * w - - local percentRequired = challengeCache[challenge.id]["percent_required"] - local percentBarOffsetX = x + 0.281 * w - local percentBarOffsetY = y + 0.856 * h - local percentBarWidth = 0.273 * w - local percentBarHeight = 0.02 * h - local percentBarLeftColor = {255, 0, 0} - local percentBarRightColor = {255, 128, 0} - - local scoreUpperOffsetX = 0 - local scoreUpperOffsetY = 0 - local scoreOffsetX = x + w * 0.74 - local scoreOffsetY = y + h * 0.9 - local scoreUpperSize = 0.2 * textSizeCorrection - local scoreSize = 0.125 * textSizeCorrection - - local badgeOffsetX = x + 0.886 * w - local badgeOffsetY = y + 0.8 * h - local badgeSize = 0.155 * h - - local gradeOffsetX = x + 0.933 * w - local gradeOffsetY = y + 0.798 * h - local gradeSize = 0.163 * h - - local percent = challengeCache[challenge.id]["percent"] - local scoreUpper = math.floor(challengeCache[challenge.id]["total_score"] / 10000) - local score = challengeCache[challenge.id]["total_score"] - local badge = challenge.topBadge and badges[challenge.topBadge] or nil - local grade = challenge.grade and grades[challenge.grade] or nil - - if selected then - percentOffsetX = x + 11 / 24 * w - percentOffsetY = y + 49 / 64 * h - percentSize = 0.12 * textSizeCorrection - percentImageOffsetX = percentOffsetX + 0.074 * w - - scoreUpperOffsetX = x + w * 0.63 - scoreUpperOffsetY = y + h * 0.82 - scoreOffsetX = x + w * 0.762 - scoreOffsetY = y + h * 0.835 - scoreUpperSize = 0.12 * textSizeCorrection - scoreSize = 0.09 * textSizeCorrection - - badgeOffsetX = x + 0.86 * w - badgeOffsetY = y + 0.715 * h - badgeSize = 0.165 * h - - gradeOffsetX = x + 0.927 * w - gradeOffsetY = y + 0.708 * h - gradeSize = 0.175 * h - end - - percentImageWidth = percentImageWidth * percentSize * 0.75 - percentImageHeight = percentImageHeight * percentSize * 0.75 - percentImageOffsetY = percentOffsetY - percentImageHeight * 0.25 - - -- Draw percentage - Numbers.draw_number(percentOffsetX, percentOffsetY, 1, percent, 3, scoreNumbers, true, percentSize, 1, false) - gfx.BeginPath() - gfx.ImageRect(percentImageOffsetX, percentImageOffsetY, percentImageWidth, percentImageHeight, percentImage, 1, 0) - - if selected then - -- Draw percentBar - gfx.BeginPath() - local paint = gfx.LinearGradient( - percentBarOffsetX, percentBarOffsetY, - percentBarOffsetX + percentBarWidth, percentBarOffsetY - ) - gfx.FillPaint(paint) - gfx.GradientColors( - percentBarLeftColor[1], percentBarLeftColor[2], percentBarLeftColor[3], 255, - percentBarRightColor[1], percentBarRightColor[2], percentBarRightColor[3], 255 - ) - gfx.Rect(percentBarOffsetX, percentBarOffsetY, percentBarWidth * math.min(1, percent / percentRequired), percentBarHeight) - gfx.Fill() - - -- Draw percentBar highlight - gfx.BeginPath() - gfx.FillColor(255, 255, 255, 64) - gfx.Rect(percentBarOffsetX, percentBarOffsetY, percentBarWidth * math.min(1, percent / 100), percentBarHeight * 0.5) - gfx.Fill() - - -- Draw score - Numbers.draw_number( - scoreUpperOffsetX, scoreUpperOffsetY, 1, scoreUpper, 4, scoreNumbers, true, scoreUpperSize, 1 - ) - Numbers.draw_number(scoreOffsetX, scoreOffsetY, 1, score, 4, scoreNumbers, true, scoreSize, 1) - else - Numbers.draw_number(scoreOffsetX, scoreOffsetY, 1, score, 8, scoreNumbers, true, scoreSize, 1) - end - - if badge then - gfx.BeginPath() - gfx.ImageRect(badgeOffsetX, badgeOffsetY, 93/81 * badgeSize, badgeSize, badge, 1, 0) - - gfx.BeginPath() - gfx.ImageRect(gradeOffsetX, gradeOffsetY, gradeSize, gradeSize, grade, 1, 0) - end - - ---------------------------------------------------------- - -- draw charts section - ---------------------------------------------------------- - local diffIconScale = 0.0048 * h - - local paddingY = 0.18 * h - local offsetX = x + 0.242 * w - local offsetY = y + 0.233 * h - - local titleMargin = 6 - local titleMaxWidth = 6 / 9 * w - local titleCenterX = x + w - titleMargin - titleMaxWidth / 2 -- align right - - if selected then - diffIconScale = 0.0032 * h - - paddingY = 0.123 * h - offsetX = x + 0.259 * w - offsetY = y + 0.248 * h - - titleFontSize = math.floor(0.075 * h) -- must be an integer - titleMaxWidth = 3 / 5 * w - titleCenterX = x + w - titleMargin - titleMaxWidth / 2 -- align right - end - - for i, chart in ipairs(challengeCache[challenge.id]["charts"]) do - local ypos = offsetY + paddingY * (i - 1) - - DiffRectangle.render(timer, offsetX, ypos, diffIconScale, chart.difficulty, chart.level) - - local _, titleHeight = gfx.LabelSize(chart.title) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER | gfx.TEXT_ALIGN_MIDDLE) - gfx.DrawLabel(chart.title, titleCenterX, ypos + titleHeight / 2, titleMaxWidth) - end - - gfx.ForceRender() - -end - -draw_selected = function(challenge, x, y, w, h) - if not challenge then - return - end - - check_or_create_cache(challenge) - - draw_challenge(challenge, x, y, w, h, true) - -end - -draw_chalwheel = function(x, y, w, h) - local challengeAspect = 4.367 - local selectedChallengeAspect = 3.305 - - local width = math.floor(w * 0.839) - local height = math.floor(width / challengeAspect) - - local selectedWidth = math.floor(w * 0.944) - local selectedHeight = math.floor(selectedWidth / selectedChallengeAspect) - - local offsetX = w / 2 - width / 2 -- center - local centerY = h / 2 - height / 2 - local selectedOffsetX = w / 2 - selectedWidth / 2 - local selectedCenterY = h / 2 - selectedHeight / 2 - local margin = h / 128 - local centerMargin = h / 100 - - local imin = math.ceil(selectedIndex - wheelSize / 2) - local imax = math.floor(selectedIndex + wheelSize / 2) - for i = math.max(imin, 1), math.min(imax, #chalwheel.challenges) do - local current = selectedIndex - i - if not (current == 0) then - local challenge = chalwheel.challenges[i] - local xpos = x + offsetX - -- local offsetY = current * (height - (wheelSize / 2 * (current * aspectFloat))) - local offsetY = math.abs(current) * (height + margin) + (selectedHeight - height) / 2 - local ypos = y + centerY - if current < 0 then - ypos = ypos + centerMargin + offsetY - else -- if current > 0 then - ypos = ypos - centerMargin - offsetY - end - draw_challenge(challenge, xpos, ypos, width, height) - end - end - - -- render selected song information - local xpos = x + selectedOffsetX - local ypos = y + selectedCenterY - draw_selected(chalwheel.challenges[selectedIndex], xpos, ypos, selectedWidth, selectedHeight) -end - ---[[ will be reimplemented sometime later -draw_legend_pane = function(x, y, w, h, obj) - local xpos = x + 5 - local ypos = y - local imageSize = h - - gfx.BeginPath() - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT) - gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0) - xpos = xpos + imageSize + 5 - gfx.FontSize(16); - if h < (w - (10 + imageSize)) / 2 then - gfx.DrawLabel(obj.labelSingleLine, xpos, y + (h / 2), w - (10 + imageSize)) - else - gfx.DrawLabel(obj.labelMultiLine, xpos, y + (h / 2), w - (10 + imageSize)) - end - gfx.ForceRender() -end - -draw_legend = function(x, y, w, h) - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT); - gfx.BeginPath() - gfx.FillColor(0, 0, 0, 170) - gfx.Rect(x, y, w, h) - gfx.Fill() - local xpos = 10; - local legendWidth = math.floor((w - 20) / #legendTable) - for i, v in ipairs(legendTable) do - local xOffset = draw_legend_pane(xpos + (legendWidth * (i - 1)), y + 5, legendWidth, h - 10, legendTable[i]) - end -end ---]] - ---[[ will be reimplemented sometime later -draw_search = function(x, y, w, h) - soffset = soffset + (searchIndex) - (chalwheel.searchInputActive and 0 or 1) - if searchIndex ~= (chalwheel.searchInputActive and 0 or 1) then - game.PlaySample("woosh") - end - searchIndex = chalwheel.searchInputActive and 0 or 1 - - gfx.BeginPath() - local bgfade = 1 - (searchIndex + soffset) - -- if not chalwheel.searchInputActive then bgfade = soffset end - gfx.FillColor(0, 0, 0, math.floor(200 * bgfade)) - gfx.Rect(0, 0, resX, resY) - gfx.Fill() - gfx.ForceRender() - local xpos = x + (searchIndex + soffset) * w - gfx.UpdateLabel(searchText, string.format("Search: %s", chalwheel.searchText), 30) - - gfx.BeginPath() - gfx.RoundedRect(xpos, y, w, h, h / 2) - gfx.FillColor(30, 30, 30) - gfx.StrokeColor(0, 128, 255) - gfx.StrokeWidth(1) - gfx.Fill() - gfx.Stroke() - - gfx.BeginPath(); - gfx.LoadSkinFont("dfmarugoth.ttf"); - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); - gfx.DrawLabel(searchText, xpos + 10, y + (h / 2), w - 20) - -end ---]] - -render = function(deltaTime) - gfx.FontFace("dfmarugoth.ttf"); - - -- detect resolution change - local resx, resy = game.GetResolution(); - if resx ~= resX or resy ~= resY then - resolutionChange(resx, resy) - end - - -- draw background image - 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() - - if chalwheel.challenges and chalwheel.challenges[1] then - local wheelCenterX = (resX - fullX) / 2 - -- draw surface background - gfx.BeginPath() - gfx.ImageRect(wheelCenterX, 0, fullX, fullY, challengeBGImage, 1, 0) - - -- draw chalwheel - gfx.BeginPath(); - draw_chalwheel(wheelCenterX, 0, fullX, fullY) - - -- Draw Legend Information - --[[ will be reimplemented sometime later - draw_legend(0, fullX * 14 / 15, fullX, fullY / 15) - --]] - - -- draw text search - --[[ will be reimplemented sometime later - draw_search(fullX * 2 / 5, 5, fullX * 3 / 5, fullY / 25) - - soffset = soffset * 0.8 - if chalwheel.searchStatus then - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.FontSize(20); - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(chalwheel.searchStatus, 3, 3) - end - --]] - - gfx.Translate(wheelCenterX, 0) - gfx.Scale(fullX / desw, fullY / desh); - - Header.draw(deltaTime) - Footer.draw(deltaTime) - - gfx.ResetTransform() - end -end - -get_page_size = function() - return math.floor(wheelSize / 2) -end - -set_index = function(newIndex, scrollamt) - if newIndex ~= selectedIndex then - game.PlaySample("menu_click") - end - selectedIndex = newIndex -end - -challenges_changed = function(withAll) - -end +local Common = require("common.common") +local Numbers = require("common.numbers") +local DiffRectangle = require("components.diff_rectangle") +local Header = require("components.headers.challengeSelectHeader") +local Footer = require("components.footer") + +local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY) +local challengeBGImage = gfx.CreateSkinImage("challenge_select/bg.png", 0) +local challengeCardBGImage = gfx.CreateSkinImage("challenge_select/small_box.png", 0) +local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) + +--[[ will be reimplemented sometime later +local soffset = 0 +local searchText = gfx.CreateLabel("", 5, 0) +local searchIndex = 1 + +local showGuide = game.GetSkinSetting("show_guide") +local legendTable = { + { + ["labelSingleLine"] = gfx.CreateLabel("SCROLL INFO", 16, 0), + ["labelMultiLine"] = gfx.CreateLabel("SCROLL\nINFO", 16, 0), + ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0) + }, + { + ["labelSingleLine"] = gfx.CreateLabel("CHALL SELECT", 16, 0), + ["labelMultiLine"] = gfx.CreateLabel("CHALLENGE\nSELECT", 16, 0), + ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0) + }, + { + ["labelSingleLine"] = gfx.CreateLabel("FILTER CHALLS", 16, 0), + ["labelMultiLine"] = gfx.CreateLabel("FILTER\nCHALLENGES", 16, 0), + ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0) + }, + { + ["labelSingleLine"] = gfx.CreateLabel("SORT CHALLS", 16, 0), + ["labelMultiLine"] = gfx.CreateLabel("SORT\nCHALLENGES", 16, 0), + ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0) + }, + { + ["labelSingleLine"] = gfx.CreateLabel("GAME SETTINGS", 16, 0), + ["labelMultiLine"] = gfx.CreateLabel("GAME\nSETTINGS", 16, 0), + ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0) + }, + { + ["labelSingleLine"] = gfx.CreateLabel("PLAY", 16, 0), + ["labelMultiLine"] = gfx.CreateLabel("PLAY", 16, 0), + ["image"] = gfx.CreateSkinImage("legend/start.png", 0) + } +} +--]] + +local grades = { + ["D"] = gfx.CreateSkinImage("common/grades/D.png", 0), + ["C"] = gfx.CreateSkinImage("common/grades/C.png", 0), + ["B"] = gfx.CreateSkinImage("common/grades/B.png", 0), + ["A"] = gfx.CreateSkinImage("common/grades/A.png", 0), + ["A+"] = gfx.CreateSkinImage("common/grades/A+.png", 0), + ["AA"] = gfx.CreateSkinImage("common/grades/AA.png", 0), + ["AA+"] = gfx.CreateSkinImage("common/grades/AA+.png", 0), + ["AAA"] = gfx.CreateSkinImage("common/grades/AAA.png", 0), + ["AAA+"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0), + ["S"] = gfx.CreateSkinImage("common/grades/S.png", 0) +} + +local badges = { + gfx.CreateSkinImage("song_select/medal/played.png", gfx.IMAGE_GENERATE_MIPMAPS), + gfx.CreateSkinImage("song_select/medal/clear.png", gfx.IMAGE_GENERATE_MIPMAPS), + gfx.CreateSkinImage("song_select/medal/hard.png", gfx.IMAGE_GENERATE_MIPMAPS), + gfx.CreateSkinImage("song_select/medal/uc.png", gfx.IMAGE_GENERATE_MIPMAPS), + gfx.CreateSkinImage("song_select/medal/puc.png", gfx.IMAGE_GENERATE_MIPMAPS) +} + +local passStates = { + gfx.CreateSkinImage("challenge_select/pass_states/not_played.png", 0), + gfx.CreateSkinImage("challenge_select/pass_states/failed.png", 0), + gfx.CreateSkinImage("challenge_select/pass_states/cleared.png", 0) +} + +local scoreNumbers = Numbers.load_number_image("score_num") +local percentImage = gfx.CreateSkinImage("score_num/percent.png", 0) + +gfx.LoadSkinFont("dfmarugoth.ttf"); + +game.LoadSkinSample("menu_click") +game.LoadSkinSample("woosh") + +-- Wheel variables +local wheelSize = 5 + +local selectedIndex = 1 +local challengeCache = {} +local timer = 0 + +-- Window variables +local desw = 1080 +local desh = 1920 +local resX, resY + +-- Aspect Ratios +local landscapeWidescreenRatio = 16 / 9 +local landscapeStandardRatio = 4 / 3 +local portraitWidescreenRatio = 9 / 16 + +-- Portrait sizes +local fullX, fullY + +local resolutionChange = function(x, y) + resX = x + resY = y + fullX = portraitWidescreenRatio * y + fullY = y +end + +local update_cache_labels = function(challenge, titleFontSize) + if challengeCache[challenge.id] then + local _, fontsize = gfx.LabelSize(challengeCache[challenge.id]["title"]) + if fontsize ~= titleFontSize then + gfx.UpdateLabel(challengeCache[challenge.id]["title"], challengeCache[challenge.id]["title_raw"], titleFontSize) + for _, chart in ipairs(challengeCache[challenge.id]["charts"]) do + gfx.UpdateLabel(chart["title"], chart["title_raw"], titleFontSize) + end + end + end +end + +local check_or_create_cache = function(challenge) + local defaultLabelSize = 16 + + if not challengeCache[challenge.id] then + challengeCache[challenge.id] = {} + end + + if not challengeCache[challenge.id]["title"] then + challengeCache[challenge.id]["title"] = gfx.CreateLabel(challenge.title, defaultLabelSize, 0) + challengeCache[challenge.id]["title_raw"] = challenge.title + end + + if not challengeCache[challenge.id]["charts"] then + if challenge.missing_chart then + local missing_text = "*COULD NOT FIND ALL CHARTS!*" + challengeCache[challenge.id]["charts"] = { + { + ["title"] = gfx.CreateLabel(missing_text, defaultLabelSize, 0), + ["title_raw"] = missing_text, + ["level"] = 0, + ["difficulty"] = 0 + } + } + else -- if not challenge.missing_chart then + local charts = {} + for _, chart in ipairs(challenge.charts) do + table.insert(charts, { + ["title"] = gfx.CreateLabel(chart.title, defaultLabelSize, 0), + ["title_raw"] = chart.title, + ["level"] = chart.level, + ["difficulty"] = chart.difficulty + }) + end + challengeCache[challenge.id]["charts"] = charts + end + end + + if not challengeCache[challenge.id]["percent_required"] then + local percentRequired = 100 + local reqTextWords = Common.splitString(challenge.requirement_text, ' '); + for _, word in ipairs(reqTextWords) do + if string.find(word, '%%') ~= nil then -- %% = %, because % is an escape char + local percentNumber = tonumber(string.gsub(word, '%%', ''), 10) + percentRequired = percentNumber; + break + end + end + challengeCache[challenge.id]["percent_required"] = percentRequired + end + + if (not challengeCache[challenge.id]["percent"] or not challengeCache[challenge.id]["total_score"] + or challengeCache[challenge.id]["total_score"] ~= challenge.bestScore) then + challengeCache[challenge.id]["percent"] = math.max(0, (challenge.bestScore - 8000000) // 10000) + challengeCache[challenge.id]["total_score"] = challenge.bestScore + end + + local passState = math.min(challenge.topBadge, 2) + 1 -- challenge.topBadge -> [1, 3] + if (not challengeCache[challenge.id]["pass_state"] or not challengeCache[challenge.id]["pass_state_idx"] + or challengeCache[challenge.id]["pass_state_idx"] ~= passState) then + challengeCache[challenge.id]["pass_state_idx"] = passState + challengeCache[challenge.id]["pass_state"] = passStates[passState] + end + + local lastChart = challenge.charts[#challenge.charts] + if not challengeCache[challenge.id]["jacket"] then + if challenge.missing_chart then + challengeCache[challenge.id]["jacket"] = jacketFallback + else + challengeCache[challenge.id]["jacket"] = gfx.LoadImageJob(lastChart.jacketPath, jacketFallback, 200, 200) + end + elseif not challenge.missing_chart and challengeCache[challenge.id]["jacket"] == jacketFallback then + challengeCache[challenge.id]["jacket"] = gfx.LoadImageJob(lastChart.jacketPath, jacketFallback, 200, 200) + end +end + +draw_challenge = function(challenge, x, y, w, h, selected) + if not challenge then + return + end + + check_or_create_cache(challenge) + + ---------------------------------------------------------- + -- draw card bg section + ---------------------------------------------------------- + if not selected then + gfx.BeginPath() + gfx.ImageRect(x, y, w, h, challengeCardBGImage, 1, 0) + end + + ---------------------------------------------------------- + -- draw info section + ---------------------------------------------------------- + local stateLabel = challengeCache[challenge.id]["pass_state"] + local stateLabelWidth, stateLabelHeight = gfx.ImageSize(stateLabel) + local stateLabelAspect = stateLabelWidth / stateLabelHeight + + local stateWidth = w / 5 + local stateHeight = stateWidth / stateLabelAspect + local stateOffsetX = x + w / 32 + local stateOffsetY = y + h / 32 + + local titleMargin = 6 + local titleFontSize = math.floor(0.11 * h) -- must be an integer + local titleMaxWidth = 6 / 9 * w + local titleCenterX = x + w - titleMargin - titleMaxWidth / 2 -- align right + local titleBaselineY = y + 0.025 * h -- align baseline + + if selected then + stateWidth = stateWidth / 0.9 + stateHeight = stateHeight / 0.9 + stateOffsetY = y + h / 16 + + titleFontSize = math.floor(0.075 * h) -- must be an integer + titleMaxWidth = 3 / 5 * w + titleCenterX = x + w - titleMargin - titleMaxWidth / 2 -- align right + titleBaselineY = y + 0.09 * h -- align baseline + end + + update_cache_labels(challenge, titleFontSize) + + gfx.BeginPath() + gfx.ImageRect(stateOffsetX, stateOffsetY, stateWidth, stateHeight, stateLabel, 1, 0) + + gfx.FontFace("divlit_custom.ttf") + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER | gfx.TEXT_ALIGN_BASELINE) + gfx.DrawLabel(challengeCache[challenge.id]["title"], titleCenterX, titleBaselineY, titleMaxWidth) + + ---------------------------------------------------------- + -- draw jacket section + ---------------------------------------------------------- + local size = h * 0.68 + local offsetX = x + w / 32 + local offsetY = y + h - size - h * 0.05 + + if not selected then + size = h * 0.66 + offsetX = x + w * 0.058 + offsetY = y + h - size - h * 0.066 + end + + gfx.BeginPath() + gfx.ImageRect(offsetX, offsetY, size, size, challengeCache[challenge.id]["jacket"], 1, 0) + + ---------------------------------------------------------- + -- draw stats section + ---------------------------------------------------------- + local textSizeCorrection = h / gfx.ImageSize(scoreNumbers[1]) + + local percentOffsetX = x + 0.5 * w + local percentOffsetY = y + 0.87 * h + local percentSize = 0.17 * textSizeCorrection + local percentImageWidth, percentImageHeight = gfx.ImageSize(percentImage) + local percentImageOffsetX = percentOffsetX + 0.08 * w + + local percentRequired = challengeCache[challenge.id]["percent_required"] + local percentBarOffsetX = x + 0.281 * w + local percentBarOffsetY = y + 0.856 * h + local percentBarWidth = 0.273 * w + local percentBarHeight = 0.02 * h + local percentBarLeftColor = {255, 0, 0} + local percentBarRightColor = {255, 128, 0} + + local scoreUpperOffsetX = 0 + local scoreUpperOffsetY = 0 + local scoreOffsetX = x + w * 0.74 + local scoreOffsetY = y + h * 0.9 + local scoreUpperSize = 0.2 * textSizeCorrection + local scoreSize = 0.125 * textSizeCorrection + + local badgeOffsetX = x + 0.886 * w + local badgeOffsetY = y + 0.8 * h + local badgeSize = 0.155 * h + + local gradeOffsetX = x + 0.933 * w + local gradeOffsetY = y + 0.798 * h + local gradeSize = 0.163 * h + + local percent = challengeCache[challenge.id]["percent"] + local scoreUpper = math.floor(challengeCache[challenge.id]["total_score"] / 10000) + local score = challengeCache[challenge.id]["total_score"] + local badge = challenge.topBadge and badges[challenge.topBadge] or nil + local grade = challenge.grade and grades[challenge.grade] or nil + + if selected then + percentOffsetX = x + 11 / 24 * w + percentOffsetY = y + 49 / 64 * h + percentSize = 0.12 * textSizeCorrection + percentImageOffsetX = percentOffsetX + 0.074 * w + + scoreUpperOffsetX = x + w * 0.63 + scoreUpperOffsetY = y + h * 0.82 + scoreOffsetX = x + w * 0.762 + scoreOffsetY = y + h * 0.835 + scoreUpperSize = 0.12 * textSizeCorrection + scoreSize = 0.09 * textSizeCorrection + + badgeOffsetX = x + 0.86 * w + badgeOffsetY = y + 0.715 * h + badgeSize = 0.165 * h + + gradeOffsetX = x + 0.927 * w + gradeOffsetY = y + 0.708 * h + gradeSize = 0.175 * h + end + + percentImageWidth = percentImageWidth * percentSize * 0.75 + percentImageHeight = percentImageHeight * percentSize * 0.75 + percentImageOffsetY = percentOffsetY - percentImageHeight * 0.25 + + -- Draw percentage + Numbers.draw_number(percentOffsetX, percentOffsetY, 1, percent, 3, scoreNumbers, true, percentSize, 1, false) + gfx.BeginPath() + gfx.ImageRect(percentImageOffsetX, percentImageOffsetY, percentImageWidth, percentImageHeight, percentImage, 1, 0) + + if selected then + -- Draw percentBar + gfx.BeginPath() + local paint = gfx.LinearGradient( + percentBarOffsetX, percentBarOffsetY, + percentBarOffsetX + percentBarWidth, percentBarOffsetY + ) + gfx.FillPaint(paint) + gfx.GradientColors( + percentBarLeftColor[1], percentBarLeftColor[2], percentBarLeftColor[3], 255, + percentBarRightColor[1], percentBarRightColor[2], percentBarRightColor[3], 255 + ) + gfx.Rect(percentBarOffsetX, percentBarOffsetY, percentBarWidth * math.min(1, percent / percentRequired), percentBarHeight) + gfx.Fill() + + -- Draw percentBar highlight + gfx.BeginPath() + gfx.FillColor(255, 255, 255, 64) + gfx.Rect(percentBarOffsetX, percentBarOffsetY, percentBarWidth * math.min(1, percent / 100), percentBarHeight * 0.5) + gfx.Fill() + + -- Draw score + Numbers.draw_number( + scoreUpperOffsetX, scoreUpperOffsetY, 1, scoreUpper, 4, scoreNumbers, true, scoreUpperSize, 1 + ) + Numbers.draw_number(scoreOffsetX, scoreOffsetY, 1, score, 4, scoreNumbers, true, scoreSize, 1) + else + Numbers.draw_number(scoreOffsetX, scoreOffsetY, 1, score, 8, scoreNumbers, true, scoreSize, 1) + end + + if badge then + gfx.BeginPath() + gfx.ImageRect(badgeOffsetX, badgeOffsetY, 93/81 * badgeSize, badgeSize, badge, 1, 0) + + gfx.BeginPath() + gfx.ImageRect(gradeOffsetX, gradeOffsetY, gradeSize, gradeSize, grade, 1, 0) + end + + ---------------------------------------------------------- + -- draw charts section + ---------------------------------------------------------- + local diffIconScale = 0.0048 * h + + local paddingY = 0.18 * h + local offsetX = x + 0.242 * w + local offsetY = y + 0.233 * h + + local titleMargin = 6 + local titleMaxWidth = 6 / 9 * w + local titleCenterX = x + w - titleMargin - titleMaxWidth / 2 -- align right + + if selected then + diffIconScale = 0.0032 * h + + paddingY = 0.123 * h + offsetX = x + 0.259 * w + offsetY = y + 0.248 * h + + titleFontSize = math.floor(0.075 * h) -- must be an integer + titleMaxWidth = 3 / 5 * w + titleCenterX = x + w - titleMargin - titleMaxWidth / 2 -- align right + end + + for i, chart in ipairs(challengeCache[challenge.id]["charts"]) do + local ypos = offsetY + paddingY * (i - 1) + + DiffRectangle.render(timer, offsetX, ypos, diffIconScale, chart.difficulty, chart.level) + + local _, titleHeight = gfx.LabelSize(chart.title) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER | gfx.TEXT_ALIGN_MIDDLE) + gfx.DrawLabel(chart.title, titleCenterX, ypos + titleHeight / 2, titleMaxWidth) + end + + gfx.ForceRender() + +end + +draw_selected = function(challenge, x, y, w, h) + if not challenge then + return + end + + check_or_create_cache(challenge) + + draw_challenge(challenge, x, y, w, h, true) + +end + +draw_chalwheel = function(x, y, w, h) + local challengeAspect = 4.367 + local selectedChallengeAspect = 3.305 + + local width = math.floor(w * 0.839) + local height = math.floor(width / challengeAspect) + + local selectedWidth = math.floor(w * 0.944) + local selectedHeight = math.floor(selectedWidth / selectedChallengeAspect) + + local offsetX = w / 2 - width / 2 -- center + local centerY = h / 2 - height / 2 + local selectedOffsetX = w / 2 - selectedWidth / 2 + local selectedCenterY = h / 2 - selectedHeight / 2 + local margin = h / 128 + local centerMargin = h / 100 + + local imin = math.ceil(selectedIndex - wheelSize / 2) + local imax = math.floor(selectedIndex + wheelSize / 2) + for i = math.max(imin, 1), math.min(imax, #chalwheel.challenges) do + local current = selectedIndex - i + if not (current == 0) then + local challenge = chalwheel.challenges[i] + local xpos = x + offsetX + -- local offsetY = current * (height - (wheelSize / 2 * (current * aspectFloat))) + local offsetY = math.abs(current) * (height + margin) + (selectedHeight - height) / 2 + local ypos = y + centerY + if current < 0 then + ypos = ypos + centerMargin + offsetY + else -- if current > 0 then + ypos = ypos - centerMargin - offsetY + end + draw_challenge(challenge, xpos, ypos, width, height) + end + end + + -- render selected song information + local xpos = x + selectedOffsetX + local ypos = y + selectedCenterY + draw_selected(chalwheel.challenges[selectedIndex], xpos, ypos, selectedWidth, selectedHeight) +end + +--[[ will be reimplemented sometime later +draw_legend_pane = function(x, y, w, h, obj) + local xpos = x + 5 + local ypos = y + local imageSize = h + + gfx.BeginPath() + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT) + gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0) + xpos = xpos + imageSize + 5 + gfx.FontSize(16); + if h < (w - (10 + imageSize)) / 2 then + gfx.DrawLabel(obj.labelSingleLine, xpos, y + (h / 2), w - (10 + imageSize)) + else + gfx.DrawLabel(obj.labelMultiLine, xpos, y + (h / 2), w - (10 + imageSize)) + end + gfx.ForceRender() +end + +draw_legend = function(x, y, w, h) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT); + gfx.BeginPath() + gfx.FillColor(0, 0, 0, 170) + gfx.Rect(x, y, w, h) + gfx.Fill() + local xpos = 10; + local legendWidth = math.floor((w - 20) / #legendTable) + for i, v in ipairs(legendTable) do + local xOffset = draw_legend_pane(xpos + (legendWidth * (i - 1)), y + 5, legendWidth, h - 10, legendTable[i]) + end +end +--]] + +--[[ will be reimplemented sometime later +draw_search = function(x, y, w, h) + soffset = soffset + (searchIndex) - (chalwheel.searchInputActive and 0 or 1) + if searchIndex ~= (chalwheel.searchInputActive and 0 or 1) then + game.PlaySample("woosh") + end + searchIndex = chalwheel.searchInputActive and 0 or 1 + + gfx.BeginPath() + local bgfade = 1 - (searchIndex + soffset) + -- if not chalwheel.searchInputActive then bgfade = soffset end + gfx.FillColor(0, 0, 0, math.floor(200 * bgfade)) + gfx.Rect(0, 0, resX, resY) + gfx.Fill() + gfx.ForceRender() + local xpos = x + (searchIndex + soffset) * w + gfx.UpdateLabel(searchText, string.format("Search: %s", chalwheel.searchText), 30) + + gfx.BeginPath() + gfx.RoundedRect(xpos, y, w, h, h / 2) + gfx.FillColor(30, 30, 30) + gfx.StrokeColor(0, 128, 255) + gfx.StrokeWidth(1) + gfx.Fill() + gfx.Stroke() + + gfx.BeginPath(); + gfx.LoadSkinFont("dfmarugoth.ttf"); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); + gfx.DrawLabel(searchText, xpos + 10, y + (h / 2), w - 20) + +end +--]] + +render = function(deltaTime) + gfx.FontFace("dfmarugoth.ttf"); + + -- detect resolution change + local resx, resy = game.GetResolution(); + if resx ~= resX or resy ~= resY then + resolutionChange(resx, resy) + end + + -- draw background image + 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() + + if chalwheel.challenges and chalwheel.challenges[1] then + local wheelCenterX = (resX - fullX) / 2 + -- draw surface background + gfx.BeginPath() + gfx.ImageRect(wheelCenterX, 0, fullX, fullY, challengeBGImage, 1, 0) + + -- draw chalwheel + gfx.BeginPath(); + draw_chalwheel(wheelCenterX, 0, fullX, fullY) + + -- Draw Legend Information + --[[ will be reimplemented sometime later + draw_legend(0, fullX * 14 / 15, fullX, fullY / 15) + --]] + + -- draw text search + --[[ will be reimplemented sometime later + draw_search(fullX * 2 / 5, 5, fullX * 3 / 5, fullY / 25) + + soffset = soffset * 0.8 + if chalwheel.searchStatus then + gfx.BeginPath() + gfx.FillColor(255, 255, 255) + gfx.FontSize(20); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.Text(chalwheel.searchStatus, 3, 3) + end + --]] + + gfx.Translate(wheelCenterX, 0) + gfx.Scale(fullX / desw, fullY / desh); + + Header.draw(deltaTime) + Footer.draw(deltaTime) + + gfx.ResetTransform() + end +end + +get_page_size = function() + return math.floor(wheelSize / 2) +end + +set_index = function(newIndex, scrollamt) + if newIndex ~= selectedIndex then + game.PlaySample("menu_click") + end + selectedIndex = newIndex +end + +challenges_changed = function(withAll) + +end diff --git a/scripts/songselect/songwheel new.lua b/scripts/songselect/songwheel new.lua index 15b93b8..24d06c2 100644 --- a/scripts/songselect/songwheel new.lua +++ b/scripts/songselect/songwheel new.lua @@ -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 diff --git a/scripts/songselect/songwheel3.lua b/scripts/songselect/songwheel3.lua index f6beb3f..915b262 100644 --- a/scripts/songselect/songwheel3.lua +++ b/scripts/songselect/songwheel3.lua @@ -1,897 +1,897 @@ ---Horizontal alignment -TEXT_ALIGN_LEFT = 1 -TEXT_ALIGN_CENTER = 2 -TEXT_ALIGN_RIGHT = 4 ---Vertical alignment -TEXT_ALIGN_TOP = 8 -TEXT_ALIGN_MIDDLE = 16 -TEXT_ALIGN_BOTTOM = 32 -TEXT_ALIGN_BASELINE = 64 - -local jacket = nil; -local selectedIndex = 1 -local selectedDiff = 1 -local songCache = {} -local ioffset = 0 -local doffset = 0 -local soffset = 0 -local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}} -local timer = 0 -local effector = 0 -local searchText = gfx.CreateLabel("",5,0) -local searchIndex = 1 -local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) -local showGuide = game.GetSkinSetting("show_guide") -local legendTable = { - {["labelSingleLine"] = gfx.CreateLabel("DIFFICULTY SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("DIFFICULTY\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0)}, - {["labelSingleLine"] = gfx.CreateLabel("MUSIC SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0)}, - {["labelSingleLine"] = gfx.CreateLabel("FILTER MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("FILTER\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0)}, - {["labelSingleLine"] = gfx.CreateLabel("SORT MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("SORT\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0)}, - {["labelSingleLine"] = gfx.CreateLabel("MUSIC MODS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nMODS",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0)}, - {["labelSingleLine"] = gfx.CreateLabel("PLAY",16, 0), ["labelMultiLine"] = gfx.CreateLabel("PLAY",16, 0), ["image"] = gfx.CreateSkinImage("legend/start.png", 0)} -} -local grades = { - {["max"] = 6999999, ["image"] = gfx.CreateSkinImage("common/grades/D.png", 0)}, - {["max"] = 7999999, ["image"] = gfx.CreateSkinImage("common/grades/C.png", 0)}, - {["max"] = 8699999, ["image"] = gfx.CreateSkinImage("common/grades/B.png", 0)}, - {["max"] = 8999999, ["image"] = gfx.CreateSkinImage("common/grades/A.png", 0)}, - {["max"] = 9299999, ["image"] = gfx.CreateSkinImage("common/grades/A+.png", 0)}, - {["max"] = 9499999, ["image"] = gfx.CreateSkinImage("common/grades/AA.png", 0)}, - {["max"] = 9699999, ["image"] = gfx.CreateSkinImage("common/grades/AA+.png", 0)}, - {["max"] = 9799999, ["image"] = gfx.CreateSkinImage("common/grades/AAA.png", 0)}, - {["max"] = 9899999, ["image"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0)}, - {["max"] = 99999999, ["image"] = gfx.CreateSkinImage("common/grades/S.png", 0)} -} - -local badges = { - gfx.CreateSkinImage("badges/played.png", 0), - gfx.CreateSkinImage("badges/clear.png", 0), - gfx.CreateSkinImage("badges/hard-clear.png", 0), - gfx.CreateSkinImage("badges/full-combo.png", 0), - gfx.CreateSkinImage("badges/perfect.png", 0) -} - -local recordCache = {} - -gfx.LoadSkinFont("dfmarugoth.ttf"); - -game.LoadSkinSample("menu_click") -game.LoadSkinSample("click-02") -game.LoadSkinSample("woosh") - -local wheelSize = 12 - -get_page_size = function() - return math.floor(wheelSize/2) -end - --- Responsive UI variables --- Aspect Ratios -local aspectFloat = 1.850 -local aspectRatio = "widescreen" -local landscapeWidescreenRatio = 1.850 -local landscapeStandardRatio = 1.500 -local portraitWidescreenRatio = 0.5 - --- Responsive sizes -local fifthX = 0 -local fourthX= 0 -local thirdX = 0 -local halfX = 0 -local fullX = 0 - -local fifthY = 0 -local fourthY= 0 -local thirdY = 0 -local halfY = 0 -local fullY = 0 - - -adjustScreen = function(x,y) - local a = x/y; - if x >= y and a <= landscapeStandardRatio then - aspectRatio = "landscapeStandard" - aspectFloat = 1.1 - elseif x >= y and landscapeStandardRatio <= a and a <= landscapeWidescreenRatio then - aspectRatio = "landscapeWidescreen" - aspectFloat = 1.2 - elseif x <= y and portraitWidescreenRatio <= a and a < landscapeStandardRatio then - aspectRatio = "PortraitWidescreen" - aspectFloat = 0.5 - else - aspectRatio = "landscapeWidescreen" - aspectFloat = 1.0 - end - fifthX = x/5 - fourthX= x/4 - thirdX = x/3 - halfX = x/2 - fullX = x - - fifthY = y/5 - fourthY= y/4 - thirdY = y/3 - halfY = y/2 - fullY = y -end - - -check_or_create_cache = function(song, loadJacket) - if not songCache[song.id] then songCache[song.id] = {} end - - if not songCache[song.id]["title"] then - songCache[song.id]["title"] = gfx.CreateLabel(song.title, 14, 0) - end - - if not songCache[song.id]["artist"] then - songCache[song.id]["artist"] = gfx.CreateLabel(song.artist, 25, 0) - end - - if not songCache[song.id]["bpm"] then - songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0) - end - - if not songCache[song.id]["effector"] then - songCache[song.id]["effector"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0) - end - - if not songCache[song.id]["jacket"] and loadJacket then - songCache[song.id]["jacket"] = gfx.CreateImage(song.difficulties[1].jacketPath, 0) - end -end - -function record_handler_factory(hash) - return (function(res) - if res.statusCode == 42 then - recordCache[hash] = {good=false, reason="Untracked"} - elseif res.statusCode == 20 and res.body ~= nil then - recordCache[hash] = {good=true, record=res.body.record} - elseif res.statusCode == 44 then - recordCache[hash] = {good=true, record=nil} - else - recordCache[hash] = {good=false, reason="Failed"} - end - end) -end - -function get_record(hash) - if recordCache[hash] then return recordCache[hash] end - - recordCache[hash] = {good=false, reason="Loading..."} - - IR.Record(hash, record_handler_factory(hash)) - - return recordCache[hash] -end - -function log_table(table) - str = "{" - for k, v in pairs(table) do - str = str .. k .. ": " - - t = type(v) - - if t == "table" then - str = str .. log_table(v) - elseif t == "string" then - str = str .. "\"" .. v .. "\"" - elseif t == "boolean" then - if v then - str = str .. "true" - else - str = str .. "false" - end - else - str = str .. v - end - - str = str .. ", " - end - - return str .. "}" -end - -draw_scores_ir = function(difficulty, x, y, w, h) - -- draw the top score for this difficulty - local xOffset = 5 - local height = h/3 - 10 - local ySpacing = h/3 - local yOffset = h/3 - gfx.FontSize(30); - gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER); - - gfx.FastText("HIGH SCORE", x +(w/4), y+(h/2)) - gfx.FastText("IR RECORD", x + (3/4 * w), y + (h/2)) - - gfx.BeginPath() - gfx.Rect(x+xOffset,y+h/2,w/2-(xOffset*2),h/2) - gfx.FillColor(30,30,30,10) - gfx.StrokeColor(0,128,255) - gfx.StrokeWidth(1) - gfx.Fill() - gfx.Stroke() - - gfx.BeginPath() - gfx.Rect(x + xOffset + w/2,y+h/2,w/2-(xOffset*2),h/2) - gfx.FillColor(30,30,30,10) - gfx.StrokeColor(0,128,255) - gfx.StrokeWidth(1) - gfx.Fill() - gfx.Stroke() - - if difficulty.scores[1] ~= nil then - local highScore = difficulty.scores[1] - scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0) - for i,v in ipairs(grades) do - if v.max > highScore.score then - gfx.BeginPath() - iw,ih = gfx.ImageSize(v.image) - iarr = ih / iw - oldheight = h/2 - 10 - newheight = iarr * (h/2-10) - centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh - gfx.ImageRect(x+xOffset, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me - break - end - end - if difficulty.topBadge ~= 0 then - gfx.BeginPath() - gfx.ImageRect(x+xOffset+w/2-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0) - end - - gfx.FillColor(255,255,255) - gfx.FontSize(40); - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); - gfx.DrawLabel(scoreLabel, x+(w/4),y+(h/4)*3,w/2) - end - - irRecord = get_record(difficulty.hash) - - if not irRecord.good then - recordLabel = gfx.CreateLabel(irRecord.reason, 40, 0) - gfx.FillColor(255, 255, 255) - gfx.FontSize(40) - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); - gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2) - elseif irRecord.record == nil then --record not set, but can be tracked - recordLabel = gfx.CreateLabel(string.format("%08d", 0), 40, 0) - gfx.FillColor(170, 170, 170) - gfx.FontSize(40) - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); - gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2) - else - - recordScoreLabel = gfx.CreateLabel(string.format("%08d", irRecord.record.score), 26, 0) - recordPlayerLabel = gfx.CreateLabel(irRecord.record.username, 26, 0) - - if irRecord.record.lamp ~= 0 then - gfx.BeginPath() - gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[irRecord.record.lamp], 1, 0) - end - - for i,v in ipairs(grades) do - if v.max > irRecord.record.score then - gfx.BeginPath() - iw,ih = gfx.ImageSize(v.image) - iarr = ih / iw - oldheight = h/2 - 10 - newheight = iarr * (h/2-10) - centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh - gfx.ImageRect(x+xOffset+w/2, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me - break - end - end - - gfx.FillColor(255, 255, 255) - gfx.FontSize(40) - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); - gfx.DrawLabel(recordPlayerLabel, x+(w * 3/4),y+(h/4)*2.55,w/2) - gfx.DrawLabel(recordScoreLabel, x+(w * 3/4),y+(h/4)*3.45,w/2) - end -end - -draw_scores = function(difficulty, x, y, w, h) - if IRData.Active then return draw_scores_ir(difficulty, x, y, w, h) end - - -- draw the top score for this difficulty - local xOffset = 5 - local height = h/3 - 10 - local ySpacing = h/3 - local yOffset = h/3 - gfx.FontSize(30); - gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER); - gfx.FastText("HIGH SCORE", x +(w/2), y+(h/2)) - gfx.BeginPath() - gfx.Rect(x+xOffset,y+h/2,w-(xOffset*2),h/2) - gfx.FillColor(30,30,30,10) - gfx.StrokeColor(0,128,255) - gfx.StrokeWidth(1) - gfx.Fill() - gfx.Stroke() - if difficulty.scores[1] ~= nil then - local highScore = difficulty.scores[1] - scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0) - for i,v in ipairs(grades) do - if v.max > highScore.score then - gfx.BeginPath() - iw,ih = gfx.ImageSize(v.image) - iar = iw / ih; - gfx.ImageRect(x+xOffset,y+h/2 +5, iar * (h/2-10),h/2-10, v.image, 1, 0) - break - end - end - if difficulty.topBadge ~= 0 then - gfx.BeginPath() - gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0) - end - gfx.FillColor(255,255,255) - gfx.FontSize(40); - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); - gfx.DrawLabel(scoreLabel, x+(w/2),y+(h/4)*3,w) - end -end - -draw_song = function(song, x, y, w, h, selected) - local difficulty = song.difficulties[selectedDiff] - local clearLampR = 255 - local clearLampG = 255 - local clearLampB = 255 - local clearLampA = 100 - - if difficulty ~= nil then - if difficulty.scores[1] ~= nil then - if difficulty.topBadge == 1 then -- fail/played - clearLampR = 255 - clearLampG = 25 - clearLampB = 25 - clearLampA = 200 - end - if difficulty.topBadge == 2 then -- clear - clearLampR = 25 - clearLampG = 255 - clearLampB = 25 - clearLampA = 200 - end - if difficulty.topBadge == 3 then -- hard clear - clearLampR = 255 - clearLampG = 25 - clearLampB = 255 - clearLampA = 200 - end - if difficulty.topBadge == 4 then -- full combo - clearLampR = 255 - clearLampG = 100 - clearLampB = 25 - clearLampA = 200 - end - if difficulty.topBadge == 5 then -- perfect - clearLampR = 255 - clearLampG = 255 - clearLampB = 25 - clearLampA = 200 - end - end - end - - check_or_create_cache(song) - gfx.BeginPath() - gfx.Rect(x+1,y+1, w-2, h-2) - gfx.FillColor(220,220,220) - gfx.StrokeColor(0,8,0) - gfx.StrokeWidth(2) - gfx.Fill() - gfx.Stroke() - gfx.FillColor(255,255,255) - if not songCache[song.id][1] or songCache[song.id][1] == jacketFallback then - songCache[song.id][1] = gfx.LoadImageJob(song.difficulties[1].jacketPath, jacketFallback, 200,200) - end - if songCache[song.id][1] then - gfx.BeginPath() - gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id][1], 1, 0) - end - - -- Song title - gfx.BeginPath() - gfx.Rect(x+1, y + h - h/4 - 1, w-2, h/4) - gfx.FillColor(0,0,0,200) - gfx.Fill() - - gfx.BeginPath() - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT) - gfx.DrawLabel(songCache[song.id]["title"], (x)+h/2 + 4, y + h - 7, -1) - --gfx.DrawLabel(songCache[song.id]["artist"], x+10, y + 50, w-10) - - -- Song difficulty - gfx.BeginPath() - gfx.Rect(x - 1, y + h-h/2 - 4, h/2, h/2) - gfx.FillColor(0,0,0,200) - gfx.Fill() - - gfx.FillColor(255,255,255) - gfx.LoadSkinFont("commext.ttf") - gfx.FontSize(28) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM) - - if (song.difficulties[selectedDiff] ~= nil) then - gfx.FastText(song.difficulties[selectedDiff].level, x + h/4, y + h - 10) - else - gfx.FastText(song.difficulties[selectedDiff - 1].level, x + h/4, y + h - 10) - end - - - -- CLEAN THIS SHIT UP - local diff_long = "" - local diff_short = "" - if (song.difficulties[selectedDiff] ~= nil) then - if (song.difficulties[selectedDiff].difficulty == 0) then - diff_long = "NOVICE" - diff_short = "NOV" - elseif (song.difficulties[selectedDiff].difficulty == 1) then - diff_long = "ADVANCED" - diff_short = "ADV" - elseif (song.difficulties[selectedDiff].difficulty == 2) then - diff_long = "EXHAUST" - diff_short = "EXH" - elseif (song.difficulties[selectedDiff].difficulty == 3) then - diff_long = "INFINITE" - diff_short = "INF" - else - diff_long = "UNKNOWN" - diff_short = "???" - end - else - if (song.difficulties[selectedDiff - 1].difficulty == 0) then - diff_long = "NOVICE" - diff_short = "NOV" - elseif (song.difficulties[selectedDiff - 1].difficulty == 1) then - diff_long = "ADVANCED" - diff_short = "ADV" - elseif (song.difficulties[selectedDiff - 1].difficulty == 2) then - diff_long = "EXHAUST" - diff_short = "EXH" - elseif (song.difficulties[selectedDiff - 1].difficulty == 3) then - diff_long = "INFINITE" - diff_short = "INF" - else - diff_long = "UNKNOWN" - diff_short = "???" - end - end - - gfx.FontSize(8) - gfx.LoadSkinFont("dfmarugoth.ttf") - gfx.FastText(diff_long, x + h/4, y + h - 7) - - local seldiff = nil - if song.difficulties[selectedDiff] ~= nil then - seldiff = selectedDiff - else - seldiff = selectedDiff - 1 - end - - if song.difficulties[seldiff].topBadge ~= 0 then - if song.difficulties[seldiff].scores[1] ~= nil then - local highScore = song.difficulties[seldiff].scores[1] - for i,v in ipairs(grades) do - if v.max > highScore.score then - gfx.BeginPath() - iw,ih = gfx.ImageSize(v.image) - iar = iw / ih; - gfx.ImageRect(x + w/1.45, y + h/8 + 2, (h/1.5-14), h/1.5-14, v.image, 1, 0) - break - end - end - end - gfx.BeginPath() - gfx.ImageRect(x + w/2, y + h/8, (h/1.5-10), h/1.5-10, badges[song.difficulties[seldiff].topBadge], 1, 0) - end - end - -draw_diff_icon = function(diff, x, y, w, h, selected) - local shrinkX = w/4 - local shrinkY = h/4 - if selected then - gfx.FontSize(h/2) - shrinkX = w/6 - shrinkY = h/6 - else - gfx.FontSize(math.floor(h / 3)) - end - gfx.BeginPath() - gfx.RoundedRectVarying(x+shrinkX,y+shrinkY,w-shrinkX*2,h-shrinkY*2,0,0,0,0) - gfx.FillColor(15,15,15) - gfx.StrokeColor(table.unpack(diffColors[diff.difficulty + 1])) - gfx.StrokeWidth(2) - gfx.Fill() - gfx.Stroke() - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER) - gfx.FastText(tostring(diff.level), x+(w/2),y+(h/2)) -end - -draw_cursor = function(x,y,rotation,width) - gfx.Save() - gfx.BeginPath(); - gfx.Translate(x,y) - gfx.Rotate(rotation) - gfx.StrokeColor(255,128,0) - gfx.StrokeWidth(4) - gfx.Rect(-width/2, -width/2, width, width) - gfx.Stroke() - gfx.Restore() -end - -draw_diffs = function(diffs, x, y, w, h) - local diffWidth = w/2.5 - local diffHeight = w/2.5 - local diffCount = #diffs - gfx.Scissor(x,y,w,h) - for i = math.max(selectedDiff - 2, 1), math.max(selectedDiff - 1,1) do - local diff = diffs[i] - local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) - if i ~= selectedDiff then - draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) - end - end - - --after selected - for i = math.min(selectedDiff + 2, diffCount), selectedDiff + 1,-1 do - local diff = diffs[i] - local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) - if i ~= selectedDiff then - draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) - end - end - local diff = diffs[selectedDiff] - local xpos = x + ((w/2 - diffWidth/2) + (doffset)*(-0.8*diffWidth)) - draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, true) - gfx.BeginPath() - gfx.FillColor(0,128,255) - gfx.Rect(x,y+10,2,diffHeight-h/6) - gfx.Fill() - gfx.BeginPath() - gfx.Rect(x+w-2,y+10,2,diffHeight-h/6) - gfx.Fill() - gfx.ResetScissor() - draw_cursor(x + w/2, y +diffHeight/2, timer * math.pi, diffHeight / 1.5) -end - -draw_selected = function(song, x, y, w, h) - check_or_create_cache(song) - -- set up padding and margins - local xPadding = math.floor(w/16) - local yPadding = math.floor(h/32) - local xMargin = math.floor(w/16) - local yMargin = math.floor(h/32) - local width = (w-(xMargin*2)) - local height = (h-(yMargin*2)) - local xpos = x+xMargin - local ypos = y+yMargin - if aspectRatio == "PortraitWidescreen" then - xPadding = math.floor(w/32) - yPadding = math.floor(h/32) - xMargin = math.floor(w/34) - yMargin = math.floor(h/32) - width = ((w/2)-(xMargin)) - height = (h-(yMargin*2)) - xpos = x+xMargin/2 - ypos = y+yMargin - end - --Border - local diff = song.difficulties[selectedDiff] - gfx.BeginPath() - gfx.RoundedRectVarying(xpos,ypos,width,height,yPadding,yPadding,yPadding,yPadding) - gfx.FillColor(30,30,30,100) - gfx.StrokeColor(0,128,255) - gfx.StrokeWidth(1) - gfx.Fill() - gfx.Stroke() - - -- jacket should take up 1/3 of height, always be square, and be centered - local imageSize = math.floor(height/3) - local imageXPos = ((width/2) - (imageSize/2)) + x+xMargin - if aspectRatio == "PortraitWidescreen" then - --Unless its portrait widesreen.. - imageSize = math.floor((height/8)*2) - imageXPos = x+xMargin - end - if not songCache[song.id][selectedDiff] or songCache[song.id][selectedDiff] == jacketFallback then - songCache[song.id][selectedDiff] = gfx.LoadImageJob(diff.jacketPath, jacketFallback, 200,200) - end - - if songCache[song.id][selectedDiff] then - gfx.BeginPath() - gfx.ImageRect(imageXPos, y+yMargin+yPadding, imageSize, imageSize, songCache[song.id][selectedDiff], 1, 0) - end - -- difficulty should take up 1/6 of height, full width, and be centered - gfx.LoadSkinFont("commext.ttf") - if aspectRatio == "PortraitWidescreen" then - --difficulty wheel should be right below the jacketImage, and the same width as - --the jacketImage - draw_diffs(song.difficulties,xpos+xPadding,(ypos+yPadding+imageSize),imageSize,math.floor((height/3)*1)-yPadding) - else - -- difficulty should take up 1/6 of height, full width, and be centered - draw_diffs(song.difficulties,(w/2)-(imageSize/2),(ypos+yPadding+imageSize),imageSize,math.floor(height/6)) - end - -- effector / bpm should take up 1/3 of height, full width - gfx.LoadSkinFont("dfmarugoth.ttf") - if aspectRatio == "PortraitWidescreen" then - gfx.FontSize(40) - gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT) - gfx.DrawLabel(songCache[song.id]["title"], xpos+xPadding+imageSize, y+yMargin+yPadding, width-imageSize-20) - gfx.FontSize(30) - gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 45, width-imageSize-20) - gfx.FontSize(20) - gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 85, width-imageSize-20) - gfx.FastText(string.format("Effector: %s", diff.effector), xpos+xPadding+imageSize+3, y+yMargin+yPadding + 115) - else - gfx.FontSize(40) - gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT) - gfx.DrawLabel(songCache[song.id]["title"], xpos+10, (height/10)*6, width-20) - gfx.FontSize(30) - gfx.DrawLabel(songCache[song.id]["artist"], xpos+10, (height/10)*6 + 45, width-20) - gfx.FillColor(255,255,255) - gfx.FontSize(20) - gfx.DrawLabel(songCache[song.id]["bpm"], xpos+10, (height/10)*6 + 85) - gfx.FastText(string.format("Effector: %s", diff.effector),xpos+10, (height/10)*6 + 115) - end - if aspectRatio == "PortraitWidescreen" then - draw_scores(diff, xpos+xPadding+imageSize+3, (height/3)*2, width-imageSize-20, (height/3)-yPadding) - else - draw_scores(diff, xpos, (height/6)*5, width, (height/6)) - end - gfx.ForceRender() -end - -draw_songwheel = function(x,y,w,h) - local offsetX = fifthX/2 - local width = math.floor((w/5)*4) - if aspectRatio == "landscapeWidescreen" then - wheelSize = 12 - offsetX = 80 - elseif aspectRatio == "landscapeStandard" then - wheelSize = 10 - offsetX = 40 - elseif aspectRatio == "PortraitWidescreen" then - wheelSize = 20 - offsetX = 20 - width = w/2 - end - local height = math.floor((h/wheelSize)*1.75) - - for i = math.max(selectedIndex - wheelSize/2, 1), math.max(selectedIndex - 1,0) do - local song = songwheel.songs[i] - local xpos = x + width - local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05) - local ypos = y+((h/2 - height/2) - offsetY) - draw_song(song, xpos, ypos, width, height) - end - - --after selected - for i = math.min(selectedIndex + wheelSize/2, #songwheel.songs), selectedIndex + 1,-1 do - local song = songwheel.songs[i] - local xpos = x + width - local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05) - local ypos = y+((h/2 - height/2) - (selectedIndex - i) - offsetY) - local alpha = 255 - (selectedIndex - i + ioffset) * 31 - draw_song(song, xpos, ypos, width, height) - end - -- draw selected - local xpos = x + width - local offsetY = (ioffset/2) * ( height - (wheelSize/2*((1)*aspectFloat))) - local ypos = y+((h/2 - height/2) - (ioffset) - offsetY) - draw_song(songwheel.songs[selectedIndex], xpos, ypos, width, height, true) - -- cursor - gfx.BeginPath() - local ypos = y+((h/2 - height/2)) - gfx.Rect(xpos, ypos, width, height) - gfx.FillColor(0,0,0,0) - gfx.StrokeColor(255,128,0) - gfx.StrokeWidth(3) - gfx.Fill() - gfx.Stroke() - - return songwheel.songs[selectedIndex] -end -draw_legend_pane = function(x,y,w,h,obj) - local xpos = x+5 - local ypos = y - local imageSize = h - gfx.BeginPath() - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT) - gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0) - xpos = xpos + imageSize + 5 - gfx.FontSize(16); - if h < (w-(10+imageSize))/2 then - gfx.DrawLabel(obj.labelSingleLine, xpos, y+(h/2), w-(10+imageSize)) - else - gfx.DrawLabel(obj.labelMultiLine, xpos, y+(h/2), w-(10+imageSize)) - end - gfx.ForceRender() -end - -draw_legend = function(x,y,w,h) - gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT); - gfx.BeginPath() - gfx.FillColor(0,0,0,170) - gfx.Rect(x,y,w,h) - gfx.Fill() - local xpos = 10; - local legendWidth = math.floor((w-20)/#legendTable) - for i,v in ipairs(legendTable) do - local xOffset = draw_legend_pane(xpos+(legendWidth*(i-1)), y+5,legendWidth,h-10,legendTable[i]) - end -end - -draw_search = function(x,y,w,h) - soffset = soffset + (searchIndex) - (songwheel.searchInputActive and 0 or 1) - if searchIndex ~= (songwheel.searchInputActive and 0 or 1) then - game.PlaySample("woosh") - end - searchIndex = songwheel.searchInputActive and 0 or 1 - - gfx.BeginPath() - local bgfade = 1 - (searchIndex + soffset) - --if not songwheel.searchInputActive then bgfade = soffset end - gfx.FillColor(0,0,0,math.floor(200 * bgfade)) - gfx.Rect(0,0,resx,resy) - gfx.Fill() - gfx.ForceRender() - local xpos = x + (searchIndex + soffset)*w - gfx.UpdateLabel(searchText ,string.format("Search: %s",songwheel.searchText), 30, 0) - gfx.BeginPath() - gfx.RoundedRect(xpos,y,w,h,h/2) - gfx.FillColor(30,30,30) - gfx.StrokeColor(0,128,255) - gfx.StrokeWidth(1) - gfx.Fill() - gfx.Stroke() - gfx.BeginPath(); - gfx.LoadSkinFont("NotoSans-Regular.ttf"); - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); - gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20) - -end - -render = function(deltaTime) - timer = (timer + deltaTime) - timer = timer % 2 - resx,resy = game.GetResolution(); - adjustScreen(resx,resy); - gfx.BeginPath(); - gfx.LoadSkinFont("dfmarugoth.ttf"); - gfx.FontSize(40); - gfx.FillColor(255,255,255); - if songwheel.songs[1] ~= nil then - --draw songwheel and get selected song - if aspectRatio == "PortraitWidescreen" then - local song = draw_songwheel(0,0,fullX,fullY) - --render selected song information - draw_selected(song, 0,0,fullX,resy) - else - local song = draw_songwheel(fifthX*2,0,fifthX*3,fullY) - --render selected song information - draw_selected(song, 0,0,fifthX*2,(fifthY/2)*9) - end - end - --Draw Legend Information - -- if showGuide then - -- if aspectRatio == "PortraitWidescreen" then - -- draw_legend(0,(fifthY/3)*14, fullX, (fifthY/3)*1) - -- else - -- draw_legend(0,(fifthY/2)*9, fullX, (fifthY/2)) - -- end - -- end - - --draw text search - if aspectRatio == "PortraitWidescreen" then - draw_search(fifthX*2,5,fifthX*3,fifthY/5) - else - draw_search(fifthX*2,5,fifthX*3,fifthY/3) - end - - ioffset = ioffset * 0.9 - doffset = doffset * 0.9 - soffset = soffset * 0.8 - if songwheel.searchStatus then - gfx.BeginPath() - gfx.FillColor(255,255,255) - gfx.FontSize(20); - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(songwheel.searchStatus, 3, 3) - end - if totalForce then - gfx.BeginPath() - gfx.FillColor(255,255,255) - gfx.FontSize(20); - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) - local forceText = string.format("Force: %.2f", totalForce) - gfx.Text(forceText, 0, fullY) - end - gfx.LoadSkinFont("NotoSans-Regular.ttf"); - gfx.ResetTransform() - gfx.ForceRender() -end - -set_index = function(newIndex) - if newIndex ~= selectedIndex then - game.PlaySample("menu_click") - end - ioffset = ioffset + selectedIndex - newIndex - selectedIndex = newIndex -end; - -set_diff = function(newDiff) - if newDiff ~= selectedDiff then - game.PlaySample("click-02") - end - doffset = doffset + selectedDiff - newDiff - selectedDiff = newDiff -end; - --- force calculation --------------------- -totalForce = nil - -local badgeRates = { - 0.5, -- Played - 1.0, -- Cleared - 1.02, -- Hard clear - 1.04, -- UC - 1.1 -- PUC -} - -local gradeRates = { - {["min"] = 9900000, ["rate"] = 1.05}, -- S - {["min"] = 9800000, ["rate"] = 1.02}, -- AAA+ - {["min"] = 9700000, ["rate"] = 1}, -- AAA - {["min"] = 9500000, ["rate"] = 0.97}, -- AA+ - {["min"] = 9300000, ["rate"] = 0.94}, -- AA - {["min"] = 9000000, ["rate"] = 0.91}, -- A+ - {["min"] = 8700000, ["rate"] = 0.88}, -- A - {["min"] = 7500000, ["rate"] = 0.85}, -- B - {["min"] = 6500000, ["rate"] = 0.82}, -- C - {["min"] = 0, ["rate"] = 0.8} -- D -} - -calculate_force = function(diff) - if #diff.scores < 1 then - return 0 - end - local score = diff.scores[1] - local badgeRate = badgeRates[diff.topBadge] - local gradeRate - for i, v in ipairs(gradeRates) do - if score.score >= v.min then - gradeRate = v.rate - break - end - end - return math.floor((diff.level * 2) * (score.score / 10000000) * gradeRate * badgeRate) / 100 -end - -songs_changed = function(withAll) - if not withAll then return end - - recordCache = {} - - local diffs = {} - for i = 1, #songwheel.allSongs do - local song = songwheel.allSongs[i] - for j = 1, #song.difficulties do - local diff = song.difficulties[j] - diff.force = calculate_force(diff) - table.insert(diffs, diff) - end - end - table.sort(diffs, function (l, r) - return l.force > r.force - end) - totalForce = 0 - for i = 1, 50 do - if diffs[i] then - totalForce = totalForce + diffs[i].force - end - end -end +--Horizontal alignment +TEXT_ALIGN_LEFT = 1 +TEXT_ALIGN_CENTER = 2 +TEXT_ALIGN_RIGHT = 4 +--Vertical alignment +TEXT_ALIGN_TOP = 8 +TEXT_ALIGN_MIDDLE = 16 +TEXT_ALIGN_BOTTOM = 32 +TEXT_ALIGN_BASELINE = 64 + +local jacket = nil; +local selectedIndex = 1 +local selectedDiff = 1 +local songCache = {} +local ioffset = 0 +local doffset = 0 +local soffset = 0 +local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}} +local timer = 0 +local effector = 0 +local searchText = gfx.CreateLabel("",5,0) +local searchIndex = 1 +local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) +local showGuide = game.GetSkinSetting("show_guide") +local legendTable = { + {["labelSingleLine"] = gfx.CreateLabel("DIFFICULTY SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("DIFFICULTY\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0)}, + {["labelSingleLine"] = gfx.CreateLabel("MUSIC SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0)}, + {["labelSingleLine"] = gfx.CreateLabel("FILTER MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("FILTER\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0)}, + {["labelSingleLine"] = gfx.CreateLabel("SORT MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("SORT\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0)}, + {["labelSingleLine"] = gfx.CreateLabel("MUSIC MODS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nMODS",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0)}, + {["labelSingleLine"] = gfx.CreateLabel("PLAY",16, 0), ["labelMultiLine"] = gfx.CreateLabel("PLAY",16, 0), ["image"] = gfx.CreateSkinImage("legend/start.png", 0)} +} +local grades = { + {["max"] = 6999999, ["image"] = gfx.CreateSkinImage("common/grades/D.png", 0)}, + {["max"] = 7999999, ["image"] = gfx.CreateSkinImage("common/grades/C.png", 0)}, + {["max"] = 8699999, ["image"] = gfx.CreateSkinImage("common/grades/B.png", 0)}, + {["max"] = 8999999, ["image"] = gfx.CreateSkinImage("common/grades/A.png", 0)}, + {["max"] = 9299999, ["image"] = gfx.CreateSkinImage("common/grades/A+.png", 0)}, + {["max"] = 9499999, ["image"] = gfx.CreateSkinImage("common/grades/AA.png", 0)}, + {["max"] = 9699999, ["image"] = gfx.CreateSkinImage("common/grades/AA+.png", 0)}, + {["max"] = 9799999, ["image"] = gfx.CreateSkinImage("common/grades/AAA.png", 0)}, + {["max"] = 9899999, ["image"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0)}, + {["max"] = 99999999, ["image"] = gfx.CreateSkinImage("common/grades/S.png", 0)} +} + +local badges = { + gfx.CreateSkinImage("badges/played.png", 0), + gfx.CreateSkinImage("badges/clear.png", 0), + gfx.CreateSkinImage("badges/hard-clear.png", 0), + gfx.CreateSkinImage("badges/full-combo.png", 0), + gfx.CreateSkinImage("badges/perfect.png", 0) +} + +local recordCache = {} + +gfx.LoadSkinFont("dfmarugoth.ttf"); + +game.LoadSkinSample("menu_click") +game.LoadSkinSample("click-02") +game.LoadSkinSample("woosh") + +local wheelSize = 12 + +get_page_size = function() + return math.floor(wheelSize/2) +end + +-- Responsive UI variables +-- Aspect Ratios +local aspectFloat = 1.850 +local aspectRatio = "widescreen" +local landscapeWidescreenRatio = 1.850 +local landscapeStandardRatio = 1.500 +local portraitWidescreenRatio = 0.5 + +-- Responsive sizes +local fifthX = 0 +local fourthX= 0 +local thirdX = 0 +local halfX = 0 +local fullX = 0 + +local fifthY = 0 +local fourthY= 0 +local thirdY = 0 +local halfY = 0 +local fullY = 0 + + +adjustScreen = function(x,y) + local a = x/y; + if x >= y and a <= landscapeStandardRatio then + aspectRatio = "landscapeStandard" + aspectFloat = 1.1 + elseif x >= y and landscapeStandardRatio <= a and a <= landscapeWidescreenRatio then + aspectRatio = "landscapeWidescreen" + aspectFloat = 1.2 + elseif x <= y and portraitWidescreenRatio <= a and a < landscapeStandardRatio then + aspectRatio = "PortraitWidescreen" + aspectFloat = 0.5 + else + aspectRatio = "landscapeWidescreen" + aspectFloat = 1.0 + end + fifthX = x/5 + fourthX= x/4 + thirdX = x/3 + halfX = x/2 + fullX = x + + fifthY = y/5 + fourthY= y/4 + thirdY = y/3 + halfY = y/2 + fullY = y +end + + +check_or_create_cache = function(song, loadJacket) + if not songCache[song.id] then songCache[song.id] = {} end + + if not songCache[song.id]["title"] then + songCache[song.id]["title"] = gfx.CreateLabel(song.title, 14, 0) + end + + if not songCache[song.id]["artist"] then + songCache[song.id]["artist"] = gfx.CreateLabel(song.artist, 25, 0) + end + + if not songCache[song.id]["bpm"] then + songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0) + end + + if not songCache[song.id]["effector"] then + songCache[song.id]["effector"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0) + end + + if not songCache[song.id]["jacket"] and loadJacket then + songCache[song.id]["jacket"] = gfx.CreateImage(song.difficulties[1].jacketPath, 0) + end +end + +function record_handler_factory(hash) + return (function(res) + if res.statusCode == 42 then + recordCache[hash] = {good=false, reason="Untracked"} + elseif res.statusCode == 20 and res.body ~= nil then + recordCache[hash] = {good=true, record=res.body.record} + elseif res.statusCode == 44 then + recordCache[hash] = {good=true, record=nil} + else + recordCache[hash] = {good=false, reason="Failed"} + end + end) +end + +function get_record(hash) + if recordCache[hash] then return recordCache[hash] end + + recordCache[hash] = {good=false, reason="Loading..."} + + IR.Record(hash, record_handler_factory(hash)) + + return recordCache[hash] +end + +function log_table(table) + str = "{" + for k, v in pairs(table) do + str = str .. k .. ": " + + t = type(v) + + if t == "table" then + str = str .. log_table(v) + elseif t == "string" then + str = str .. "\"" .. v .. "\"" + elseif t == "boolean" then + if v then + str = str .. "true" + else + str = str .. "false" + end + else + str = str .. v + end + + str = str .. ", " + end + + return str .. "}" +end + +draw_scores_ir = function(difficulty, x, y, w, h) + -- draw the top score for this difficulty + local xOffset = 5 + local height = h/3 - 10 + local ySpacing = h/3 + local yOffset = h/3 + gfx.FontSize(30); + gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER); + + gfx.FastText("HIGH SCORE", x +(w/4), y+(h/2)) + gfx.FastText("IR RECORD", x + (3/4 * w), y + (h/2)) + + gfx.BeginPath() + gfx.Rect(x+xOffset,y+h/2,w/2-(xOffset*2),h/2) + gfx.FillColor(30,30,30,10) + gfx.StrokeColor(0,128,255) + gfx.StrokeWidth(1) + gfx.Fill() + gfx.Stroke() + + gfx.BeginPath() + gfx.Rect(x + xOffset + w/2,y+h/2,w/2-(xOffset*2),h/2) + gfx.FillColor(30,30,30,10) + gfx.StrokeColor(0,128,255) + gfx.StrokeWidth(1) + gfx.Fill() + gfx.Stroke() + + if difficulty.scores[1] ~= nil then + local highScore = difficulty.scores[1] + scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0) + for i,v in ipairs(grades) do + if v.max > highScore.score then + gfx.BeginPath() + iw,ih = gfx.ImageSize(v.image) + iarr = ih / iw + oldheight = h/2 - 10 + newheight = iarr * (h/2-10) + centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh + gfx.ImageRect(x+xOffset, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me + break + end + end + if difficulty.topBadge ~= 0 then + gfx.BeginPath() + gfx.ImageRect(x+xOffset+w/2-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0) + end + + gfx.FillColor(255,255,255) + gfx.FontSize(40); + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); + gfx.DrawLabel(scoreLabel, x+(w/4),y+(h/4)*3,w/2) + end + + irRecord = get_record(difficulty.hash) + + if not irRecord.good then + recordLabel = gfx.CreateLabel(irRecord.reason, 40, 0) + gfx.FillColor(255, 255, 255) + gfx.FontSize(40) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); + gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2) + elseif irRecord.record == nil then --record not set, but can be tracked + recordLabel = gfx.CreateLabel(string.format("%08d", 0), 40, 0) + gfx.FillColor(170, 170, 170) + gfx.FontSize(40) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); + gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2) + else + + recordScoreLabel = gfx.CreateLabel(string.format("%08d", irRecord.record.score), 26, 0) + recordPlayerLabel = gfx.CreateLabel(irRecord.record.username, 26, 0) + + if irRecord.record.lamp ~= 0 then + gfx.BeginPath() + gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[irRecord.record.lamp], 1, 0) + end + + for i,v in ipairs(grades) do + if v.max > irRecord.record.score then + gfx.BeginPath() + iw,ih = gfx.ImageSize(v.image) + iarr = ih / iw + oldheight = h/2 - 10 + newheight = iarr * (h/2-10) + centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh + gfx.ImageRect(x+xOffset+w/2, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me + break + end + end + + gfx.FillColor(255, 255, 255) + gfx.FontSize(40) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); + gfx.DrawLabel(recordPlayerLabel, x+(w * 3/4),y+(h/4)*2.55,w/2) + gfx.DrawLabel(recordScoreLabel, x+(w * 3/4),y+(h/4)*3.45,w/2) + end +end + +draw_scores = function(difficulty, x, y, w, h) + if IRData.Active then return draw_scores_ir(difficulty, x, y, w, h) end + + -- draw the top score for this difficulty + local xOffset = 5 + local height = h/3 - 10 + local ySpacing = h/3 + local yOffset = h/3 + gfx.FontSize(30); + gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER); + gfx.FastText("HIGH SCORE", x +(w/2), y+(h/2)) + gfx.BeginPath() + gfx.Rect(x+xOffset,y+h/2,w-(xOffset*2),h/2) + gfx.FillColor(30,30,30,10) + gfx.StrokeColor(0,128,255) + gfx.StrokeWidth(1) + gfx.Fill() + gfx.Stroke() + if difficulty.scores[1] ~= nil then + local highScore = difficulty.scores[1] + scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0) + for i,v in ipairs(grades) do + if v.max > highScore.score then + gfx.BeginPath() + iw,ih = gfx.ImageSize(v.image) + iar = iw / ih; + gfx.ImageRect(x+xOffset,y+h/2 +5, iar * (h/2-10),h/2-10, v.image, 1, 0) + break + end + end + if difficulty.topBadge ~= 0 then + gfx.BeginPath() + gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0) + end + gfx.FillColor(255,255,255) + gfx.FontSize(40); + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); + gfx.DrawLabel(scoreLabel, x+(w/2),y+(h/4)*3,w) + end +end + +draw_song = function(song, x, y, w, h, selected) + local difficulty = song.difficulties[selectedDiff] + local clearLampR = 255 + local clearLampG = 255 + local clearLampB = 255 + local clearLampA = 100 + + if difficulty ~= nil then + if difficulty.scores[1] ~= nil then + if difficulty.topBadge == 1 then -- fail/played + clearLampR = 255 + clearLampG = 25 + clearLampB = 25 + clearLampA = 200 + end + if difficulty.topBadge == 2 then -- clear + clearLampR = 25 + clearLampG = 255 + clearLampB = 25 + clearLampA = 200 + end + if difficulty.topBadge == 3 then -- hard clear + clearLampR = 255 + clearLampG = 25 + clearLampB = 255 + clearLampA = 200 + end + if difficulty.topBadge == 4 then -- full combo + clearLampR = 255 + clearLampG = 100 + clearLampB = 25 + clearLampA = 200 + end + if difficulty.topBadge == 5 then -- perfect + clearLampR = 255 + clearLampG = 255 + clearLampB = 25 + clearLampA = 200 + end + end + end + + check_or_create_cache(song) + gfx.BeginPath() + gfx.Rect(x+1,y+1, w-2, h-2) + gfx.FillColor(220,220,220) + gfx.StrokeColor(0,8,0) + gfx.StrokeWidth(2) + gfx.Fill() + gfx.Stroke() + gfx.FillColor(255,255,255) + if not songCache[song.id][1] or songCache[song.id][1] == jacketFallback then + songCache[song.id][1] = gfx.LoadImageJob(song.difficulties[1].jacketPath, jacketFallback, 200,200) + end + if songCache[song.id][1] then + gfx.BeginPath() + gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id][1], 1, 0) + end + + -- Song title + gfx.BeginPath() + gfx.Rect(x+1, y + h - h/4 - 1, w-2, h/4) + gfx.FillColor(0,0,0,200) + gfx.Fill() + + gfx.BeginPath() + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT) + gfx.DrawLabel(songCache[song.id]["title"], (x)+h/2 + 4, y + h - 7, -1) + --gfx.DrawLabel(songCache[song.id]["artist"], x+10, y + 50, w-10) + + -- Song difficulty + gfx.BeginPath() + gfx.Rect(x - 1, y + h-h/2 - 4, h/2, h/2) + gfx.FillColor(0,0,0,200) + gfx.Fill() + + gfx.FillColor(255,255,255) + gfx.LoadSkinFont("commext.ttf") + gfx.FontSize(28) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM) + + if (song.difficulties[selectedDiff] ~= nil) then + gfx.FastText(song.difficulties[selectedDiff].level, x + h/4, y + h - 10) + else + gfx.FastText(song.difficulties[selectedDiff - 1].level, x + h/4, y + h - 10) + end + + + -- CLEAN THIS SHIT UP + local diff_long = "" + local diff_short = "" + if (song.difficulties[selectedDiff] ~= nil) then + if (song.difficulties[selectedDiff].difficulty == 0) then + diff_long = "NOVICE" + diff_short = "NOV" + elseif (song.difficulties[selectedDiff].difficulty == 1) then + diff_long = "ADVANCED" + diff_short = "ADV" + elseif (song.difficulties[selectedDiff].difficulty == 2) then + diff_long = "EXHAUST" + diff_short = "EXH" + elseif (song.difficulties[selectedDiff].difficulty == 3) then + diff_long = "INFINITE" + diff_short = "INF" + else + diff_long = "UNKNOWN" + diff_short = "???" + end + else + if (song.difficulties[selectedDiff - 1].difficulty == 0) then + diff_long = "NOVICE" + diff_short = "NOV" + elseif (song.difficulties[selectedDiff - 1].difficulty == 1) then + diff_long = "ADVANCED" + diff_short = "ADV" + elseif (song.difficulties[selectedDiff - 1].difficulty == 2) then + diff_long = "EXHAUST" + diff_short = "EXH" + elseif (song.difficulties[selectedDiff - 1].difficulty == 3) then + diff_long = "INFINITE" + diff_short = "INF" + else + diff_long = "UNKNOWN" + diff_short = "???" + end + end + + gfx.FontSize(8) + gfx.LoadSkinFont("dfmarugoth.ttf") + gfx.FastText(diff_long, x + h/4, y + h - 7) + + local seldiff = nil + if song.difficulties[selectedDiff] ~= nil then + seldiff = selectedDiff + else + seldiff = selectedDiff - 1 + end + + if song.difficulties[seldiff].topBadge ~= 0 then + if song.difficulties[seldiff].scores[1] ~= nil then + local highScore = song.difficulties[seldiff].scores[1] + for i,v in ipairs(grades) do + if v.max > highScore.score then + gfx.BeginPath() + iw,ih = gfx.ImageSize(v.image) + iar = iw / ih; + gfx.ImageRect(x + w/1.45, y + h/8 + 2, (h/1.5-14), h/1.5-14, v.image, 1, 0) + break + end + end + end + gfx.BeginPath() + gfx.ImageRect(x + w/2, y + h/8, (h/1.5-10), h/1.5-10, badges[song.difficulties[seldiff].topBadge], 1, 0) + end + end + +draw_diff_icon = function(diff, x, y, w, h, selected) + local shrinkX = w/4 + local shrinkY = h/4 + if selected then + gfx.FontSize(h/2) + shrinkX = w/6 + shrinkY = h/6 + else + gfx.FontSize(math.floor(h / 3)) + end + gfx.BeginPath() + gfx.RoundedRectVarying(x+shrinkX,y+shrinkY,w-shrinkX*2,h-shrinkY*2,0,0,0,0) + gfx.FillColor(15,15,15) + gfx.StrokeColor(table.unpack(diffColors[diff.difficulty + 1])) + gfx.StrokeWidth(2) + gfx.Fill() + gfx.Stroke() + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER) + gfx.FastText(tostring(diff.level), x+(w/2),y+(h/2)) +end + +draw_cursor = function(x,y,rotation,width) + gfx.Save() + gfx.BeginPath(); + gfx.Translate(x,y) + gfx.Rotate(rotation) + gfx.StrokeColor(255,128,0) + gfx.StrokeWidth(4) + gfx.Rect(-width/2, -width/2, width, width) + gfx.Stroke() + gfx.Restore() +end + +draw_diffs = function(diffs, x, y, w, h) + local diffWidth = w/2.5 + local diffHeight = w/2.5 + local diffCount = #diffs + gfx.Scissor(x,y,w,h) + for i = math.max(selectedDiff - 2, 1), math.max(selectedDiff - 1,1) do + local diff = diffs[i] + local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) + if i ~= selectedDiff then + draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) + end + end + + --after selected + for i = math.min(selectedDiff + 2, diffCount), selectedDiff + 1,-1 do + local diff = diffs[i] + local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) + if i ~= selectedDiff then + draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) + end + end + local diff = diffs[selectedDiff] + local xpos = x + ((w/2 - diffWidth/2) + (doffset)*(-0.8*diffWidth)) + draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, true) + gfx.BeginPath() + gfx.FillColor(0,128,255) + gfx.Rect(x,y+10,2,diffHeight-h/6) + gfx.Fill() + gfx.BeginPath() + gfx.Rect(x+w-2,y+10,2,diffHeight-h/6) + gfx.Fill() + gfx.ResetScissor() + draw_cursor(x + w/2, y +diffHeight/2, timer * math.pi, diffHeight / 1.5) +end + +draw_selected = function(song, x, y, w, h) + check_or_create_cache(song) + -- set up padding and margins + local xPadding = math.floor(w/16) + local yPadding = math.floor(h/32) + local xMargin = math.floor(w/16) + local yMargin = math.floor(h/32) + local width = (w-(xMargin*2)) + local height = (h-(yMargin*2)) + local xpos = x+xMargin + local ypos = y+yMargin + if aspectRatio == "PortraitWidescreen" then + xPadding = math.floor(w/32) + yPadding = math.floor(h/32) + xMargin = math.floor(w/34) + yMargin = math.floor(h/32) + width = ((w/2)-(xMargin)) + height = (h-(yMargin*2)) + xpos = x+xMargin/2 + ypos = y+yMargin + end + --Border + local diff = song.difficulties[selectedDiff] + gfx.BeginPath() + gfx.RoundedRectVarying(xpos,ypos,width,height,yPadding,yPadding,yPadding,yPadding) + gfx.FillColor(30,30,30,100) + gfx.StrokeColor(0,128,255) + gfx.StrokeWidth(1) + gfx.Fill() + gfx.Stroke() + + -- jacket should take up 1/3 of height, always be square, and be centered + local imageSize = math.floor(height/3) + local imageXPos = ((width/2) - (imageSize/2)) + x+xMargin + if aspectRatio == "PortraitWidescreen" then + --Unless its portrait widesreen.. + imageSize = math.floor((height/8)*2) + imageXPos = x+xMargin + end + if not songCache[song.id][selectedDiff] or songCache[song.id][selectedDiff] == jacketFallback then + songCache[song.id][selectedDiff] = gfx.LoadImageJob(diff.jacketPath, jacketFallback, 200,200) + end + + if songCache[song.id][selectedDiff] then + gfx.BeginPath() + gfx.ImageRect(imageXPos, y+yMargin+yPadding, imageSize, imageSize, songCache[song.id][selectedDiff], 1, 0) + end + -- difficulty should take up 1/6 of height, full width, and be centered + gfx.LoadSkinFont("commext.ttf") + if aspectRatio == "PortraitWidescreen" then + --difficulty wheel should be right below the jacketImage, and the same width as + --the jacketImage + draw_diffs(song.difficulties,xpos+xPadding,(ypos+yPadding+imageSize),imageSize,math.floor((height/3)*1)-yPadding) + else + -- difficulty should take up 1/6 of height, full width, and be centered + draw_diffs(song.difficulties,(w/2)-(imageSize/2),(ypos+yPadding+imageSize),imageSize,math.floor(height/6)) + end + -- effector / bpm should take up 1/3 of height, full width + gfx.LoadSkinFont("dfmarugoth.ttf") + if aspectRatio == "PortraitWidescreen" then + gfx.FontSize(40) + gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT) + gfx.DrawLabel(songCache[song.id]["title"], xpos+xPadding+imageSize, y+yMargin+yPadding, width-imageSize-20) + gfx.FontSize(30) + gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 45, width-imageSize-20) + gfx.FontSize(20) + gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 85, width-imageSize-20) + gfx.FastText(string.format("Effector: %s", diff.effector), xpos+xPadding+imageSize+3, y+yMargin+yPadding + 115) + else + gfx.FontSize(40) + gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT) + gfx.DrawLabel(songCache[song.id]["title"], xpos+10, (height/10)*6, width-20) + gfx.FontSize(30) + gfx.DrawLabel(songCache[song.id]["artist"], xpos+10, (height/10)*6 + 45, width-20) + gfx.FillColor(255,255,255) + gfx.FontSize(20) + gfx.DrawLabel(songCache[song.id]["bpm"], xpos+10, (height/10)*6 + 85) + gfx.FastText(string.format("Effector: %s", diff.effector),xpos+10, (height/10)*6 + 115) + end + if aspectRatio == "PortraitWidescreen" then + draw_scores(diff, xpos+xPadding+imageSize+3, (height/3)*2, width-imageSize-20, (height/3)-yPadding) + else + draw_scores(diff, xpos, (height/6)*5, width, (height/6)) + end + gfx.ForceRender() +end + +draw_songwheel = function(x,y,w,h) + local offsetX = fifthX/2 + local width = math.floor((w/5)*4) + if aspectRatio == "landscapeWidescreen" then + wheelSize = 12 + offsetX = 80 + elseif aspectRatio == "landscapeStandard" then + wheelSize = 10 + offsetX = 40 + elseif aspectRatio == "PortraitWidescreen" then + wheelSize = 20 + offsetX = 20 + width = w/2 + end + local height = math.floor((h/wheelSize)*1.75) + + for i = math.max(selectedIndex - wheelSize/2, 1), math.max(selectedIndex - 1,0) do + local song = songwheel.songs[i] + local xpos = x + width + local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05) + local ypos = y+((h/2 - height/2) - offsetY) + draw_song(song, xpos, ypos, width, height) + end + + --after selected + for i = math.min(selectedIndex + wheelSize/2, #songwheel.songs), selectedIndex + 1,-1 do + local song = songwheel.songs[i] + local xpos = x + width + local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05) + local ypos = y+((h/2 - height/2) - (selectedIndex - i) - offsetY) + local alpha = 255 - (selectedIndex - i + ioffset) * 31 + draw_song(song, xpos, ypos, width, height) + end + -- draw selected + local xpos = x + width + local offsetY = (ioffset/2) * ( height - (wheelSize/2*((1)*aspectFloat))) + local ypos = y+((h/2 - height/2) - (ioffset) - offsetY) + draw_song(songwheel.songs[selectedIndex], xpos, ypos, width, height, true) + -- cursor + gfx.BeginPath() + local ypos = y+((h/2 - height/2)) + gfx.Rect(xpos, ypos, width, height) + gfx.FillColor(0,0,0,0) + gfx.StrokeColor(255,128,0) + gfx.StrokeWidth(3) + gfx.Fill() + gfx.Stroke() + + return songwheel.songs[selectedIndex] +end +draw_legend_pane = function(x,y,w,h,obj) + local xpos = x+5 + local ypos = y + local imageSize = h + gfx.BeginPath() + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT) + gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0) + xpos = xpos + imageSize + 5 + gfx.FontSize(16); + if h < (w-(10+imageSize))/2 then + gfx.DrawLabel(obj.labelSingleLine, xpos, y+(h/2), w-(10+imageSize)) + else + gfx.DrawLabel(obj.labelMultiLine, xpos, y+(h/2), w-(10+imageSize)) + end + gfx.ForceRender() +end + +draw_legend = function(x,y,w,h) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT); + gfx.BeginPath() + gfx.FillColor(0,0,0,170) + gfx.Rect(x,y,w,h) + gfx.Fill() + local xpos = 10; + local legendWidth = math.floor((w-20)/#legendTable) + for i,v in ipairs(legendTable) do + local xOffset = draw_legend_pane(xpos+(legendWidth*(i-1)), y+5,legendWidth,h-10,legendTable[i]) + end +end + +draw_search = function(x,y,w,h) + soffset = soffset + (searchIndex) - (songwheel.searchInputActive and 0 or 1) + if searchIndex ~= (songwheel.searchInputActive and 0 or 1) then + game.PlaySample("woosh") + end + searchIndex = songwheel.searchInputActive and 0 or 1 + + gfx.BeginPath() + local bgfade = 1 - (searchIndex + soffset) + --if not songwheel.searchInputActive then bgfade = soffset end + gfx.FillColor(0,0,0,math.floor(200 * bgfade)) + gfx.Rect(0,0,resx,resy) + gfx.Fill() + gfx.ForceRender() + local xpos = x + (searchIndex + soffset)*w + gfx.UpdateLabel(searchText ,string.format("Search: %s",songwheel.searchText), 30, 0) + gfx.BeginPath() + gfx.RoundedRect(xpos,y,w,h,h/2) + gfx.FillColor(30,30,30) + gfx.StrokeColor(0,128,255) + gfx.StrokeWidth(1) + gfx.Fill() + gfx.Stroke() + gfx.BeginPath(); + gfx.LoadSkinFont("NotoSans-Regular.ttf"); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); + gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20) + +end + +render = function(deltaTime) + timer = (timer + deltaTime) + timer = timer % 2 + resx,resy = game.GetResolution(); + adjustScreen(resx,resy); + gfx.BeginPath(); + gfx.LoadSkinFont("dfmarugoth.ttf"); + gfx.FontSize(40); + gfx.FillColor(255,255,255); + if songwheel.songs[1] ~= nil then + --draw songwheel and get selected song + if aspectRatio == "PortraitWidescreen" then + local song = draw_songwheel(0,0,fullX,fullY) + --render selected song information + draw_selected(song, 0,0,fullX,resy) + else + local song = draw_songwheel(fifthX*2,0,fifthX*3,fullY) + --render selected song information + draw_selected(song, 0,0,fifthX*2,(fifthY/2)*9) + end + end + --Draw Legend Information + -- if showGuide then + -- if aspectRatio == "PortraitWidescreen" then + -- draw_legend(0,(fifthY/3)*14, fullX, (fifthY/3)*1) + -- else + -- draw_legend(0,(fifthY/2)*9, fullX, (fifthY/2)) + -- end + -- end + + --draw text search + if aspectRatio == "PortraitWidescreen" then + draw_search(fifthX*2,5,fifthX*3,fifthY/5) + else + draw_search(fifthX*2,5,fifthX*3,fifthY/3) + end + + ioffset = ioffset * 0.9 + doffset = doffset * 0.9 + soffset = soffset * 0.8 + if songwheel.searchStatus then + gfx.BeginPath() + gfx.FillColor(255,255,255) + gfx.FontSize(20); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.Text(songwheel.searchStatus, 3, 3) + end + if totalForce then + gfx.BeginPath() + gfx.FillColor(255,255,255) + gfx.FontSize(20); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) + local forceText = string.format("Force: %.2f", totalForce) + gfx.Text(forceText, 0, fullY) + end + gfx.LoadSkinFont("NotoSans-Regular.ttf"); + gfx.ResetTransform() + gfx.ForceRender() +end + +set_index = function(newIndex) + if newIndex ~= selectedIndex then + game.PlaySample("menu_click") + end + ioffset = ioffset + selectedIndex - newIndex + selectedIndex = newIndex +end; + +set_diff = function(newDiff) + if newDiff ~= selectedDiff then + game.PlaySample("click-02") + end + doffset = doffset + selectedDiff - newDiff + selectedDiff = newDiff +end; + +-- force calculation +-------------------- +totalForce = nil + +local badgeRates = { + 0.5, -- Played + 1.0, -- Cleared + 1.02, -- Hard clear + 1.04, -- UC + 1.1 -- PUC +} + +local gradeRates = { + {["min"] = 9900000, ["rate"] = 1.05}, -- S + {["min"] = 9800000, ["rate"] = 1.02}, -- AAA+ + {["min"] = 9700000, ["rate"] = 1}, -- AAA + {["min"] = 9500000, ["rate"] = 0.97}, -- AA+ + {["min"] = 9300000, ["rate"] = 0.94}, -- AA + {["min"] = 9000000, ["rate"] = 0.91}, -- A+ + {["min"] = 8700000, ["rate"] = 0.88}, -- A + {["min"] = 7500000, ["rate"] = 0.85}, -- B + {["min"] = 6500000, ["rate"] = 0.82}, -- C + {["min"] = 0, ["rate"] = 0.8} -- D +} + +calculate_force = function(diff) + if #diff.scores < 1 then + return 0 + end + local score = diff.scores[1] + local badgeRate = badgeRates[diff.topBadge] + local gradeRate + for i, v in ipairs(gradeRates) do + if score.score >= v.min then + gradeRate = v.rate + break + end + end + return math.floor((diff.level * 2) * (score.score / 10000000) * gradeRate * badgeRate) / 100 +end + +songs_changed = function(withAll) + if not withAll then return end + + recordCache = {} + + local diffs = {} + for i = 1, #songwheel.allSongs do + local song = songwheel.allSongs[i] + for j = 1, #song.difficulties do + local diff = song.difficulties[j] + diff.force = calculate_force(diff) + table.insert(diffs, diff) + end + end + table.sort(diffs, function (l, r) + return l.force > r.force + end) + totalForce = 0 + for i = 1, 50 do + if diffs[i] then + totalForce = totalForce + diffs[i].force + end + end +end diff --git a/scripts/songselect/sortwheel.lua b/scripts/songselect/sortwheel.lua index ad4fe9b..2d2a600 100644 --- a/scripts/songselect/sortwheel.lua +++ b/scripts/songselect/sortwheel.lua @@ -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 diff --git a/scripts/songtransition.lua b/scripts/songtransition.lua index 7258f22..f655210 100644 --- a/scripts/songtransition.lua +++ b/scripts/songtransition.lua @@ -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 \ No newline at end of file diff --git a/scripts/titlescreen OLD.lua b/scripts/titlescreen OLD.lua index adcfbe4..9b246b1 100644 --- a/scripts/titlescreen OLD.lua +++ b/scripts/titlescreen OLD.lua @@ -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 diff --git a/shaders/blackLaser.fs b/shaders/blackLaser.fs index a620f9f..0216861 100644 --- a/shaders/blackLaser.fs +++ b/shaders/blackLaser.fs @@ -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); } \ No newline at end of file diff --git a/shaders/blackLaser.vs b/shaders/blackLaser.vs index 20d9734..712b0c3 100644 --- a/shaders/blackLaser.vs +++ b/shaders/blackLaser.vs @@ -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); } \ No newline at end of file diff --git a/shaders/track.fs b/shaders/track.fs index 25d30f8..7569dae 100644 --- a/shaders/track.fs +++ b/shaders/track.fs @@ -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; } \ No newline at end of file diff --git a/shaders/track.vs b/shaders/track.vs index 20d9734..712b0c3 100644 --- a/shaders/track.vs +++ b/shaders/track.vs @@ -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); } \ No newline at end of file diff --git a/shaders/trackCover.fs b/shaders/trackCover.fs index 50346fe..818a493 100644 --- a/shaders/trackCover.fs +++ b/shaders/trackCover.fs @@ -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 } \ No newline at end of file diff --git a/shaders/trackCover.vs b/shaders/trackCover.vs index 52f4b0e..82fa942 100644 --- a/shaders/trackCover.vs +++ b/shaders/trackCover.vs @@ -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); } \ No newline at end of file diff --git a/textures/crew/make-a-crew/instructions.txt b/textures/crew/make-a-crew/instructions.txt index 85f995f..0b6954a 100644 --- a/textures/crew/make-a-crew/instructions.txt +++ b/textures/crew/make-a-crew/instructions.txt @@ -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! \ No newline at end of file