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/.gitignore b/.gitignore index 7e70a9d..8912bcc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,21 @@ +# IDE files +.vscode + +# secret(?) assets _asset song-assets skin-assets + +# generated skin files nautica.json skin.cfg -.vscode -/textures/crew/ \ No newline at end of file + +# any crew that's not the default one, we do not package crew +textures/crew/* +!textures/crew/appeal_card.png +!textures/crew/frame.png +!textures/crew/portrait.png +!textures/crew/make-a-crew/frame_glow.png +!textures/crew/make-a-crew/frame_metal.png +!textures/crew/make-a-crew/instructions.txt +!textures/crew/anim/nothing \ No newline at end of file diff --git a/README.md b/README.md index d679a97..21d672e 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,38 @@ -# 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 +- RealFD +- fdigl +- Hoshikara +- GSK Bladez +- Local +- Kuenaimaku + +## 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/audio/temp audio folder/3intro.wav b/audio/temp audio folder/Title/3 intro.wav similarity index 100% rename from audio/temp audio folder/3intro.wav rename to audio/temp audio folder/Title/3 intro.wav diff --git a/audio/titlescreen/splash/splash1.wav b/audio/titlescreen/splash/splash1.wav new file mode 100644 index 0000000..e57df8b Binary files /dev/null and b/audio/titlescreen/splash/splash1.wav differ 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/backgrounds/fallback/textures/background/anim/eye-thomas.jpg b/backgrounds/fallback/textures/background/anim/eye-thomas.jpg index 1000622..a41538f 100644 Binary files a/backgrounds/fallback/textures/background/anim/eye-thomas.jpg and b/backgrounds/fallback/textures/background/anim/eye-thomas.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/goldleaves-c.jpg b/backgrounds/fallback/textures/background/anim/goldleaves-c.jpg index 1d805b7..fa8cf2f 100644 Binary files a/backgrounds/fallback/textures/background/anim/goldleaves-c.jpg and b/backgrounds/fallback/textures/background/anim/goldleaves-c.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/goldleaves.jpg b/backgrounds/fallback/textures/background/anim/goldleaves.jpg index 5fccbc2..4a37fd3 100644 Binary files a/backgrounds/fallback/textures/background/anim/goldleaves.jpg and b/backgrounds/fallback/textures/background/anim/goldleaves.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/sakura1-c.jpg b/backgrounds/fallback/textures/background/anim/sakura1-c.jpg index 904663f..c878783 100644 Binary files a/backgrounds/fallback/textures/background/anim/sakura1-c.jpg and b/backgrounds/fallback/textures/background/anim/sakura1-c.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/sakura1.jpg b/backgrounds/fallback/textures/background/anim/sakura1.jpg index d5047ef..f8ad28d 100644 Binary files a/backgrounds/fallback/textures/background/anim/sakura1.jpg and b/backgrounds/fallback/textures/background/anim/sakura1.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/snow-c.jpg b/backgrounds/fallback/textures/background/anim/snow-c.jpg index e053ecf..8ccd829 100644 Binary files a/backgrounds/fallback/textures/background/anim/snow-c.jpg and b/backgrounds/fallback/textures/background/anim/snow-c.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/snow.jpg b/backgrounds/fallback/textures/background/anim/snow.jpg index 3d14405..9a23a00 100644 Binary files a/backgrounds/fallback/textures/background/anim/snow.jpg and b/backgrounds/fallback/textures/background/anim/snow.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/specific1-c.jpg b/backgrounds/fallback/textures/background/anim/specific1-c.jpg index a2438a0..7d3ca41 100644 Binary files a/backgrounds/fallback/textures/background/anim/specific1-c.jpg and b/backgrounds/fallback/textures/background/anim/specific1-c.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/specific1.jpg b/backgrounds/fallback/textures/background/anim/specific1.jpg index b6a41a9..e23c02c 100644 Binary files a/backgrounds/fallback/textures/background/anim/specific1.jpg and b/backgrounds/fallback/textures/background/anim/specific1.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/specific12-c.jpg b/backgrounds/fallback/textures/background/anim/specific12-c.jpg index fa8a398..02cd37a 100644 Binary files a/backgrounds/fallback/textures/background/anim/specific12-c.jpg and b/backgrounds/fallback/textures/background/anim/specific12-c.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/specific12.jpg b/backgrounds/fallback/textures/background/anim/specific12.jpg index 2034619..3d7248b 100644 Binary files a/backgrounds/fallback/textures/background/anim/specific12.jpg and b/backgrounds/fallback/textures/background/anim/specific12.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/specific13-c.jpg b/backgrounds/fallback/textures/background/anim/specific13-c.jpg index 4e73cee..430b878 100644 Binary files a/backgrounds/fallback/textures/background/anim/specific13-c.jpg and b/backgrounds/fallback/textures/background/anim/specific13-c.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/specific13.jpg b/backgrounds/fallback/textures/background/anim/specific13.jpg index 3ac85e9..510da74 100644 Binary files a/backgrounds/fallback/textures/background/anim/specific13.jpg and b/backgrounds/fallback/textures/background/anim/specific13.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/technocircle-c.jpg b/backgrounds/fallback/textures/background/anim/technocircle-c.jpg index 122e9d5..cac553d 100644 Binary files a/backgrounds/fallback/textures/background/anim/technocircle-c.jpg and b/backgrounds/fallback/textures/background/anim/technocircle-c.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/technocircle.jpg b/backgrounds/fallback/textures/background/anim/technocircle.jpg index e7fd09c..7e43717 100644 Binary files a/backgrounds/fallback/textures/background/anim/technocircle.jpg and b/backgrounds/fallback/textures/background/anim/technocircle.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/xcalibur-c.jpg b/backgrounds/fallback/textures/background/anim/xcalibur-c.jpg index c3e9037..b6a3e29 100644 Binary files a/backgrounds/fallback/textures/background/anim/xcalibur-c.jpg and b/backgrounds/fallback/textures/background/anim/xcalibur-c.jpg differ diff --git a/backgrounds/fallback/textures/background/anim/xcalibur.jpg b/backgrounds/fallback/textures/background/anim/xcalibur.jpg index 6e198d9..832a326 100644 Binary files a/backgrounds/fallback/textures/background/anim/xcalibur.jpg and b/backgrounds/fallback/textures/background/anim/xcalibur.jpg differ diff --git a/backgrounds/fallback/textures/background/beach.png b/backgrounds/fallback/textures/background/beach.png index b4feb3d..7af0d44 100644 Binary files a/backgrounds/fallback/textures/background/beach.png and b/backgrounds/fallback/textures/background/beach.png differ diff --git a/backgrounds/fallback/textures/background/bedroom-c.jpg b/backgrounds/fallback/textures/background/bedroom-c.jpg index 888ab2d..2dc31ee 100644 Binary files a/backgrounds/fallback/textures/background/bedroom-c.jpg and b/backgrounds/fallback/textures/background/bedroom-c.jpg differ diff --git a/backgrounds/fallback/textures/background/bedroom.jpg b/backgrounds/fallback/textures/background/bedroom.jpg index 5d67282..f4a4708 100644 Binary files a/backgrounds/fallback/textures/background/bedroom.jpg and b/backgrounds/fallback/textures/background/bedroom.jpg differ diff --git a/backgrounds/fallback/textures/background/brown_planet.jpg b/backgrounds/fallback/textures/background/brown_planet.jpg index 3dd9f3c..65f49ff 100644 Binary files a/backgrounds/fallback/textures/background/brown_planet.jpg and b/backgrounds/fallback/textures/background/brown_planet.jpg differ diff --git a/backgrounds/fallback/textures/background/city.png b/backgrounds/fallback/textures/background/city.png index 1e9d0e4..8706882 100644 Binary files a/backgrounds/fallback/textures/background/city.png and b/backgrounds/fallback/textures/background/city.png differ diff --git a/backgrounds/fallback/textures/background/cloudy-c.jpg b/backgrounds/fallback/textures/background/cloudy-c.jpg index 26bf071..ec7bfc1 100644 Binary files a/backgrounds/fallback/textures/background/cloudy-c.jpg and b/backgrounds/fallback/textures/background/cloudy-c.jpg differ diff --git a/backgrounds/fallback/textures/background/cloudy.jpg b/backgrounds/fallback/textures/background/cloudy.jpg index f8c199e..bfd2435 100644 Binary files a/backgrounds/fallback/textures/background/cloudy.jpg and b/backgrounds/fallback/textures/background/cloudy.jpg differ diff --git a/backgrounds/fallback/textures/background/cyber-c.jpg b/backgrounds/fallback/textures/background/cyber-c.jpg index ea386f9..1982883 100644 Binary files a/backgrounds/fallback/textures/background/cyber-c.jpg and b/backgrounds/fallback/textures/background/cyber-c.jpg differ diff --git a/backgrounds/fallback/textures/background/cyber.jpg b/backgrounds/fallback/textures/background/cyber.jpg index 5129ee5..f4c6ab5 100644 Binary files a/backgrounds/fallback/textures/background/cyber.jpg and b/backgrounds/fallback/textures/background/cyber.jpg differ diff --git a/backgrounds/fallback/textures/background/cyberspace-c.jpg b/backgrounds/fallback/textures/background/cyberspace-c.jpg index 7f2db0b..6a7a84e 100644 Binary files a/backgrounds/fallback/textures/background/cyberspace-c.jpg and b/backgrounds/fallback/textures/background/cyberspace-c.jpg differ diff --git a/backgrounds/fallback/textures/background/cyberspace.jpg b/backgrounds/fallback/textures/background/cyberspace.jpg index 84d64e4..663247e 100644 Binary files a/backgrounds/fallback/textures/background/cyberspace.jpg and b/backgrounds/fallback/textures/background/cyberspace.jpg differ diff --git a/backgrounds/fallback/textures/background/cyberspace_night.png b/backgrounds/fallback/textures/background/cyberspace_night.png index 070b39c..6b16b6e 100644 Binary files a/backgrounds/fallback/textures/background/cyberspace_night.png and b/backgrounds/fallback/textures/background/cyberspace_night.png differ diff --git a/backgrounds/fallback/textures/background/cyberspace_night_starburst.png b/backgrounds/fallback/textures/background/cyberspace_night_starburst.png index 61b7b28..9a47550 100644 Binary files a/backgrounds/fallback/textures/background/cyberspace_night_starburst.png and b/backgrounds/fallback/textures/background/cyberspace_night_starburst.png differ diff --git a/backgrounds/fallback/textures/background/cyberspace_sunrise-f.png b/backgrounds/fallback/textures/background/cyberspace_sunrise-f.png index 8f8b36f..3bf08eb 100644 Binary files a/backgrounds/fallback/textures/background/cyberspace_sunrise-f.png and b/backgrounds/fallback/textures/background/cyberspace_sunrise-f.png differ diff --git a/backgrounds/fallback/textures/background/cyberspace_sunrise.png b/backgrounds/fallback/textures/background/cyberspace_sunrise.png index 42e114d..1cbaa67 100644 Binary files a/backgrounds/fallback/textures/background/cyberspace_sunrise.png and b/backgrounds/fallback/textures/background/cyberspace_sunrise.png differ diff --git a/backgrounds/fallback/textures/background/deepsea-c.jpg b/backgrounds/fallback/textures/background/deepsea-c.jpg index 88e1eb2..7a663eb 100644 Binary files a/backgrounds/fallback/textures/background/deepsea-c.jpg and b/backgrounds/fallback/textures/background/deepsea-c.jpg differ diff --git a/backgrounds/fallback/textures/background/deepsea.jpg b/backgrounds/fallback/textures/background/deepsea.jpg index 9351938..fa6ca69 100644 Binary files a/backgrounds/fallback/textures/background/deepsea.jpg and b/backgrounds/fallback/textures/background/deepsea.jpg differ diff --git a/backgrounds/fallback/textures/background/desert-c.jpg b/backgrounds/fallback/textures/background/desert-c.jpg index 8634ba4..9e30a16 100644 Binary files a/backgrounds/fallback/textures/background/desert-c.jpg and b/backgrounds/fallback/textures/background/desert-c.jpg differ diff --git a/backgrounds/fallback/textures/background/desert-c2.jpg b/backgrounds/fallback/textures/background/desert-c2.jpg index 619e193..a8d1571 100644 Binary files a/backgrounds/fallback/textures/background/desert-c2.jpg and b/backgrounds/fallback/textures/background/desert-c2.jpg differ diff --git a/backgrounds/fallback/textures/background/desert.jpg b/backgrounds/fallback/textures/background/desert.jpg index 04d0f0f..73cdb49 100644 Binary files a/backgrounds/fallback/textures/background/desert.jpg and b/backgrounds/fallback/textures/background/desert.jpg differ diff --git a/backgrounds/fallback/textures/background/fantasy-c.jpg b/backgrounds/fallback/textures/background/fantasy-c.jpg index f8ec236..94a0b11 100644 Binary files a/backgrounds/fallback/textures/background/fantasy-c.jpg and b/backgrounds/fallback/textures/background/fantasy-c.jpg differ diff --git a/backgrounds/fallback/textures/background/fantasy.jpg b/backgrounds/fallback/textures/background/fantasy.jpg index 5853650..6751fdf 100644 Binary files a/backgrounds/fallback/textures/background/fantasy.jpg and b/backgrounds/fallback/textures/background/fantasy.jpg differ diff --git a/backgrounds/fallback/textures/background/flame-c.jpg b/backgrounds/fallback/textures/background/flame-c.jpg index ee63b35..431f659 100644 Binary files a/backgrounds/fallback/textures/background/flame-c.jpg and b/backgrounds/fallback/textures/background/flame-c.jpg differ diff --git a/backgrounds/fallback/textures/background/flame.jpg b/backgrounds/fallback/textures/background/flame.jpg index 5242e22..f215ab8 100644 Binary files a/backgrounds/fallback/textures/background/flame.jpg and b/backgrounds/fallback/textures/background/flame.jpg differ diff --git a/backgrounds/fallback/textures/background/galaxy-c.jpg b/backgrounds/fallback/textures/background/galaxy-c.jpg index b86bce1..5757271 100644 Binary files a/backgrounds/fallback/textures/background/galaxy-c.jpg and b/backgrounds/fallback/textures/background/galaxy-c.jpg differ diff --git a/backgrounds/fallback/textures/background/game-f.png b/backgrounds/fallback/textures/background/game-f.png index 8941642..28a3134 100644 Binary files a/backgrounds/fallback/textures/background/game-f.png and b/backgrounds/fallback/textures/background/game-f.png differ diff --git a/backgrounds/fallback/textures/background/grass-c.jpg b/backgrounds/fallback/textures/background/grass-c.jpg index 939c4ba..45daf6e 100644 Binary files a/backgrounds/fallback/textures/background/grass-c.jpg and b/backgrounds/fallback/textures/background/grass-c.jpg differ diff --git a/backgrounds/fallback/textures/background/grass.jpg b/backgrounds/fallback/textures/background/grass.jpg index d89d931..d181264 100644 Binary files a/backgrounds/fallback/textures/background/grass.jpg and b/backgrounds/fallback/textures/background/grass.jpg differ diff --git a/backgrounds/fallback/textures/background/jomanda.png b/backgrounds/fallback/textures/background/jomanda.png index 9ae7adf..9aa3aaf 100644 Binary files a/backgrounds/fallback/textures/background/jomanda.png and b/backgrounds/fallback/textures/background/jomanda.png differ diff --git a/backgrounds/fallback/textures/background/mars-c.jpg b/backgrounds/fallback/textures/background/mars-c.jpg index 42cb365..67fdf97 100644 Binary files a/backgrounds/fallback/textures/background/mars-c.jpg and b/backgrounds/fallback/textures/background/mars-c.jpg differ diff --git a/backgrounds/fallback/textures/background/mars.jpg b/backgrounds/fallback/textures/background/mars.jpg index 91d574d..d671fb6 100644 Binary files a/backgrounds/fallback/textures/background/mars.jpg and b/backgrounds/fallback/textures/background/mars.jpg differ diff --git a/backgrounds/fallback/textures/background/moon_blue-c.jpg b/backgrounds/fallback/textures/background/moon_blue-c.jpg index cda0710..862f7a6 100644 Binary files a/backgrounds/fallback/textures/background/moon_blue-c.jpg and b/backgrounds/fallback/textures/background/moon_blue-c.jpg differ diff --git a/backgrounds/fallback/textures/background/moon_blue.jpg b/backgrounds/fallback/textures/background/moon_blue.jpg index 332a026..53afe74 100644 Binary files a/backgrounds/fallback/textures/background/moon_blue.jpg and b/backgrounds/fallback/textures/background/moon_blue.jpg differ diff --git a/backgrounds/fallback/textures/background/moon_purple-c.jpg b/backgrounds/fallback/textures/background/moon_purple-c.jpg index bffe18b..f0ba815 100644 Binary files a/backgrounds/fallback/textures/background/moon_purple-c.jpg and b/backgrounds/fallback/textures/background/moon_purple-c.jpg differ diff --git a/backgrounds/fallback/textures/background/moon_purple.jpg b/backgrounds/fallback/textures/background/moon_purple.jpg index af4c20b..6334de8 100644 Binary files a/backgrounds/fallback/textures/background/moon_purple.jpg and b/backgrounds/fallback/textures/background/moon_purple.jpg differ diff --git a/backgrounds/fallback/textures/background/night-c.jpg b/backgrounds/fallback/textures/background/night-c.jpg index 3e385b0..2554bed 100644 Binary files a/backgrounds/fallback/textures/background/night-c.jpg and b/backgrounds/fallback/textures/background/night-c.jpg differ diff --git a/backgrounds/fallback/textures/background/night.jpg b/backgrounds/fallback/textures/background/night.jpg index 8778234..d8d3fd3 100644 Binary files a/backgrounds/fallback/textures/background/night.jpg and b/backgrounds/fallback/textures/background/night.jpg differ diff --git a/backgrounds/fallback/textures/background/ocean-c.jpg b/backgrounds/fallback/textures/background/ocean-c.jpg index 1b032f2..bf81458 100644 Binary files a/backgrounds/fallback/textures/background/ocean-c.jpg and b/backgrounds/fallback/textures/background/ocean-c.jpg differ diff --git a/backgrounds/fallback/textures/background/ocean.jpg b/backgrounds/fallback/textures/background/ocean.jpg index 6bb2269..54f1e62 100644 Binary files a/backgrounds/fallback/textures/background/ocean.jpg and b/backgrounds/fallback/textures/background/ocean.jpg differ diff --git a/backgrounds/fallback/textures/background/prettygalaxy-c.jpg b/backgrounds/fallback/textures/background/prettygalaxy-c.jpg index e209bb1..3006056 100644 Binary files a/backgrounds/fallback/textures/background/prettygalaxy-c.jpg and b/backgrounds/fallback/textures/background/prettygalaxy-c.jpg differ diff --git a/backgrounds/fallback/textures/background/prettygalaxy.jpg b/backgrounds/fallback/textures/background/prettygalaxy.jpg index 91afb13..25a3da6 100644 Binary files a/backgrounds/fallback/textures/background/prettygalaxy.jpg and b/backgrounds/fallback/textures/background/prettygalaxy.jpg differ diff --git a/backgrounds/fallback/textures/background/red_dusk-c.jpg b/backgrounds/fallback/textures/background/red_dusk-c.jpg index 76e052a..34099de 100644 Binary files a/backgrounds/fallback/textures/background/red_dusk-c.jpg and b/backgrounds/fallback/textures/background/red_dusk-c.jpg differ diff --git a/backgrounds/fallback/textures/background/red_dusk.jpg b/backgrounds/fallback/textures/background/red_dusk.jpg index ea4e17f..4790166 100644 Binary files a/backgrounds/fallback/textures/background/red_dusk.jpg and b/backgrounds/fallback/textures/background/red_dusk.jpg differ diff --git a/backgrounds/fallback/textures/background/redblur-c.jpg b/backgrounds/fallback/textures/background/redblur-c.jpg index a61db5c..3df54f4 100644 Binary files a/backgrounds/fallback/textures/background/redblur-c.jpg and b/backgrounds/fallback/textures/background/redblur-c.jpg differ diff --git a/backgrounds/fallback/textures/background/redblur.jpg b/backgrounds/fallback/textures/background/redblur.jpg index b9e908e..3bda6e1 100644 Binary files a/backgrounds/fallback/textures/background/redblur.jpg and b/backgrounds/fallback/textures/background/redblur.jpg differ diff --git a/backgrounds/fallback/textures/background/redgradient.jpg b/backgrounds/fallback/textures/background/redgradient.jpg index 7939d75..4bdde1b 100644 Binary files a/backgrounds/fallback/textures/background/redgradient.jpg and b/backgrounds/fallback/textures/background/redgradient.jpg differ diff --git a/backgrounds/fallback/textures/background/sakura-c.jpg b/backgrounds/fallback/textures/background/sakura-c.jpg index 4ce1ec1..ce2efeb 100644 Binary files a/backgrounds/fallback/textures/background/sakura-c.jpg and b/backgrounds/fallback/textures/background/sakura-c.jpg differ diff --git a/backgrounds/fallback/textures/background/sakura.jpg b/backgrounds/fallback/textures/background/sakura.jpg index be9af0f..d3c2db6 100644 Binary files a/backgrounds/fallback/textures/background/sakura.jpg and b/backgrounds/fallback/textures/background/sakura.jpg differ diff --git a/backgrounds/fallback/textures/background/sea-day-f.png b/backgrounds/fallback/textures/background/sea-day-f.png index d468753..38eac66 100644 Binary files a/backgrounds/fallback/textures/background/sea-day-f.png and b/backgrounds/fallback/textures/background/sea-day-f.png differ diff --git a/backgrounds/fallback/textures/background/sea-day.png b/backgrounds/fallback/textures/background/sea-day.png index 28e82f2..454a671 100644 Binary files a/backgrounds/fallback/textures/background/sea-day.png and b/backgrounds/fallback/textures/background/sea-day.png differ diff --git a/backgrounds/fallback/textures/background/sea-ice-f.png b/backgrounds/fallback/textures/background/sea-ice-f.png index 4ac5a17..bdfb803 100644 Binary files a/backgrounds/fallback/textures/background/sea-ice-f.png and b/backgrounds/fallback/textures/background/sea-ice-f.png differ diff --git a/backgrounds/fallback/textures/background/sea-ice.png b/backgrounds/fallback/textures/background/sea-ice.png index 2a97991..4e02263 100644 Binary files a/backgrounds/fallback/textures/background/sea-ice.png and b/backgrounds/fallback/textures/background/sea-ice.png differ diff --git a/backgrounds/fallback/textures/background/sea-night-f.png b/backgrounds/fallback/textures/background/sea-night-f.png index e6adb09..f6c8465 100644 Binary files a/backgrounds/fallback/textures/background/sea-night-f.png and b/backgrounds/fallback/textures/background/sea-night-f.png differ diff --git a/backgrounds/fallback/textures/background/sea-night.png b/backgrounds/fallback/textures/background/sea-night.png index 18838c4..7196a14 100644 Binary files a/backgrounds/fallback/textures/background/sea-night.png and b/backgrounds/fallback/textures/background/sea-night.png differ diff --git a/backgrounds/fallback/textures/background/sea-storm-f.png b/backgrounds/fallback/textures/background/sea-storm-f.png index 5293550..d77d03d 100644 Binary files a/backgrounds/fallback/textures/background/sea-storm-f.png and b/backgrounds/fallback/textures/background/sea-storm-f.png differ diff --git a/backgrounds/fallback/textures/background/sea-storm.png b/backgrounds/fallback/textures/background/sea-storm.png index 26be8cf..086e080 100644 Binary files a/backgrounds/fallback/textures/background/sea-storm.png and b/backgrounds/fallback/textures/background/sea-storm.png differ diff --git a/backgrounds/fallback/textures/background/sea-thunder-f.png b/backgrounds/fallback/textures/background/sea-thunder-f.png index 91026a2..f160e4c 100644 Binary files a/backgrounds/fallback/textures/background/sea-thunder-f.png and b/backgrounds/fallback/textures/background/sea-thunder-f.png differ diff --git a/backgrounds/fallback/textures/background/sea-thunder.png b/backgrounds/fallback/textures/background/sea-thunder.png index a2ee02e..8e4cc7d 100644 Binary files a/backgrounds/fallback/textures/background/sea-thunder.png and b/backgrounds/fallback/textures/background/sea-thunder.png differ diff --git a/backgrounds/fallback/textures/background/sky-c.jpg b/backgrounds/fallback/textures/background/sky-c.jpg index 9b61185..8c22f52 100644 Binary files a/backgrounds/fallback/textures/background/sky-c.jpg and b/backgrounds/fallback/textures/background/sky-c.jpg differ diff --git a/backgrounds/fallback/textures/background/sky-iv-c.jpg b/backgrounds/fallback/textures/background/sky-iv-c.jpg index 4ea0f82..15cf491 100644 Binary files a/backgrounds/fallback/textures/background/sky-iv-c.jpg and b/backgrounds/fallback/textures/background/sky-iv-c.jpg differ diff --git a/backgrounds/fallback/textures/background/sky-iv-dark.jpg b/backgrounds/fallback/textures/background/sky-iv-dark.jpg index 2ded8cc..e15c756 100644 Binary files a/backgrounds/fallback/textures/background/sky-iv-dark.jpg and b/backgrounds/fallback/textures/background/sky-iv-dark.jpg differ diff --git a/backgrounds/fallback/textures/background/sky-iv.jpg b/backgrounds/fallback/textures/background/sky-iv.jpg index 5530115..297b0c8 100644 Binary files a/backgrounds/fallback/textures/background/sky-iv.jpg and b/backgrounds/fallback/textures/background/sky-iv.jpg differ diff --git a/backgrounds/fallback/textures/background/sky.jpg b/backgrounds/fallback/textures/background/sky.jpg index a75f570..3865177 100644 Binary files a/backgrounds/fallback/textures/background/sky.jpg and b/backgrounds/fallback/textures/background/sky.jpg differ diff --git a/backgrounds/fallback/textures/background/sky_iv_2-c.png b/backgrounds/fallback/textures/background/sky_iv_2-c.png index 2166991..c5080ab 100644 Binary files a/backgrounds/fallback/textures/background/sky_iv_2-c.png and b/backgrounds/fallback/textures/background/sky_iv_2-c.png differ diff --git a/backgrounds/fallback/textures/background/sky_iv_2.png b/backgrounds/fallback/textures/background/sky_iv_2.png index e6c6672..6afd759 100644 Binary files a/backgrounds/fallback/textures/background/sky_iv_2.png and b/backgrounds/fallback/textures/background/sky_iv_2.png differ diff --git a/backgrounds/fallback/textures/background/star-c.png b/backgrounds/fallback/textures/background/star-c.png index 212e29c..2979788 100644 Binary files a/backgrounds/fallback/textures/background/star-c.png and b/backgrounds/fallback/textures/background/star-c.png differ diff --git a/backgrounds/fallback/textures/background/star.png b/backgrounds/fallback/textures/background/star.png index 0f988b1..06de084 100644 Binary files a/backgrounds/fallback/textures/background/star.png and b/backgrounds/fallback/textures/background/star.png differ diff --git a/backgrounds/fallback/textures/background/sunset-c.jpg b/backgrounds/fallback/textures/background/sunset-c.jpg index eda49fe..1cb6b07 100644 Binary files a/backgrounds/fallback/textures/background/sunset-c.jpg and b/backgrounds/fallback/textures/background/sunset-c.jpg differ diff --git a/backgrounds/fallback/textures/background/sunset.jpg b/backgrounds/fallback/textures/background/sunset.jpg index 1081fd9..552453f 100644 Binary files a/backgrounds/fallback/textures/background/sunset.jpg and b/backgrounds/fallback/textures/background/sunset.jpg differ diff --git a/backgrounds/fallback/textures/background/twilight-c.png b/backgrounds/fallback/textures/background/twilight-c.png index 9fad729..2ca8220 100644 Binary files a/backgrounds/fallback/textures/background/twilight-c.png and b/backgrounds/fallback/textures/background/twilight-c.png differ diff --git a/backgrounds/fallback/textures/background/twilight.png b/backgrounds/fallback/textures/background/twilight.png index 7c21217..5909b57 100644 Binary files a/backgrounds/fallback/textures/background/twilight.png and b/backgrounds/fallback/textures/background/twilight.png differ diff --git a/backgrounds/fallback/textures/background/undersea-c.png b/backgrounds/fallback/textures/background/undersea-c.png index 79bb58a..a6d9ac2 100644 Binary files a/backgrounds/fallback/textures/background/undersea-c.png and b/backgrounds/fallback/textures/background/undersea-c.png differ diff --git a/backgrounds/fallback/textures/background/undersea.png b/backgrounds/fallback/textures/background/undersea.png index 2bc7c80..011c162 100644 Binary files a/backgrounds/fallback/textures/background/undersea.png and b/backgrounds/fallback/textures/background/undersea.png differ diff --git a/backgrounds/fallback/textures/background/underwater-c.jpg b/backgrounds/fallback/textures/background/underwater-c.jpg index 8e9d1b4..f0bfac1 100644 Binary files a/backgrounds/fallback/textures/background/underwater-c.jpg and b/backgrounds/fallback/textures/background/underwater-c.jpg differ diff --git a/backgrounds/fallback/textures/background/underwater.jpg b/backgrounds/fallback/textures/background/underwater.jpg index 30cf8fc..2703e4b 100644 Binary files a/backgrounds/fallback/textures/background/underwater.jpg and b/backgrounds/fallback/textures/background/underwater.jpg differ diff --git a/backgrounds/fallback/textures/background/watervault-c.jpg b/backgrounds/fallback/textures/background/watervault-c.jpg index c06299b..7ff642e 100644 Binary files a/backgrounds/fallback/textures/background/watervault-c.jpg and b/backgrounds/fallback/textures/background/watervault-c.jpg differ diff --git a/backgrounds/fallback/textures/background/watervault.jpg b/backgrounds/fallback/textures/background/watervault.jpg index dd941cb..9ea3004 100644 Binary files a/backgrounds/fallback/textures/background/watervault.jpg and b/backgrounds/fallback/textures/background/watervault.jpg differ diff --git a/backgrounds/fallback/textures/center/anim/hiyuki.png b/backgrounds/fallback/textures/center/anim/hiyuki.png index 5f74170..54c6818 100644 Binary files a/backgrounds/fallback/textures/center/anim/hiyuki.png and b/backgrounds/fallback/textures/center/anim/hiyuki.png differ diff --git a/backgrounds/fallback/textures/center/chikyu.png b/backgrounds/fallback/textures/center/chikyu.png index b244f4b..925c9db 100644 Binary files a/backgrounds/fallback/textures/center/chikyu.png and b/backgrounds/fallback/textures/center/chikyu.png differ diff --git a/backgrounds/fallback/textures/center/cyberspace_shine.png b/backgrounds/fallback/textures/center/cyberspace_shine.png index 11e1fea..00d8e8d 100644 Binary files a/backgrounds/fallback/textures/center/cyberspace_shine.png and b/backgrounds/fallback/textures/center/cyberspace_shine.png differ diff --git a/backgrounds/fallback/textures/center/glowshine.png b/backgrounds/fallback/textures/center/glowshine.png index 992ed82..704a24d 100644 Binary files a/backgrounds/fallback/textures/center/glowshine.png and b/backgrounds/fallback/textures/center/glowshine.png differ diff --git a/backgrounds/fallback/textures/center/glowshine_cyan.png b/backgrounds/fallback/textures/center/glowshine_cyan.png index 5566c0d..74a7984 100644 Binary files a/backgrounds/fallback/textures/center/glowshine_cyan.png and b/backgrounds/fallback/textures/center/glowshine_cyan.png differ diff --git a/backgrounds/fallback/textures/center/glowshine_green.png b/backgrounds/fallback/textures/center/glowshine_green.png index 21f3ec3..aa0224a 100644 Binary files a/backgrounds/fallback/textures/center/glowshine_green.png and b/backgrounds/fallback/textures/center/glowshine_green.png differ diff --git a/backgrounds/fallback/textures/center/glowshine_orange.png b/backgrounds/fallback/textures/center/glowshine_orange.png index f95c061..a3b7024 100644 Binary files a/backgrounds/fallback/textures/center/glowshine_orange.png and b/backgrounds/fallback/textures/center/glowshine_orange.png differ diff --git a/backgrounds/fallback/textures/center/glowshine_partial.png b/backgrounds/fallback/textures/center/glowshine_partial.png index 447a8a0..cffd1f9 100644 Binary files a/backgrounds/fallback/textures/center/glowshine_partial.png and b/backgrounds/fallback/textures/center/glowshine_partial.png differ diff --git a/backgrounds/fallback/textures/center/glowshine_pink.png b/backgrounds/fallback/textures/center/glowshine_pink.png index 4d31618..5583e3f 100644 Binary files a/backgrounds/fallback/textures/center/glowshine_pink.png and b/backgrounds/fallback/textures/center/glowshine_pink.png differ diff --git a/backgrounds/fallback/textures/center/glowshine_sun.png b/backgrounds/fallback/textures/center/glowshine_sun.png index ba6a139..8a2d721 100644 Binary files a/backgrounds/fallback/textures/center/glowshine_sun.png and b/backgrounds/fallback/textures/center/glowshine_sun.png differ diff --git a/backgrounds/fallback/textures/center/kac-hikari-2.png b/backgrounds/fallback/textures/center/kac-hikari-2.png index 814cd46..6bdcfe4 100644 Binary files a/backgrounds/fallback/textures/center/kac-hikari-2.png and b/backgrounds/fallback/textures/center/kac-hikari-2.png differ diff --git a/backgrounds/fallback/textures/center/kac-hikari.png b/backgrounds/fallback/textures/center/kac-hikari.png index 321960b..a8f15cf 100644 Binary files a/backgrounds/fallback/textures/center/kac-hikari.png and b/backgrounds/fallback/textures/center/kac-hikari.png differ diff --git a/backgrounds/fallback/textures/center/kac_hikari_3.png b/backgrounds/fallback/textures/center/kac_hikari_3.png index b887c5f..aa87257 100644 Binary files a/backgrounds/fallback/textures/center/kac_hikari_3.png and b/backgrounds/fallback/textures/center/kac_hikari_3.png differ diff --git a/backgrounds/fallback/textures/center/kac_hikari_cyan.png b/backgrounds/fallback/textures/center/kac_hikari_cyan.png index 3589e9f..49f3917 100644 Binary files a/backgrounds/fallback/textures/center/kac_hikari_cyan.png and b/backgrounds/fallback/textures/center/kac_hikari_cyan.png differ diff --git a/backgrounds/fallback/textures/center/kac_hikari_iseki.png b/backgrounds/fallback/textures/center/kac_hikari_iseki.png index 01f7fb2..fba46fd 100644 Binary files a/backgrounds/fallback/textures/center/kac_hikari_iseki.png and b/backgrounds/fallback/textures/center/kac_hikari_iseki.png differ diff --git a/backgrounds/fallback/textures/center/kac_hikari_pink.png b/backgrounds/fallback/textures/center/kac_hikari_pink.png index 227eb59..9e13e80 100644 Binary files a/backgrounds/fallback/textures/center/kac_hikari_pink.png and b/backgrounds/fallback/textures/center/kac_hikari_pink.png differ diff --git a/backgrounds/fallback/textures/center/kac_hikari_sakura.png b/backgrounds/fallback/textures/center/kac_hikari_sakura.png index 29fd64d..4050953 100644 Binary files a/backgrounds/fallback/textures/center/kac_hikari_sakura.png and b/backgrounds/fallback/textures/center/kac_hikari_sakura.png differ diff --git a/backgrounds/fallback/textures/center/kac_maxima_gold.png b/backgrounds/fallback/textures/center/kac_maxima_gold.png index 0e7199d..47feea5 100644 Binary files a/backgrounds/fallback/textures/center/kac_maxima_gold.png and b/backgrounds/fallback/textures/center/kac_maxima_gold.png differ diff --git a/backgrounds/fallback/textures/center/kirari.png b/backgrounds/fallback/textures/center/kirari.png index 105dc34..90053af 100644 Binary files a/backgrounds/fallback/textures/center/kirari.png and b/backgrounds/fallback/textures/center/kirari.png differ diff --git a/backgrounds/fallback/textures/center/kouhai_i_eab.png b/backgrounds/fallback/textures/center/kouhai_i_eab.png index 8cb6826..b859445 100644 Binary files a/backgrounds/fallback/textures/center/kouhai_i_eab.png and b/backgrounds/fallback/textures/center/kouhai_i_eab.png differ diff --git a/backgrounds/fallback/textures/center/logo-c.png b/backgrounds/fallback/textures/center/logo-c.png index 37011fe..1c4fcef 100644 Binary files a/backgrounds/fallback/textures/center/logo-c.png and b/backgrounds/fallback/textures/center/logo-c.png differ diff --git a/backgrounds/fallback/textures/center/logo.png b/backgrounds/fallback/textures/center/logo.png index fee6dd3..64bbc5c 100644 Binary files a/backgrounds/fallback/textures/center/logo.png and b/backgrounds/fallback/textures/center/logo.png differ diff --git a/backgrounds/fallback/textures/center/magic_circle-c.png b/backgrounds/fallback/textures/center/magic_circle-c.png index 096bc3e..34bd1c9 100644 Binary files a/backgrounds/fallback/textures/center/magic_circle-c.png and b/backgrounds/fallback/textures/center/magic_circle-c.png differ diff --git a/backgrounds/fallback/textures/center/maid.png b/backgrounds/fallback/textures/center/maid.png index c30cb84..5c6cabe 100644 Binary files a/backgrounds/fallback/textures/center/maid.png and b/backgrounds/fallback/textures/center/maid.png differ diff --git a/backgrounds/fallback/textures/center/maxma.png b/backgrounds/fallback/textures/center/maxma.png index 45f8068..a35932a 100644 Binary files a/backgrounds/fallback/textures/center/maxma.png and b/backgrounds/fallback/textures/center/maxma.png differ diff --git a/backgrounds/fallback/textures/center/moon1.png b/backgrounds/fallback/textures/center/moon1.png index b648d04..75543b7 100644 Binary files a/backgrounds/fallback/textures/center/moon1.png and b/backgrounds/fallback/textures/center/moon1.png differ diff --git a/backgrounds/fallback/textures/center/moon2-c.png b/backgrounds/fallback/textures/center/moon2-c.png index 98058c3..de0375d 100644 Binary files a/backgrounds/fallback/textures/center/moon2-c.png and b/backgrounds/fallback/textures/center/moon2-c.png differ diff --git a/backgrounds/fallback/textures/center/moon2.png b/backgrounds/fallback/textures/center/moon2.png index 50b9c9a..a10c0a4 100644 Binary files a/backgrounds/fallback/textures/center/moon2.png and b/backgrounds/fallback/textures/center/moon2.png differ diff --git a/backgrounds/fallback/textures/center/moon2_shine.png b/backgrounds/fallback/textures/center/moon2_shine.png index 8367e24..5ecced8 100644 Binary files a/backgrounds/fallback/textures/center/moon2_shine.png and b/backgrounds/fallback/textures/center/moon2_shine.png differ diff --git a/backgrounds/fallback/textures/center/moon_orange-c.png b/backgrounds/fallback/textures/center/moon_orange-c.png index ffea28f..83e7615 100644 Binary files a/backgrounds/fallback/textures/center/moon_orange-c.png and b/backgrounds/fallback/textures/center/moon_orange-c.png differ diff --git a/backgrounds/fallback/textures/center/moon_orange.png b/backgrounds/fallback/textures/center/moon_orange.png index 712a7f4..9e353c6 100644 Binary files a/backgrounds/fallback/textures/center/moon_orange.png and b/backgrounds/fallback/textures/center/moon_orange.png differ diff --git a/backgrounds/fallback/textures/center/moon_orange_shine.png b/backgrounds/fallback/textures/center/moon_orange_shine.png index 2df0eca..492890f 100644 Binary files a/backgrounds/fallback/textures/center/moon_orange_shine.png and b/backgrounds/fallback/textures/center/moon_orange_shine.png differ diff --git a/backgrounds/fallback/textures/center/moon_pink-c.png b/backgrounds/fallback/textures/center/moon_pink-c.png index d1626fe..f02fa38 100644 Binary files a/backgrounds/fallback/textures/center/moon_pink-c.png and b/backgrounds/fallback/textures/center/moon_pink-c.png differ diff --git a/backgrounds/fallback/textures/center/moon_pink.png b/backgrounds/fallback/textures/center/moon_pink.png index 2fe338c..648a272 100644 Binary files a/backgrounds/fallback/textures/center/moon_pink.png and b/backgrounds/fallback/textures/center/moon_pink.png differ diff --git a/backgrounds/fallback/textures/center/moon_pink_shine.png b/backgrounds/fallback/textures/center/moon_pink_shine.png index 1391247..cfe72a4 100644 Binary files a/backgrounds/fallback/textures/center/moon_pink_shine.png and b/backgrounds/fallback/textures/center/moon_pink_shine.png differ diff --git a/backgrounds/fallback/textures/center/moon_pink_shine2.png b/backgrounds/fallback/textures/center/moon_pink_shine2.png index fd9c1b4..c85433f 100644 Binary files a/backgrounds/fallback/textures/center/moon_pink_shine2.png and b/backgrounds/fallback/textures/center/moon_pink_shine2.png differ diff --git a/backgrounds/fallback/textures/center/moon_twilight.png b/backgrounds/fallback/textures/center/moon_twilight.png index a478223..957fdf0 100644 Binary files a/backgrounds/fallback/textures/center/moon_twilight.png and b/backgrounds/fallback/textures/center/moon_twilight.png differ diff --git a/backgrounds/fallback/textures/center/moon_twilight_shine.png b/backgrounds/fallback/textures/center/moon_twilight_shine.png index bc067cc..f106df6 100644 Binary files a/backgrounds/fallback/textures/center/moon_twilight_shine.png and b/backgrounds/fallback/textures/center/moon_twilight_shine.png differ diff --git a/backgrounds/fallback/textures/center/rainbow.png b/backgrounds/fallback/textures/center/rainbow.png index d019d8f..9b8bfa9 100644 Binary files a/backgrounds/fallback/textures/center/rainbow.png and b/backgrounds/fallback/textures/center/rainbow.png differ diff --git a/backgrounds/fallback/textures/center/rainbow2.png b/backgrounds/fallback/textures/center/rainbow2.png index 2239c55..af66894 100644 Binary files a/backgrounds/fallback/textures/center/rainbow2.png and b/backgrounds/fallback/textures/center/rainbow2.png differ diff --git a/backgrounds/fallback/textures/center/sdvx_iv.png b/backgrounds/fallback/textures/center/sdvx_iv.png index 07a7b7e..084afbb 100644 Binary files a/backgrounds/fallback/textures/center/sdvx_iv.png and b/backgrounds/fallback/textures/center/sdvx_iv.png differ diff --git a/backgrounds/fallback/textures/center/shinwa.png b/backgrounds/fallback/textures/center/shinwa.png index 61d0a00..7e18acc 100644 Binary files a/backgrounds/fallback/textures/center/shinwa.png and b/backgrounds/fallback/textures/center/shinwa.png differ diff --git a/backgrounds/fallback/textures/center/ship-day.png b/backgrounds/fallback/textures/center/ship-day.png index 4cb814f..04d0da6 100644 Binary files a/backgrounds/fallback/textures/center/ship-day.png and b/backgrounds/fallback/textures/center/ship-day.png differ diff --git a/backgrounds/fallback/textures/center/ship-ice.png b/backgrounds/fallback/textures/center/ship-ice.png index dd308d3..be652d1 100644 Binary files a/backgrounds/fallback/textures/center/ship-ice.png and b/backgrounds/fallback/textures/center/ship-ice.png differ diff --git a/backgrounds/fallback/textures/center/ship-night.png b/backgrounds/fallback/textures/center/ship-night.png index 9eaa21d..3450c7c 100644 Binary files a/backgrounds/fallback/textures/center/ship-night.png and b/backgrounds/fallback/textures/center/ship-night.png differ diff --git a/backgrounds/fallback/textures/center/ship-storm.png b/backgrounds/fallback/textures/center/ship-storm.png index 337e941..88c3bc9 100644 Binary files a/backgrounds/fallback/textures/center/ship-storm.png and b/backgrounds/fallback/textures/center/ship-storm.png differ diff --git a/backgrounds/fallback/textures/center/sonar.png b/backgrounds/fallback/textures/center/sonar.png index c13761d..ae4e633 100644 Binary files a/backgrounds/fallback/textures/center/sonar.png and b/backgrounds/fallback/textures/center/sonar.png differ diff --git a/backgrounds/fallback/textures/center/star_core.png b/backgrounds/fallback/textures/center/star_core.png index 8e59653..769b8a6 100644 Binary files a/backgrounds/fallback/textures/center/star_core.png and b/backgrounds/fallback/textures/center/star_core.png differ diff --git a/backgrounds/fallback/textures/center/tama_01.png b/backgrounds/fallback/textures/center/tama_01.png index 877e724..edb69b7 100644 Binary files a/backgrounds/fallback/textures/center/tama_01.png and b/backgrounds/fallback/textures/center/tama_01.png differ diff --git a/backgrounds/fallback/textures/center/techno-eye.png b/backgrounds/fallback/textures/center/techno-eye.png index 0618f72..ba167a3 100644 Binary files a/backgrounds/fallback/textures/center/techno-eye.png and b/backgrounds/fallback/textures/center/techno-eye.png differ diff --git a/backgrounds/fallback/textures/center/wolfgirl.png b/backgrounds/fallback/textures/center/wolfgirl.png index ef25361..2cf55e6 100644 Binary files a/backgrounds/fallback/textures/center/wolfgirl.png and b/backgrounds/fallback/textures/center/wolfgirl.png differ diff --git a/backgrounds/fallback/textures/layer/colorbokeh-c.jpg b/backgrounds/fallback/textures/layer/colorbokeh-c.jpg index 9a2969b..cf1fd30 100644 Binary files a/backgrounds/fallback/textures/layer/colorbokeh-c.jpg and b/backgrounds/fallback/textures/layer/colorbokeh-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/colorbokeh.jpg b/backgrounds/fallback/textures/layer/colorbokeh.jpg index c4b35b3..8f76b1b 100644 Binary files a/backgrounds/fallback/textures/layer/colorbokeh.jpg and b/backgrounds/fallback/textures/layer/colorbokeh.jpg differ diff --git a/backgrounds/fallback/textures/layer/electro1-c.jpg b/backgrounds/fallback/textures/layer/electro1-c.jpg index 55bde0e..064d700 100644 Binary files a/backgrounds/fallback/textures/layer/electro1-c.jpg and b/backgrounds/fallback/textures/layer/electro1-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/electro1.jpg b/backgrounds/fallback/textures/layer/electro1.jpg index ea2d325..f89704d 100644 Binary files a/backgrounds/fallback/textures/layer/electro1.jpg and b/backgrounds/fallback/textures/layer/electro1.jpg differ diff --git a/backgrounds/fallback/textures/layer/electro2-c.jpg b/backgrounds/fallback/textures/layer/electro2-c.jpg index 311f8bc..95b7f09 100644 Binary files a/backgrounds/fallback/textures/layer/electro2-c.jpg and b/backgrounds/fallback/textures/layer/electro2-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/electro2.jpg b/backgrounds/fallback/textures/layer/electro2.jpg index 4aec9e5..09adb4a 100644 Binary files a/backgrounds/fallback/textures/layer/electro2.jpg and b/backgrounds/fallback/textures/layer/electro2.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon-inverse-c.jpg b/backgrounds/fallback/textures/layer/moon-inverse-c.jpg index 3cf34ac..b526cc3 100644 Binary files a/backgrounds/fallback/textures/layer/moon-inverse-c.jpg and b/backgrounds/fallback/textures/layer/moon-inverse-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon-inverse.jpg b/backgrounds/fallback/textures/layer/moon-inverse.jpg index f19691f..867032b 100644 Binary files a/backgrounds/fallback/textures/layer/moon-inverse.jpg and b/backgrounds/fallback/textures/layer/moon-inverse.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon1-c.jpg b/backgrounds/fallback/textures/layer/moon1-c.jpg index e600567..5cb2eb8 100644 Binary files a/backgrounds/fallback/textures/layer/moon1-c.jpg and b/backgrounds/fallback/textures/layer/moon1-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon1.jpg b/backgrounds/fallback/textures/layer/moon1.jpg index d61799f..f40de48 100644 Binary files a/backgrounds/fallback/textures/layer/moon1.jpg and b/backgrounds/fallback/textures/layer/moon1.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon2-c.jpg b/backgrounds/fallback/textures/layer/moon2-c.jpg index 7c63513..35b3341 100644 Binary files a/backgrounds/fallback/textures/layer/moon2-c.jpg and b/backgrounds/fallback/textures/layer/moon2-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon2.jpg b/backgrounds/fallback/textures/layer/moon2.jpg index 395991a..342d4ce 100644 Binary files a/backgrounds/fallback/textures/layer/moon2.jpg and b/backgrounds/fallback/textures/layer/moon2.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon3-c.jpg b/backgrounds/fallback/textures/layer/moon3-c.jpg index 9f4e7e6..08c49f3 100644 Binary files a/backgrounds/fallback/textures/layer/moon3-c.jpg and b/backgrounds/fallback/textures/layer/moon3-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon3.jpg b/backgrounds/fallback/textures/layer/moon3.jpg index e973092..fe76349 100644 Binary files a/backgrounds/fallback/textures/layer/moon3.jpg and b/backgrounds/fallback/textures/layer/moon3.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon5-c.jpg b/backgrounds/fallback/textures/layer/moon5-c.jpg index 9757e66..b7a4568 100644 Binary files a/backgrounds/fallback/textures/layer/moon5-c.jpg and b/backgrounds/fallback/textures/layer/moon5-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/moon5.jpg b/backgrounds/fallback/textures/layer/moon5.jpg index 3e6f7f7..9f7f943 100644 Binary files a/backgrounds/fallback/textures/layer/moon5.jpg and b/backgrounds/fallback/textures/layer/moon5.jpg differ diff --git a/backgrounds/fallback/textures/layer/particles5-c.jpg b/backgrounds/fallback/textures/layer/particles5-c.jpg index 6efcb70..9f6320b 100644 Binary files a/backgrounds/fallback/textures/layer/particles5-c.jpg and b/backgrounds/fallback/textures/layer/particles5-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/particles5.jpg b/backgrounds/fallback/textures/layer/particles5.jpg index b812ae8..ed2fd52 100644 Binary files a/backgrounds/fallback/textures/layer/particles5.jpg and b/backgrounds/fallback/textures/layer/particles5.jpg differ diff --git a/backgrounds/fallback/textures/layer/plasmatunnel-c.jpg b/backgrounds/fallback/textures/layer/plasmatunnel-c.jpg index cfcdc36..da1078a 100644 Binary files a/backgrounds/fallback/textures/layer/plasmatunnel-c.jpg and b/backgrounds/fallback/textures/layer/plasmatunnel-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/plasmatunnel.jpg b/backgrounds/fallback/textures/layer/plasmatunnel.jpg index a79f259..534951b 100644 Binary files a/backgrounds/fallback/textures/layer/plasmatunnel.jpg and b/backgrounds/fallback/textures/layer/plasmatunnel.jpg differ diff --git a/backgrounds/fallback/textures/layer/smoke-c.jpg b/backgrounds/fallback/textures/layer/smoke-c.jpg index cfa420b..097f16d 100644 Binary files a/backgrounds/fallback/textures/layer/smoke-c.jpg and b/backgrounds/fallback/textures/layer/smoke-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/smoke.jpg b/backgrounds/fallback/textures/layer/smoke.jpg index 9275378..6dd4cdc 100644 Binary files a/backgrounds/fallback/textures/layer/smoke.jpg and b/backgrounds/fallback/textures/layer/smoke.jpg differ diff --git a/backgrounds/fallback/textures/layer/spider1-c.jpg b/backgrounds/fallback/textures/layer/spider1-c.jpg index bea4518..1b3617a 100644 Binary files a/backgrounds/fallback/textures/layer/spider1-c.jpg and b/backgrounds/fallback/textures/layer/spider1-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/spider1.jpg b/backgrounds/fallback/textures/layer/spider1.jpg index a032df2..af4c2e2 100644 Binary files a/backgrounds/fallback/textures/layer/spider1.jpg and b/backgrounds/fallback/textures/layer/spider1.jpg differ diff --git a/backgrounds/fallback/textures/layer/spider2-c.jpg b/backgrounds/fallback/textures/layer/spider2-c.jpg index 58398b1..f8b6413 100644 Binary files a/backgrounds/fallback/textures/layer/spider2-c.jpg and b/backgrounds/fallback/textures/layer/spider2-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/spider2.jpg b/backgrounds/fallback/textures/layer/spider2.jpg index 6794623..86f23a7 100644 Binary files a/backgrounds/fallback/textures/layer/spider2.jpg and b/backgrounds/fallback/textures/layer/spider2.jpg differ diff --git a/backgrounds/fallback/textures/layer/spider3-c.jpg b/backgrounds/fallback/textures/layer/spider3-c.jpg index 85e6354..8d9d4ec 100644 Binary files a/backgrounds/fallback/textures/layer/spider3-c.jpg and b/backgrounds/fallback/textures/layer/spider3-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/spider3.jpg b/backgrounds/fallback/textures/layer/spider3.jpg index 9e80c57..14b85b0 100644 Binary files a/backgrounds/fallback/textures/layer/spider3.jpg and b/backgrounds/fallback/textures/layer/spider3.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave2-c.jpg b/backgrounds/fallback/textures/layer/wave2-c.jpg index 1bbee96..9a9b23f 100644 Binary files a/backgrounds/fallback/textures/layer/wave2-c.jpg and b/backgrounds/fallback/textures/layer/wave2-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave2.jpg b/backgrounds/fallback/textures/layer/wave2.jpg index ba5cb09..8d5cd16 100644 Binary files a/backgrounds/fallback/textures/layer/wave2.jpg and b/backgrounds/fallback/textures/layer/wave2.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave3-c.jpg b/backgrounds/fallback/textures/layer/wave3-c.jpg index e64e86f..bb64a2c 100644 Binary files a/backgrounds/fallback/textures/layer/wave3-c.jpg and b/backgrounds/fallback/textures/layer/wave3-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave3.jpg b/backgrounds/fallback/textures/layer/wave3.jpg index b11a017..ec1153f 100644 Binary files a/backgrounds/fallback/textures/layer/wave3.jpg and b/backgrounds/fallback/textures/layer/wave3.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave4-c.jpg b/backgrounds/fallback/textures/layer/wave4-c.jpg index 383e85d..385f8f6 100644 Binary files a/backgrounds/fallback/textures/layer/wave4-c.jpg and b/backgrounds/fallback/textures/layer/wave4-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave4.jpg b/backgrounds/fallback/textures/layer/wave4.jpg index 99675ed..c17ced6 100644 Binary files a/backgrounds/fallback/textures/layer/wave4.jpg and b/backgrounds/fallback/textures/layer/wave4.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave6-c.jpg b/backgrounds/fallback/textures/layer/wave6-c.jpg index c596fb8..aa136d8 100644 Binary files a/backgrounds/fallback/textures/layer/wave6-c.jpg and b/backgrounds/fallback/textures/layer/wave6-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave6.jpg b/backgrounds/fallback/textures/layer/wave6.jpg index ce41c8a..7ed12c6 100644 Binary files a/backgrounds/fallback/textures/layer/wave6.jpg and b/backgrounds/fallback/textures/layer/wave6.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave7-c.jpg b/backgrounds/fallback/textures/layer/wave7-c.jpg index 2675dc5..ba857c9 100644 Binary files a/backgrounds/fallback/textures/layer/wave7-c.jpg and b/backgrounds/fallback/textures/layer/wave7-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave7.jpg b/backgrounds/fallback/textures/layer/wave7.jpg index 6950c09..ba73f5d 100644 Binary files a/backgrounds/fallback/textures/layer/wave7.jpg and b/backgrounds/fallback/textures/layer/wave7.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave8-c.jpg b/backgrounds/fallback/textures/layer/wave8-c.jpg index e4fcb63..2a6dedb 100644 Binary files a/backgrounds/fallback/textures/layer/wave8-c.jpg and b/backgrounds/fallback/textures/layer/wave8-c.jpg differ diff --git a/backgrounds/fallback/textures/layer/wave8.jpg b/backgrounds/fallback/textures/layer/wave8.jpg index dd4a19e..6e709d1 100644 Binary files a/backgrounds/fallback/textures/layer/wave8.jpg and b/backgrounds/fallback/textures/layer/wave8.jpg differ diff --git a/backgrounds/fallback/textures/luaparticle/4_eff_dot_bl.png b/backgrounds/fallback/textures/luaparticle/4_eff_dot_bl.png index ce3f268..6c3e966 100644 Binary files a/backgrounds/fallback/textures/luaparticle/4_eff_dot_bl.png and b/backgrounds/fallback/textures/luaparticle/4_eff_dot_bl.png differ diff --git a/backgrounds/fallback/textures/luaparticle/4_logo_01c_i_eab.png b/backgrounds/fallback/textures/luaparticle/4_logo_01c_i_eab.png index b184f01..8b2f6fb 100644 Binary files a/backgrounds/fallback/textures/luaparticle/4_logo_01c_i_eab.png and b/backgrounds/fallback/textures/luaparticle/4_logo_01c_i_eab.png differ diff --git a/backgrounds/fallback/textures/luaparticle/petal1.png b/backgrounds/fallback/textures/luaparticle/petal1.png index 20f2d5d..f0093d0 100644 Binary files a/backgrounds/fallback/textures/luaparticle/petal1.png and b/backgrounds/fallback/textures/luaparticle/petal1.png differ diff --git a/backgrounds/fallback/textures/luaparticle/petal2.png b/backgrounds/fallback/textures/luaparticle/petal2.png index a07cc64..ec57cc2 100644 Binary files a/backgrounds/fallback/textures/luaparticle/petal2.png and b/backgrounds/fallback/textures/luaparticle/petal2.png differ diff --git a/backgrounds/fallback/textures/luaparticle/petal3.png b/backgrounds/fallback/textures/luaparticle/petal3.png index 9445f88..bcea16e 100644 Binary files a/backgrounds/fallback/textures/luaparticle/petal3.png and b/backgrounds/fallback/textures/luaparticle/petal3.png differ diff --git a/backgrounds/fallback/textures/luaparticle/star-particle.png b/backgrounds/fallback/textures/luaparticle/star-particle.png index c681339..74d8981 100644 Binary files a/backgrounds/fallback/textures/luaparticle/star-particle.png and b/backgrounds/fallback/textures/luaparticle/star-particle.png differ diff --git a/backgrounds/fallback/textures/particle/bullets.png b/backgrounds/fallback/textures/particle/bullets.png index 756df5d..0babf40 100644 Binary files a/backgrounds/fallback/textures/particle/bullets.png and b/backgrounds/fallback/textures/particle/bullets.png differ diff --git a/backgrounds/fallback/textures/particle/extra/dangan.png b/backgrounds/fallback/textures/particle/extra/dangan.png index 438248e..024c351 100644 Binary files a/backgrounds/fallback/textures/particle/extra/dangan.png and b/backgrounds/fallback/textures/particle/extra/dangan.png differ diff --git a/backgrounds/fallback/textures/particle/extra/particle_dangan_01.png b/backgrounds/fallback/textures/particle/extra/particle_dangan_01.png index 353acfe..e2db64c 100644 Binary files a/backgrounds/fallback/textures/particle/extra/particle_dangan_01.png and b/backgrounds/fallback/textures/particle/extra/particle_dangan_01.png differ diff --git a/backgrounds/fallback/textures/particle/extra/particle_dangan_02.png b/backgrounds/fallback/textures/particle/extra/particle_dangan_02.png index b1710db..99804a6 100644 Binary files a/backgrounds/fallback/textures/particle/extra/particle_dangan_02.png and b/backgrounds/fallback/textures/particle/extra/particle_dangan_02.png differ diff --git a/backgrounds/fallback/textures/particle/extra/particle_ora_01.png b/backgrounds/fallback/textures/particle/extra/particle_ora_01.png index 395fe82..585c7d3 100644 Binary files a/backgrounds/fallback/textures/particle/extra/particle_ora_01.png and b/backgrounds/fallback/textures/particle/extra/particle_ora_01.png differ diff --git a/backgrounds/fallback/textures/particle/extra/puchun_01_i_eab.png b/backgrounds/fallback/textures/particle/extra/puchun_01_i_eab.png index 258db85..e59fab7 100644 Binary files a/backgrounds/fallback/textures/particle/extra/puchun_01_i_eab.png and b/backgrounds/fallback/textures/particle/extra/puchun_01_i_eab.png differ diff --git a/backgrounds/fallback/textures/particle/extra/puchun_02_i_eab.png b/backgrounds/fallback/textures/particle/extra/puchun_02_i_eab.png index e8b5355..84e32c0 100644 Binary files a/backgrounds/fallback/textures/particle/extra/puchun_02_i_eab.png and b/backgrounds/fallback/textures/particle/extra/puchun_02_i_eab.png differ diff --git a/backgrounds/fallback/textures/particle/feathers.png b/backgrounds/fallback/textures/particle/feathers.png index a279e66..ede18f8 100644 Binary files a/backgrounds/fallback/textures/particle/feathers.png and b/backgrounds/fallback/textures/particle/feathers.png differ diff --git a/backgrounds/fallback/textures/particle/hexes.png b/backgrounds/fallback/textures/particle/hexes.png index 1032eed..36b4317 100644 Binary files a/backgrounds/fallback/textures/particle/hexes.png and b/backgrounds/fallback/textures/particle/hexes.png differ diff --git a/backgrounds/fallback/textures/particle/ice.png b/backgrounds/fallback/textures/particle/ice.png index b9d3a86..3b25651 100644 Binary files a/backgrounds/fallback/textures/particle/ice.png and b/backgrounds/fallback/textures/particle/ice.png differ diff --git a/backgrounds/fallback/textures/particle/icons-c.png b/backgrounds/fallback/textures/particle/icons-c.png index 0a34e9c..a4d6da5 100644 Binary files a/backgrounds/fallback/textures/particle/icons-c.png and b/backgrounds/fallback/textures/particle/icons-c.png differ diff --git a/backgrounds/fallback/textures/particle/icons.png b/backgrounds/fallback/textures/particle/icons.png index 0a4e748..d598a44 100644 Binary files a/backgrounds/fallback/textures/particle/icons.png and b/backgrounds/fallback/textures/particle/icons.png differ diff --git a/backgrounds/fallback/textures/particle/lights_default-c.png b/backgrounds/fallback/textures/particle/lights_default-c.png index 4ce401d..1b01c86 100644 Binary files a/backgrounds/fallback/textures/particle/lights_default-c.png and b/backgrounds/fallback/textures/particle/lights_default-c.png differ diff --git a/backgrounds/fallback/textures/particle/lights_default.png b/backgrounds/fallback/textures/particle/lights_default.png index 0d2c984..dba5fd1 100644 Binary files a/backgrounds/fallback/textures/particle/lights_default.png and b/backgrounds/fallback/textures/particle/lights_default.png differ diff --git a/backgrounds/fallback/textures/particle/lights_moonblue-c.png b/backgrounds/fallback/textures/particle/lights_moonblue-c.png index ee084c4..0cdc4b4 100644 Binary files a/backgrounds/fallback/textures/particle/lights_moonblue-c.png and b/backgrounds/fallback/textures/particle/lights_moonblue-c.png differ diff --git a/backgrounds/fallback/textures/particle/lights_moonblue.png b/backgrounds/fallback/textures/particle/lights_moonblue.png index 22a677c..f0b876a 100644 Binary files a/backgrounds/fallback/textures/particle/lights_moonblue.png and b/backgrounds/fallback/textures/particle/lights_moonblue.png differ diff --git a/backgrounds/fallback/textures/particle/lights_orangepink.png b/backgrounds/fallback/textures/particle/lights_orangepink.png index d30fec5..da2ade5 100644 Binary files a/backgrounds/fallback/textures/particle/lights_orangepink.png and b/backgrounds/fallback/textures/particle/lights_orangepink.png differ diff --git a/backgrounds/fallback/textures/particle/lights_pink-c.png b/backgrounds/fallback/textures/particle/lights_pink-c.png index 86562d0..5ea3ba5 100644 Binary files a/backgrounds/fallback/textures/particle/lights_pink-c.png and b/backgrounds/fallback/textures/particle/lights_pink-c.png differ diff --git a/backgrounds/fallback/textures/particle/lights_pink.png b/backgrounds/fallback/textures/particle/lights_pink.png index 359f2ee..6ca2889 100644 Binary files a/backgrounds/fallback/textures/particle/lights_pink.png and b/backgrounds/fallback/textures/particle/lights_pink.png differ diff --git a/backgrounds/fallback/textures/particle/lights_purplish.png b/backgrounds/fallback/textures/particle/lights_purplish.png index 78113fc..40bca65 100644 Binary files a/backgrounds/fallback/textures/particle/lights_purplish.png and b/backgrounds/fallback/textures/particle/lights_purplish.png differ diff --git a/backgrounds/fallback/textures/particle/lights_sea-c.png b/backgrounds/fallback/textures/particle/lights_sea-c.png index 528c2ec..f703dcb 100644 Binary files a/backgrounds/fallback/textures/particle/lights_sea-c.png and b/backgrounds/fallback/textures/particle/lights_sea-c.png differ diff --git a/backgrounds/fallback/textures/particle/lights_sea.png b/backgrounds/fallback/textures/particle/lights_sea.png index 459429d..7af28a1 100644 Binary files a/backgrounds/fallback/textures/particle/lights_sea.png and b/backgrounds/fallback/textures/particle/lights_sea.png differ diff --git a/backgrounds/fallback/textures/particle/lights_yellow.png b/backgrounds/fallback/textures/particle/lights_yellow.png index cc2e239..8eea253 100644 Binary files a/backgrounds/fallback/textures/particle/lights_yellow.png and b/backgrounds/fallback/textures/particle/lights_yellow.png differ diff --git a/backgrounds/fallback/textures/particle/lights_yellowgreen.png b/backgrounds/fallback/textures/particle/lights_yellowgreen.png index 6e4cd6a..4805b19 100644 Binary files a/backgrounds/fallback/textures/particle/lights_yellowgreen.png and b/backgrounds/fallback/textures/particle/lights_yellowgreen.png differ diff --git a/backgrounds/fallback/textures/particle/shines1.png b/backgrounds/fallback/textures/particle/shines1.png index 2b6359c..9362a68 100644 Binary files a/backgrounds/fallback/textures/particle/shines1.png and b/backgrounds/fallback/textures/particle/shines1.png differ diff --git a/backgrounds/fallback/textures/particle/shines2.png b/backgrounds/fallback/textures/particle/shines2.png index 64366c5..c42a476 100644 Binary files a/backgrounds/fallback/textures/particle/shines2.png and b/backgrounds/fallback/textures/particle/shines2.png differ diff --git a/backgrounds/fallback/textures/particle/squares-c.png b/backgrounds/fallback/textures/particle/squares-c.png index 9fa6216..40d2c6f 100644 Binary files a/backgrounds/fallback/textures/particle/squares-c.png and b/backgrounds/fallback/textures/particle/squares-c.png differ diff --git a/backgrounds/fallback/textures/particle/squares.png b/backgrounds/fallback/textures/particle/squares.png index d39c82a..10760d3 100644 Binary files a/backgrounds/fallback/textures/particle/squares.png and b/backgrounds/fallback/textures/particle/squares.png differ diff --git a/backgrounds/fallback/textures/particle/star_particles-c.png b/backgrounds/fallback/textures/particle/star_particles-c.png index a709159..98f680c 100644 Binary files a/backgrounds/fallback/textures/particle/star_particles-c.png and b/backgrounds/fallback/textures/particle/star_particles-c.png differ diff --git a/backgrounds/fallback/textures/particle/star_particles.png b/backgrounds/fallback/textures/particle/star_particles.png index 8e7617e..e07484c 100644 Binary files a/backgrounds/fallback/textures/particle/star_particles.png and b/backgrounds/fallback/textures/particle/star_particles.png differ diff --git a/backgrounds/fallback/textures/particle/street_lanterns-c.png b/backgrounds/fallback/textures/particle/street_lanterns-c.png index eee34e0..195740d 100644 Binary files a/backgrounds/fallback/textures/particle/street_lanterns-c.png and b/backgrounds/fallback/textures/particle/street_lanterns-c.png differ diff --git a/backgrounds/fallback/textures/particle/street_lanterns.png b/backgrounds/fallback/textures/particle/street_lanterns.png index 93d4e12..7b4316f 100644 Binary files a/backgrounds/fallback/textures/particle/street_lanterns.png and b/backgrounds/fallback/textures/particle/street_lanterns.png differ diff --git a/backgrounds/fallback/textures/particle/technosparkles.png b/backgrounds/fallback/textures/particle/technosparkles.png index e379839..71e017d 100644 Binary files a/backgrounds/fallback/textures/particle/technosparkles.png and b/backgrounds/fallback/textures/particle/technosparkles.png differ diff --git a/backgrounds/fallback/textures/particle/triangles.png b/backgrounds/fallback/textures/particle/triangles.png index 0e77725..be75164 100644 Binary files a/backgrounds/fallback/textures/particle/triangles.png and b/backgrounds/fallback/textures/particle/triangles.png differ diff --git a/backgrounds/fallback/textures/particle/twilight-c.png b/backgrounds/fallback/textures/particle/twilight-c.png index 67a5a24..ad20287 100644 Binary files a/backgrounds/fallback/textures/particle/twilight-c.png and b/backgrounds/fallback/textures/particle/twilight-c.png differ diff --git a/backgrounds/fallback/textures/particle/twilight.png b/backgrounds/fallback/textures/particle/twilight.png index 41cbd60..475f94d 100644 Binary files a/backgrounds/fallback/textures/particle/twilight.png and b/backgrounds/fallback/textures/particle/twilight.png differ diff --git a/backgrounds/fallback/textures/tunnel/arrows-large-c.png b/backgrounds/fallback/textures/tunnel/arrows-large-c.png index a3e781c..b08ca7d 100644 Binary files a/backgrounds/fallback/textures/tunnel/arrows-large-c.png and b/backgrounds/fallback/textures/tunnel/arrows-large-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/arrows-large.png b/backgrounds/fallback/textures/tunnel/arrows-large.png index e2d11b1..4fb5778 100644 Binary files a/backgrounds/fallback/textures/tunnel/arrows-large.png and b/backgrounds/fallback/textures/tunnel/arrows-large.png differ diff --git a/backgrounds/fallback/textures/tunnel/arrows-small-c.png b/backgrounds/fallback/textures/tunnel/arrows-small-c.png index 0ae7e3e..f4ac2be 100644 Binary files a/backgrounds/fallback/textures/tunnel/arrows-small-c.png and b/backgrounds/fallback/textures/tunnel/arrows-small-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/arrows-small.png b/backgrounds/fallback/textures/tunnel/arrows-small.png index e8b72b6..b151c5c 100644 Binary files a/backgrounds/fallback/textures/tunnel/arrows-small.png and b/backgrounds/fallback/textures/tunnel/arrows-small.png differ diff --git a/backgrounds/fallback/textures/tunnel/beams-c.png b/backgrounds/fallback/textures/tunnel/beams-c.png index 8b6f269..6c4c51d 100644 Binary files a/backgrounds/fallback/textures/tunnel/beams-c.png and b/backgrounds/fallback/textures/tunnel/beams-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/beams.png b/backgrounds/fallback/textures/tunnel/beams.png index 48c2215..f7de972 100644 Binary files a/backgrounds/fallback/textures/tunnel/beams.png and b/backgrounds/fallback/textures/tunnel/beams.png differ diff --git a/backgrounds/fallback/textures/tunnel/clouds-dark-c.png b/backgrounds/fallback/textures/tunnel/clouds-dark-c.png index 6a815f4..2d3ac47 100644 Binary files a/backgrounds/fallback/textures/tunnel/clouds-dark-c.png and b/backgrounds/fallback/textures/tunnel/clouds-dark-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/clouds-dark.png b/backgrounds/fallback/textures/tunnel/clouds-dark.png index 8e53265..401a894 100644 Binary files a/backgrounds/fallback/textures/tunnel/clouds-dark.png and b/backgrounds/fallback/textures/tunnel/clouds-dark.png differ diff --git a/backgrounds/fallback/textures/tunnel/clouds.png b/backgrounds/fallback/textures/tunnel/clouds.png index 66848c2..a41a637 100644 Binary files a/backgrounds/fallback/textures/tunnel/clouds.png and b/backgrounds/fallback/textures/tunnel/clouds.png differ diff --git a/backgrounds/fallback/textures/tunnel/dome-c.png b/backgrounds/fallback/textures/tunnel/dome-c.png index 9977c0e..4f267a2 100644 Binary files a/backgrounds/fallback/textures/tunnel/dome-c.png and b/backgrounds/fallback/textures/tunnel/dome-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/dome-red-c.png b/backgrounds/fallback/textures/tunnel/dome-red-c.png index 95c2636..fdfc7d3 100644 Binary files a/backgrounds/fallback/textures/tunnel/dome-red-c.png and b/backgrounds/fallback/textures/tunnel/dome-red-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/dome-red.png b/backgrounds/fallback/textures/tunnel/dome-red.png index 1d3b5db..b34a5e5 100644 Binary files a/backgrounds/fallback/textures/tunnel/dome-red.png and b/backgrounds/fallback/textures/tunnel/dome-red.png differ diff --git a/backgrounds/fallback/textures/tunnel/dome.png b/backgrounds/fallback/textures/tunnel/dome.png index 45faa88..7c1547f 100644 Binary files a/backgrounds/fallback/textures/tunnel/dome.png and b/backgrounds/fallback/textures/tunnel/dome.png differ diff --git a/backgrounds/fallback/textures/tunnel/electro-blue.png b/backgrounds/fallback/textures/tunnel/electro-blue.png index 10ee447..9af759e 100644 Binary files a/backgrounds/fallback/textures/tunnel/electro-blue.png and b/backgrounds/fallback/textures/tunnel/electro-blue.png differ diff --git a/backgrounds/fallback/textures/tunnel/electro-c.png b/backgrounds/fallback/textures/tunnel/electro-c.png index 15de9f2..5561959 100644 Binary files a/backgrounds/fallback/textures/tunnel/electro-c.png and b/backgrounds/fallback/textures/tunnel/electro-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/electro.png b/backgrounds/fallback/textures/tunnel/electro.png index d5f34f7..f068aa9 100644 Binary files a/backgrounds/fallback/textures/tunnel/electro.png and b/backgrounds/fallback/textures/tunnel/electro.png differ diff --git a/backgrounds/fallback/textures/tunnel/game.png b/backgrounds/fallback/textures/tunnel/game.png index cb7a5a3..f843d6c 100644 Binary files a/backgrounds/fallback/textures/tunnel/game.png and b/backgrounds/fallback/textures/tunnel/game.png differ diff --git a/backgrounds/fallback/textures/tunnel/genom-c.png b/backgrounds/fallback/textures/tunnel/genom-c.png index ae7ae81..06d50e9 100644 Binary files a/backgrounds/fallback/textures/tunnel/genom-c.png and b/backgrounds/fallback/textures/tunnel/genom-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/genom.png b/backgrounds/fallback/textures/tunnel/genom.png index 459f5d7..af80adc 100644 Binary files a/backgrounds/fallback/textures/tunnel/genom.png and b/backgrounds/fallback/textures/tunnel/genom.png differ diff --git a/backgrounds/fallback/textures/tunnel/hexagons-c.png b/backgrounds/fallback/textures/tunnel/hexagons-c.png index 071401a..9987c86 100644 Binary files a/backgrounds/fallback/textures/tunnel/hexagons-c.png and b/backgrounds/fallback/textures/tunnel/hexagons-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/hexagons-gray.png b/backgrounds/fallback/textures/tunnel/hexagons-gray.png index f1e6cd8..e1146d0 100644 Binary files a/backgrounds/fallback/textures/tunnel/hexagons-gray.png and b/backgrounds/fallback/textures/tunnel/hexagons-gray.png differ diff --git a/backgrounds/fallback/textures/tunnel/hexagons.png b/backgrounds/fallback/textures/tunnel/hexagons.png index b369380..ed3cd28 100644 Binary files a/backgrounds/fallback/textures/tunnel/hexagons.png and b/backgrounds/fallback/textures/tunnel/hexagons.png differ diff --git a/backgrounds/fallback/textures/tunnel/idofront-c.png b/backgrounds/fallback/textures/tunnel/idofront-c.png index 10962b0..da96cfb 100644 Binary files a/backgrounds/fallback/textures/tunnel/idofront-c.png and b/backgrounds/fallback/textures/tunnel/idofront-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/idofront.png b/backgrounds/fallback/textures/tunnel/idofront.png index 8a6f156..6c1f0bb 100644 Binary files a/backgrounds/fallback/textures/tunnel/idofront.png and b/backgrounds/fallback/textures/tunnel/idofront.png differ diff --git a/backgrounds/fallback/textures/tunnel/iseki-c.png b/backgrounds/fallback/textures/tunnel/iseki-c.png index b15c345..f2a2937 100644 Binary files a/backgrounds/fallback/textures/tunnel/iseki-c.png and b/backgrounds/fallback/textures/tunnel/iseki-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/iseki.png b/backgrounds/fallback/textures/tunnel/iseki.png index e109396..09db935 100644 Binary files a/backgrounds/fallback/textures/tunnel/iseki.png and b/backgrounds/fallback/textures/tunnel/iseki.png differ diff --git a/backgrounds/fallback/textures/tunnel/kac1-c.png b/backgrounds/fallback/textures/tunnel/kac1-c.png index 1a749b1..7d66293 100644 Binary files a/backgrounds/fallback/textures/tunnel/kac1-c.png and b/backgrounds/fallback/textures/tunnel/kac1-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/kac1.png b/backgrounds/fallback/textures/tunnel/kac1.png index e546626..dfea2dc 100644 Binary files a/backgrounds/fallback/textures/tunnel/kac1.png and b/backgrounds/fallback/textures/tunnel/kac1.png differ diff --git a/backgrounds/fallback/textures/tunnel/sky-gears-c.png b/backgrounds/fallback/textures/tunnel/sky-gears-c.png index 8cb462d..4ee4812 100644 Binary files a/backgrounds/fallback/textures/tunnel/sky-gears-c.png and b/backgrounds/fallback/textures/tunnel/sky-gears-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/sky-gears.png b/backgrounds/fallback/textures/tunnel/sky-gears.png index 8889ddb..af17863 100644 Binary files a/backgrounds/fallback/textures/tunnel/sky-gears.png and b/backgrounds/fallback/textures/tunnel/sky-gears.png differ diff --git a/backgrounds/fallback/textures/tunnel/smoke.png b/backgrounds/fallback/textures/tunnel/smoke.png index 8c24249..4c43571 100644 Binary files a/backgrounds/fallback/textures/tunnel/smoke.png and b/backgrounds/fallback/textures/tunnel/smoke.png differ diff --git a/backgrounds/fallback/textures/tunnel/wave-blue-c.png b/backgrounds/fallback/textures/tunnel/wave-blue-c.png index 06c9d25..e7fe552 100644 Binary files a/backgrounds/fallback/textures/tunnel/wave-blue-c.png and b/backgrounds/fallback/textures/tunnel/wave-blue-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/wave-blue.png b/backgrounds/fallback/textures/tunnel/wave-blue.png index 7c46e5e..9f875c2 100644 Binary files a/backgrounds/fallback/textures/tunnel/wave-blue.png and b/backgrounds/fallback/textures/tunnel/wave-blue.png differ diff --git a/backgrounds/fallback/textures/tunnel/wave-green-c.png b/backgrounds/fallback/textures/tunnel/wave-green-c.png index 4f83430..6a8f11b 100644 Binary files a/backgrounds/fallback/textures/tunnel/wave-green-c.png and b/backgrounds/fallback/textures/tunnel/wave-green-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/wave-green.png b/backgrounds/fallback/textures/tunnel/wave-green.png index 3de9a87..a3f411a 100644 Binary files a/backgrounds/fallback/textures/tunnel/wave-green.png and b/backgrounds/fallback/textures/tunnel/wave-green.png differ diff --git a/backgrounds/fallback/textures/tunnel/wave-orange-c.png b/backgrounds/fallback/textures/tunnel/wave-orange-c.png index 450398b..d8ade5b 100644 Binary files a/backgrounds/fallback/textures/tunnel/wave-orange-c.png and b/backgrounds/fallback/textures/tunnel/wave-orange-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/wave-orange.png b/backgrounds/fallback/textures/tunnel/wave-orange.png index 5a2d04c..8da2b6e 100644 Binary files a/backgrounds/fallback/textures/tunnel/wave-orange.png and b/backgrounds/fallback/textures/tunnel/wave-orange.png differ diff --git a/backgrounds/fallback/textures/tunnel/wave-red-c.png b/backgrounds/fallback/textures/tunnel/wave-red-c.png index 375a99a..4d24fe6 100644 Binary files a/backgrounds/fallback/textures/tunnel/wave-red-c.png and b/backgrounds/fallback/textures/tunnel/wave-red-c.png differ diff --git a/backgrounds/fallback/textures/tunnel/wave-red.png b/backgrounds/fallback/textures/tunnel/wave-red.png index 715140e..a31d00d 100644 Binary files a/backgrounds/fallback/textures/tunnel/wave-red.png and b/backgrounds/fallback/textures/tunnel/wave-red.png differ diff --git a/config-definitions.json b/config-definitions.json index be4160e..d2b5376 100644 --- a/config-definitions.json +++ b/config-definitions.json @@ -1,68 +1,101 @@ -{ - "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": "Crew idol animations, folder name in `crew/anim/`", - "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 + }, + + "animations_skipIntro": { + "type": "bool", + "label": "Skip intro splash screens", + "default": false + }, + + "separator_c": {}, + + "Crew": { "type": "label" }, + "single_idol": { + "label": "Crew idol animations, folder name in `crew/anim/`", + "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 + }, + + "gameplay_showSearchControls": { + "label": "Show song select controls when searching", + "type": "bool", + "default": true + }, + + "gameplay_earlyLateFor": { + "label": "Show Early/Late display for", + "type": "selection", + "default": "NEAR (or worse)", + "values": ["CRITICAL (or worse)", "NEAR (or worse)", "OFF"] + }, + + "gameplay_earlyLatePosition": { + "label": "Early/Late display position", + "type": "selection", + "default": "STANDARD", + "values": ["UPPER+", "UPPER", "STANDARD", "LOWER"] + }, + + "gameplay_msFor": { + "label": "Show millisecond display for", + "type": "selection", + "default": "NEAR (or worse)", + "values": ["ALL", "CRITICAL (or worse)", "NEAR (or worse)", "NONE"] + }, + + "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/docs/lua_api/IR.lua b/docs/lua_api/IR.lua new file mode 100644 index 0000000..2deea4a --- /dev/null +++ b/docs/lua_api/IR.lua @@ -0,0 +1,69 @@ +-- IR State enum +---@class States +local States = { + Unused = 0, + Pending = 10, + Success = 20, + Accepted = 22, + BadRequest = 40, + Unauthorized = 41, + ChartRefused = 42, + Forbidden = 43, + NotFound = 44, + ServerError = 50, + RequestFailure = 60 +} + +---@class IRData +---@field Active boolean # USC IR configured and active +---@field States States # IR reposonse state enum +IRData = {} + +---@class IRHeartbeatResponseBody +---@field serverTime integer +---@field serverName string +---@field irVersion string +IRHeartbeatResponseBody = {} + +---@class IRRecordResponseBody +---@field record ServerScore +IRRecordResponseBody = {} + +---@class IRLeaderboardResponseBody +---@field scores ServerScore[] +IRLeaderboardResponseBody = {} + +---@class IRResponse +---@field statusCode integer +---@field description string +---@field body nil|IRHeartbeatResponseBody|IRRecordResponseBody|IRLeaderboardResponseBody +IRResponse = {} + +-- Performs a Heartbeat request. +---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter +local function Heartbeat(callback) end + +-- Performs a Chart Tracked request for the chart with the provided hash. +---@param hash string # song hash +---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter +local function ChartTracked(hash, callback) end + +-- Performs a Record request for the chart with the provided hash. +---@param hash string # song hash +---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter +local function Record(hash, callback) end + +-- Performs a Leaderboard request for the chart with the provided hash, with parameters mode and n. +---@param hash string # song hash +---@param mode "best"|"rivals" # request leaderboard mode +---@param n integer # limit the number of requested scores +---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter +local function Leaderboard(hash, mode, n, callback) end + +---@type table +IR = { + Heartbeat = Heartbeat, + ChartTracked = ChartTracked, + Record = Record, + Leaderboard = Leaderboard +} \ No newline at end of file diff --git a/docs/lua_api/game.lua b/docs/lua_api/game.lua index 1758dc0..d5d68a3 100644 --- a/docs/lua_api/game.lua +++ b/docs/lua_api/game.lua @@ -7,7 +7,7 @@ GetButton = function(button) end -- Gets the absolute rotation of the specified knob ---@param knob integer # `0 = left`, `1 = right` ----@return number angle # in radians, `0.0` to `2*pi` +---@return number angle # in radians, `-2*pi` to `0` (turning CCW) and `0` to `2*pi` (turning CW) GetKnob = function(knob) end -- Gets the color of the specified laser diff --git a/docs/lua_api/result.lua b/docs/lua_api/result.lua index 23e11ed..aa17fb9 100644 --- a/docs/lua_api/result.lua +++ b/docs/lua_api/result.lua @@ -41,6 +41,30 @@ Score = {}; ---@field failReason string # Fail reason if a challenge requirement was not met ChartResult = {}; +---@class ServerScoreOptions +---@field gaugeType integer # An enum value representing the gauge type used. 0 = normal, 1 = hard. Further values are not currently specified. +---@field gaugeOpt integer # Reserved +---@field mirror boolean # Mirror mode enabled +---@field random boolean # Note shuffle enabled +---@field autoFlags integer # A bitfield of elements of the game that are automated. Any non-zero value means that the score was at least partially auto. +ServerScoreOptions = {} + +---@class ServerScore +---@field score integer # Submitted score +---@field gauge number # Submitted Gauge result +---@field timestamp integer # Unix timestamp of the score +---@field crit integer # Hits inside the critical window +---@field near integer # Hits inside the near window +---@field early integer # Hits inside the near window which were early +---@field late integer # Hits inside the near window which were late +---@field combo integer # Best combo reached +---@field error integer # Missed notes +---@field options ServerScoreOptions # The options in use. Includes gauge type, etc. +---@field windows table # {perfect, good, hold, miss, slam} hit windows in milliseconds +---@field yours boolean # This score belongs to the current player +---@field justSet boolean # This score belongs to the current player, and is the score that was just achieved +ServerScore = {} + ---@class result ---@field artist string # Chart artist ---@field auto_flags integer # Autoplay flag @@ -72,7 +96,9 @@ ChartResult = {}; ---@field hitWindow HitWindow # Result hit windows ---@field holdHitStats HitStat[]|nil # Hit stats for every hold object, only available for singleplayer if `isSelf = true` ---@field illustrator string # Chart jacket illustrator ----@field irState integer # Internet ranking flag +---@field irState integer # Current state of the IR score submission request (a USC-IR code, including extensions 0/10/60) +---@field irDescription string # The description in the IR response (nil if irState is 0 or 10) +---@field irScores ServerScore[]|nil # Score submission result, nil if irState != 20 ---@field isSelf boolean # Only for multiplayer, `false` if score is of another player ---@field jacketPath string # Full filepath to the jacket image on the disk ---@field laserHitStats HitStat[]|nil # Hit stats for every laser object, only available for singleplayer if `isSelf = true` diff --git a/docs/lua_api/titlescreen.lua b/docs/lua_api/titlescreen.lua new file mode 100644 index 0000000..749eb74 --- /dev/null +++ b/docs/lua_api/titlescreen.lua @@ -0,0 +1,35 @@ +local function Exit() end + +local function Settings() end + +local function Start() end + +local function DLScreen() end + +local function Update() end + +local function Multiplayer() end + +local function Challenges() end + +Menu = { + Exit = Exit, + Settings = Settings, + Start = Start, + DLScreen = DLScreen, + Update = Update, + Multiplayer = Multiplayer, + Challenges = Challenges +} + +--- Render frame for titlescreen +---@param deltaTime number Elapsed frametime since last frame +function render(deltaTime) end + +--- Button event handler for titlescreen +---@param buttonCode integer Corresponds to game.Button_* +function button_pressed(buttonCode) end + +--- Mouse event handler for titlescreen +---@param button integer +function mouse_pressed(button) end \ No newline at end of file diff --git a/fonts/Digital-Serial-ExtraBold.ttf b/fonts/Digital-Serial-ExtraBold.ttf new file mode 100644 index 0000000..52d9fd6 Binary files /dev/null and b/fonts/Digital-Serial-ExtraBold.ttf differ diff --git a/fonts/commext.ttf b/fonts/commext.ttf deleted file mode 100644 index 3572373..0000000 Binary files a/fonts/commext.ttf and /dev/null differ diff --git a/fonts/rounded-mplus-1c-bold.ttf b/fonts/rounded-mplus-1c-bold.ttf deleted file mode 100644 index b80dc42..0000000 Binary files a/fonts/rounded-mplus-1c-bold.ttf and /dev/null differ diff --git a/scripts/api/animation.lua b/scripts/api/animation.lua new file mode 100644 index 0000000..af8641c --- /dev/null +++ b/scripts/api/animation.lua @@ -0,0 +1,202 @@ + +require "common.class" + +require "api.graphics" + +local Image = require "api.image" + +---@class AnimationParams +---@field fps number? +---@field loop boolean? +---@field loopPoint integer? +---@field width number? +---@field height number? +---@field x number? +---@field y number? +---@field scaleX number? +---@field scaleY number? +---@field centered boolean? +---@field blendOp integer? +---@field color number[]? +---@field alpha number? +---@field stroke StrokeParams? + +---@class Animation +---@field frames Image[] +---@field frameCount integer +---@field frameTime number +---@field loop boolean +---@field loopPoint integer +---@field width number? +---@field height number? +---@field x number? +---@field y number? +---@field scaleX number? +---@field scaleY number? +---@field centered boolean? +---@field blendOp integer? +---@field color number[]? +---@field alpha number? +---@field stroke StrokeParams? +local Animation = { }; + +---@class AnimationState +---@field animation Animation # The animation data this state is playing through +---@field frameIndex integer # Current frame in the animation +---@field timer number # Timer used to determine when to change to the next frame +---@field running boolean # Is the animation currently running and accepting updates? +---@field callback function? # Called when the animation completes +local AnimationState = { }; + +local function loadSequentialAnimationFrames(animPath) + local frames = { }; + local count = 0; + + local detectedFormat = nil; + + while (true) do + local frame = nil; + if (detectedFormat) then + frame = Image.new(detectedFormat:format(animPath, count + 1), true); + else + for i = 1, 4 do + local format = '%s/%0' .. i .. 'd.png'; + frame = Image.new(format:format(animPath, count + 1), true); + + if (frame) then + detectedFormat = format; + break; + end + end + end + + if (not frame) then + break; + end + + count = count + 1; + frames[count] = frame; + end + + return frames, count; +end + +---Animation constructor +---@param animPath string +---@param params AnimationParams +---@return Animation +function Animation.new(animPath, params) + local frames, frameCount = loadSequentialAnimationFrames(animPath); + + local instance = { + frames = frames, + frameCount = frameCount, + + frameTime = 1 / (params.fps or 30), + loop = params.loop or false, + loopPoint = params.loopPoint or 1, + }; + + if (params.width ~= nil) then instance.width = params.width; end + if (params.height ~= nil) then instance.height = params.height; end + if (params.x ~= nil) then instance.x = params.x; end + if (params.y ~= nil) then instance.y = params.y; end + if (params.scaleX ~= nil) then instance.scaleX = params.scaleX; end + if (params.scaleY ~= nil) then instance.scaleY = params.scaleY; end + if (params.centered ~= nil) then instance.centered = params.centered; end + if (params.blendOp ~= nil) then instance.blendOp = params.blendOp; end + if (params.color ~= nil) then instance.color = params.color; end + if (params.alpha ~= nil) then instance.alpha = params.alpha; end + if (params.stroke ~= nil) then instance.stroke = params.stroke; end + + return CreateInstance(Animation, instance); +end + +---Create an AnimationState to play this animation. +---The AnimationState is not started. +---@param callback function? +---@return AnimationState +function Animation:createState(callback) + ---@type AnimationState + local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false }; + return CreateInstance(AnimationState, state); +end + +---Create an AnimationState to play this animation and start it. +---@param callback function? +---@return AnimationState +function Animation:start(callback) + local state = self:createState(callback); + state:start(); + + return state; +end + +---Start this AnimationState. +---Does nothing if it's already running. +function AnimationState:start() + self.running = true; +end + +---Restart this AnimationState. +---The frame index is reset to 1. +function AnimationState:restart() + self.running = true; + self.frameIndex = 1; + self.timer = 0; +end + +---Stop this AnimationState. +function AnimationState:stop() + self.running = false; +end + +---Updates this AnimationState and then rendersit, passing on the given ImageParams to each frame. +---@param deltaTime number +---@param params? ImageParams +function AnimationState:render(deltaTime, params) + if (not self.running) then return; end; + + self.timer = self.timer + deltaTime; + + while (self.timer > self.animation.frameTime) do + self.timer = self.timer - self.animation.frameTime; + self.frameIndex = self.frameIndex + 1; + + if (self.frameIndex > self.animation.frameCount) then + if (self.animation.loop) then + self.frameIndex = self.animation.loopPoint; + else + self.running = false; + + if (self.callback) then + self.callback(); + end + + return; + end + end + end + + if (params) then + if (params.width == nil) then params.width = self.animation.width; end + if (params.height == nil) then params.height = self.animation.height; end + if (params.x == nil) then params.x = self.animation.x; end + if (params.y == nil) then params.y = self.animation.y; end + if (params.scaleX == nil) then params.scaleX = self.animation.scaleX; end + if (params.scaleY == nil) then params.scaleY = self.animation.scaleY; end + if (params.centered == nil) then params.centered = self.animation.centered; end + if (params.blendOp == nil) then params.blendOp = self.animation.blendOp; end + if (params.alpha == nil) then params.alpha = self.animation.alpha; end + if (params.stroke == nil) then params.stroke = self.animation.stroke; end + end + + local frame = self.animation.frames[self.frameIndex]; + if (not frame) then + -- TODO(local): what do + else + frame:render(params); + end +end + +return Animation; diff --git a/scripts/api/graphics.lua b/scripts/api/graphics.lua new file mode 100644 index 0000000..38a8e55 --- /dev/null +++ b/scripts/api/graphics.lua @@ -0,0 +1,6 @@ + +-- TODO(local): put these class types somewhere more common +---@class StrokeParams +---@field color number[]? +---@field alpha number? +---@field size number? diff --git a/scripts/api/image.lua b/scripts/api/image.lua new file mode 100644 index 0000000..6fe6531 --- /dev/null +++ b/scripts/api/image.lua @@ -0,0 +1,171 @@ + +require "common.class" + +require "api.graphics" + +---@class ImageParams +---@field width number +---@field height number +---@field x number? +---@field y number? +---@field scaleX number? +---@field scaleY number? +---@field centered boolean? +---@field blendOp integer? +---@field color number[]? +---@field alpha number? +---@field stroke StrokeParams? + +---@class Image +---@field handle integer +---@field width number +---@field height number +---@field x number? +---@field y number? +---@field scaleX number? +---@field scaleY number? +---@field centered boolean? +---@field blendOp integer? +---@field color number[]? +---@field alpha number? +---@field stroke StrokeParams? +local Image = { }; + +---Image constructor +---@param imagePath string # The path to the skin image to load +---@return Image +function Image.new(imagePath, noFallback) + local handle = gfx.CreateSkinImage(imagePath or '', 0); + if (not handle) then + game.Log('Failed to load image "' .. imagePath .. '"', game.LOGGER_ERROR); + + if (noFallback) then return nil; end + + handle = gfx.CreateSkinImage('missing.png', 0); + if (not handle) then + game.Log('Failed to load fallback image "missing.png"', game.LOGGER_ERROR); + end + end + + local width, height = 64, 64; + if (handle) then + width, height = gfx.ImageSize(handle); + end + + local instance = { + handle = handle, + width = width, + height = height, + }; + + return CreateInstance(Image, instance); +end + +---Set the width and height of this Image. +---@param width number +---@param height number +---@return Image # Returns self for method chaining +function Image:setSize(width, height) + if (type(width) ~= "number") then width = 0; end + if (type(height) ~= "number") then height = 0; end + + self.width = width; + self.height = height; + + return self; +end + +---Set the stored position for this Image. +---If the position of this Image will not change frequently, +---using this method allows you to cache the render position +---instead of passing it to the render method on each invocation. +---@param x number +---@param y number +---@return Image # Returns self for method chaining +function Image:setPosition(x, y) + if (type(x) ~= "number") then x = 0; end + if (type(y) ~= "number") then y = 0; end + + self.x = x; + self.y = y; + + return self; +end + +---Renders this Image, applying any of the given ImageParams, +---then any of the cached Image fields, then any default values. +---@param params? ImageParams +function Image:render(params) + params = params or { }; + + local sx = params.scaleX or self.scaleX or 1; + local sy = params.scaleY or self.scaleY or 1; + + local x = params.x or self.x or 0; + local y = params.y or self.y or 0; + + local w = (params.width or self.width ) * sx; + local h = (params.height or self.height) * sy; + + if (params.centered or self.centered) then + x = x - w / 2; + y = y - h / 2; + end + + local blendOp = params.blendOp or self.blendOp or gfx.BLEND_OP_SOURCE_OVER; + + local r = 255; + local g = 255; + local b = 255; + + if (params.color) then + r = params.color[1]; + g = params.color[2]; + b = params.color[3]; + elseif (self.color) then + r = self.color[1]; + g = self.color[2]; + b = self.color[3]; + end + + local a = params.alpha or self.alpha or 1; + + gfx.BeginPath(); + gfx.GlobalCompositeOperation(blendOp); + + if (not self.handle) then + gfx.FillColor(r, g, b, a); + gfx.Rect(x, y, w, h); + gfx.FillColor(255, 255, 255, 255); + else + gfx.SetImageTint(r, g, b); + gfx.ImageRect(x, y, w, h, self.handle, a, 0); + gfx.SetImageTint(255, 255, 255); + end + + if (params.stroke or self.stroke) then + r = 255; + g = 255; + b = 255; + + if (params.stroke.color) then + r = params.stroke.color[1]; + g = params.stroke.color[2]; + b = params.stroke.color[3]; + elseif (self.stroke and self.stroke.color) then + r = self.stroke.color[1]; + g = self.stroke.color[2]; + b = self.stroke.color[3]; + end + + a = params.stroke.alpha or (self.stroke and self.stroke.alpha) or 255; + + local size = params.stroke.size or (self.stroke and self.stroke.size) or 1; + + gfx.StrokeColor(r, g, b, a); + gfx.StrokeWidth(size); + gfx.Stroke(); + end +end + +return Image; diff --git a/scripts/challengeresult.lua b/scripts/challengeresult.lua index 8e86d64..533ee17 100644 --- a/scripts/challengeresult.lua +++ b/scripts/challengeresult.lua @@ -1,302 +1,316 @@ -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.util'); +local Sound = require("common.sound") + +local Numbers = require('components.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 = nil; +local percGet = nil; + +-- 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 + Sound.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+1, 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) + + if (percRequired == nil) then + return + end + + 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() + if (result.requirement_text == nil) then + return + end + + local reqTextWords = common.split(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 + + if (percRequired == nil) then + return + 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/common.lua b/scripts/common.lua index 8e02166..61c1440 100644 --- a/scripts/common.lua +++ b/scripts/common.lua @@ -1,156 +1,6 @@ +-- NOTE(local): DO NOT PUT ANYTHING HERE PLEASE THANKS +-- IF YOU DO THE GAME WILL IMMEDIATELY CRASH AND IT'S NOT THE BEST +-- THANK YOU HAVE A GOOD LUA gfx.LoadSkinFont("segoeui.ttf") --- Memo class -------------- -Memo = {} -Memo.new = function() - local this = { - cache = {} - } - setmetatable(this, {__index = Memo}) - return this -end - -Memo.memoize = function(this, key, generator) - local value = this.cache[key] - if not value then - value = generator() - this.cache[key] = value - end - return value -end - - --- Image class --------------- -Image = { - ANCHOR_LEFT = 1, - ANCHOR_CENTER = 2, - ANCHOR_RIGHT = 4, - ANCHOR_TOP = 8, - ANCHOR_BOTTOM = 16 -} -Image.skin = function(filename, imageFlags) - imageFlags = imageFlags or 0 - local image = gfx.CreateSkinImage(filename, imageFlags) - return Image.wrap(image) -end -Image.wrap = function(image) - local this = { - image = image - } - local w, h = gfx.ImageSize(this.image) - this.w = w - this.h = h - setmetatable(this, {__index = Image}) - return this -end - -Image.draw = function(this, params) - local x = params.x - local y = params.y - local w = params.w or this.w - local h = params.h or this.h - local alpha = params.alpha or 1 - local angle = params.angle or 0 - local anchor_h = params.anchor_h or Image.ANCHOR_CENTER - local anchor_v = params.anchor_v or Image.ANCHOR_CENTER - local scale = params.scale or 1; - - w = w * scale; - h = h * scale; - - if anchor_h == Image.ANCHOR_CENTER then - x = x - w / 2 - elseif anchor_h == Image.ANCHOR_RIGHT then - x = x - w - end - - if anchor_v == Image.ANCHOR_CENTER then - y = y - h / 2 - elseif anchor_v == Image.ANCHOR_BOTTOM then - y = y - h - end - - gfx.BeginPath() - gfx.ImageRect(x, y, w, h, this.image, alpha, angle) -end - - --- ImageFont class ------------------- -ImageFont = {} -ImageFont.new = function(path, chars) - local this = { - images = {} - } - -- load character images - for i = 1, chars:len() do - local c = chars:sub(i, i) - local n = c - if c == "." then - n = "dot" - end - local image = Image.skin(string.format("%s/%s.png", path, n), 0) - this.images[c] = image - end - -- use size of first char as font size - local w, h = gfx.ImageSize(this.images[chars:sub(1, 1)].image) - this.w = w - this.h = h - - setmetatable(this, {__index = ImageFont}) - return this -end -ImageFont.draw = function(this, text, x, y, alpha, hFlag, vFlag) - local totalW = text:len() * this.w - - -- adjust horizontal alignment - if hFlag == gfx.TEXT_ALIGN_CENTER then - x = x - totalW / 2 - elseif hFlag == gfx.TEXT_ALIGN_RIGHT then - x = x - totalW - end - - -- adjust vertical alignment - if vFlag == gfx.TEXT_ALIGN_MIDDLE then - y = y - this.h / 2 - elseif vFlag == gfx.TEXT_ALIGN_BOTTOM then - y = y - this.h - end - - for i = 1, text:len() do - local c = text:sub(i, i) - local image = this.images[c] - if image ~= nil then - gfx.BeginPath() - gfx.ImageRect(x, y, this.w, this.h, image.image, alpha, 0) - end - x = x + this.w - end -end - -function GetDisplayDifficulty(jacketPath, difficulty) - local strippedPath = string.match(jacketPath:lower(), "[/\\][^\\/]+$") - if difficulty == 3 and strippedPath then - if string.find(strippedPath, "inf") ~= nil then - return 5 - elseif string.find(strippedPath, "grv") ~= nil then - return 6 - elseif string.find(strippedPath, "hvn") ~= nil then - return 7 - elseif string.find(strippedPath, "vvd") ~= nil then - return 8 - end - end - - return difficulty+1 -end - -function split(s, delimiter) - result = {}; - for match in (s..delimiter):gmatch("(.-)"..delimiter) do - table.insert(result, match); - end - return result; -end \ No newline at end of file +function useFootGun() while true do end end diff --git a/scripts/common/charting.lua b/scripts/common/charting.lua new file mode 100644 index 0000000..261c6d5 --- /dev/null +++ b/scripts/common/charting.lua @@ -0,0 +1,26 @@ +local Charting = { }; + +function Charting.GetDisplayDifficulty(jacketPath, difficulty) + if jacketPath == nil then + return difficulty + end + + local strippedPath = string.match(jacketPath:lower(), "[/\\][^\\/]+$") + if difficulty == 3 and strippedPath then + if string.find(strippedPath, "inf") ~= nil then + return 5 + elseif string.find(strippedPath, "grv") ~= nil then + return 6 + elseif string.find(strippedPath, "hvn") ~= nil then + return 7 + elseif string.find(strippedPath, "vvd") ~= nil then + return 8 + elseif string.find(strippedPath, "xcd") ~= nil then + return 9 + end + end + + return difficulty + 1 +end + +return Charting; diff --git a/scripts/common/class.lua b/scripts/common/class.lua new file mode 100644 index 0000000..b3ed3ab --- /dev/null +++ b/scripts/common/class.lua @@ -0,0 +1,37 @@ +---Member lookup helper function +---@param key string +---@param bases any +---@return any +local function search(key, bases) + for _, base in ipairs(bases) do + local v = base[key] -- try `i'-th superclass + if v then return v end + end +end + +---Create polimorphic class +---@generic BaseT, T +---@param cls T # class metatable +---@param o? table # initial parameters +---@param ... BaseT # base class metatables (if any) +---@return T # class instance +function CreateInstance(cls, o, ...) + o = o or {} + local nargs = select("#", ...) + local vargs = { select(1, ...) } + cls.__index = cls + if nargs == 1 then + -- single inheritance + local base = vargs[1] + setmetatable(cls, {__index = base}) + o = base.new(o) + elseif nargs > 1 then + -- multiple inheritance (note: slow(er) member lookup) + setmetatable(cls, {__index = function(t, k) return search(k, vargs) end}) + for _, base in ipairs(vargs) do + o = base.new(o) + end + end + setmetatable(o, cls) + return o +end diff --git a/scripts/common/common.lua b/scripts/common/common.lua deleted file mode 100644 index 4fa8d9f..0000000 --- a/scripts/common/common.lua +++ /dev/null @@ -1,34 +0,0 @@ -local stopMusic = function () - local musicPlaying = game.GetSkinSetting('_musicPlaying'); - if musicPlaying and musicPlaying ~= '' then - game.StopSample(musicPlaying); - game.SetSkinSetting("_musicPlaying", "") - end -end - -local function splitString(inputstr, sep) - if sep == nil then - sep = "%s" - end - local t={} - for str in string.gmatch(inputstr, "([^"..sep.."]+)") do - table.insert(t, str) - end - return t -end - -local function filter(tableIn, predicate) - local out = {} - for _, val in ipairs(tableIn) do - if predicate(val) then - table.insert(out, val) - end - end - return out -end - -return { - stopMusic = stopMusic, - splitString = splitString, - filter = filter -} \ No newline at end of file diff --git a/scripts/common/dimensions.lua b/scripts/common/dimensions.lua new file mode 100644 index 0000000..6e35543 --- /dev/null +++ b/scripts/common/dimensions.lua @@ -0,0 +1,64 @@ +local dimtable = { + design = {width = 1080, height = 1920}, + screen = {width = nil, height = nil}, + view = {width = nil, height = nil}, + ratio = {landscapeUW = 21 / 9, landscapeWide = 16 / 9, landscapeStd = 4 / 3, portrait = 9 / 16}, +} + +dimtable.transformToScreenSpace = function() + gfx.Translate((dimtable.screen.width - dimtable.view.width) / 2, 0); + gfx.Scale(dimtable.view.width / dimtable.design.width, dimtable.view.height / dimtable.design.height); + gfx.Scissor(0, 0, dimtable.design.width, dimtable.design.height); +end + +dimtable.updateResolution = function(ratio) + if not ratio then ratio = dimtable.ratio.portrait end + + local screenWidth, screenHeight = game.GetResolution() + if screenWidth ~= dimtable.screen.width or screenHeight ~= dimtable.screen.height then + dimtable.screen.width, dimtable.screen.height = screenWidth, screenHeight + dimtable.view.width, dimtable.view.height = ratio * dimtable.screen.height, dimtable.screen.height + end +end + +---Convert screenspace coordinates to viewspace coordinates +---@param screenX number +---@param screenY number +---@param offsetX? number Viewport offset from the left side (defaults to the portrait viewport offset) +---@param offsetY? number Viewport offset from the top side (defaults to 0) +---@return number, number +dimtable.toViewSpace = function(screenX, screenY, offsetX, offsetY) + offsetX = offsetX or (dimtable.screen.width - dimtable.view.width) / 2 + offsetY = offsetY or 0 + + local viewX, viewY, scaleX, scaleY + + scaleX = dimtable.design.width / dimtable.view.width + scaleY = dimtable.design.height / dimtable.view.height + + viewX = (screenX - offsetX) * scaleX + viewY = (screenY - offsetY) * scaleY + + return viewX, viewY +end + +---Set's up scaled transforms based on the current resolution. +---@param x number +---@param y number +---@param rotation number +---@return number, boolean # The scale applied to the transform and the current landscape state +function dimtable.setUpTransforms(x, y, rotation) + local isLandscape = dimtable.view.width > dimtable.view.height; + + local designWidth = isLandscape and dimtable.design.height or dimtable.design.width + local scale = dimtable.view.width / designWidth + + gfx.ResetTransform(); + gfx.Translate(x, y); + gfx.Rotate(rotation); + gfx.Scale(scale, scale); + + return scale, isLandscape; +end + +return dimtable diff --git a/scripts/common/filereader.lua b/scripts/common/filereader.lua new file mode 100644 index 0000000..1ac858f --- /dev/null +++ b/scripts/common/filereader.lua @@ -0,0 +1,105 @@ +require("common.globals") +--file reader utility functions + +---Get game path +---@return string, string +local function getGamePath() + return debug.getinfo(1,"S").source:sub(2):match("(.*)([\\/])skins") -- this is very hacky :) +end + +local function readBytes(_file) + local out = {} + repeat + local buffer = _file:read(4*1024) + for c in (buffer or ''):gmatch(".") do + table.insert(out, c:byte()) + end + until not buffer + return out +end + +---Read a file in the game folder by lines +---@param path string relative path to game file +---@param mode? openmode default "r" +---@return nil|string[] +function ReadGameFileLines(path, mode) + mode = mode or "r" + + local gamepath, sep = getGamePath() + + local lines = {} + + local f = io.open(gamepath .. sep .. path, mode) + if not f then return nil end + + for line in f:lines("l") do + table.insert(lines, line) + end + f:close() + + return lines +end + +---Read a file in the game folder +---@param path string # relative path to game file +---@param mode? openmode # default "r" +---@return nil|string|integer[] +function ReadGameFile(path, mode) + mode = mode or "r" + + local gamepath, sep = getGamePath() + local out + + local f = io.open(gamepath .. sep .. path, mode) + if not f then return nil end + + if mode:match(".*b") then + out = readBytes(f) + else + out = f:read("a") + end + f:close() + + return out +end + +---Find patterns in file +---@param path string # relative path to game file +---@param pattern string # search pattern +---@return table # {{group1, group2, ...}, ...} +function FindPatterns(path, pattern) + local matches = {} + for _, line in ipairs(ReadGameFileLines(path, "r")) do + if line:match(pattern) then + table.insert(matches, {line:match(pattern)}) + end + end + return matches +end + +--- Check if a file or directory exists in this path +---@param file string # relative path to game file +---@return boolean # file exists +---@return string # error message +function IsFileExists(file) + local gamepath, sep = getGamePath() + file = gamepath .. sep .. file + + local ok, err, code = os.rename(file, file) + if not ok then + game.Log("err: "..err..", code: "..code, game.LOGGER_DEBUG) + if code == 13 then + -- Permission denied, but it exists + return true + end + end + return ok, err +end + +--- Check if a directory exists in this path +---@param path string # relative path to game directory +---@return boolean # directory exists +function IsDir(path) + -- "/" works on both Unix and Windows + return IsFileExists(path .. "/") +end diff --git a/scripts/common/gameconfig.lua b/scripts/common/gameconfig.lua new file mode 100644 index 0000000..06accbf --- /dev/null +++ b/scripts/common/gameconfig.lua @@ -0,0 +1,11 @@ +require("common.filereader") + +GameConfig = {} + +function RefreshConfig() + for _, match in ipairs(FindPatterns("Main.cfg", "(%w*)%s*=%s*\"?([^\"%s]*)\"?")) do + GameConfig[match[1]] = match[2] + end +end + +RefreshConfig() \ No newline at end of file diff --git a/scripts/common/globals.lua b/scripts/common/globals.lua new file mode 100644 index 0000000..b3fbf1a --- /dev/null +++ b/scripts/common/globals.lua @@ -0,0 +1,2 @@ +---Drewol, what are you doing? Why is there no game.LOGGER_DEBUG? +game.LOGGER_DEBUG = 0 diff --git a/scripts/json.lua b/scripts/common/json.lua similarity index 96% rename from scripts/json.lua rename to scripts/common/json.lua index 2d37c58..d008bf7 100644 --- a/scripts/json.lua +++ b/scripts/common/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/common/sound.lua b/scripts/common/sound.lua new file mode 100644 index 0000000..e14cc17 --- /dev/null +++ b/scripts/common/sound.lua @@ -0,0 +1,11 @@ +local stopMusic = function () + local musicPlaying = game.GetSkinSetting('_musicPlaying'); + if musicPlaying and musicPlaying ~= '' then + game.StopSample(musicPlaying); + game.SetSkinSetting("_musicPlaying", "") + end +end + +return { + stopMusic = stopMusic +} \ No newline at end of file diff --git a/scripts/common/util.lua b/scripts/common/util.lua new file mode 100644 index 0000000..e7ab595 --- /dev/null +++ b/scripts/common/util.lua @@ -0,0 +1,89 @@ +local function split(s, delimiter) + local result = {}; + for match in (s..delimiter):gmatch("(.-)"..delimiter) do + table.insert(result, match); + end + return result; +end + +local function filter(tableIn, predicate) + local out = {} + for _, val in ipairs(tableIn) do + if predicate(val) then + table.insert(out, val) + end + end + return out +end + +local function clamp(x, min, max) + if x < min then + x = min + end + if x > max then + x = max + end + + return x +end + +local function round(num) + return num + (2^52 + 2^51) - (2^52 + 2^51) +end + +local function sign(x) + return ( + (x > 0) and 1 + or + (x < 0) and -1 + or + 0 + ) +end + +local function roundToZero(x) + if x < 0 then + return math.ceil(x) + elseif x > 0 then + return math.floor(x) + else + return 0 + end +end + +local function areaOverlap(x, y, areaX, areaY, areaW, areaH) + return x > areaX and y > areaY and x < areaX + areaW and y < areaY + areaH +end + +local function lerp(x, x0, y0, x1, y1) + return y0 + (x - x0) * (y1 - y0) / (x1 - x0) +end + +--modulo operation for index value +local function modIndex(index, mod) + return (index - 1) % mod + 1 +end + +local function firstAlphaNum(s) + for i = 1, string.len(s) do + local byte = string.byte(s, i); + if ((byte >= 65 and byte <= 90) or (byte >= 97 and byte <= 122) or (byte >= 48 and byte <= 57)) then + return string.sub(s, i, i); + end + end + + return ''; +end + +return { + split = split, + filter = filter, + clamp = clamp, + round = round, + sign = sign, + roundToZero = roundToZero, + areaOverlap = areaOverlap, + lerp = lerp, + modIndex = modIndex, + firstAlphaNum = firstAlphaNum, +} diff --git a/scripts/common/version.lua b/scripts/common/version.lua index 1290af7..f634fd2 100644 --- a/scripts/common/version.lua +++ b/scripts/common/version.lua @@ -1,6 +1,21 @@ +local MAJOR = 0 +local MINOR = 2 +local PATCH = 3 + +local function getLongVersion() + return "USC:E:G:S:" .. MAJOR .. MINOR .. PATCH +end + +---Get version string +---@return string +local function getVersion() + return table.concat({MAJOR, MINOR, PATCH}, ".") +end return { - MAJOR = 0, - MINOR = 2, - PATCH = 3 + MAJOR = MAJOR, + MINOR = MINOR, + PATCH = PATCH, + getLongVersion = getLongVersion, + getVersion = getVersion } diff --git a/scripts/components/background.lua b/scripts/components/background.lua index b100a7a..3f5c9be 100644 --- a/scripts/components/background.lua +++ b/scripts/components/background.lua @@ -1,5 +1,3 @@ - - local resx, resy = game.GetResolution() local desw, desh = 1080,1920; local scale = 1; diff --git a/scripts/components/diff_rectangle.lua b/scripts/components/diff_rectangle.lua index b32c19d..4cf81ec 100644 --- a/scripts/components/diff_rectangle.lua +++ b/scripts/components/diff_rectangle.lua @@ -7,7 +7,8 @@ local difficultyLabelImages = { gfx.CreateSkinImage("diff/5 infinite.png", 0), gfx.CreateSkinImage("diff/6 gravity.png", 0), gfx.CreateSkinImage("diff/7 heavenly.png", 0), - gfx.CreateSkinImage("diff/8 vivid.png", 0) + gfx.CreateSkinImage("diff/8 vivid.png", 0), + gfx.CreateSkinImage("diff/9 exceed.png", 0), } local difficultyLabelTexts = { @@ -15,12 +16,20 @@ local difficultyLabelTexts = { "ADV", "EXH", "MXM", + "INF", "GRV", "HVN", - "VVD" + "VVD", + "EXC" } function render(deltatime, x, y, scale, diff, level) + local difficultyLabelImage = difficultyLabelImages[diff] + if difficultyLabelImage == nil then + game.Log("Unknown chart difficulty index "..diff..", fallback to MXM", game.LOGGER_WARNING) + difficultyLabelImage = difficultyLabelImages[4] + end + gfx.Save() gfx.Translate(x,y); gfx.Scale(scale,scale) @@ -29,7 +38,7 @@ function render(deltatime, x, y, scale, diff, level) gfx.BeginPath(); gfx.ImageRect(0, 0, 140, 31 , - difficultyLabelImages[diff+1] or + difficultyLabelImages[diff] or difficultyLabelImages[4], 1, 0); @@ -42,7 +51,7 @@ function render(deltatime, x, y, scale, diff, level) gfx.FontSize(22) gfx.Scale(1.2,1); -- Make the diff text more W I D E gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.Text(difficultyLabelTexts[diff+1], 18, 17); + gfx.Text(difficultyLabelTexts[diff], 18, 17); @@ -60,4 +69,4 @@ end return { render = render -} \ No newline at end of file +} diff --git a/scripts/components/footer.lua b/scripts/components/footer.lua index 9b07302..89660ef 100644 --- a/scripts/components/footer.lua +++ b/scripts/components/footer.lua @@ -1,91 +1,67 @@ +local version = require("common.version") +local Dim = require("common.dimensions") -local version = require('common.version') - -local resx, resy = game.GetResolution() -local desw, desh = 1080,1920; -local scale = 1; - -local BAR_ALPHA = 191; +local BAR_ALPHA = 191 local FOOTER_HEIGHT = 128 -local footerY = desh - FOOTER_HEIGHT; +local footerY = Dim.design.height - FOOTER_HEIGHT -- Images -local footerRightImage = gfx.CreateSkinImage("components/bars/footer_right.png", 0); +local footerRightImage = gfx.CreateSkinImage("components/bars/footer_right.png", 0) -- Animation related -local entryTransitionScale = 0; -local entryTransitionFooterYOffset = 0; +local entryTransitionScale = 0 +local entryTransitionFooterYOffset = 0 -local legend = { - { - control = 'START', - text = 'Confirm selection' - }, - { - control = 'KNOB', - text = 'Scroll' - }, -} +local legend = {{control = "START", text = "Confirm selection"}, {control = "KNOB", text = "Scroll"}} -local set = function () - -end +local function set() end -function resetLayoutInformation() - resx, resy = game.GetResolution() - desw = 1080 - desh = 1920 - scale = resx / desw -end +local function drawFooter() + gfx.BeginPath() + gfx.FillColor(0, 0, 0, BAR_ALPHA) + gfx.Rect(0, footerY, Dim.design.width, FOOTER_HEIGHT) + gfx.Fill() -local drawFooter = function () - gfx.BeginPath(); - gfx.FillColor(0,0,0,BAR_ALPHA); - gfx.Rect(0,footerY,desw, FOOTER_HEIGHT); - gfx.Fill(); - - - gfx.BeginPath(); - gfx.ImageRect(desw-275, footerY-25, 328*0.85, 188*0.85, footerRightImage, 1, 0); + gfx.BeginPath() + gfx.ImageRect(Dim.design.width - 275, footerY - 25, 328 * 0.85, 188 * 0.85, footerRightImage, 1, 0) - gfx.BeginPath(); - gfx.LoadSkinFont("Digital-Serial-Bold.ttf"); + gfx.BeginPath() + gfx.LoadSkinFont("Digital-Serial-Bold.ttf") gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.FillColor(255, 255, 255, 255); - gfx.Text('EXPERIMENTALGEAR ' .. version.MAJOR .. '.' .. version.MINOR .. '.' .. version.PATCH .. '', 8, 1895); + gfx.FillColor(255, 255, 255, 255) + gfx.Text("EXPERIMENTALGEAR " .. version.MAJOR .. "." .. version.MINOR .. "." .. version.PATCH .. "", 8, 1895) end -local progressTransitions = function (deltaTime) - entryTransitionScale = entryTransitionScale + deltaTime / 0.3; - if (entryTransitionScale > 1) then - entryTransitionScale = 1; - end +local function progressTransitions(deltaTime) + entryTransitionScale = entryTransitionScale + deltaTime / 0.3 + if (entryTransitionScale > 1) then entryTransitionScale = 1 end - entryTransitionFooterYOffset = FOOTER_HEIGHT*(1-entryTransitionScale) - footerY = desh-FOOTER_HEIGHT+entryTransitionFooterYOffset; + entryTransitionFooterYOffset = FOOTER_HEIGHT * (1 - entryTransitionScale) + footerY = Dim.design.height - FOOTER_HEIGHT + entryTransitionFooterYOffset end -local draw = function (deltaTime, params) - if (params) then - if params.noEnterTransition then - entryTransitionScale = 1; - end +local function draw(deltaTime, params) + if params and params.noEnterTransition then + entryTransitionScale = 1 end gfx.Save() - - gfx.LoadSkinFont("NotoSans-Regular.ttf"); + gfx.ResetTransform() - drawFooter(); + Dim.updateResolution() + + Dim.transformToScreenSpace() + + gfx.LoadSkinFont("NotoSans-Regular.ttf") + + drawFooter() + + progressTransitions(deltaTime) - progressTransitions(deltaTime); gfx.Restore() end -return { - set = set, - draw = draw -}; \ No newline at end of file +return {set = set, draw = draw} diff --git a/scripts/components/headers/challengeSelectHeader.lua b/scripts/components/headers/challengeSelectHeader.lua index 9e73b3b..28599c1 100644 --- a/scripts/components/headers/challengeSelectHeader.lua +++ b/scripts/components/headers/challengeSelectHeader.lua @@ -1,34 +1,28 @@ -local desw = 1080; -local desh = 1920; -local scale = 1; +local Dim = require("common.dimensions") -local BAR_ALPHA = 191; +local BAR_ALPHA = 191 local HEADER_HEIGHT = 100 -local headerY = 0; -local animationHeaderGlowScale = 0; -local animationHeaderGlowAlpha = 0; +local animationHeaderGlowScale = 0 +local animationHeaderGlowAlpha = 0 -- Images local headerTitleImage = gfx.CreateSkinImage("challenge_select/skill_analyzer.png", gfx.IMAGE_GENERATE_MIPMAPS) -local drawHeader = function () - gfx.BeginPath(); - gfx.FillColor(0,0,0,BAR_ALPHA); - gfx.Rect(0,0,desw, HEADER_HEIGHT); - gfx.Fill(); +local function drawHeader() + gfx.BeginPath() + gfx.FillColor(0, 0, 0, BAR_ALPHA) + gfx.Rect(0, 0, Dim.design.width, HEADER_HEIGHT) + gfx.Fill() gfx.ClosePath() local headerImageWidth, headerImageHeight = gfx.ImageSize(headerTitleImage) - gfx.ImageRect( - (desw - headerImageWidth) / 2, (HEADER_HEIGHT - headerImageHeight) / 2 - 12, -- asset png is not centered on the y axis - headerImageWidth, headerImageHeight, - headerTitleImage, 1, 0 - ) + gfx.ImageRect((Dim.design.width - headerImageWidth) / 2, (HEADER_HEIGHT - headerImageHeight) / 2 - 12, -- asset png is not centered on the y axis + headerImageWidth, headerImageHeight, headerTitleImage, 1, 0) end -local progressTransitions = function (deltatime) +local function progressTransitions(deltatime) -- HEADER GLOW ANIMATION if animationHeaderGlowScale < 1 then animationHeaderGlowScale = animationHeaderGlowScale + deltatime / 1 -- transition should last for that time in seconds @@ -37,24 +31,29 @@ local progressTransitions = function (deltatime) end if animationHeaderGlowScale < 0.5 then - animationHeaderGlowAlpha = animationHeaderGlowScale * 2; + animationHeaderGlowAlpha = animationHeaderGlowScale * 2 else - animationHeaderGlowAlpha = 1-((animationHeaderGlowScale-0.5) * 2); + animationHeaderGlowAlpha = 1 - ((animationHeaderGlowScale - 0.5) * 2) end - animationHeaderGlowAlpha = animationHeaderGlowAlpha*0.4 + animationHeaderGlowAlpha = animationHeaderGlowAlpha * 0.4 end -local draw = function (deltatime) +local function draw(deltatime) gfx.Save() - - gfx.LoadSkinFont("NotoSans-Regular.ttf"); - drawHeader(); + gfx.ResetTransform() + + Dim.updateResolution() + + Dim.transformToScreenSpace() + + gfx.LoadSkinFont("NotoSans-Regular.ttf") + + drawHeader() + + progressTransitions(deltatime) - --progressTransitions(deltatime); gfx.Restore() end -return { - draw = draw -}; \ No newline at end of file +return {draw = draw} diff --git a/scripts/components/headers/songSelectHeader.lua b/scripts/components/headers/songSelectHeader.lua index f51e216..9e025b9 100644 --- a/scripts/components/headers/songSelectHeader.lua +++ b/scripts/components/headers/songSelectHeader.lua @@ -1,35 +1,28 @@ +local Dim = require("common.dimensions") - -local resx, resy = game.GetResolution(); -local desw = 1080; -local desh = 1920; -local scale = 1; - -local BAR_ALPHA = 191; +local BAR_ALPHA = 191 local HEADER_HEIGHT = 100 -local headerY = 0; -local animationHeaderGlowScale = 0; -local animationHeaderGlowAlpha = 0; +local animationHeaderGlowScale = 0 +local animationHeaderGlowAlpha = 0 -- Images local headerTitleImage = gfx.CreateSkinImage("song_select/header/title.png", 1) local headerGlowTitleImage = gfx.CreateSkinImage("song_select/header/title_glow.png", 1) - -local drawHeader = function () - gfx.BeginPath(); - gfx.FillColor(0,0,0,BAR_ALPHA); - gfx.Rect(0,0,desw, HEADER_HEIGHT); - gfx.Fill(); +local drawHeader = function() + gfx.BeginPath() + gfx.FillColor(0, 0, 0, BAR_ALPHA) + gfx.Rect(0, 0, Dim.design.width, HEADER_HEIGHT) + gfx.Fill() gfx.ClosePath() - gfx.ImageRect(42, 14, 423*0.85, 80*0.85, headerTitleImage, 1, 0) - gfx.ImageRect(42, 14, 423*0.85, 80*0.85, headerGlowTitleImage, animationHeaderGlowAlpha, 0) + gfx.ImageRect(42, 14, 423 * 0.85, 80 * 0.85, headerTitleImage, 1, 0) + gfx.ImageRect(42, 14, 423 * 0.85, 80 * 0.85, headerGlowTitleImage, animationHeaderGlowAlpha, 0) end -local progressTransitions = function (deltatime) +local progressTransitions = function(deltatime) -- HEADER GLOW ANIMATION if animationHeaderGlowScale < 1 then animationHeaderGlowScale = animationHeaderGlowScale + deltatime / 1 -- transition should last for that time in seconds @@ -38,24 +31,29 @@ local progressTransitions = function (deltatime) end if animationHeaderGlowScale < 0.5 then - animationHeaderGlowAlpha = animationHeaderGlowScale * 2; + animationHeaderGlowAlpha = animationHeaderGlowScale * 2 else - animationHeaderGlowAlpha = 1-((animationHeaderGlowScale-0.5) * 2); + animationHeaderGlowAlpha = 1 - ((animationHeaderGlowScale - 0.5) * 2) end - animationHeaderGlowAlpha = animationHeaderGlowAlpha*0.4 + animationHeaderGlowAlpha = animationHeaderGlowAlpha * 0.4 end -local draw = function (deltatime) +local draw = function(deltatime) gfx.Save() - - gfx.LoadSkinFont("NotoSans-Regular.ttf"); - drawHeader(); + gfx.ResetTransform() + + Dim.updateResolution() + + Dim.transformToScreenSpace() + + gfx.LoadSkinFont("NotoSans-Regular.ttf") + + drawHeader() + + progressTransitions(deltatime) - progressTransitions(deltatime); gfx.Restore() end -return { - draw = draw -}; \ No newline at end of file +return {draw = draw} diff --git a/scripts/common/numbers.lua b/scripts/components/numbers.lua similarity index 100% rename from scripts/common/numbers.lua rename to scripts/components/numbers.lua diff --git a/scripts/components/pager/containerfield.lua b/scripts/components/pager/containerfield.lua new file mode 100644 index 0000000..49a1426 --- /dev/null +++ b/scripts/components/pager/containerfield.lua @@ -0,0 +1,68 @@ +require("common.class") +local Field = require("components.pager.field") + +---@class ContainerField: Field +---@field content Field[] +local ContainerField = { + __tostring = function() return "ContainerField" end, +} + +---Create a new ContainerField instance +---@param o? table # initial parameters +---@return ContainerField +function ContainerField.new(o) + o = o or {} + + --set instance members + + o.content = o.content or {} + + local this = CreateInstance(ContainerField, o, Field) + + this:refreshFields() + + return this +end + +---Add content to container +---@param field Field +function ContainerField:addField(field) + field.parent = self + table.insert(self.content, field) +end + +---Refresh content parameters +function ContainerField:refreshFields() + for _, child in ipairs(self.content) do + child.parent = self + end +end + +---@param deltaTime number # frametime in seconds +function ContainerField:drawBackground(deltaTime) end + +---@param deltaTime number # frametime in seconds +function ContainerField:drawContent(deltaTime) + for _, child in ipairs(self.content) do + child:render(deltaTime) + end +end + +---@param deltaTime number # frametime in seconds +function ContainerField:drawForeground(deltaTime) end + +---@param deltaTime number # frametime in seconds +function ContainerField:render(deltaTime) + gfx.Save() + + gfx.Translate(self.posX, self.posY) + gfx.Scissor(0, 0, self.aabbW, self.aabbH) + + self:drawBackground(deltaTime) + self:drawContent(deltaTime) + self:drawForeground(deltaTime) + + gfx.Restore() +end + +return ContainerField diff --git a/scripts/components/pager/field.lua b/scripts/components/pager/field.lua new file mode 100644 index 0000000..8e5ab0d --- /dev/null +++ b/scripts/components/pager/field.lua @@ -0,0 +1,113 @@ +require("common.class") + +---@class Field +---@field parent Page|ContainerField +---@field posX number +---@field posY number +---@field aabbW number +---@field aabbH number +local Field = { + __tostring = function() return "Field" end, +} + +---Create a new Field instance +---@param o? table # initial parameters +---@return Field +function Field.new(o) + o = o or {} + + --set instance members + + o.parent = o.parent or nil + o.posX = o.posX or 0 + o.posY = o.posY or 0 + o.aabbW = o.aabbW or 0 + o.aabbH = o.aabbH or 0 + + return CreateInstance(Field, o) +end + +---Get the containing top-level parent page +---@return Field|Page +function Field:getParentPage() + if self.parent and self.parent.getParentPage then + return self.parent:getParentPage() + else + return self.parent + end +end + +---@param obj? any # message object for the field +function Field:activate(obj) end + +---@param obj? any # message object for the field +function Field:focus(obj) end + +---@param obj? any # message object for the field +function Field:deactivate(obj) end + +---@param button integer # options are under the `game` table prefixed with `BUTTON` +---@return boolean # true if further button input processing should be stopped, otherwise false +function Field:handleButtonInput(button) + return false +end + +---@param knob integer # `0` = Left, `1` = Right +---@param delta number # in radians, `-2*pi` to `0` (turning CCW) and `0` to `2*pi` (turning CW) +---@return boolean # true if further button input processing should be stopped, otherwise false +function Field:handleKnobInput(knob, delta) + return false +end + +---@param deltaTime number # frametime in seconds +function Field:drawContent(deltaTime) + -- dummy field content + + gfx.ResetScissor() + + local offX = -50 + local offY = -50 + local aabbW = 100 + local aabbH = 100 + + gfx.BeginPath() + gfx.FillColor(255, 0, 128, 192) + gfx.StrokeColor(0, 0, 0) + gfx.StrokeWidth(2) + gfx.Rect(offX, offY, aabbW, aabbH) + gfx.Fill() + gfx.Stroke() + + gfx.BeginPath() + gfx.MoveTo(offX, 0) + gfx.LineTo(offX + aabbW, 0) + gfx.MoveTo(0, offY) + gfx.LineTo(0, offY + aabbH) + gfx.StrokeColor(0, 0, 0, 64) + gfx.StrokeWidth(2) + gfx.Stroke() + + local fontSize = 18 + local fontMargin = 4 + gfx.BeginPath() + gfx.FontSize(fontSize) + gfx.LoadSkinFont("dfmarugoth.ttf") + gfx.FillColor(0, 0, 0) + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER | gfx.TEXT_ALIGN_MIDDLE) + gfx.Text("TEXTURE", 0, -fontSize / 2 - fontMargin) + gfx.Text("MISSING", 0, fontSize / 2 + fontMargin) +end + +---@param deltaTime number # frametime in seconds +function Field:render(deltaTime) + gfx.Save() + + gfx.Translate(self.posX, self.posY) + gfx.Scissor(0, 0, self.aabbW, self.aabbH) + + self:drawContent(deltaTime) + + gfx.Restore() +end + +return Field diff --git a/scripts/components/pager/linkfield.lua b/scripts/components/pager/linkfield.lua new file mode 100644 index 0000000..107d9a1 --- /dev/null +++ b/scripts/components/pager/linkfield.lua @@ -0,0 +1,44 @@ +require("common.class") +local Field = require("components.pager.field") + +---@class LinkField: Field +---@field link Page +local LinkField = { + __tostring = function() return "LinkField" end +} + +---Create a new LinkField instance +---@param o? table # initial parameters +---@return LinkField +function LinkField.new(o) + o = o or {} + + o.link = o.link or nil + + return CreateInstance(LinkField, o, Field) +end + +---@param button integer # options are under the `game` table prefixed with `BUTTON` +---@return boolean # true if further button input processing should be stopped, otherwise false +function LinkField:handleButtonInput(button) + if not self.link then + game.Log(tostring(self) .. " does not have a valid link", game.LOGGER_ERROR) + return false + end + + if button == game.BUTTON_STA then + local parentPage = self:getParentPage() + if parentPage and parentPage.viewHandler then + game.Log(tostring(self) .. " viewHandler:navigate(" .. tostring(self.link) .. ") called", game.LOGGER_INFO) + parentPage.viewHandler:navigate(self.link) + return true + else + local target = (parentPage and parentPage.viewHandler or "PageView") + game.Log(tostring(self) .. " can't access " .. tostring(target) .. " instance", game.LOGGER_ERROR) + end + end + + return false +end + +return LinkField diff --git a/scripts/components/pager/page.lua b/scripts/components/pager/page.lua new file mode 100644 index 0000000..8496f0f --- /dev/null +++ b/scripts/components/pager/page.lua @@ -0,0 +1,72 @@ +require("common.globals") +require("common.class") + +---@class Page +---@field content Field[] +---@field viewHandler nil|PageView +local Page = { + __tostring = function() return "Page" end, +} + +---Create a new Page instance +---@param o? table # initial parameters +---@return Page +function Page.new(o) + o = o or {} + + --set instance members + + o.content = o.content or {} + o.viewHandler = o.viewHandler or nil + + return CreateInstance(Page, o) +end + +---Add field to page +---@param field Field +function Page:addField(field) + field.parent = self + table.insert(self.content, field) +end + +---Refresh content values +function Page:refreshFields() + for _, field in ipairs(self.content) do + field.parent = self + end +end + +---@param button integer # options are under the `game` table prefixed with `BUTTON` +function Page:handleButtonInput(button) + if button == game.BUTTON_BCK then + if self.viewHandler then + self.viewHandler:back() + end + end +end + +---@param knob integer # `0` = Left, `1` = Right +---@param delta number # in radians, `-2*pi` to `0` (turning CCW) and `0` to `2*pi` (turning CW) +function Page:handleKnobInput(knob, delta) end + +---@param deltaTime number # frametime in seconds +function Page:drawBackground(deltaTime) end + +---@param deltaTime number # frametime in seconds +function Page:drawContent(deltaTime) + for _, child in ipairs(self.content) do + child:render(deltaTime) + end +end + +---@param deltaTime number # frametime in seconds +function Page:drawForeground(deltaTime) end + +---@param deltaTime number # frametime in seconds +function Page:render(deltaTime) + self:drawBackground(deltaTime) + self:drawContent(deltaTime) + self:drawForeground(deltaTime) +end + +return Page diff --git a/scripts/components/pager/pageview.lua b/scripts/components/pager/pageview.lua new file mode 100644 index 0000000..4fa2725 --- /dev/null +++ b/scripts/components/pager/pageview.lua @@ -0,0 +1,84 @@ +require("common.class") + +---@class PageView +---@field pageStack Page[] +local PageView = { + __tostring = function() return "PageView" end +} + +local function pushStack(t, o) + table.insert(t, 1, o) +end + +local function popStack(t) + return table.remove(t, 1) +end + +---Create a new PageView instance +---@param rootPage Page +---@return PageView +function PageView.new(rootPage) + local o = {} + + --set viewHandler as this instance for rootPage + + rootPage.viewHandler = o + + --set instance members + + o.pageStack = {} + pushStack(o.pageStack, rootPage) + + return CreateInstance(PageView, o) +end + +---Get page from pageStack +---@param index? integer # defaults to 1 (top of the stack) +---@return Page +function PageView:get(index) + index = index or 1 + + return self.pageStack[index] +end + +---Navigate to page +---@param page Page # page to put on top of the pageStack +function PageView:navigate(page) + page.viewHandler = self + pushStack(self.pageStack, page) +end + +---Replace the current pageStack with a new root page +---@param rootPage Page +function PageView:replace(rootPage) + self:clear() + self:navigate(rootPage) +end + +---Navigate to the previous page +function PageView:back() + if not self:get() then + game.Log(self .. ":back() : pageStack empty, cannot go back", game.LOGGER_WARNING) + return + end + + self:get().viewHandler = nil + popStack(self.pageStack) +end + +---Clear the pageStack +function PageView:clear() + --clear pageStack + while self:get() do + self:back() + end +end + +---@param deltaTime number # frametime in seconds +function PageView:render(deltaTime) + if self:get() then + self:get():render(deltaTime) + end +end + +return PageView diff --git a/scripts/components/wallpaper.lua b/scripts/components/wallpaper.lua new file mode 100644 index 0000000..a719ebf --- /dev/null +++ b/scripts/components/wallpaper.lua @@ -0,0 +1,23 @@ +local Dim = require("common.dimensions") + +local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY) +local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage) + +local patternAngle = 0 +local patternAlpha = 0.2 + +function render() + gfx.Save() + gfx.ResetTransform() + + gfx.BeginPath() + gfx.Rect(0, 0, Dim.screen.width, Dim.screen.height) + gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, patternAngle, backgroundImage, patternAlpha)) + gfx.Fill() + + gfx.Restore() +end + +return { + render = render +} \ No newline at end of file diff --git a/scripts/downloadscreen.lua b/scripts/downloadscreen.lua index 8accd7b..b3aeecb 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 "common.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/gameplay.lua b/scripts/gameplay.lua index 3a9ae51..5673702 100644 --- a/scripts/gameplay.lua +++ b/scripts/gameplay.lua @@ -1,5 +1,11 @@ local VolforceWindow = require('components.volforceWindow') +local Dimensions = require 'common.dimensions'; + +do + local resx, resy = game.GetResolution(); + Dimensions.updateResolution(resx / resy); +end local Banner = require('gameplay.banner') local CritLine = require('gameplay.crit_line') @@ -11,31 +17,14 @@ local Gauge = require('gameplay.gauge') local Chain = require('gameplay.chain') local LaserAlert = require('gameplay.laser_alert') +local HitFX = require 'gameplay.hitfx' +local EarlyLate = require 'gameplay.earlylate' + local TrackEnd = require('gameplay.track_end') -local json = require "json" +local json = require("common.json") --- 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 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 +local showHitAnims = true; local users = nil @@ -44,11 +33,8 @@ local chain = 0; local score = 0; function render(deltaTime) - -- detect resolution change local resx, resy = game.GetResolution(); - if resx ~= resX or resy ~= resY then - resolutionChange(resx, resy) - end + Dimensions.updateResolution(resx / resy); Banner.render(deltaTime, users, gameplay.user_id); @@ -74,15 +60,26 @@ function render(deltaTime) Chain.render(deltaTime, gameplay.comboState, chain, gameplay.critLine.x, gameplay.critLine.y); LaserAlert.render(deltaTime); + + EarlyLate.render(deltaTime) end function render_crit_base(deltaTime) - CritLine.renderBase(deltaTime, gameplay.critLine.x, gameplay.critLine.y, -gameplay.critLine.rotation, gameplay.critLine.cursors); - Console.render(deltaTime, gameplay.critLine.x, gameplay.critLine.y, -gameplay.critLine.rotation); + local cl = gameplay.critLine + + CritLine.renderBase(deltaTime, cl.x, cl.y, -cl.rotation); + Console.render(deltaTime, cl.x, cl.y, -cl.rotation); end function render_crit_overlay(deltaTime) + local cl = gameplay.critLine + local centerX = cl.x + local centerY = cl.y + local rot = -cl.rotation + HitFX.renderButtons(deltaTime, centerX, centerY, rot); + HitFX.renderLasers(deltaTime, centerX, centerY, rot, cl.cursors); + CritLine.renderOverlay(deltaTime, centerX, centerY, rot, cl.cursors, gameplay.laserActive) end function render_intro(deltaTime) @@ -103,6 +100,9 @@ end function update_score(newScore) score = newScore + if (score == 0) then + maxChain = 0; + end end function update_combo(newCombo) @@ -118,11 +118,22 @@ function near_hit(wasLate) end function button_hit(button, rating, delta) + if (showHitAnims) then + if (rating == 1) then + HitFX.TriggerAnimation("Near", button + 1) + elseif (rating == 2) then + HitFX.TriggerAnimation("Crit", button + 1) + end + end + if 0 < rating and rating < 3 then + EarlyLate.TriggerAnimation(rating, delta) + end end function laser_slam_hit(slamLength, startPos, endPost, index) - + if (showHitAnims) then + end end function laser_alert(isRight) @@ -153,9 +164,9 @@ end -- Update the users in the scoreboard function score_callback(response) - if response.status ~= 200 then - error() - return + if response.status ~= 200 then + error() + return end local jsondata = json.decode(response.text) users = {} diff --git a/scripts/gameplay/chain.lua b/scripts/gameplay/chain.lua index 47eb90f..1ad9cae 100644 --- a/scripts/gameplay/chain.lua +++ b/scripts/gameplay/chain.lua @@ -1,4 +1,4 @@ -local Numbers = require('common.numbers') +local Numbers = require('components.numbers') local chainLabel = gfx.CreateSkinImage("gameplay/chain/label.png", 0) diff --git a/scripts/gameplay/console.lua b/scripts/gameplay/console.lua index 0cfe8f1..568fde9 100644 --- a/scripts/gameplay/console.lua +++ b/scripts/gameplay/console.lua @@ -1,47 +1,89 @@ -local consoleBaseImage = gfx.CreateSkinImage("gameplay/console/base.png", 0) +local Dimensions = require 'common.dimensions' local CONSOLE_W = 1352; local CONSOLE_H = 712; --- Similar to crit line transforms, since the console needs to follow the lane rotation -local setUpTransforms = function (x,y,rotation) - local resx, resy = game.GetResolution() - local desw = 1080 - local desh = 1920 - local scale = resx / desw - - - gfx.Translate(x, y) - gfx.Rotate(rotation) - gfx.Scale(scale,scale) +local function createConsoleImage(name) + return gfx.CreateSkinImage("gameplay/console/"..name..".png", 0) end - -local render = function (deltaTime, critLineCenterX, critLineCenterY, critLineRotation) - local resx, resy = game.GetResolution(); - if (resx > resy) then - return - end - - setUpTransforms( - critLineCenterX, - critLineCenterY, - critLineRotation - ) - +local function renderConsoleImage(image, alpha) gfx.BeginPath(); gfx.ImageRect( -CONSOLE_W/2, -CONSOLE_H/2+350, CONSOLE_W, CONSOLE_H, - consoleBaseImage, - 1, + image, + alpha, 0 ); end +local consoleBaseImage = createConsoleImage("base") + +local buttonGlowImages = { + [0] = createConsoleImage("glow_bta"), + createConsoleImage("glow_btb"), + createConsoleImage("glow_btc"), + createConsoleImage("glow_btd"), + createConsoleImage("glow_fxl"), + createConsoleImage("glow_fxr"), +}; + +local knobGlowImages = { + [0] = createConsoleImage("glow_voll"), + createConsoleImage("glow_volr"), +}; + +local lastKnobState = { + [0] = -1, + -1 +}; + +local doFlash = true; + +local render = function (deltaTime, critLineCenterX, critLineCenterY, critLineRotation) + local resx, resy = game.GetResolution(); + if (resx > resy) then + return + end + + Dimensions.setUpTransforms( + critLineCenterX, + critLineCenterY, + critLineRotation + ) + + renderConsoleImage(consoleBaseImage, 1) + + if doFlash then + for button=0,5 do + if game.GetButton(button) then + renderConsoleImage(buttonGlowImages[button], 0.75) + end + end + + -- Knobs also work + -- commented out do to missing/incorrect textures + + --[[ + for knob=0,1 do + local state = game.GetKnob(knob) + + if state ~= lastKnobState[knob] then + renderConsoleImage(knobGlowImages[knob], 1) + + lastKnobState[knob] = state + end + end + ]] + end + + doFlash = not doFlash; +end + return { render=render } \ No newline at end of file diff --git a/scripts/gameplay/crit_line.lua b/scripts/gameplay/crit_line.lua index 7408dd9..e3edb54 100644 --- a/scripts/gameplay/crit_line.lua +++ b/scripts/gameplay/crit_line.lua @@ -1,5 +1,10 @@ +local Dimensions = require 'common.dimensions' + +local blackGradientImage = gfx.CreateSkinImage('gameplay/crit_line/black_gradient.png', 0) + local baseImage = gfx.CreateSkinImage("gameplay/crit_line/base.png", 0) +local baseImageLandscape = gfx.CreateSkinImage("gameplay/crit_line/base_landscape.png", 0) local textImage = gfx.CreateSkinImage("gameplay/crit_line/text.png", 0) local cursorImage = gfx.CreateSkinImage("gameplay/crit_line/cursor.png", 0); @@ -12,46 +17,48 @@ local cursorGlowTopImages = { gfx.CreateSkinImage("gameplay/crit_line/cursor_glow_top_left.png", 0), gfx.CreateSkinImage("gameplay/crit_line/cursor_glow_top_right.png", 0), } +local cursorTailImages = { + gfx.CreateSkinImage("gameplay/crit_line/cursor_tail_l.png", 0), + gfx.CreateSkinImage("gameplay/crit_line/cursor_tail_r.png", 0), +} -local CRITBAR_W = 1496 -local CRITBAR_H = 348 - -local scale; +local CRITBAR_W = 1080 * 1.4 +local CRITBAR_H = 251 * 1.4 local isLandscape = false; -local setUpTransforms = function (x,y,rotation) - local resx, resy = game.GetResolution(); - isLandscape = resx > resy; +local drawCursors = function (scale, cursors, laserActive) + local cursorW = 598 * 0.165; + local cursorH = 673 * 0.14; - local desw, desh; + local tailW = cursorW * 9 + local tailH = cursorH * 9 - if (isLandscape) then - desw = 1920; - desh = 1080; - else - desw = 1080; - desh = 1920; - end - - scale = resx / desw - - gfx.Translate(x, y) - gfx.Rotate(rotation) - gfx.Scale(scale,scale) -end - -local drawCursors = function (centerX, centerY,cursors) - local cursorW = 598*0.2; - local cursorH = 673*0.2; for i = 0, 1, 1 do - gfx.Save(); + local luaIndex = i + 1 local cursor = cursors[i]; - gfx.BeginPath(); - gfx.SkewX(cursor.skew) - local cursorX = (cursor.pos *(1/scale) - cursorW/2); - local cursorY = (-cursorH/2); + gfx.Save(); + gfx.BeginPath(); + + local skew = cursor.pos * 0.001; + gfx.SkewX(skew); + + local cursorPos = cursor.pos * (1 / scale) + local cursorX = cursorPos - cursorW / 2; + local cursorY = -cursorH / 2; + + if laserActive[luaIndex] then + gfx.ImageRect( + cursorPos - tailW / 2, + - tailH / 2, + tailW, + tailH, + cursorTailImages[luaIndex], + cursor.alpha / 2, + 0 + ) + end gfx.ImageRect( cursorX, @@ -68,7 +75,7 @@ local drawCursors = function (centerX, centerY,cursors) cursorY, cursorW, cursorH, - cursorGlowBottomImages[i+1], + cursorGlowBottomImages[luaIndex], cursor.alpha, 0 ); @@ -88,7 +95,7 @@ local drawCursors = function (centerX, centerY,cursors) cursorY, cursorW, cursorH, - cursorGlowTopImages[i+1], + cursorGlowTopImages[luaIndex], cursor.alpha, 0 ); @@ -97,32 +104,31 @@ local drawCursors = function (centerX, centerY,cursors) end end -local renderBase = function (deltaTime, centerX, centerY, rotation, cursors) - setUpTransforms(centerX, centerY, rotation) +local renderBase = function (deltaTime, centerX, centerY, rotation) + _, isLandscape = Dimensions.setUpTransforms(centerX, centerY, rotation) gfx.BeginPath() gfx.FillColor(0, 0, 0, 192) - gfx.Rect(-1080/2, 0, 1080, 1080) + gfx.Rect(-9999, 0, 9999 * 2, 1080) gfx.Fill() - gfx.BeginPath(); - gfx.ImageRect( - -CRITBAR_W/2, - -CRITBAR_H/2, - CRITBAR_W, - CRITBAR_H, - baseImage, - 1, - 0 - ); + if (isLandscape) then + gfx.BeginPath(); + gfx.ImageRect(-9999, -CRITBAR_H/2, 9999 * 2, CRITBAR_H, baseImageLandscape, 1, 0); + else + gfx.BeginPath(); + gfx.ImageRect(-CRITBAR_W/2, -CRITBAR_H/2, CRITBAR_W, CRITBAR_H, baseImage, 1, 0); + end - drawCursors(centerX, centerY, cursors) - gfx.ResetTransform() end -local renderOverlay = function (deltaTime) - +local renderOverlay = function (deltaTime, centerX, centerY, rotation, cursors, laserActive) + scale, _ = Dimensions.setUpTransforms(centerX, centerY, rotation) + + drawCursors(scale, cursors, laserActive) + + gfx.ResetTransform() end return { diff --git a/scripts/gameplay/earlylate.lua b/scripts/gameplay/earlylate.lua index ce6aadf..78909bb 100644 --- a/scripts/gameplay/earlylate.lua +++ b/scripts/gameplay/earlylate.lua @@ -1,30 +1,109 @@ +local Dimensions = require "common.dimensions" -local desw = 1080; -local desh = 1920; +-- Used for comparing button_hit()'s delta parameter with the +-- gameplay_earlyLateFor/gameplay_msDisplay skin settings values. +-- If the number is <= delta then the EarlyLate/ms should be shown +local compare = { + ["ALL"] = 2, + ["CRITICAL (or worse)"] = 2, + ["NEAR (or worse)"] = 1, + ["NONE"] = -1, + ["OFF"] = -1 +} -local transitionExistScale = 0; +local portraitHeightFractions = { + ["UPPER+"] = 2.4, + ["UPPER"] = 3, + ["STANDARD"] = 4.2, + ["LOWER"] = 5.3, +} -local tickTransitions = function (deltaTime) - - if transitionExistScale < 1 then - transitionExistScale = transitionExistScale + deltaTime / 2 -- transition should last for that time in seconds - end -end +local landscapeHeightFractions = { + ["UPPER+"] = 1.5, + ["UPPER"] = 2.7, + ["STANDARD"] = 4.1, + ["LOWER"] = 6.7, +} -local render = function (deltaTime, comboState, combo, critLineCenterX, critLineCenterY) - tickTransitions(deltaTime) +local earlyLateFor = compare[game.GetSkinSetting("gameplay_earlyLateFor")] +local msFor = compare[game.GetSkinSetting("gameplay_msFor")] +local earlyLatePosition = game.GetSkinSetting("gameplay_earlyLatePosition") - if (transitionExistScale >= 1) then - return; +local EarlyLate = { + timer = 0, + color = {}, + earlyLateText = "", + millisecText = "" +} + +function EarlyLate.render(deltaTime) + if EarlyLate.timer <= 0 then + return end + EarlyLate.timer = EarlyLate.timer - deltaTime * 100 -end + local screenW, screenH = Dimensions.screen.width, Dimensions.screen.height + local screenCenterX = screenW / 2 -local trigger = function () + local desh, fractionTable + + if screenH > screenW then + desh = 1600 + fractionTable = portraitHeightFractions + else + desh = 1080 + fractionTable = landscapeHeightFractions + end + + local scale = screenH / desh + local y = screenH / 8 * fractionTable[earlyLatePosition] + + gfx.BeginPath() + gfx.LoadSkinFont("Digital-Serial-ExtraBold.ttf") + gfx.FontSize(20 * scale) + + local color = EarlyLate.color + gfx.FillColor(color[1], color[2], color[3]) + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE) + + gfx.FastText(EarlyLate.earlyLateText, screenCenterX - 100 * scale, y) + gfx.FastText(EarlyLate.millisecText, screenCenterX + 100 * scale, y) end -return { - render=render -} \ No newline at end of file +function EarlyLate.TriggerAnimation(rating, millisec) + local showEarlyLate = rating <= earlyLateFor + local showMillisec = rating <= msFor + local isEarly = millisec < 0 + + if millisec == 0 then return end + if not showEarlyLate and not showMillisec then return end + + if showEarlyLate then + EarlyLate.earlyLateText = isEarly and "EARLY" or "LATE" + else + EarlyLate.earlyLateText = "" + end + + if showMillisec then + local millisecText = string.format("%dms", millisec) + + -- prepend + sign for lates + millisecText = isEarly and millisecText or "+"..millisecText + + EarlyLate.millisecText = millisecText + else + EarlyLate.millisecText = "" + end + + if isEarly then + EarlyLate.color = {206, 94, 135} + else + EarlyLate.color = {53, 102, 197} + end + + EarlyLate.timer = 120 +end + +return EarlyLate \ No newline at end of file diff --git a/scripts/gameplay/hitfx.lua b/scripts/gameplay/hitfx.lua new file mode 100644 index 0000000..64500fd --- /dev/null +++ b/scripts/gameplay/hitfx.lua @@ -0,0 +1,245 @@ + +require 'common.globals' + +local Dimensions = require 'common.dimensions' + +local Animation = require 'api.animation' + +local Animations = { + Crit = Animation.new('gameplay/hit_animation_frames/critical_taps', { + centered = true, + }), + + Near = Animation.new('gameplay/hit_animation_frames/near_taps', { + centered = true, + }), + + HoldCrit = Animation.new('gameplay/hit_animation_frames/hold_critical', { + centered = true, + loop = true, + }), + + HoldDome = Animation.new('gameplay/hit_animation_frames/hold_dome', { + centered = true, + loop = true, + loopPoint = 10 + }), + + HoldEnd = Animation.new('gameplay/hit_animation_frames/hold_end', { + centered = true, + }), + + HoldInner = Animation.new('gameplay/hit_animation_frames/hold_inner', { + centered = true, + loop = true, + }), + + LaserCrit = Animation.new('gameplay/hit_animation_frames/laser_critical', { + loop = true, + }), + + LaserDome = Animation.new('gameplay/hit_animation_frames/laser_dome', { + loop = true, + }), + + LaserEndOuter = Animation.new('gameplay/hit_animation_frames/laser_end_outer', {}), + + LaserEndLeft = Animation.new('gameplay/hit_animation_frames/laser_end_l_inner', {}), + + LaserEndRight = Animation.new('gameplay/hit_animation_frames/laser_end_r_inner', {}), +}; + +---@class LaserStateTable +---@field Crit AnimationState +---@field Dome AnimationState +---@field EndInner AnimationState +---@field EndOuter AnimationState + +---@type LaserStateTable[] +local laserStateTables = { + { + Crit = Animations.LaserCrit:createState(), + Dome = Animations.LaserDome:createState(), + EndInner = Animations.LaserEndLeft:createState(), + EndOuter = Animations.LaserEndOuter:createState() + }, + { + Crit = Animations.LaserCrit:createState(), + Dome = Animations.LaserDome:createState(), + EndInner = Animations.LaserEndRight:createState(), + EndOuter = Animations.LaserEndOuter:createState() + } +} + +---@class HoldStateTable +---@field Crit AnimationState +---@field Dome AnimationState +---@field End AnimationState +---@field Inner AnimationState + +---@type HoldStateTable[] +local holdStateTables = {} + +for i = 1, 6 do + holdStateTables[i] = { + Crit = Animations.HoldCrit:createState(), + Dome = Animations.HoldDome:createState(), + End = Animations.HoldEnd:createState(), + Inner = Animations.HoldInner:createState() + } +end + +---@type AnimationState[] +local tapStates = {} + +local HitFX = { }; + +local function setUpTransform(critCenterX, critCenterY, critRotation, xScalar) + local critLine = gameplay.critLine + local x = critCenterX + (critLine.line.x2 - critLine.line.x1) * xScalar + local y = critCenterY + (critLine.line.y2 - critLine.line.y1) * xScalar + + Dimensions.setUpTransforms(x, y, critRotation) +end + +function HitFX.renderLasers(deltaTime, critCenterX, critCenterY, critRotation, cursors) + local hitSize = 406 + + -- Lasers + for laser = 1, 2 do + -- Update + local isActive = gameplay.laserActive[laser] + local laserState = laserStateTables[laser] + local isAnimationPlaying = laserState.Dome.running + + if isActive and not isAnimationPlaying then + laserState.Crit:restart() + laserState.Dome:restart() + end + + if not isActive and isAnimationPlaying then + laserState.Crit:stop() + laserState.Dome:stop() + + laserState.EndInner:restart() + laserState.EndOuter:restart() + end + + -- Render + scale, _ = Dimensions.setUpTransforms(critCenterX, critCenterY, critRotation) + + local laserColor = {game.GetLaserColor(laser - 1)} + local x = cursors[laser - 1].pos * (1 / scale) + + laserState.Dome:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize, + color = laserColor, + x = x, + }) + + laserState.Crit:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize, + x = x, + }) + laserState.EndInner:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize, + x = x, + }) + laserState.EndOuter:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize, + color = laserColor, + x = x, + }) + end +end + +function HitFX.renderButtons(deltaTime, critCenterX, critCenterY, critRotation) + --local baseHitSize = 325; + local hitSize = 406 + + -- BT + FX + for i = 1, 6 do + --[[ + local hitSize = baseHitSize; + if (i > 4) then + hitSize = hitSize * 1.5; + end + ]] + + local laneWidth = (track.GetCurrentLaneXPos(2) - track.GetCurrentLaneXPos(1)) * (i <= 4 and 1 or 2); + local lanePosition = track.GetCurrentLaneXPos(i) + laneWidth / 2 + if (i == 5) then + lanePosition = -track.GetCurrentLaneXPos(6) - laneWidth / 2 + end + + -- Update Holds + local isHeld = gameplay.noteHeld[i] + local holdStates = holdStateTables[i] + local isAnimationPlaying = holdStates.Dome.running + + if isHeld and not isAnimationPlaying then + holdStates.Crit:restart() + holdStates.Dome:restart() + holdStates.Inner:restart() + end + + if not isHeld and isAnimationPlaying then + holdStates.Crit:stop() + holdStates.Dome:stop() + holdStates.Inner:stop() + + holdStates.End:restart() + end + + -- Render holds + setUpTransform(critCenterX, critCenterY, critRotation, lanePosition) + + holdStates.Inner:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize + }) + holdStates.Dome:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize + }) + holdStates.Crit:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize + }) + holdStates.End:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize + }) + + -- Render Taps + local tapState = tapStates[i] + + if tapState then + tapState:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize, + }); + end + end + + gfx.ResetTransform() +end + +function HitFX.TriggerAnimation(name, lane) + tapStates[lane] = Animations[name]:start(); +end + +return HitFX; diff --git a/scripts/gameplay/score_panel.lua b/scripts/gameplay/score_panel.lua index 155cd05..ede7ede 100644 --- a/scripts/gameplay/score_panel.lua +++ b/scripts/gameplay/score_panel.lua @@ -1,4 +1,4 @@ -local Numbers = require('common.numbers') +local Numbers = require('components.numbers') local bgImage = gfx.CreateSkinImage("gameplay/score_panel/bg.png", 0) diff --git a/scripts/gameplay/song_panel.lua b/scripts/gameplay/song_panel.lua index a0dce49..7b95dd0 100644 --- a/scripts/gameplay/song_panel.lua +++ b/scripts/gameplay/song_panel.lua @@ -1,4 +1,5 @@ +local Charting = require('common.charting'); local DiffRectangle = require('components.diff_rectangle'); local desw = 1080; @@ -98,7 +99,8 @@ local render = function (deltaTime, bpm, laneSpeed, jacketPath, diff, level, pro ); -- Draw diff rectangle - DiffRectangle.render(deltaTime, 31, y+140, 0.84, diff, level); + local adjustedDiff = Charting.GetDisplayDifficulty(gameplay.jacketPath, diff) + DiffRectangle.render(deltaTime, 31, y+140, 0.84, adjustedDiff, level); gfx.FontSize(30); gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) diff --git a/scripts/gameplay/track_end.lua b/scripts/gameplay/track_end.lua index 1829bba..0a07d87 100644 --- a/scripts/gameplay/track_end.lua +++ b/scripts/gameplay/track_end.lua @@ -1,4 +1,4 @@ -local Common = require('common.common') +local Common = require('common.util') local Easing = require('common.easing') local bgImage = gfx.CreateSkinImage("gameplay/track_end/bg.png", 0) diff --git a/scripts/gameplay_old.lua b/scripts/gameplay_old.lua deleted file mode 100644 index afa8546..0000000 --- a/scripts/gameplay_old.lua +++ /dev/null @@ -1,1016 +0,0 @@ --- The following code slightly simplifies the render/update code, making it easier to explain in the comments --- It replaces a few of the functions built into USC and changes behaviour slightly --- Ideally, this should be in the common.lua file, but the rest of the skin does not support it --- I'll be further refactoring and documenting the default skin and making it more easy to --- modify for those who either don't know how to skin well or just want to change a few images --- or behaviours of the default to better suit them. --- Skinning should be easy and fun! - -local VolforceWindow = require('components.volforceWindow') - -local RECT_FILL = "fill" -local RECT_STROKE = "stroke" -local RECT_FILL_STROKE = RECT_FILL .. RECT_STROKE - -gfx._ImageAlpha = 1 - -gfx._FillColor = gfx.FillColor -gfx._StrokeColor = gfx.StrokeColor -gfx._SetImageTint = gfx.SetImageTint - --- we aren't even gonna overwrite it here, it's just dead to us -gfx.SetImageTint = nil - -function gfx.FillColor(r, g, b, a) - r = math.floor(r or 255) - g = math.floor(g or 255) - b = math.floor(b or 255) - a = math.floor(a or 255) - - gfx._ImageAlpha = a / 255 - gfx._FillColor(r, g, b, a) - gfx._SetImageTint(r, g, b) -end - -function gfx.StrokeColor(r, g, b) - r = math.floor(r or 255) - g = math.floor(g or 255) - b = math.floor(b or 255) - - gfx._StrokeColor(r, g, b) -end - -function gfx.DrawRect(kind, x, y, w, h) - local doFill = kind == RECT_FILL or kind == RECT_FILL_STROKE - local doStroke = kind == RECT_STROKE or kind == RECT_FILL_STROKE - - local doImage = not (doFill or doStroke) - - gfx.BeginPath() - - if doImage then - gfx.ImageRect(x, y, w, h, kind, gfx._ImageAlpha, 0) - else - gfx.Rect(x, y, w, h) - if doFill then gfx.Fill() end - if doStroke then gfx.Stroke() end - end -end - -local buttonStates = { } -local buttonsInOrder = { - game.BUTTON_BTA, - game.BUTTON_BTB, - game.BUTTON_BTC, - game.BUTTON_BTD, - - game.BUTTON_FXL, - game.BUTTON_FXR, - - game.BUTTON_STA, -} - -function UpdateButtonStatesAfterProcessed() - for i = 1, 6 do - local button = buttonsInOrder[i] - buttonStates[button] = game.GetButton(button) - end -end - -function game.GetButtonPressed(button) - return game.GetButton(button) and not buttonStates[button] -end --- -------------------------------------------------------------------------- -- --- game.IsUserInputActive: -- --- Used to determine if (valid) controller input is happening. -- --- Valid meaning that laser motion will not return true unless the laser is -- --- active in gameplay as well. -- --- This restriction is not applied to buttons. -- --- The player may press their buttons whenever and the function returns true. -- --- Lane starts at 1 and ends with 8. -- -function game.IsUserInputActive(lane) - if lane < 7 then - return game.GetButton(buttonsInOrder[lane]) - end - return gameplay.IsLaserHeld(lane - 7) -end --- -------------------------------------------------------------------------- -- --- gfx.FillLaserColor: -- --- Sets the current fill color to the laser color of the given index. -- --- An optional alpha value may be given as well. -- --- Index may be 1 or 2. -- -function gfx.FillLaserColor(index, alpha) - alpha = math.floor(alpha or 255) - local r, g, b = game.GetLaserColor(index - 1) - gfx.FillColor(r, g, b, alpha) -end --- -------------------------------------------------------------------------- -- -function load_number_image(path) - local images = {} - for i = 0, 9 do - images[i + 1] = gfx.CreateSkinImage(string.format("%s/%d.png", path, i), 0) - end - return images -end --- -------------------------------------------------------------------------- -- -function draw_number(x, y, alpha, num, digits, images, is_dim, scale, kern) - scale = scale or 1; - kern = kern or 1; - local tw, th = gfx.ImageSize(images[1]) - tw = tw * scale; - th = th * scale; - x = x + (tw * (digits - 1)) / 2 - y = y - th / 2 - for i = 1, digits do - local mul = 10 ^ (i - 1) - local digit = math.floor(num / mul) % 10 - local a = alpha - if is_dim and num < mul then - a = 0.4 - end - gfx.BeginPath() - gfx.ImageRect(x, y, tw, th, images[digit + 1], a, 0) - x = x - (tw * kern) - end -end - --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- The actual gameplay script starts here! -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- Global data used by many things: -- -local resx, resy -- The resolution of the window -local portrait -- whether the window is in portrait orientation -local desw, desh -- The resolution of the deisign -local scale -- the scale to get from design to actual units --- -------------------------------------------------------------------------- -- --- All images used by the script: -- -local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) -local bottomFill = gfx.CreateSkinImage("console/console.png", 0) -local topFill = gfx.CreateSkinImage("fill_top.png", 0) -local critAnim = gfx.CreateSkinImage("crit_anim.png", 0) -local critBar = gfx.CreateSkinImage("crit_bar.png", 0) -local critConsole = gfx.CreateSkinImage("console/crit_console.png", 0) -local laserTail = gfx.CreateSkinImage("laser_tail.png", 0) -local laserCursor = gfx.CreateSkinImage("pointer.png", 0) -local laserCursorText = gfx.CreateSkinImage("pointer_bottom.png", 0) -local laserCursorOverlay = gfx.CreateSkinImage("pointer_overlay.png", 0) -local laserCursorGlow = gfx.CreateSkinImage("pointer_glow.png", 0) -local laserCursorShine = gfx.CreateSkinImage("pointer_shine.png", 0) -local laserTopWave = gfx.CreateSkinImage("laser_top_wave.png", 0) -local scoreEarly = gfx.CreateSkinImage("score_early.png", 0) -local scoreLate = gfx.CreateSkinImage("score_late.png", 0) -local numberImages = load_number_image("number") - -local prevGaugeType = nil -local gaugeTransition = nil - ---Skin Settings info -local username = game.GetSkinSetting('username') or ''; - -local ioConsoleDetails = { - gfx.CreateSkinImage("console/detail_left.png", 0), - gfx.CreateSkinImage("console/detail_right.png", 0), -} - -local consoleAnimImages = { - gfx.CreateSkinImage("console/glow_bta.png", 0), - gfx.CreateSkinImage("console/glow_btb.png", 0), - gfx.CreateSkinImage("console/glow_btc.png", 0), - gfx.CreateSkinImage("console/glow_btd.png", 0), - - gfx.CreateSkinImage("console/glow_fxl.png", 0), - gfx.CreateSkinImage("console/glow_fxr.png", 0), - - gfx.CreateSkinImage("console/glow_voll.png", 0), - gfx.CreateSkinImage("console/glow_volr.png", 0), -} --- -------------------------------------------------------------------------- -- --- Timers, used for animations: -- -local introTimer = 2 -local outroTimer = 0 - -local earlateTimer = 0 -local critAnimTimer = 0 - -local consoleAnimSpeed = 10 -local consoleAnimTimers = { 0, 0, 0, 0, 0, 0, 0, 0 } --- -------------------------------------------------------------------------- -- --- Miscelaneous, currently unsorted: -- -local score = 0 -local jacket = nil -local critLinePos = { 0.95, 0.75 }; -local late = false -local clearTexts = {"TRACK FAILED", "TRACK COMPLETE", "TRACK COMPLETE", "FULL COMBO", "PERFECT" } --- -------------------------------------------------------------------------- -- --- ResetLayoutInformation: -- --- Resets the layout values used by the skin. -- -function ResetLayoutInformation() - resx, resy = game.GetResolution() - portrait = resy > resx - desw = portrait and 1080 or 1920 - desh = desw * (resy / resx) - scale = resx / desw -end --- -------------------------------------------------------------------------- -- --- render: -- --- The primary & final render call. -- --- Use this to render basically anything that isn't the crit line or the -- --- intro/outro transitions. -- -function render(deltaTime) - -- make sure that our transform is cleared, clean working space - -- TODO: this shouldn't be necessary!!! - gfx.ResetTransform() - gfx.Scale(scale, scale) - - local yshift = 0 - - -- In portrait, we draw a banner across the top - -- The rest of the UI needs to be drawn below that banner - -- TODO: this isn't how it'll work in the long run, I don't think - if portrait then yshift = draw_banner(deltaTime) end - - -- gfx.Translate(0, yshift - 150 * math.max(introTimer - 1, 0)) - gfx.Translate(0, yshift) - draw_song_info(deltaTime) - draw_score(deltaTime) - -- gfx.Translate(0, -yshift + 150 * math.max(introTimer - 1, 0)) - gfx.Translate(0, -yshift) - draw_status(deltaTime) - draw_gauge(deltaTime) - draw_earlate(deltaTime) - draw_combo(deltaTime) - draw_alerts(deltaTime) -end --- -------------------------------------------------------------------------- -- --- SetUpCritTransform: -- --- Utility function which aligns the graphics transform to the center of the -- --- crit line on screen, rotation include. -- --- This function resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function SetUpCritTransform() - -- start us with a clean empty transform - gfx.ResetTransform() - -- translate and rotate accordingly - gfx.Translate(gameplay.critLine.x, gameplay.critLine.y) - gfx.Rotate(-gameplay.critLine.rotation) -end --- -------------------------------------------------------------------------- -- --- GetCritLineCenteringOffset: -- --- Utility function which returns the magnitude of an offset to center the -- --- crit line on the screen based on its position and rotation. -- -function GetCritLineCenteringOffset() - local distFromCenter = resx / 2 - gameplay.critLine.x - local dvx = math.cos(gameplay.critLine.rotation) - local dvy = math.sin(gameplay.critLine.rotation) - return math.sqrt(dvx * dvx + dvy * dvy) * distFromCenter -end --- -------------------------------------------------------------------------- -- --- render_crit_base: -- --- Called after rendering the highway and playable objects, but before -- --- the built-in hit effects. -- --- This is the first render function to be called each frame. -- --- This call resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function render_crit_base(deltaTime) - -- Kind of a hack, but here (since this is the first render function - -- that gets called per frame) we update the layout information. - -- This means that the player can resize their window and - -- not break everything - ResetLayoutInformation() - - critAnimTimer = critAnimTimer + deltaTime - SetUpCritTransform() - - -- Figure out how to offset the center of the crit line to remain - -- centered on the players screen - local xOffset = GetCritLineCenteringOffset() - gfx.Translate(xOffset, 0) - - -- Draw a transparent black overlay below the crit line - -- This darkens the play area as it passes - gfx.FillColor(0, 0, 0, 200) - gfx.DrawRect(RECT_FILL, -resx, 0, resx * 2, resy) - gfx.FillColor(255, 255, 255) - - -- The absolute width of the crit line itself - -- we check to see if we're playing in portrait mode and - -- change the width accordingly - local critWidth = resx * (portrait and 1.25 or 0.8) - - -- get the scaled dimensions of the crit line pieces - local clw, clh = gfx.ImageSize(critAnim) - local critAnimHeight = 12 * scale - local critAnimWidth = critAnimHeight * (clw / clh) - - local cbw, cbh = gfx.ImageSize(critBar) - local critBarHeight = critAnimHeight * (cbh / clh) - local critBarWidth = critBarHeight * (cbw / cbh) - - -- render the core of the crit line - do - -- The crit line is made up of many small pieces scrolling outward - -- Calculate how many pieces, starting at what offset, are require to - -- completely fill the space with no gaps from edge to center - local animWidth = critWidth * 0.65 - local numPieces = 1 + math.ceil(animWidth / (critAnimWidth * 2)) - local startOffset = critAnimWidth * ((critAnimTimer * 0.15) % 1) - - -- left side - -- Use a scissor to limit the drawable area to only what should be visible - gfx.Scissor(-animWidth / 2, -critAnimHeight / 2, animWidth / 2, critAnimHeight) - for i = 1, numPieces do - gfx.DrawRect(critAnim, -startOffset - critAnimWidth * (i - 1), -critAnimHeight / 2, critAnimWidth, critAnimHeight) - end - gfx.ResetScissor() - - -- right side - -- exactly the same, but in reverse - gfx.Scissor(0, -critAnimHeight / 2, animWidth / 2, critAnimHeight) - for i = 1, numPieces do - gfx.DrawRect(critAnim, -critAnimWidth + startOffset + critAnimWidth * (i - 1), -critAnimHeight / 2, critAnimWidth, critAnimHeight) - end - gfx.ResetScissor() - end - - -- Draw the critical bar - gfx.DrawRect(critBar, -critWidth / 2, -critBarHeight / 2 - 5 * scale + 24, critWidth, critBarHeight) - - -- Draw back portion of the console - if portrait then - local ccw, cch = gfx.ImageSize(critConsole) - local critConsoleHeight = 190 * scale - local critConsoleWidth = critConsoleHeight * (ccw / cch) - - local critConsoleY = 180 * scale - gfx.DrawRect(critConsole, -critConsoleWidth / 2, -critConsoleHeight / 2 + critConsoleY, critConsoleWidth, critConsoleHeight) - end - - -- we're done, reset graphics stuffs - gfx.FillColor(255, 255, 255) - gfx.ResetTransform() -end --- -------------------------------------------------------------------------- -- --- render_crit_overlay: -- --- Called after rendering built-int crit line effects. -- --- Use this to render laser cursors or an IO Console in portrait mode! -- --- This call resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function render_crit_overlay(deltaTime) - SetUpCritTransform() - - -- Figure out how to offset the center of the crit line to remain - -- centered on the players screen. - local xOffset = GetCritLineCenteringOffset() - - -- When in portrait, we can draw the console at the bottom - if portrait then - -- We're going to make temporary modifications to the transform - gfx.Save() - gfx.Translate(xOffset * 0.5, -45) - - local bfw, bfh = gfx.ImageSize(bottomFill) - - local distBetweenKnobs = 0.446 - local distCritVertical = -0.125 - - local ioFillTx = bfw / 2 - local ioFillTy = bfh * distCritVertical -- 0.098 - - -- The total dimensions for the console image - local io_x, io_y, io_w, io_h = -ioFillTx, -ioFillTy, bfw, bfh - - -- Adjust the transform accordingly first - local consoleFillScale = (resx * 0.550) / (bfw * distBetweenKnobs) - gfx.Scale(consoleFillScale, consoleFillScale); - - -- Actually draw the fill - gfx.FillColor(255, 255, 255) - gfx.DrawRect(bottomFill, io_x, io_y, io_w, io_h) - - -- Then draw the details which need to be colored to match the lasers - -- for i = 1, 2 do - -- gfx.FillLaserColor(i) - -- gfx.DrawRect(ioConsoleDetails[i], io_x, io_y, io_w, io_h) - -- end - - -- Draw the button press animations by overlaying transparent images - gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER) - for i = 1, 6 do - -- While a button is held, increment a timer - -- If not held, that timer is set back to 0 - if game.GetButton(buttonsInOrder[i]) then - consoleAnimTimers[i] = consoleAnimTimers[i] + deltaTime * consoleAnimSpeed * 3.14 * 2 - else - consoleAnimTimers[i] = 0 - end - - -- If the timer is active, flash based on a sin wave - local timer = consoleAnimTimers[i] - if timer ~= 0 then - local image = consoleAnimImages[i] - local alpha = (math.sin(timer) * 0.5 + 0.5) * 0.5 + 0.25 - gfx.FillColor(255, 255, 255, alpha * 255); - gfx.DrawRect(image, io_x, io_y, io_w, io_h) - end - end - gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER) - - -- Undo those modifications - gfx.Restore(); - end - - local cw, ch = gfx.ImageSize(laserCursor) - local cursorWidth = 60 * scale - local cursorHeight = cursorWidth * (ch / cw) - - -- draw each laser cursor - for i = 1, 2 do - local cursor = gameplay.critLine.cursors[i - 1] - local pos, skew = cursor.pos, cursor.skew - - gfx.Save(); - -- Add a kinda-perspective effect with a horizontal skew - gfx.SkewX(skew) - - --Add the tail, only active in critical zone - if (gameplay.laserActive[i]) then - gfx.FillLaserColor(i, cursor.alpha * 255) - gfx.DrawRect(laserTail, pos - cursorWidth / 2 - 64, -cursorHeight / 2 - 5, cursorWidth * 5, cursorHeight * 5) - end - - -- Draw the SDVX Icon eye and tails below the overlay - gfx.FillColor(255, 255, 255, cursor.alpha * 255) - gfx.DrawRect(laserCursorText, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - -- Draw the colored background with the appropriate laser color - gfx.FillLaserColor(i, cursor.alpha * 130) - gfx.DrawRect(laserCursor, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - - --Add the top wave effect, only active in critical zone - if (gameplay.laserActive[i]) then - gfx.FillLaserColor(i, cursor.alpha * 180) - gfx.DrawRect(laserTopWave, pos - cursorWidth / 2 - 80, -cursorHeight / 2 - 24, cursorWidth * 6, cursorHeight * 6) - end - - -- Draw the uncolored overlay on top of the color - gfx.FillColor(255, 255, 255, cursor.alpha * 255) - gfx.DrawRect(laserCursorOverlay, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - -- Draw the colored glow on top of the pointer - gfx.FillLaserColor(i, cursor.alpha * 160) - gfx.DrawRect(laserCursorGlow, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 20, cursorWidth * 2, cursorHeight * 2) - -- Draw the uncolored overlay on top of the color - gfx.FillColor(255, 255, 255, cursor.alpha * 150) - gfx.DrawRect(laserCursorShine, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 20, cursorWidth * 2, cursorHeight * 2) - - - -- Un-skew - gfx.SkewX(-skew) - gfx.Restore(); - end - - -- We're done, reset graphics stuffs - gfx.FillColor(255, 255, 255) - gfx.ResetTransform() -end --- -------------------------------------------------------------------------- -- --- draw_banner: -- --- Renders the banner across the top of the screen in portrait. -- --- This function expects no graphics transform except the design scale. -- -function draw_banner(deltaTime) - local bannerWidth, bannerHeight = gfx.ImageSize(topFill) - local actualHeight = desw * (bannerHeight / bannerWidth) - - gfx.FillColor(255, 255, 255) - gfx.DrawRect(topFill, 0, 0, desw, actualHeight) - - return actualHeight -end --- -------------------------------------------------------------------------- -- --- draw_stat: -- --- Draws a formatted name + value combination at x, y over w, h area. -- -function draw_stat(x, y, w, h, name, value, format, r, g, b) - gfx.Save() - - -- Translate from the parent transform, wherever that may be - gfx.Translate(x, y) - - -- Draw the `name` top-left aligned at `h` size - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.FontSize(h) - gfx.Text(name .. ":", 0, 0) -- 0, 0, is x, y after translation - - -- Realign the text and draw the value, formatted - gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.format(format, value), w, 0) - -- This draws an underline beneath the text - -- The line goes from 0, h to w, h - gfx.BeginPath() - gfx.MoveTo(0, h) - gfx.LineTo(w, h) -- only defines the line, does NOT draw it yet - - -- If a color is provided, set it - if r then gfx.StrokeColor(r, g, b) - -- otherwise, default to a light grey - else gfx.StrokeColor(200, 200, 200) end - - -- Stroke out the line - gfx.StrokeWidth(1) - gfx.Stroke() - -- Undo our transform changes - gfx.Restore() - - -- Return the next `y` position, for easier vertical stacking - return y + h + 5 -end --- -------------------------------------------------------------------------- -- --- draw_song_info: -- --- Draws current song information at the top left of the screen. -- --- This function expects no graphics transform except the design scale. -- -local songBack = gfx.CreateSkinImage("song_back.png", 0) -local numberDot = gfx.CreateSkinImage("number/dot.png", 0) -local diffImages = { - gfx.CreateSkinImage("diff/1 novice.png", 0), - gfx.CreateSkinImage("diff/2 advanced.png", 0), - gfx.CreateSkinImage("diff/3 exhaust.png", 0), - gfx.CreateSkinImage("diff/4 maximum.png", 0), - gfx.CreateSkinImage("diff/5 infinite.png", 0), - gfx.CreateSkinImage("diff/6 gravity.png", 0), - gfx.CreateSkinImage("diff/7 heavenly.png", 0), - gfx.CreateSkinImage("diff/8 vivid.png", 0) -} -local memo = Memo.new() - -function draw_song_info(deltaTime) - local jacketWidth = 105 - - -- Check to see if there's a jacket to draw, and attempt to load one if not - if jacket == nil or jacket == jacketFallback then - jacket = gfx.LoadImageJob(gameplay.jacketPath, jacketFallback) - end - gfx.Save() - - if not portrait then - gfx.Translate(0, 112) - end - - -- Ensure the font has been loaded - gfx.LoadSkinFont("segoeui.ttf") - - -- Draw the background - local tw, th = gfx.ImageSize(songBack) - gfx.FillColor(255,255,255) - gfx.BeginPath() - gfx.ImageRect(-2, -71, tw * 0.855, th * 0.855, songBack, 1, 0) - - -- Draw the jacket - gfx.BeginPath() - gfx.ImageRect(31, -39, jacketWidth, jacketWidth, jacket, 1, 0) - - -- Draw level name - local diffIdx = GetDisplayDifficulty(gameplay.jacketPath, gameplay.difficulty) - gfx.BeginPath() - tw, th = gfx.ImageSize(diffImages[diffIdx]) - gfx.ImageRect(28, 71, tw * 0.85, th * 0.85, diffImages[diffIdx], 1, 0) - - -- Draw level number - draw_number(110, 84, 1.0, gameplay.level, 2, numberImages, false) - - -- Draw the song title, scaled to fit as best as possible - local title = memo:memoize("title", function () - local titleText = gameplay.title .. " / " .. gameplay.artist - local titleWidth = 520 - gfx.LoadSkinFont("rounded-mplus-1c-bold.ttf") - return gfx.CreateLabel(titleText, 18, 0) - end) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE) - gfx.FillColor(255, 255, 255, 255) - gfx.DrawLabel(title, desw / 2.77, portrait and -23 or -90, 470) - - -- Draw the BPM - gfx.FillColor(255,255,255) - draw_number(220, 178, 1.0, gameplay.bpm, 3, numberImages, false) - - -- Draw the hi-speed - gfx.FontSize(16) - draw_number(213 + 20, 212, 1.0, math.floor((gameplay.hispeed + 0.05) * 10) % 10, 1, numberImages, false) - tw, th = gfx.ImageSize(numberDot) - gfx.BeginPath() - gfx.ImageRect(213 + 5, 206, tw, th, numberDot, 1, 0) - draw_number(213, 212, 1.0, math.floor(gameplay.hispeed), 1, numberImages, false) - -- gfx.Text(string.format("%.1f", gameplay.hispeed), 208, 9) - - -- Fill the progress bar - gfx.BeginPath() - gfx.FillColor(244, 204, 101) - gfx.Rect(233, 11, 625 * gameplay.progress, 3) - gfx.Fill() - - -- When the player is holding Start, the hispeed can be changed - -- Shows the current hispeed values - if game.GetButton(game.BUTTON_STA) then - gfx.BeginPath() - gfx.FillColor(255,255,255) - gfx.Text(string.format("HiSpeed: %.0f x %.1f = %.0f", - gameplay.bpm, gameplay.hispeed, gameplay.bpm * gameplay.hispeed), - 0, 115) - end - gfx.Restore() -end --- -------------------------------------------------------------------------- -- --- draw_best_diff: -- --- If there are other saved scores, this displays the difference between -- --- the current play and your best. -- -function draw_best_diff(deltaTime, x, y) - -- Don't do anything if there's nothing to do - if not gameplay.scoreReplays[1] then return end - - -- Calculate the difference between current and best play - local difference = score - gameplay.scoreReplays[1].currentScore - local prefix = "" -- used to properly display negative values - - gfx.BeginPath() - gfx.FontSize(26) - - gfx.FillColor(255, 255, 255) - if difference < 0 then - -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(255, 90, 70) - difference = math.abs(difference) - prefix = "- " - - elseif difference > 0 then - -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(120, 146, 218) - difference = math.abs(difference) - prefix = "+ " - end - - -- %08d formats a number to 8 characters - -- This includes the minus sign, so we do that separately - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.format("%s%08d", prefix, difference), x, y) -end - -function draw_username(deltaTime, x, y) - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.sub(username, 1, 8), x, y) -end - --- -------------------------------------------------------------------------- -- --- draw_score: -- -local scoreBack = gfx.CreateSkinImage("score_back.png", 0) -local scoreNumber = load_number_image("score_num") -local maxCombo = 0 -function draw_score(deltaTime) - local tw, th = gfx.ImageSize(scoreBack) - gfx.FillColor(255, 255, 255) - gfx.BeginPath() - tw = tw * 0.61; - th = th * 0.61; - gfx.ImageRect(desw - tw + 12, portrait and 50 or 0, tw, th, scoreBack, 1, 0) - - gfx.FillColor(255, 255, 255) - draw_number(desw - 305, portrait and 132 or 64, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.38, 1.12) - draw_number(desw - 110, portrait and 137 or 68, 1.0, score, 4, scoreNumber, true, 0.28, 1.12) - - -- Draw max combo - gfx.FillColor(255, 255, 255) - draw_number(desw - 300, portrait and 207 or 110, 1.0, maxCombo, 4, numberImages, true) -end --- -------------------------------------------------------------------------- -- --- draw_gauge: -- -local gaugeMarkerBgImage = gfx.CreateSkinImage("gameplay/gauges/marker_bg.png", 0) - -local gaugeWarnTransitionScale = 0; - -local gaugeEffBgImage = gfx.CreateSkinImage("gameplay/gauges/effective/gauge_back.png", 0) -local gaugeEffFailFillImage = gfx.CreateSkinImage("gameplay/gauges/effective/gauge_fill_fail.png", 0) -local gaugeEffPassFillImage = gfx.CreateSkinImage("gameplay/gauges/effective/gauge_fill_pass.png", 0) - -local gaugeExcBgImage = gfx.CreateSkinImage("gameplay/gauges/excessive/gauge_back.png", 0) -local gaugeExcFillImage = gfx.CreateSkinImage("gameplay/gauges/excessive/gauge_fill.png", 0) - -local gaugeExcArsBgImage = gfx.CreateSkinImage("gameplay/gauges/excessive_ars/gauge_back.png", 0) -local gaugeExcArsFillImage = gfx.CreateSkinImage("gameplay/gauges/excessive_ars/gauge_fill.png", 0) - -local gaugePermBgImage = gfx.CreateSkinImage("gameplay/gauges/permissive/gauge_back.png", 0) -local gaugePermFillImage = gfx.CreateSkinImage("gameplay/gauges/permissive/gauge_fill.png", 0) - -local gaugeBlastiveBgImage = gfx.CreateSkinImage("gameplay/gauges/blastive/gauge_back.png", 0) -local gaugeBlastiveFillImage = gfx.CreateSkinImage("gameplay/gauges/blastive/gauge_fill.png", 0) - - -function draw_gauge(deltaTime) - -- fallbacks in case of unsupported type - local gaugeFillAlpha = 1; - local gaugeBgImage = gaugeEffBgImage; - local gaugeFillImage = gaugeEffPassFillImage; - local gaugeBreakpoint = 0; - - if gameplay.gauge.type == 0 then - gaugeBgImage = gaugeEffBgImage; - gaugeBreakpoint = 0.7; - - if gameplay.gauge.value <= 0.7 then - gaugeFillImage = gaugeEffFailFillImage; - else - gaugeFillImage = gaugeEffPassFillImage; - end - - elseif gameplay.gauge.type == 1 then - gaugeBgImage = gaugeExcBgImage; - gaugeFillImage = gaugeExcFillImage; - - if (game.GetSkinSetting('_gaugeARS') == 1) then - gaugeBgImage = gaugeExcArsBgImage - gaugeFillImage = gaugeExcArsFillImage - end - - gaugeBreakpoint = 0.3; - - if gameplay.gauge.value < 0.3 then - gaugeFillAlpha = 1 - math.abs(gaugeWarnTransitionScale - 0.25); -- 100 -> 20 -> 100 - - gaugeWarnTransitionScale = gaugeWarnTransitionScale + deltaTime*10; - if gaugeWarnTransitionScale > 1 then - gaugeWarnTransitionScale = 0; - end - end - elseif gameplay.gauge.type == 2 then - gaugeBgImage = gaugePermBgImage; - gaugeFillImage = gaugePermFillImage; - - if gameplay.gauge.value < 0.3 then - gaugeFillAlpha = 1 - math.abs(gaugeWarnTransitionScale - 0.25); -- 100 -> 52 -> 100 - - gaugeWarnTransitionScale = gaugeWarnTransitionScale + deltaTime*10; - if gaugeWarnTransitionScale > 1 then - gaugeWarnTransitionScale = 0; - end - end - elseif gameplay.gauge.type == 3 then -- BLASTIVE RATE - gaugeBgImage = gaugeBlastiveBgImage; - gaugeFillImage = gaugeBlastiveFillImage; - - if gameplay.gauge.value < 0.3 then - gaugeFillAlpha = 1 - math.abs(gaugeWarnTransitionScale - 0.25); -- 100 -> 20 -> 100 - - gaugeWarnTransitionScale = gaugeWarnTransitionScale + deltaTime*10; - if gaugeWarnTransitionScale > 1 then - gaugeWarnTransitionScale = 0; - end - end - end - - - local BgW, BgH = gfx.ImageSize(gaugeBgImage); - local FillW, FillH = gfx.ImageSize(gaugeFillImage); - local gaugePosX = 1080 - BgW - 110; - local gaugePosY = 1920/2 - BgH/2 - 95; - - -- gfx.Text('RESX: ' .. resx .. ' // RESY: ' .. resy .. ' // GPX: ' .. gaugePosX, 255,1200); - - gfx.BeginPath() - gfx.ImageRect(gaugePosX, gaugePosY, BgW, BgH, gaugeBgImage, 1, 0) - - gfx.GlobalAlpha(gaugeFillAlpha); - gfx.BeginPath() - gfx.Scissor(gaugePosX+18, gaugePosY+9+(FillH-(FillH*(gameplay.gauge.value))), FillW, FillH*(gameplay.gauge.value)) - gfx.ImageRect(gaugePosX+18, gaugePosY+9, FillW, FillH, gaugeFillImage, 1, 0) - gfx.ResetScissor(); - gfx.GlobalAlpha(1); - - -- Draw the breakpoint line if needed - if (gaugeBreakpoint > 0) then - gfx.Save() - gfx.BeginPath() - gfx.GlobalAlpha(0.75); - - local lineY = gaugePosY+6+(FillH-(FillH*(gaugeBreakpoint))) - - gfx.MoveTo(gaugePosX+18, lineY) - gfx.LineTo(gaugePosX+36, lineY) - - gfx.StrokeWidth(2) - gfx.StrokeColor(255,255,255) - gfx.Stroke() - - gfx.ClosePath() - gfx.Restore() - end - - -- Draw gauge % label - local gaugeMarkerY = gaugePosY-6+(FillH-(FillH*(gameplay.gauge.value))) - - gfx.BeginPath() - gfx.ImageRect(gaugePosX-64, gaugeMarkerY, 83*0.85, 37*0.85, gaugeMarkerBgImage, 1, 0) - - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(22) - gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) - gfx.Text(math.floor(gameplay.gauge.value * 100), gaugePosX-16, gaugeMarkerY+17) - - gfx.FontSize(16) - gfx.Text('%', gaugePosX-4, gaugeMarkerY+17) -end --- -------------------------------------------------------------------------- -- --- draw_combo: -- -local comboBottom = gfx.CreateSkinImage("chain/chain.png", 0) -local comboPUC = load_number_image("chain/puc") -local comboUC = load_number_image("chain/uc") -local comboREG = load_number_image("chain/reg") -local comboTimer = 0 -local combo = 0 -local comboCurrent -function draw_combo(deltaTime) - if combo == 0 then return end - comboTimer = comboTimer + deltaTime - local posx = desw / 2 + 5 - local posy = desh * critLinePos[1] - 100 - if portrait then posy = desh * critLinePos[2] - 150 end - if gameplay.comboState == 2 then - comboCurrent = comboPUC --puc - elseif gameplay.comboState == 1 then - comboCurrent = comboUC --uc - else - comboCurrent = comboREG --regular - end - local alpha = math.floor(comboTimer * 20) % 2 - alpha = (alpha * 100 + 155) / 255 - - -- \_ chain _/ - local tw, th - tw, th = gfx.ImageSize(comboBottom) - gfx.BeginPath() - gfx.ImageRect(posx - tw / 2 + 10, posy - th / 4 - 210, tw * 0.85, th * 0.85, comboBottom, alpha, 0) - - tw, th = gfx.ImageSize(comboCurrent[1]) - posy = posy - th + 32 - - local comboScale = 0.45; - draw_number(desw/2 - (tw*4*comboScale)/2+(tw*comboScale*1.5)+10, posy - th / 2, 1.0, combo, 4, comboCurrent, true, comboScale, 1.12) -end --- -------------------------------------------------------------------------- -- --- draw_earlate: -- -function draw_earlate(deltaTime) - earlateTimer = math.max(earlateTimer - deltaTime,0) - if earlateTimer == 0 then return nil end - local alpha = math.floor(earlateTimer * 20) % 2 - alpha = (alpha * 100 + 155) / 255 - gfx.BeginPath() - - local xpos = desw / 2 - local ypos = desh * critLinePos[1] - 220 - if portrait then ypos = desh * critLinePos[2] - 240 end - local tw, th - if late then - tw, th = gfx.ImageSize(scoreLate) - gfx.ImageRect(xpos - tw / 2, ypos - th / 2, tw, th, scoreLate, alpha, 0) - else - tw, th = gfx.ImageSize(scoreEarly) - gfx.ImageRect(xpos - tw / 2, ypos - th / 2, tw, th, scoreEarly, alpha, 0) - end -end --- -------------------------------------------------------------------------- -- --- draw_alerts: -- -local alertTimers = {-2,-2} -local alertBgR = Image.skin("alert_bg.png") -local alertBgL = Image.skin("alert_bg2.png") -local alertL = Image.skin("alert_l.png") -local alertR = Image.skin("alert_r.png") - -function draw_alerts(deltaTime) - alertTimers[1] = math.max(alertTimers[1] - deltaTime,-2) - alertTimers[2] = math.max(alertTimers[2] - deltaTime,-2) - if alertTimers[1] > 0 then --draw left alert - gfx.Save() - local posx = desw / 2 - 220 - local posy = desh * critLinePos[1] - 135 - if portrait then - posy = desh * critLinePos[2] - 240 - posx = 105 - end - gfx.Translate(posx,posy) - local r,g,b = game.GetLaserColor(0) - local alertScale = (-(alertTimers[1] ^ 2.0) + (1.5 * alertTimers[1])) * 5.0 - alertScale = math.min(alertScale, 1) - gfx.Scale(1, alertScale) - gfx.FillColor(r, g, b) - alertBgL:draw({ x = 0, y = 0 }) - gfx.FillColor(255, 255, 255) - alertL:draw({ x = 0, y = 0 }) - gfx.Restore() - end - if alertTimers[2] > 0 then --draw right alert - gfx.Save() - local posx = desw / 2 + 220 - local posy = desh * critLinePos[1] - 40 - if portrait then - posy = desh * critLinePos[2] - 240 - posx = desw - 105 - end - gfx.Translate(posx,posy) - local r,g,b = game.GetLaserColor(1) - local alertScale = (-(alertTimers[2] ^ 2.0) + (1.5 * alertTimers[2])) * 5.0 - alertScale = math.min(alertScale, 1) - gfx.Scale(1, alertScale) - gfx.FillColor(r, g, b) - alertBgR:draw({ x = 0, y = 0 }) - gfx.FillColor(255, 255, 255) - alertR:draw({ x = 0, y = 0 }) - gfx.Restore() - end -end --- -------------------------------------------------------------------------- -- --- draw_status: -- -local statusBack = Image.skin("status_back.png") -local apealCard = Image.skin("crew/appeal_card.png") -local dan = Image.skin("dan.png") - -function draw_status(deltaTime) - -- Draw the background - gfx.FillColor(255, 255, 255) - statusBack:draw({ x = 0, y = desh / 2 - 195, w = statusBack.w * 0.85, h = statusBack.h * 0.85, anchor_h = Image.ANCHOR_LEFT }) - - -- Draw the apeal card - apealCard:draw({ x = 12, y = desh / 2 - 220, w = apealCard.w * 0.62, h = apealCard.h * 0.62, anchor_h = Image.ANCHOR_LEFT, anchor_v = Image.ANCHOR_TOP }) - - -- Draw the dan - dan:draw({ x = 164, y = desh / 2 - 117, w = dan.w * 0.32, h = dan.h * 0.32 }) - - -- Draw the Volforce - VolforceWindow.render(deltaTime, 220, desh / 2 - 140); - - -- Draw the best difference - draw_best_diff(deltaTime, 145, desh / 2 - 174) - - -- Draw the username - draw_username(deltatime, 145, desh / 2 - 198) -end - --- -------------------------------------------------------------------------- -- --- render_intro: -- -function render_intro(deltaTime) - if not game.GetButton(game.BUTTON_STA) then - introTimer = introTimer - deltaTime - end - introTimer = math.max(introTimer, 0) - return introTimer <= 0 -end --- -------------------------------------------------------------------------- -- --- render_outro: -- -function render_outro(deltaTime, clearState) - if clearState == 0 then return true end - gfx.ResetTransform() - gfx.BeginPath() - gfx.Rect(0,0,resx,resy) - gfx.FillColor(0,0,0, math.floor(127 * math.min(outroTimer, 1))) - gfx.Fill() - gfx.Scale(scale,scale) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FillColor(255,255,255, math.floor(255 * math.min(outroTimer, 1))) - gfx.LoadSkinFont("NovaMono.ttf") - gfx.FontSize(70) - gfx.Text(clearTexts[clearState], desw / 2, desh / 2) - outroTimer = outroTimer + deltaTime - return outroTimer > 2, 1 - outroTimer -end --- -------------------------------------------------------------------------- -- --- update_score: -- -function update_score(newScore) - score = newScore -end --- -------------------------------------------------------------------------- -- --- update_combo: -- -function update_combo(newCombo) - combo = newCombo - if combo > maxCombo then - maxCombo = combo - end -end --- -------------------------------------------------------------------------- -- --- near_hit: -- -function near_hit(wasLate) --for updating early/late display - late = wasLate - earlateTimer = 0.75 -end --- -------------------------------------------------------------------------- -- --- laser_alert: -- -function laser_alert(isRight) --for starting laser alert animations - if isRight and alertTimers[2] < -1.5 then - alertTimers[2] = 1.5 - elseif alertTimers[1] < -1.5 then - alertTimers[1] = 1.5 - end -end \ No newline at end of file diff --git a/scripts/gamesettingsdialog.lua b/scripts/gamesettingsdialog.lua index 8e53861..f076b88 100644 --- a/scripts/gamesettingsdialog.lua +++ b/scripts/gamesettingsdialog.lua @@ -1,247 +1,251 @@ -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." + + gfx.Save() + + resX, resY = game.GetResolution() + local scale = resY / 1080 + gfx.ResetScissor() + 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 + + gfx.Restore() end \ No newline at end of file diff --git a/scripts/language/EN.lua b/scripts/language/EN.lua index bdaa2cf..679306f 100644 --- a/scripts/language/EN.lua +++ b/scripts/language/EN.lua @@ -2,37 +2,37 @@ local lang = { Challanges = { --rightside - ch = "Idk", - ch1 = "Torture yourself :)", + ch = "Analyze your skill!", + ch1 = "Find out how good you are by taking on challenges!\nIn this mode your ability will pushed to the limits.", }, Multiplayer = { --rightside - mp = "Wait, there's multiplayer???", - mp2 = "Yes, but nobody is ever online", + mp = "Online matchmaking!", + mp2 = "Play against your friends!\nSupport for up to 8 players.", }, Start = { --rightside - st = "Play shit idk", - st2 = "Play something blah blah blah blah\nWith newlines", + st = "Basic play!", + st2 = "Play whatever songs you like!\nComplete for high scores on an IR around the world.", --leftside st3 = "Start", - sc = "Scroll", + sc = "VOL to select", desc = "Test description. Blah blah blah", }, Nautica = { --rightside - dls = "Download more songs", - dls2 = "ksm.dev", + dls = "Download more songs!", + dls2 = "Get new charts from ksm.dev, updated daily!", }, Settings = { --rightside - se = "Adjust things", - se1= "Open settings", + se = "Open settings!", + se1= "Tweak things to your liking.", }, Exit = { --rightside - ex = "Leave this cursed game", - ex2 = "C'mon press that button!\nYou know you want to do it", + ex = "Close the game!", + ex2 = "Close the game and end this session.\nGoodbye!", }, Result = { --leftside diff --git a/scripts/language/HU.lua b/scripts/language/HU.lua new file mode 100644 index 0000000..a4d210a --- /dev/null +++ b/scripts/language/HU.lua @@ -0,0 +1,45 @@ +--TitleScreen +local lang = { + Challanges = { + --rightside + ch = "Nemtudom", + ch1 = "Hát ez kínkeserves :)", + }, + Multiplayer = { + --rightside + mp = "Na várjunk, van Multiplayer???", + mp2 = "Ja, de senki sincs fent", + }, + Start = { + --rightside + st = "Mittudomén, játssz valami szart", + st2 = "Játssz valamit blablablabla\nSortöréssel", + --leftside + st3 = "Kezdés", + sc = "Tekerés", + desc = "Teszt leírás. Blablabla", + }, + Nautica = { + --rightside + dls = "Tölts le zenéket", + dls2 = "ksm.dev", + }, + Settings = { + --rightside + se = "Állítgass", + se1= "Beállítások megnyitása", + }, + Exit = { + --rightside + ex = "Hagyd el ezt az átkozott játékot", + ex2 = "Gyerünk, nyomd meg a gombot!\nTudom, hogy azt akarod", + }, + Result = { + --leftside + re = "AUTOPLAY Aktív", + re1 = "AUTOPLAY aktív.", + re2 = "Az eredmények nem lesznek mentve", + }, +} + +return lang \ No newline at end of file diff --git a/scripts/language/call.lua b/scripts/language/call.lua index 01a62ec..39aa2c0 100644 --- a/scripts/language/call.lua +++ b/scripts/language/call.lua @@ -1,18 +1,21 @@ local call = nil - local EN = require("language.EN") - local DE = require("language.DE") - local SK = require("language.SK") - local test2 = require("language.test2") +local EN = require("language.EN") +local DE = require("language.DE") +local SK = require("language.SK") +local HU = require("language.HU") +local test2 = require("language.test2") if game.GetSkinSetting('words') == "EN" then - call = EN + call = EN elseif game.GetSkinSetting('words') == "DE" then - call = DE + call = DE elseif game.GetSkinSetting('words') == "SK" then - call = SK - elseif game.GetSkinSetting('words') == "test2" then - call = test2 + call = SK +elseif game.GetSkinSetting('words') == "HU" then + call = HU +elseif game.GetSkinSetting('words') == "test2" then + call = test2 end diff --git a/scripts/multiplayerscreen.lua b/scripts/multiplayerscreen.lua index 3df4f8a..c552be7 100644 --- a/scripts/multiplayerscreen.lua +++ b/scripts/multiplayerscreen.lua @@ -1,1001 +1,1096 @@ -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 +local json = require("common.json") + +local common = require('common.util'); +local Sound = require("common.sound") +local difbar = require("components.diff_rectangle"); +local spinnybg = require('components.background'); +local msg = game.GetSkinSetting("MSG"); +local normname = game.GetSkinSetting("username") + +local crew = game.GetSkinSetting("single_idol") + +local m_jacket = gfx.CreateSkinImage("multi/lobby/multi_jacket.png", 1); +local m_base_panel = gfx.CreateSkinImage("multi/lobby/multi_base_panel.png", 1); +local m_anim = gfx.CreateSkinImage("multi/lobby/panel_laser.png", 1); +local m_panel = gfx.CreateSkinImage("multi/lobby/matching_panel.png", 1); +local m_s_panel = gfx.CreateSkinImage("multi/lobby/song_panel.png", 1); +local m_host_panel = gfx.CreateSkinImage("multi/lobby/user_panel.png", 1); +local m_bpm_panel = gfx.CreateSkinImage("multi/lobby/lane_speed_panel.png", 1); +local m_info_panel = gfx.CreateSkinImage("multi/lobby/button_panel.png", 1); + +local headerMatchingImage = gfx.CreateSkinImage("titlescreen/entry.png", 1); + +local m_4pb_panels = gfx.CreateSkinImage("multi/lobby/user_panel_2.png", 1); + +local ready_bt = gfx.CreateSkinImage("multi/lobby/READY.png", 1); + +local bg = gfx.CreateSkinImage("multi/lobby/bg.png", 1); +local bg_graid1 = gfx.CreateSkinImage("multi/lobby/gradient_bottom.png", 1); +local bg_graid2 = gfx.CreateSkinImage("multi/lobby/gradient_top.png", 1); + +local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..crew, 1 / 30, 0, true); +local idolAnimTransitionScale = 0; +if not idolAnimation then + game.Log("Crew folder crew/anim/"..crew.." does not exist.", game.LOGGER_WARNING) +end + +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 +local jacket_size; + +local BAR_ALPHA = 191; +local HEADER_HEIGHT = 100 + +local resx, resy = game.GetResolution() +local desw = 1080 +local desh = 1920 +local scale; + +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 go; +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 irHeartbeatRequested = false; +local irText = '' + +local grades = { + {["max"] = 6900000, ["image"] = gfx.CreateSkinImage("common/grades/D.png", 0)}, + {["max"] = 7900000, ["image"] = gfx.CreateSkinImage("common/grades/C.png", 0)}, + {["max"] = 8600000, ["image"] = gfx.CreateSkinImage("common/grades/B.png", 0)}, + {["max"] = 8900000, ["image"] = gfx.CreateSkinImage("common/grades/A.png", 0)}, + {["max"] = 9200000, ["image"] = gfx.CreateSkinImage("common/grades/A+.png", 0)}, + {["max"] = 9400000, ["image"] = gfx.CreateSkinImage("common/grades/AA.png", 0)}, + {["max"] = 9600000, ["image"] = gfx.CreateSkinImage("common/grades/AA+.png", 0)}, + {["max"] = 9700000, ["image"] = gfx.CreateSkinImage("common/grades/AAA.png", 0)}, + {["max"] = 9800000, ["image"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0)}, + {["max"] = 9900000, ["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") + +local drawIdol = function(deltaTime) + if idolAnimation then + local idolAnimTickRes = gfx.TickAnimation(idolAnimation, deltaTime); + if idolAnimTickRes == 1 then + gfx.GlobalAlpha(idolAnimTransitionScale); + + idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60; + if (idolAnimTransitionScale > 1) then + idolAnimTransitionScale = 1; + end + + gfx.ImageRect(0, 0, resX,resY, idolAnimation, 1, 0); + gfx.GlobalAlpha(1); + end + end +end + +songjacket = function() -- self explanatory + + if portrait then + split = resX + jacket_size = math.min(resX/4, resY/4); + song_x_off = 0; + else + split = resX/2 + jacket_size = math.min(resX/2, resY/2); + song_x_off = 1/2*resX; + end + + local jw , jh = gfx.ImageSize(m_jacket); + gfx.BeginPath(); + gfx.ImageRect(333, 1284, jw/1.18, jh/1.18, m_jacket,1,0); + +end + +m_own_info = function() + + local x = 0 + local y = 1310 + gfx.BeginPath(); + gfx.FontSize(40) + gfx.ImageRect(x, y, 343/1.18, 361/1.18,m_host_panel,1,0) + gfx.Text(normname, x+20, y+78) + + gfx.FontSize(22) + gfx.Text(irText, x+5, y+288); + +end + +m_base_part = function() -- just the images which slide up + local jw , jh = gfx.ImageSize(m_base_panel); + gfx.BeginPath(); + gfx.ImageRect(1, 1250, jw/1.17, jh/1.18, m_base_panel,1,0); + gfx.BeginPath(); + gfx.ImageRect(1, 1250, jw/1.17, jh/1.18, m_anim,1,0); + + gfx.LoadSkinFont('Digital-Serial-Bold.ttf') + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_LEFT) + gfx.FontSize(24) +end + +m_part = function() -- room name part + local jw , jh = gfx.ImageSize(m_panel); + gfx.BeginPath(); + gfx.ImageRect(429, 1142, jw/1.175, jh/1.18, m_panel,1,0); + + gfx.FontSize(32); + gfx.LoadSkinFont('Digital-Serial-Bold.ttf') + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE) + gfx.Text(selected_room.name, 575, 1179) +end + +m_s_part = function () -- song info panel + local jw , jh = gfx.ImageSize(m_s_panel); + gfx.BeginPath(); + gfx.ImageRect(283, 1187, jw/1.175, jh/1.18, m_s_panel,1,0); + + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_LEFT); + gfx.FillColor(255,255,255) + + gfx.FontSize(32) + if selected_song == nil then + if host == user_id then + gfx.Text("NO SONG", 535, 1236) + gfx.Text("NO ARIST", 535, 1275) + gfx.FontSize(24) + gfx.Text("NO EFFECTOR", 746, 1378) + gfx.Text("NO ILLUSTRATOR", 746, 1406) + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.FontSize(22) + gfx.Text("BPM ?",780, 1307) + else + if missing_song then + gfx.Text("MISSING SONG!!!!", 535, 1235) + gfx.Text("MISSING ARIST!!!!", 535, 1275) + gfx.FontSize(24) + gfx.Text("MISSING EFFECTOR!!!!", 744, 1378) + gfx.Text("MISSING ILLUSTRATOR!!!!", 744, 1406) + else + gfx.Text("HOST IS SELECTING SONG", 535, 1235) + gfx.Text(" ", 535, 1275) + gfx.FontSize(24) + gfx.Text(" ", 746, 1378) + gfx.Text(" ", 746, 1406) + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.FontSize(22) + gfx.Text("BPM ?",780, 1307) + end + end + else + if selected_song.min_bpm ~= selected_song.max_bpm then + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.FontSize(22); + gfx.Text("BPM",777, 1307) + gfx.FontSize(26); + gfx.Text(string.format("%.0f - %.0f", + selected_song.min_bpm, selected_song.max_bpm), + 780 + 77, 1307) + else + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.FontSize(22); + gfx.Text("BPM",777, 1307) + gfx.Text(string.format("%.0f", + selected_song.min_bpm), + 780 + 77, 1307) + end + gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_LEFT); + gfx.FontSize(32); + gfx.Text(selected_song.title, 535, 1236) + gfx.Text(selected_song.artist, 535, 1275) + gfx.FontSize(24) + gfx.Text(selected_song.effector, 746, 1377) + gfx.Text(selected_song.illustrator, 746, 1406) + end +end + +m_bpm_part = function () -- bpm and lane speed + local jw , jh = gfx.ImageSize(m_bpm_panel); + gfx.BeginPath(); + gfx.ImageRect(0, 1692, jw/1.18, jh/1.18, m_bpm_panel,1,0); + + gfx.FontSize(32) + if selected_song == nil then + if host == user_id then + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.FontSize(32) + gfx.Text("BPM ?",76, 1788) + gfx.Text("LANE-SPEED ?",76, 1832) + else + if missing_song then + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.FontSize(32) + gfx.Text("BPM ?",76, 1788) + gfx.Text("LANE-SPEED ?",76, 1832) + else + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.FontSize(32) + gfx.Text("BPM ?",76, 1788) + gfx.Text("LANE-SPEED ?",76, 1832) + end + end + end + if selected_song ~= nil then + gfx.FillColor(255,255,255) + gfx.FontSize(32); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + + if selected_song.min_bpm ~= selected_song.max_bpm then + + gfx.Text("BPM",76, 1788) + gfx.Text(string.format("%.0f - %.0f", + selected_song.min_bpm, selected_song.max_bpm), + 76 + 75, 1788) + + gfx.Text("LANE-SPEED",76, 1832) + gfx.Text(string.format("%.2f = %.0f", + selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed), + 76 + 175, 1832) + else + + gfx.FontSize(32); + gfx.Text("BPM",76, 1788) + gfx.Text(string.format("%.0f", + selected_song.min_bpm), + 76 + 75, 1788) + + gfx.Text("LANE-SPEED",76, 1832) + gfx.Text(string.format("%.2f = %.0f", + selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed), + 76 + 175, 1832) + end + end +end + +m_info_part = function () -- the info panel + local jw , jh = gfx.ImageSize(m_info_panel); + gfx.BeginPath(); + gfx.ImageRect(475, 1590, jw/1.18, jh/1.18, m_info_panel,1,0); + + local check = 770 + + draw_checkbox("Excessive", check - 200, 1625, toggle_hard, hard_mode, not start_game_soon) + draw_checkbox("Mirror Mode",check - 15, 1625, toggle_mirror, mirror_mode, not start_game_soon) + draw_checkbox("Rotate Host",check + 175, 1625, toggle_rotate, do_rotate, + (owner == user_id or host == user_id) and not start_game_soon) + + for i, user in ipairs(lobby_users) do + + buttonY = 1775 + + local side_button_off = 0 + if owner == user_id and user.id ~= user_id then + draw_button("K",525+side_button_off, 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",525+side_button_off, buttonY+85, 50, function() + change_host(user); + end) + end + + end + +end + +user_setup = function () -- (semi new) user layering + local distance = 350 + for i, user in ipairs(lobby_users) do + + + if i == 1 then + draw_user(user, -distance, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5) + elseif i == 2 then + draw_user(user, 16, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5) + elseif i == 3 then + draw_user(user, 16+distance, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5) + elseif i == 4 then + draw_user(user, 16+distance+distance, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5) + elseif i > 4 then + draw_user(user, 16+distance+distance+distance+distance, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5) + end + end +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 - 200, HEADER_HEIGHT/2 - 20, 400, 40, headerMatchingImage, 1, 0) +end + +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) + 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.FillColor(201,0,0); + gfx.Text(text, x, y) + + local xmin,ymin,xmax,ymax = gfx.TextBounds(x, y, text); + + local sx = xmin - 40; + local sy = y - 15; + + if can_click and mouse_clipped(xmin-10, ymin, xmax-xmin, ymax-ymin) then + hovered = hoverindex; + end + + if current then + -- Draw checkmark + gfx.BeginPath(); + gfx.FillColor(0, 236, 0); + gfx.Text(text, x, y) + gfx.Fill(); + end +end; + +--look into user changing -- IMPORTANT !!! +draw_user = function(user, x, y , w, h, rank, breadx,bready) + local name = user.name + local showthing = false + if user.id == user_id then + name = name + end + if user.id == host then + name = name + elseif user.ready then + showthing = true + elseif not user.ready then + showthing = false + end + + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); + gfx.FillColor(255,255,255) + gfx.FontSize(42) + + + gfx.FontSize(30) + gfx.BeginPath(); + gfx.ImageRect(x, y ,w, h,m_4pb_panels,1,0) + gfx.Text(name, x+135, y+51) + + + if showthing == true then + local jw , jh = gfx.ImageSize(ready_bt); + gfx.BeginPath(); + gfx.ImageRect(x+breadx, y+bready, jw/1.18, jh/1.18, ready_bt,1,0); + elseif showthing == false then + local jw , jh = gfx.ImageSize(ready_bt); + gfx.BeginPath(); + gfx.ImageRect(x+breadx, y+bready, jw/1.18, jh/1.18, ready_bt,0,0); + 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() + + 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) + difbar.render(deltaTime, x, y, 1, diff.difficulty, diff.level); +end + +local doffset = 0; +local timer = 0; + +local possy = 1095; +local possx = 150; + +draw_diffs = function(diffs, x, y, w, h, selectedDiff) + local diffWidth = w/2 + local diffHeight = w/2 + local diffCount = #diffs + gfx.Scissor(x+84 + possx,y + possy,w/2.451,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 + possx, y + possy, 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 + possx, y + possy, 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 + possx, y + possy, diffWidth, diffHeight, true) + gfx.ResetScissor() + +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 MATCH)' + 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 + +local IR_HeartbeatResponse = function(res) + if res.statusCode == IRData.States.Success then + irText = 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 + +function render_lobby(deltaTime) + + local jw , jh = gfx.ImageSize(bg); + gfx.BeginPath(); + gfx.ImageRect(0, 0, resX, resY, bg,1,0); + drawIdol(deltaTime) + gfx.BeginPath(); + gfx.ImageRect(0, 0, resX, resY, bg_graid1,1,0); + gfx.ImageRect(0, 0, resX, resY, bg_graid2,1,0); + drawHeader() + gfx.BeginPath(); + m_base_part() + + m_own_info() + user_setup() + + m_info_part() + m_part() + m_s_part() + m_bpm_part() + songjacket() + + + if selected_song == nil then + if jacket == 0 then + jacket = placeholderJacket + end + else + 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(481, 1303+jacket_size/2) + gfx.ImageRect(-jacket_size/2,-jacket_size/2,jacket_size,jacket_size,jacket,1,0) + + if mouse_clipped(481-jacket_size/2,1423+-jacket_size/2,jacket_size,jacket_size) and host == user_id then + hovered = function() + missing_song = false + mpScreen.SelectSong() + end + end + gfx.Restore() +end + +function render_room_list(deltaTime) + + spinnybg.draw(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 +--here +function render_new_room_name(deltaTime) + gfx.BeginPath(); + gfx.LoadSkinFont("segoeui.ttf") + 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.Rect(0, resY/2-10, resX, 60) + + + 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 + + IR_Handle() + Sound.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 +local que1 = 0 +-- Handle button presses to advance the UI +button_pressed = function(button) + + if button == game.BUTTON_FXL and game.BUTTON_FXR 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_BTA then + toggle_hard(); + end + if button == game.BUTTON_BTB 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/result.lua b/scripts/result.lua index 23cc0fb..40a6540 100644 --- a/scripts/result.lua +++ b/scripts/result.lua @@ -1,11 +1,14 @@ -local Numbers = require('common.numbers') local Easing = require('common.easing'); +local Charting = require('common.charting'); local Background = require('components.background'); local Footer = require('components.footer'); +local Numbers = require('components.numbers') local DiffRectangle = require('components.diff_rectangle'); local lang = require("language.call") -local creww = game.GetSkinSetting("single_idol") +require('common.gameconfig') + +local crew = game.GetSkinSetting("single_idol") local VolforceWindow = require('components.volforceWindow') @@ -51,6 +54,10 @@ local danBadgeImage = gfx.CreateSkinImage("dan/inf.png", 0); local badgeLines = gfx.CreateSkinImage("result/badge_lines.png", 0); local badgeGrade = gfx.CreateSkinImage("result/badge_gradient.png", 0); +local gaugeTypeMirrorImage = gfx.CreateSkinImage("result/gauge_type_badges/mirror.png", 0); +local gaugeTypeRandomImage = gfx.CreateSkinImage("result/gauge_type_badges/random.png", 0); +local gaugeTypeMirrorRandomImage = gfx.CreateSkinImage("result/gauge_type_badges/random_mirror.png", 0); + local gradeImages = { S = gfx.CreateSkinImage("common/grades/S.png", 0), AAA_P = gfx.CreateSkinImage("common/grades/AAA+.png", 0), @@ -94,7 +101,8 @@ local difficultyLabelImages = { gfx.CreateSkinImage("diff/5 infinite.png", 0), gfx.CreateSkinImage("diff/6 gravity.png", 0), gfx.CreateSkinImage("diff/7 heavenly.png", 0), - gfx.CreateSkinImage("diff/8 vivid.png", 0) + gfx.CreateSkinImage("diff/8 vivid.png", 0), + gfx.CreateSkinImage("diff/9 exceed.png", 0) } local clearBadgeImages = { @@ -129,10 +137,10 @@ local clearBadgeImages = { } -- ANIMS -local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..creww, 1 / 30, 0, true); +local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..crew, 1 / 30, 0, true); if not idolAnimation then - game.Log("Crew folder crew/anim/"..creww.." does not exist.", game.LOGGER_WARNING) + game.Log("Crew folder crew/anim/"..crew.." does not exist.", game.LOGGER_WARNING) end local transitionEnterScale = 0; @@ -180,6 +188,13 @@ local irText = '' game.LoadSkinSample("result") game.LoadSkinSample("shutter") +local function isHard(result) + if result.flags == nil then + return result.gauge_type == 1 + end + return result.flags & 1 == 1 + end + local handleSfx = function() if not bgSfxPlayed then game.PlaySample("result", true) @@ -188,6 +203,55 @@ local handleSfx = function() end end +local drawGraph = function(x,y,w,h) + if isHard(result) then + gfx.BeginPath() + gfx.Rect(x,y,w,103) + gfx.FillColor(26,26,26,255) + gfx.Fill() + gfx.FillColor(255,255,255,255) + else + gfx.BeginPath() + gfx.Rect(x,y,w,h-68) + gfx.FillColor(55,27,51,255) + gfx.Fill() + gfx.BeginPath() + gfx.Rect(x,y+30,w,72) + gfx.FillColor(7,24,28,255) + gfx.Fill() + gfx.FillColor(255,255,255,255) + end + + gfx.BeginPath() + gfx.MoveTo(x,y + h + 2 - h * result.gaugeSamples[1]) + for i = 2, #result.gaugeSamples do + gfx.LineTo(x + i * w / #result.gaugeSamples,y + h + 2 - h * result.gaugeSamples[i]) + end + + if isHard(result) then + gfx.StrokeWidth(3) + gfx.StrokeColor(232,163,10) + gfx.Stroke() + gfx.Scissor(x, y + h *0.01, w, h*0.98) + gfx.Stroke() + gfx.ResetScissor() + gfx.Scissor(x, y + h * 0.99, w, (h * 0.03) + 4) + gfx.StrokeColor(255,0,0) + gfx.Stroke() + gfx.ResetScissor() + else + gfx.StrokeWidth(3) + gfx.StrokeColor(46,211,241) + gfx.Scissor(x, y + h * 0.3, w, (h * 0.7) + 4) + gfx.Stroke() + gfx.ResetScissor() + gfx.Scissor(x, y, w, h*0.3) + gfx.StrokeColor(215,48,182) + gfx.Stroke() + gfx.ResetScissor() + end +end + function drawTimingBar(y, value, max, type) gfx.BeginPath(); @@ -211,7 +275,7 @@ local drawIdol = function(deltaTime) gfx.GlobalAlpha(idolAnimTransitionScale); idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60; - if (idolAnimTransitionScale > 1) then + if (GameConfig["AutoScoreScreenshot"] or idolAnimTransitionScale > 1) then idolAnimTransitionScale = 1; end @@ -407,6 +471,21 @@ local drawRightPanelContent = function() gfx.Restore() end + -- Draw the gauge type flags if needed (mirror, random) + if(result.mirror or result.random) then + gfx.BeginPath(); + local gaugeTypeFlagPosX = gaugePosX + 10; + local gaugeTypeFlagPosY = gaugePosY - 30; + local flagw, flagh = gfx.ImageSize(gaugeTypeMirrorImage) + if(result.mirror and result.random) then + gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeMirrorRandomImage, 1, 0) + elseif(result.mirror) then + gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeMirrorImage, 1, 0) + elseif(result.random) then + gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeRandomImage, 1, 0) + end + end + -- Draw err/early/critical/late/err texts gfx.Text(earlyLateBarsStats.earlyErrors, rightPanelX + 683, @@ -503,8 +582,23 @@ local drawBottomPanelContent = function(deltatime) gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) gfx.Text(result.medianHitDelta.." ms", rightX, baseY); gfx.Text(math.floor(result.meanHitDelta).." ms", rightX, baseY + detailTextMargin); + + --Draw Graph + drawGraph(leftX-22, baseY-18, 454, 98); + + --draw Recommended Offset + local delta = math.floor(result.medianHitDelta); + local songOffset = 0; + if (songOffset == nil) then songOffset = 0; end + local offset = tonumber(songOffset) + delta; + gfx.FillColor(255,255,255,255); + gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_TOP) + gfx.Text('RECOMMENDED SONG OFFSET:', leftX + 367, baseY + 89); + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) + gfx.Text(string.format("%dms", offset), leftX + 370, baseY + 89); end + local drawJacketPanel = function() gfx.BeginPath(); local tw, th = gfx.ImageSize(jacketPanelImage); @@ -514,10 +608,9 @@ end local drawJacketPanelContent = function(deltaTime) gfx.BeginPath(); - gfx.ImageRect(jacketPanelX + 13, jacketPanelY + 28, 265, 265, - jacketImage or defaultJacketImage, 1, 0); - - DiffRectangle.render(deltaTime, jacketPanelX+183, jacketPanelY+2.5, 0.67, result.difficulty, result.level); + gfx.ImageRect(jacketPanelX + 13, jacketPanelY + 28, 265, 265, jacketImage or defaultJacketImage, 1, 0); + local adjustedDiff = Charting.GetDisplayDifficulty(result.jacketPath, result.difficulty) + DiffRectangle.render(deltaTime, jacketPanelX+183, jacketPanelY+2.5, 0.67, adjustedDiff, result.level); -- gfx.BeginPath(); -- gfx.ImageRect(jacketPanelX + 183, jacketPanelY + 2.5, 140 / 1.5, 31 / 1.5, @@ -547,7 +640,7 @@ end local tickTransitions = function(deltaTime) - if transitionEnterScale < 1 then + if not GameConfig["AutoScoreScreenshot"] and transitionEnterScale < 1 then transitionEnterScale = transitionEnterScale + deltaTime / 0.66 -- transition should last for that time in seconds else transitionEnterScale = 1 @@ -564,7 +657,7 @@ local tickTransitions = function(deltaTime) (1 - Easing.outQuad(transitionEnterScale))) - if badgeLinesAnimScale < 1 then + if not GameConfig["AutoScoreScreenshot"] and badgeLinesAnimScale < 1 then badgeLinesAnimScale = badgeLinesAnimScale + deltaTime / 0.5 -- transition should last for that time in seconds else badgeLinesAnimScale = 0 diff --git a/scripts/shared/idol.lua b/scripts/shared/idol.lua deleted file mode 100644 index 8367f5c..0000000 --- a/scripts/shared/idol.lua +++ /dev/null @@ -1,13 +0,0 @@ -local idols ={ - -- SDVX IV - nearnoah_summer={"crew/anim/nearnoah_summer", 1 / 30, 0, true} - -- SDVX V - - -- SDVX III -} - - - - - -return idols \ No newline at end of file diff --git a/scripts/songselect/chalwheel.lua b/scripts/songselect/chalwheel.lua index 91da25b..f94d29e 100644 --- a/scripts/songselect/chalwheel.lua +++ b/scripts/songselect/chalwheel.lua @@ -1,606 +1,609 @@ -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.util") +local Charting = require('common.charting') +local Numbers = require('components.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, + ["jacketPath"] = "", + } + } + 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, + ["jacketPath"] = chart.jacketPath, + }) + end + challengeCache[challenge.id]["charts"] = charts + end + end + + if not challengeCache[challenge.id]["percent_required"] then + local percentRequired = 100 + local reqTextWords = Common.split(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) + local adjustedDiff = Charting.GetDisplayDifficulty(chart.jacketPath, chart.difficulty) + DiffRectangle.render(timer, offsetX, ypos, diffIconScale, adjustedDiff, 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/filterwheel.lua b/scripts/songselect/filterwheel.lua index c9030b3..088f37f 100644 --- a/scripts/songselect/filterwheel.lua +++ b/scripts/songselect/filterwheel.lua @@ -1,16 +1,14 @@ -require('common') -local Easing = require('common.easing'); +local Easing = require('common.easing') +local Dim = require("common.dimensions") local SongSelectHeader = require('components.headers.songSelectHeader') -local Footer = require('components.footer'); - -local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY) +local Footer = require('components.footer') local defaultFolderBgImage = gfx.CreateSkinImage('song_select/filter_wheel/bg.png', 0) local collectionFolderBgImage = gfx.CreateSkinImage('song_select/filter_wheel/col_bg.png', 0) local subFolderBgImage = gfx.CreateSkinImage('song_select/filter_wheel/sub_bg.png', 0) -local scrollbarBgImage = gfx.CreateSkinImage("song_select/scrollbar/bg.png", 1) -local scrollbarFillImage = gfx.CreateSkinImage("song_select/scrollbar/fill.png", 1) +local scrollBarBackgroundImage = gfx.CreateSkinImage("song_select/scrollbar/bg.png", 1) +local scrollBarFillImage = gfx.CreateSkinImage("song_select/scrollbar/fill.png", 1) local cursorImages = { gfx.CreateSkinImage("song_select/cursor.png", 1), -- Effective rate or fallback @@ -19,7 +17,7 @@ local cursorImages = { gfx.CreateSkinImage("song_select/cursor_blast.png", 1), -- Blastive rate } -local ITEM_HEIGHT = 172; +local ITEM_HEIGHT = 172 local specialFolders = { { @@ -87,27 +85,27 @@ local specialFolders = { } -- AUDIO -game.LoadSkinSample('song_wheel/cursor_change.wav'); -game.LoadSkinSample('filter_wheel/open_close.wav'); +game.LoadSkinSample('song_wheel/cursor_change.wav') +game.LoadSkinSample('filter_wheel/open_close.wav') local resx, resy = game.GetResolution() local desw, desh = 1080, 1920 -local scale = 1; +local scale = 1 -local isFilterWheelActive = false; -local previousActiveState = false; -- for open/close sounds +local isFilterWheelActive = false +local previousActiveState = false -- for open/close sounds -local selectionMode = 'folders'; -local selectedFolder = 1; -local selectedLevel = 1; +local selectionMode = 'folders' +local selectedFolder = 1 +local selectedLevel = 1 -local transitionScrollScale = 0; -local transitionScrollOffsetY = 0; -local scrollingUp = false; +local transitionScrollScale = 0 +local transitionScrollOffsetY = 0 +local scrollingUp = false -local transitionLeaveScale = 1; -local transitionLeaveReappearTimer = 0; -local TRANSITION_LEAVE_DURATION = 0.1; +local transitionLeaveScale = 1 +local transitionLeaveReappearTimer = 0 +local TRANSITION_LEAVE_DURATION = 0.1 -- Window variables local resX, resY @@ -133,31 +131,31 @@ function resetLayoutInformation() end function getCorrectedIndex(from, offset) - local total = 1; + local total = 1 if selectionMode == 'folders' then - total = #filters.folder; + total = #filters.folder else - total = #filters.level; + total = #filters.level end - index = from + offset; + index = from + offset if index < 1 then index = total + (from + offset) -- this only happens if the offset is negative end if index > total then - indexesUntilEnd = total - from; + indexesUntilEnd = total - from index = offset - indexesUntilEnd -- this only happens if the offset is positive end - return index; + return index end function getFolderData(folderLabel) - local folderType = 'unknown'; - local isSpecial = false; - local folderBgImage = defaultFolderBgImage; + local folderType = 'unknown' + local isSpecial = false + local folderBgImage = defaultFolderBgImage if not folderLabel then return { @@ -175,16 +173,16 @@ function getFolderData(folderLabel) if (string.find(folderLabel, 'Folder: ')) then - folderType = 'folder'; + folderType = 'folder' folderLabel = folderLabel:gsub('Folder: ', '') -- Delete default prefix elseif (string.find(folderLabel, 'Collection: ')) then - folderType = 'collection'; + folderType = 'collection' folderLabel = folderLabel:gsub('Collection: ', '') -- Delete default prefix - folderBgImage = collectionFolderBgImage; + folderBgImage = collectionFolderBgImage elseif (string.find(folderLabel, 'Level: ')) then - folderType = 'level'; + folderType = 'level' folderLabel = folderLabel:gsub('Level: ', '') -- Delete default prefix - folderLabel = 'LEVEL ' .. folderLabel; + folderLabel = 'LEVEL ' .. folderLabel end local labelMatcherString = string.upper(folderLabel) @@ -192,8 +190,8 @@ function getFolderData(folderLabel) for i, specialFolder in ipairs(specialFolders) do for i, specialFolderKey in ipairs(specialFolder.keys) do if (specialFolderKey == labelMatcherString) then - folderBgImage = specialFolder.folderBg; - isSpecial = true; + folderBgImage = specialFolder.folderBg + isSpecial = true end end end @@ -221,51 +219,51 @@ function drawFolder(label, y) -- Draw the folder label, but only if the folder is not special if (not folderData.isSpecial) then - gfx.BeginPath(); + gfx.BeginPath() gfx.FontSize(38) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.FillColor(255, 255, 255, 255); - gfx.Text(folderData.label, x + 18, y + 72); + gfx.FillColor(255, 255, 255, 255) + gfx.Text(folderData.label, x + 18, y + 72) end end function drawFolderList() gfx.GlobalAlpha(1-transitionLeaveScale) - local numOfItemsAround = 7; - local selectedIndex = 1; - local folderList = filters.folder; + local numOfItemsAround = 7 + local selectedIndex = 1 + local folderList = filters.folder if selectionMode == 'folders' then selectedIndex = selectedFolder - folderList = filters.folder; + folderList = filters.folder else selectedIndex = selectedLevel - folderList = filters.level; + folderList = filters.level end - local yOffset = transitionScrollOffsetY; + local yOffset = transitionScrollOffsetY - local i = 1; + local i = 1 while (i <= numOfItemsAround) do local index = getCorrectedIndex(selectedIndex, -i) drawFolder(folderList[index], desh / 2 - ITEM_HEIGHT / 2 - ITEM_HEIGHT * i + yOffset) - i = i + 1; + i = i + 1 end -- Draw the selected song drawFolder(folderList[selectedIndex], desh / 2 - ITEM_HEIGHT / 2 + yOffset) - i = 1; + i = 1 while (i <= numOfItemsAround) do local index = getCorrectedIndex(selectedIndex, i) drawFolder(folderList[index], desh / 2 - ITEM_HEIGHT / 2 + ITEM_HEIGHT * i + yOffset) - i = i + 1; + i = i + 1 end - gfx.GlobalAlpha(1); + gfx.GlobalAlpha(1) end function drawCursor() @@ -274,7 +272,7 @@ function drawCursor() gfx.BeginPath() local cursorImageIndex = game.GetSkinSetting('_gaugeType') - local cursorImage = cursorImages[cursorImageIndex or 1]; + local cursorImage = cursorImages[cursorImageIndex or 1] gfx.ImageRect(desw / 2 - 14, desh / 2 - 213 / 2, 555, 213, cursorImage, 1, 0) end @@ -283,27 +281,39 @@ function drawScrollbar() if not isFilterWheelActive or transitionLeaveScale ~= 0 then return end gfx.BeginPath() - local bgW = 13*0.85; - local bgH = 1282*0.85; - local scrollPosX = desw-20 - local scrollPosY = desh/2-bgH/2 - - gfx.ImageRect(scrollPosX, scrollPosY, bgW, bgH, scrollbarBgImage, 1, 0) - - local total = game.GetSkinSetting('_songWheelScrollbarTotal') - local index = game.GetSkinSetting('_songWheelScrollbarIndex') - - if (index == nil) then return end; + local resize = 0.85 + local lw, lh = gfx.ImageSize(scrollBarBackgroundImage) + local lw = lw * resize + local lh = lh * resize + local xPos = desw-20 + local backgroundYPos = desh/2 - lh/2 + gfx.ImageRect(xPos, backgroundYPos, lw, lh, scrollBarBackgroundImage, 1, 0) gfx.BeginPath() - local fillW = 27*0.85 - local fillH = 65*0.85 - local fillPosOffsetY = (bgH-fillH)*( - (index-1) / - math.max(1,total-1) - ) + local sw, sh = gfx.ImageSize(scrollBarFillImage) + local sw = sw * resize + local sh = sh * resize + local fillXPos = xPos - 6 - gfx.ImageRect(scrollPosX-6, scrollPosY+fillPosOffsetY, fillW, fillH, scrollbarFillImage, 1, 0) + -- figure out index and total + local index = 1 + local total = 1 + if selectionMode == 'folders' then + index = selectedFolder + total = #filters.folder + else + index = selectedLevel + total = #filters.level + end + + + local minScrollYPos = backgroundYPos + local maxScrollYPos = backgroundYPos + lh - sh + local scrollStep = (maxScrollYPos - minScrollYPos) / (total - 1) + local scrollbarYOffset = (index - 1) * scrollStep + local scrollbarYPos = minScrollYPos + scrollbarYOffset + + gfx.ImageRect(fillXPos, scrollbarYPos, sw, sh, scrollBarFillImage, 1, 0) end function tickTransitions(deltaTime) @@ -315,10 +325,10 @@ function tickTransitions(deltaTime) if scrollingUp then transitionScrollOffsetY = Easing.inQuad(1 - transitionScrollScale) * - ITEM_HEIGHT; + ITEM_HEIGHT else transitionScrollOffsetY = Easing.inQuad(1 - transitionScrollScale) * - -ITEM_HEIGHT; + -ITEM_HEIGHT end -- LEAVE TRANSITION @@ -328,7 +338,7 @@ function tickTransitions(deltaTime) else transitionLeaveScale = 1 end - transitionLeaveReappearTimer = 1; + transitionLeaveReappearTimer = 1 else if (transitionLeaveReappearTimer == 1) then -- This stuff happens right after filterwheel is deactivated @@ -337,90 +347,86 @@ function tickTransitions(deltaTime) transitionLeaveReappearTimer = transitionLeaveReappearTimer - deltaTime / (TRANSITION_LEAVE_DURATION + 0.05) -- same reasoning as in the songwheel if (transitionLeaveReappearTimer <= 0) then - transitionLeaveScale = 0; - transitionLeaveReappearTimer = 0; + transitionLeaveScale = 0 + transitionLeaveReappearTimer = 0 end end end function drawFilterWheelContent(deltatime) - tickTransitions(deltatime); + tickTransitions(deltatime) drawFolderList() end -local drawFilterWheel = function (x,y,w,h, deltaTime) - gfx.Translate(x,y); - gfx.Scale(w/1080, h/1920); +local drawFilterWheel = function (deltaTime) drawFilterWheelContent(deltaTime) drawCursor() drawScrollbar() if (game.GetSkinSetting('_currentScreen') == 'songwheel') then - SongSelectHeader.draw(deltaTime); - Footer.draw(deltaTime); + SongSelectHeader.draw(deltaTime) + Footer.draw(deltaTime) end if (isFilterWheelActive ~= previousActiveState) then - game.PlaySample('filter_wheel/open_close.wav'); - previousActiveState = isFilterWheelActive; + game.PlaySample('filter_wheel/open_close.wav') + previousActiveState = isFilterWheelActive end -- Debug text - gfx.BeginPath(); + gfx.BeginPath() gfx.FontSize(18) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.FillColor(255, 255, 255, 255); + gfx.FillColor(255, 255, 255, 255) if game.GetSkinSetting('debug_showInformation') then gfx.Text('S_M: ' .. selectionMode .. ' // S_F: ' .. selectedFolder .. ' // S_L: ' .. selectedLevel .. ' // L_TS: ' .. - transitionLeaveScale .. ' // L_TRT: ' .. transitionLeaveReappearTimer, 8, 1870); + transitionLeaveScale .. ' // L_TRT: ' .. transitionLeaveReappearTimer, 8, 1870) end end render = function(deltaTime, shown) - isFilterWheelActive = shown; + isFilterWheelActive = shown if not shown then - game.SetSkinSetting('_songWheelOverlayActive', 0); + game.SetSkinSetting('_songWheelOverlayActive', 0) else - game.SetSkinSetting('_songWheelOverlayActive', 1); + game.SetSkinSetting('_songWheelOverlayActive', 1) end - game.SetSkinSetting('_songWheelActiveFolderLabel', getFolderData(filters.folder[selectedFolder]).label); - game.SetSkinSetting('_songWheelActiveSubFolderLabel', getFolderData(filters.level[selectedLevel]).label); + game.SetSkinSetting('_songWheelActiveFolderLabel', getFolderData(filters.folder[selectedFolder]).label) + game.SetSkinSetting('_songWheelActiveSubFolderLabel', getFolderData(filters.level[selectedLevel]).label) - -- detect resolution change - local resx, resy = game.GetResolution(); - if resx ~= resX or resy ~= resY then - resolutionChange(resx, resy) - end + Dim.updateResolution() + + Dim.transformToScreenSpace() gfx.GlobalAlpha(1) - drawFilterWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime); + drawFilterWheel(deltaTime) end set_selection = function(newIndex, isFolder) - local oldIndex = 1; - local total = 1; + local oldIndex = 1 + local total = 1 if isFolder then oldIndex = selectedFolder selectedFolder = newIndex - total = #filters.folder; + total = #filters.folder else oldIndex = selectedLevel selectedLevel = newIndex - total = #filters.level; + total = #filters.level end - transitionScrollScale = 0; + transitionScrollScale = 0 - scrollingUp = false; + scrollingUp = false if ((newIndex > oldIndex and not (newIndex == total and oldIndex == 1)) or - (newIndex == 1 and oldIndex == total)) then scrollingUp = true; end + (newIndex == 1 and oldIndex == total)) then scrollingUp = true end - game.PlaySample('song_wheel/cursor_change.wav'); + game.PlaySample('song_wheel/cursor_change.wav') end set_mode = function(isFolder) diff --git a/scripts/songselect/settingswheel.lua b/scripts/songselect/settingswheel.lua deleted file mode 100644 index 5449627..0000000 --- a/scripts/songselect/settingswheel.lua +++ /dev/null @@ -1,36 +0,0 @@ -resx,resy = game.GetResolution() -local wheelY = -resy -local bgFade = 0 -local yoff = 0 -local lastSelected = 0 - -render = function(deltaTime, shown) - gfx.ResetTransform() - gfx.BeginPath(); - gfx.LoadSkinFont("segoeui.ttf"); - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); - gfx.FontSize(40); - if shown then - bgFade = math.min(bgFade + deltaTime * 10, 1) - wheelY = math.min(wheelY + deltaTime * resy * 10, 0) - else - wheelY = math.max(wheelY - deltaTime * resy * 10, -resy) - bgFade = math.max(bgFade - deltaTime * 10, 0) - end - gfx.FillColor(0,0,0,math.floor(200 * bgFade)) - gfx.Rect(0,0,resx,resy) - gfx.Fill() - gfx.BeginPath() - yoff = 0.8 * yoff + (settings.currentSelection - lastSelected) - lastSelected = settings.currentSelection - if bgFade > 0 then - for i,setting in ipairs(settings) do - if i == settings.currentSelection then - gfx.FillColor(255,255,255) - else - gfx.FillColor(70,70,70) - end - gfx.FastText(string.format("%s: %s", setting.name, setting.value), resx/2, resy/2 + 40 * (i - settings.currentSelection + yoff) + wheelY, 40, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE); - end - end -end \ No newline at end of file diff --git a/scripts/songselect/songwheel new.lua b/scripts/songselect/songwheel new.lua deleted file mode 100644 index 15b93b8..0000000 --- a/scripts/songselect/songwheel new.lua +++ /dev/null @@ -1,149 +0,0 @@ -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/songwheel old.lua b/scripts/songselect/songwheel old.lua deleted file mode 100644 index cd4b77f..0000000 --- a/scripts/songselect/songwheel old.lua +++ /dev/null @@ -1,675 +0,0 @@ -easing = require("easing") - -gfx.LoadSkinFont("rounded-mplus-1c-bold.ttf") - -game.LoadSkinSample("cursor_song") -game.LoadSkinSample("cursor_difficulty") - -local resx, resy = game.GetResolution() - -local levelFont = ImageFont.new("font-level", "0123456789") -local diffFont = ImageFont.new("diff_num", "0123456789") -local bpmFont = ImageFont.new("number", "0123456789.") -- FIXME: font-default -local desw, desh; -local resx, resy; -local portrait; -local scale; - -function ResetLayoutInformation() - resx, resy = game.GetResolution() - portrait = resy > resx - desw = portrait and 1080 or 1920 - desh = desw * (resy / resx) - scale = resx / desw -end - -function render(deltaTime) - ResetLayoutInformation() -end - --- Grades ---------- -local noGrade = Image.skin("song_select/grade/nograde.png") -local grades = { - {["min"] = 9900000, ["image"] = Image.skin("song_select/grade/s.png")}, - {["min"] = 9800000, ["image"] = Image.skin("song_select/grade/aaap.png")}, - {["min"] = 9700000, ["image"] = Image.skin("song_select/grade/aaa.png")}, - {["min"] = 9500000, ["image"] = Image.skin("song_select/grade/aap.png")}, - {["min"] = 9300000, ["image"] = Image.skin("song_select/grade/aa.png")}, - {["min"] = 9000000, ["image"] = Image.skin("song_select/grade/ap.png")}, - {["min"] = 8700000, ["image"] = Image.skin("song_select/grade/a.png")}, - {["min"] = 7500000, ["image"] = Image.skin("song_select/grade/b.png")}, - {["min"] = 6500000, ["image"] = Image.skin("song_select/grade/c.png")}, - {["min"] = 0, ["image"] = Image.skin("song_select/grade/d.png")}, -} - -function lookup_grade_image(difficulty) - local gradeImage = noGrade - if difficulty.scores[1] ~= nil then - local highScore = difficulty.scores[1] - for i, v in ipairs(grades) do - if highScore.score >= v.min then - gradeImage = v.image - break - end - end - end - return { image = gradeImage, flicker = (gradeImage == grades[1].image) } -end - --- Medals ---------- -local noMedal = Image.skin("song_select/medal/nomedal.png") -local medals = { - Image.skin("song_select/medal/played.png"), - Image.skin("song_select/medal/clear.png"), - Image.skin("song_select/medal/hard.png"), - Image.skin("song_select/medal/uc.png"), - Image.skin("song_select/medal/puc.png") -} - -function lookup_medal_image(difficulty) - local medalImage = noMedal - local flicker = false - if difficulty.scores[1] ~= nil then - if difficulty.topBadge ~= 0 then - medalImage = medals[difficulty.topBadge] - if difficulty.topBadge >= 3 then -- hard - flicker = true - end - end - end - return { image = medalImage, flicker = flicker } -end - --- Lookup difficulty -function lookup_difficulty(diffs, diff) - local diffIndex = nil - for i, v in ipairs(diffs) do - if v.difficulty + 1 == diff then - diffIndex = i - end - end - local difficulty = nil - if diffIndex ~= nil then - difficulty = diffs[diffIndex] - end - return difficulty -end - --- JacketCache class --------------------- -JacketCache = {} -JacketCache.new = function() - local this = { - cache = {}, - images = { - loading = Image.skin("song_select/loading.png"), - } - } - setmetatable(this, {__index = JacketCache}) - return this -end - -JacketCache.get = function(this, path) - local jacket = this.cache[path] - if not jacket or jacket == this.images.loading.image then - jacket = gfx.LoadImageJob(path, this.images.loading.image) - this.cache[path] = jacket - end - return Image.wrap(jacket) -end - - --- SongData class ------------------ -SongData = {} -SongData.new = function(jacketCache) - local this = { - selectedIndex = 1, - selectedDifficulty = 0, - memo = Memo.new(), - jacketCache = jacketCache, - images = { - dataBg = Image.skin("song_select/data_bg.png"), - fg = Image.skin("song_select/fg.png"), - cursor = Image.skin("song_select/level_cursor.png"), - none = Image.skin("song_select/level/none.png"), - difficulties = { - Image.skin("song_select/level/novice.png"), - Image.skin("song_select/level/advanced.png"), - Image.skin("song_select/level/exhaust.png"), - Image.skin("song_select/level/maximum.png"), - Image.skin("song_select/level/infinite.png"), - Image.skin("song_select/level/gravity.png"), - Image.skin("song_select/level/heavenly.png"), - Image.skin("song_select/level/vivid.png") - }, - } - } - - setmetatable(this, {__index = SongData}) - return this -end - -SongData.render = function(this, deltaTime) - local song = songwheel.songs[this.selectedIndex] - if not song then return end - - -- Lookup difficulty - local diff = song.difficulties[this.selectedDifficulty] - if diff == nil then diff = song.difficulties[1] end - - -- Draw the background - this.images.dataBg:draw({ x = desw / 2, y = desh / 2, w = 1080 ,h = 1920}) - - -- Draw the jacket - local jacket = this.jacketCache:get(diff.jacketPath) - jacket:draw({ x = 97, y = 326, w = 346, h = 346, anchor_h = Image.ANCHOR_LEFT, anchor_v = Image.ANCHOR_TOP }) - - -- Draw the title - local title = this.memo:memoize(string.format("title_%s", song.id), function () - gfx.LoadSkinFont("NotoSans-Regular.ttf") - return gfx.CreateLabel(song.title, 24, 0) - end) - gfx.FillColor(255, 255, 255, 255) - gfx.DrawLabel(title, 32, desh / 2 - 4, 390) - - -- Draw the artist - local artist = this.memo:memoize(string.format("artist_%s", song.id), function () - gfx.LoadSkinFont("NotoSans-Regular.ttf") - return gfx.CreateLabel(song.artist, 24, 0) - end) - gfx.FillColor(255, 255, 255, 255) - gfx.DrawLabel(artist, 32, desh / 2 + 42, 390) - - -- Draw the effector - local effector = this.memo:memoize(string.format("eff_%s_%s", song.id, diff.id), function () - gfx.LoadSkinFont("NotoSans-Regular.ttf") - return gfx.CreateLabel(diff.effector, 16, 0) - end) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE) - gfx.FillColor(255, 255, 255, 255) - gfx.DrawLabel(effector, 260, desh / 2 + 208, 320) - - -- Draw the illustrator - if diff.illustrator then - local illustrator = this.memo:memoize(string.format("ill_%s_%s", song.id, diff.id), function () - gfx.LoadSkinFont("NotoSans-Regular.ttf") - return gfx.CreateLabel(diff.illustrator, 16, 0) - end) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE) - gfx.FillColor(255, 255, 255, 255) - gfx.DrawLabel(illustrator, 260, desh / 2 + 238, 320) - end - - -- Draw the bpm - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(24) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE) - gfx.FillColor(255, 255, 255, 255) - gfx.Text(song.bpm, 75, desh / 2 - 34) - - this:draw_cursor(diff.difficulty) - - -- Draw the hi-score - local hiScore = diff.scores[1] - if hiScore then - -- FIXME: large / small font - local scoreText = string.format("%08d", hiScore.score) - levelFont:draw(scoreText, 362, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE) - -- local scoreHiText = string.format("%04d", math.floor(hiScore.score / 1000)) - -- levelFont:draw(scoreHiText, 362, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE) - -- local scoreLoText = string.format("%04d", hiScore.score % 1000) - -- bpmFont:draw(scoreLoText, 470, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE) - end - - -- Draw the grade and medal - local grade = lookup_grade_image(diff) - grade.image:draw({ x = desw / 2 - 157, y = desh / 2 - 162, scale = 0.85, alpha = grade.flicker and glowState and 0.9 or 1 }) - local medal = lookup_medal_image(diff) - medal.image:draw({ x = desw / 2 - 72, y = desh / 2 - 199, scale = 0.86, alpha = medal.flicker and glowState and 0.9 or 1}) - - for i = 1, 4 do - local d = lookup_difficulty(song.difficulties, i) - this:draw_difficulty(i - 1, d, jacket) - end -end - -SongData.draw_title_artist = function(this, label, x, y, maxWidth) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE) - gfx.FillColor(55, 55, 55, 64) - gfx.DrawLabel(label, x + 2, y + 2, maxWidth) - gfx.FillColor(55, 55, 55, 255) - gfx.DrawLabel(label, x, y, maxWidth) -end - -SongData.set_index = function(this, newIndex) - this.selectedIndex = newIndex -end - -SongData.draw_cursor = function(this, index) - local x = 98 - local y = desh / 2 + 133 - - -- Draw the cursor - this.images.cursor:draw({ x = x + index * 115, y = y, scale = 0.85 }) - end - -SongData.set_difficulty = function(this, newDiff) - this.selectedDifficulty = newDiff -end - -SongData.draw_difficulty = function(this, index, diff, jacket) - local x = 98 - local y = desh / 2 + 135 - - -- Draw the jacket icon - local jacket = this.jacketCache.images.loading - if diff ~= nil then jacket = this.jacketCache:get(diff.jacketPath) end - - if diff == nil then - this.images.none:draw({ x = x + index * 115, y = y - 600, scale = 0.78}) - else - -- Draw the background - this.images.difficulties[diff.difficulty + 1]:draw({ x = x + index * 115, y = y, scale = 0.78}) - -- Draw the level - local levelText = string.format("%02d", diff.level) - diffFont:draw(levelText, x + index * 115, y - 20, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE) - end -end - --- SongTable class ------------------- -SongTable = {} -SongTable.new = function(jacketCache) - local this = { - cols = 1, - rows = 11, - selectedIndex = 1, - selectedDifficulty = 0, - rowOffset = 0, -- song index offset of top-left song in page - cursorPos = 0, -- cursor position in page [0..cols * rows) - displayCursorPos = 0, - cursorAnim = 0, - cursorAnimTotal = 0.1, - memo = Memo.new(), - jacketCache = jacketCache, - images = { - matchingBg = Image.skin("song_select/matching_bg.png"), - scoreBg = Image.skin("song_select/score_bg.png"), - force = Image.skin("song_select/force.png"), - cursor = Image.skin("song_select/cursor.png"), - cursorText = Image.skin("song_select/cursor_text.png"), - cursorDiamond = Image.skin("song_select/cursor_diamond.png"), - cursorDiamondWire = Image.skin("song_select/cursor_diamond_wire.png"), - plates = { - Image.skin("song_select/plate/novice.png"), - Image.skin("song_select/plate/advanced.png"), - Image.skin("song_select/plate/exhaust.png"), - Image.skin("song_select/plate/maximum.png"), - Image.skin("song_select/plate/infinite.png"), - Image.skin("song_select/plate/gravity.png"), - Image.skin("song_select/plate/heavenly.png"), - Image.skin("song_select/plate/vivid.png") - - } - } - } - setmetatable(this, {__index = SongTable}) - return this -end - -SongTable.calc_cursor_point = function(this, pos) - local col = pos % this.cols - local row = math.floor((pos) / this.cols) - local x = desw * 0.75 + col * this.images.cursor.w - local y = 0 + row * this.images.cursor.h - return x, y -end - -SongTable.set_index = function(this, newIndex) - if newIndex ~= this.selectedIndex then - game.PlaySample("cursor_song") - end - - local delta = newIndex - this.selectedIndex - if delta < -1 or delta > 1 then - local newOffset = newIndex - 1 - this.rowOffset = math.floor((newIndex - 1) / this.cols) * this.cols - this.cursorPos = (newIndex - 1) - this.rowOffset - this.displayCursorPos = this.cursorPos - else - local newCursorPos = this.cursorPos + delta - - if newCursorPos < 0 then - -- scroll up - this.rowOffset = this.rowOffset - this.cols - if this.rowOffset < 0 then - -- this.rowOffset = math.floor(#songwheel.songs / this.cols) - end - newCursorPos = newCursorPos + this.cols - elseif newCursorPos >= this.cols * this.rows then - -- scroll down - this.rowOffset = this.rowOffset + this.cols - newCursorPos = newCursorPos - this.cols - else - -- no scroll, move cursor in page - end - if this.cursorAnim > 0 then - this.displayCursorPos = easing.outQuad(0.5 - this.cursorAnim, this.displayCursorPos, this.cursorPos - this.displayCursorPos, 0.5) - end - this.cursorPos = newCursorPos - this.cursorAnim = this.cursorAnimTotal - end - this.selectedIndex = newIndex -end - -SongTable.set_difficulty = function(this, newDiff) - if newDiff ~= this.selectedDifficulty then - game.PlaySample("cursor_difficulty") - end - this.selectedDifficulty = newDiff -end - -SongTable.render = function(this, deltaTime) - this:draw_songs() - this:draw_cursor(deltaTime) -end - -SongTable.draw_songs = function(this) - for i = 1, this.cols * this.rows do - if this.rowOffset + i <= #songwheel.songs then - this:draw_song(i - 1, this.rowOffset + i) - end - end -end - --- Draw the song plate -SongTable.draw_song = function(this, pos, songIndex) - local song = songwheel.songs[songIndex] - if not song then return end - - -- Lookup difficulty - local diff = song.difficulties[this.selectedDifficulty] - if diff == nil then diff = song.difficulties[1] end - - local x, y = this:calc_cursor_point(pos) - x = x + 4 - y = y + 16 - - -- Draw the jacket - local jacket = this.jacketCache:get(diff.jacketPath) - jacket:draw({ x = x - 24, y = y - 21, w = 122, h = 122 }) - - -- Draw the background - gfx.FillColor(255, 255, 255) - this.images.scoreBg:draw({ x = x + 72, y = y + 16 }) - if diff.force and diff.force > 0 then - this.images.matchingBg:draw({ x = x + 72, y = y - 62 }) - end - this.images.plates[diff.difficulty + 1]:draw({ x = x, y = y }) - - -- Draw the title - local title = this.memo:memoize(string.format("title_%s", song.id), function () - gfx.LoadSkinFont("rounded-mplus-1c-bold.ttf") - return gfx.CreateLabel(song.title, 14, 0) - end) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BASELINE) - gfx.DrawLabel(title, x - 22, y + 53, 125) - - -- Draw the grade and medal - local grade = lookup_grade_image(diff) - grade.image:draw({ x = x + 78, y = y - 23, alpha = grade.flicker and glowState and 0.9 or 1 }) - - local medal = lookup_medal_image(diff) - medal.image:draw({ x = x + 78, y = y + 10, alpha = medal.flicker and glowState and 0.9 or 1 }) - - -- Draw the level - local levelText = string.format("%02d", diff.level) - levelFont:draw(levelText, x + 72, y + 56, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE) - - -- Draw the volforce - --if diff.force and diff.force > 0 then - --local forceText = string.format("%d", math.floor(diff.force * 100)) - --bpmFont:draw(forceText, x + , y - 60, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE) - --end - - --if diff.forceInTotal then - --this.images.force:draw({x = x - 75, y = y - 60, w = 59, h = 59 }) - --end -end - --- Draw the song cursor -SongTable.draw_cursor = function(this, deltaTime) - gfx.Save() - - local pos = this.displayCursorPos - if this.cursorAnim > 0 then - this.cursorAnim = this.cursorAnim - deltaTime - if this.cursorAnim <= 0 then - this.displayCursorPos = this.cursorPos - pos = this.cursorPos - else - pos = easing.outQuad(this.cursorAnimTotal - this.cursorAnim, this.displayCursorPos, this.cursorPos - this.displayCursorPos, this.cursorAnimTotal) - end - end - - local x, y = this:calc_cursor_point(pos) - gfx.FillColor(255, 255, 255) - - local t = currentTime % 1 - - -- scroll text - gfx.Scissor( - x - this.images.cursor.w / 2, y - (this.images.cursor.h - 30) / 2, - this.images.cursor.w, this.images.cursor.h - 30) - local offset = (currentTime * 50) % 290 - local alpha = glowState and 0.8 or 1 - this.images.cursorText:draw({ x = x + 96, y = y + offset, alpha = alpha }) - this.images.cursorText:draw({ x = x + 96, y = y - 290 + offset, alpha = alpha }) - this.images.cursorText:draw({ x = x - 96, y = y + offset, alpha = alpha }) - this.images.cursorText:draw({ x = x - 96, y = y - 290 + offset, alpha = alpha }) - gfx.ResetScissor() - - -- diamong wireframe - local h = (this.images.cursorDiamondWire.h * 1.5) * easing.outQuad(t * 2, 0, 1, 1) - this.images.cursorDiamondWire:draw({ x = x, y = y, w = this.images.cursorDiamondWire.w * 1.5, h = h, alpha = 0.5 }) - - -- ghost cursor - alpha = easing.outSine(t, 1, -1, 1) - h = this.images.cursor.h * easing.outSine(t, 0, 1, 1) - this.images.cursor:draw({ x = x, y = y, h = h, alpha = alpha }) - - -- concrete cursor - -- local w = this.images.cursor.w * easing.outSine(t, 1, 0.05, 0.5) - this.images.cursor:draw({ x = x, y = y, alpha = glowState and 0.8 or 1 }) - - -- diamond knot - gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER) - this.images.cursorDiamond:draw({ x = x + 100, y = y, alpha = 1 }) - this.images.cursorDiamond:draw({ x = x - 100, y = y, alpha = 1 }) - - local s = this.images.cursorDiamond.w / 1.5 - this.images.cursorDiamond:draw({ x = x + 90 + easing.outQuad(t, 0, -4, 0.5), y = y, w = s, h = s, alpha = 0.5 }) - this.images.cursorDiamond:draw({ x = x - 90 - easing.outQuad(t, 0, -4, 0.5), y = y, w = s, h = s, alpha = 0.5 }) - - gfx.Restore() -end - --- main -------- - -local jacketCache = JacketCache.new() -local songData = SongData.new(jacketCache) -local songTable = SongTable.new(jacketCache) - -glowState = false -currentTime = 0 - --- Callback -get_page_size = function() - return 12 -end - -searchIndex = 1 -soffset = 0 -searchText = gfx.CreateLabel("", 5, 0) - -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("segoeui.ttf"); - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); - gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20) -end - --- Callback -function render(deltaTime) - ResetLayoutInformation() - - if ((math.floor(currentTime * 1000) % 100) < 50) then - glowState = false - else - glowState = true - end - - local xshift = (resx - desw * scale) / 2 - local yshift = (resy - desh * scale) / 2 - - gfx.Translate(xshift, yshift) - --gfx.Scale(scale, scale) - - songData:render(deltaTime) - songTable:render(deltaTime) - - --if totalForce then - --local forceText = string.format("%.2f", totalForce) - -- gfx.SetImageTint(255, 254, 2) - --bpmFont:draw(forceText, 140, 353, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE) - --end - - -- Draw the search status - if songwheel.searchStatus then - gfx.BeginPath() - gfx.LoadSkinFont("segoeui.ttf") - gfx.FillColor(255, 255, 255) - gfx.FontSize(20) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) - gfx.Text(songwheel.searchStatus, 3, desh) - end - - soffset = soffset * 0.8 - draw_search(120, 5, 600, 40) -end - --- Callback -set_index = function(newIndex) - songData:set_index(newIndex) - songTable:set_index(newIndex) -end - --- Callback -set_diff = function(newDiff) - songData:set_difficulty(newDiff) - songTable:set_difficulty(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 - --- callback -songs_changed = function(withAll) - if (not withAll) then return end - local diffsById = {} - 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) - diffsById[diff.id] = 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 - diffs[i].forceInTotal = true - end - end - - for i = 1, #songwheel.songs do - local song = songwheel.songs[i] - for j = 1, #song.difficulties do - local diff = song.difficulties[j] - local newDiff = diffsById[diff.id] - song.difficulties[j] = newDiff - end - end -end diff --git a/scripts/songselect/songwheel prelocal.lua b/scripts/songselect/songwheel prelocal.lua deleted file mode 100644 index 22f91d4..0000000 --- a/scripts/songselect/songwheel prelocal.lua +++ /dev/null @@ -1,894 +0,0 @@ ---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 foreground = gfx.CreateSkinImage("song_select/fg.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, 14, 0) - end - - if not songCache[song.id]["bpm"] then - songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("%s",song.bpm), 12, 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"] then - songCache[song.id]["jacket"] = { } - end - - for i = 1, #song.difficulties do - songCache[song.id]["jacket"][i] = gfx.LoadImageJob(song.difficulties[i].jacketPath, jacketFallback, 200, 200) - 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.BeginPath() - 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_BOTTOM + gfx.TEXT_ALIGN_LEFT); - gfx.DrawLabel(scoreLabel, x/11,y/1.48,w*2) - end -end - -draw_song = function(song, x, y, w, h, selected) - local diffIndex = math.min(selectedDiff, #song.difficulties) - local difficulty = song.difficulties[diffIndex] - 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 songCache[song.id]["jacket"][diffIndex] then - gfx.BeginPath() - gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id]["jacket"][diffIndex], 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 - 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.FontSize(28) - 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 - local diffSpacingOffset = (diffWidth*0.82)*(selectedDiff-1) - for i = math.max(selectedDiff - 3, 1), math.max(selectedDiff - 1,1) do - local diff = diffs[i] - local xpos = (x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))) - ((diffWidth*0.82)*(i-selectedDiff+1)) - if i ~= selectedDiff then - draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) - end - end - - --after selected - for i = math.min(selectedDiff + 3, diffCount), selectedDiff + 1,-1 do - local diff = diffs[i] - local xpos = (x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))) + ((diffWidth*0.82)*(i-1)) - 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) + (-0.8*diffWidth)) - draw_diff_icon(diff, (xpos*0.9)+diffSpacingOffset, y, diffWidth, diffHeight, true) - gfx.BeginPath() - gfx.FillColor(0,128,255) - gfx.Fill() - gfx.BeginPath() - gfx.Fill() - gfx.ResetScissor() - draw_cursor((x + (w/5.7))*(selectedDiff^1.085), 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)*1.58) - imageXPos = (x+w)/16+(xMargin*0.8) - 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*4.45+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/1.5,(ypos*10.3+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/2, y+yMargin*15+yPadding, width) - gfx.FontSize(40) - gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding/2, y+yMargin*15.8+yPadding, width) - gfx.FontSize(10) - gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding*2, y+yMargin*14.42+yPadding, width-imageSize) - gfx.FastText(string.format("%s", diff.effector), xpos+xPadding*7.5, y+yMargin*18.87+yPadding) - 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("%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 - gfx.BeginPath(); - gfx.TextAlign(TEXT_ALIGN_CENTER + TEXT_ALIGN_MIDDLE); - gfx.ImageRect(0, 0, resx, resy, foreground, 1, 0); - - --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/songwheel.lua b/scripts/songselect/songwheel.lua index 3d6da35..44116b1 100644 --- a/scripts/songselect/songwheel.lua +++ b/scripts/songselect/songwheel.lua @@ -1,13 +1,14 @@ -require('common') +local Charting = require('common.charting') local Easing = require('common.easing') local Background = require('components.background') -local common = require('common.common') -local Numbers = require('common.numbers') +local Dim = require("common.dimensions") +local Wallpaper = require("components.wallpaper") +local common = require('common.util') +local Sound = require("common.sound") +local Numbers = require('components.numbers') local VolforceCalc = require('components.volforceCalc') -local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY) - local dataPanelImage = gfx.CreateSkinImage("song_select/data_bg_overlay.png", 1) local dataGlowOverlayImage = gfx.CreateSkinImage("song_select/data_panel/data_glow_overlay.png", 1) local gradeBgImage = gfx.CreateSkinImage("song_select/data_panel/grade_bg.png", 1) @@ -26,13 +27,20 @@ local top50JacketOverlayImage = gfx.CreateSkinImage("song_select/top50_jacket.pn local diffCursorImage = gfx.CreateSkinImage("song_select/level_cursor.png", 1) +local scrollBarBackgroundImage = gfx.CreateSkinImage("song_select/scrollbar/bg.png", 1) +local scrollBarFillImage = gfx.CreateSkinImage("song_select/scrollbar/fill.png", 1) + local filterInfoBgImage = gfx.CreateSkinImage("song_select/filter_info_bg.png", 1) local sortInfoBgImage = gfx.CreateSkinImage("song_select/sort_info_bg.png", 1) local searchBgImage = gfx.CreateSkinImage("song_select/search_bg.png", 1) +local searchActiveImage = gfx.CreateSkinImage("song_select/search_active.png", 1) +local searchInfoPanelImage = gfx.CreateSkinImage("song_select/search_info_panel.png", 1) local defaultJacketImage = gfx.CreateSkinImage("song_select/loading.png", 0) + + local difficultyLabelImages = { gfx.CreateSkinImage("song_select/plate/difficulty_labels/novice.png", 1), gfx.CreateSkinImage("song_select/plate/difficulty_labels/advanced.png", 1), @@ -42,6 +50,7 @@ local difficultyLabelImages = { gfx.CreateSkinImage("song_select/plate/difficulty_labels/gravity.png", 1), gfx.CreateSkinImage("song_select/plate/difficulty_labels/heavenly.png", 1), gfx.CreateSkinImage("song_select/plate/difficulty_labels/vivid.png", 1), + gfx.CreateSkinImage("song_select/plate/difficulty_labels/exceed.png", 1), } local badgeImages = { @@ -95,10 +104,11 @@ local difficultyLabelUnderImages = { gfx.CreateSkinImage("songtransition/difficulty_labels/grv.png", 0), gfx.CreateSkinImage("songtransition/difficulty_labels/hvn.png", 0), gfx.CreateSkinImage("songtransition/difficulty_labels/vvd.png", 0), + gfx.CreateSkinImage("songtransition/difficulty_labels/xcd.png", 0), } -game.LoadSkinSample('song_wheel/cursor_change.wav'); -game.LoadSkinSample('song_wheel/diff_change.wav'); +game.LoadSkinSample('song_wheel/cursor_change.wav') +game.LoadSkinSample('song_wheel/diff_change.wav') local scoreNumbers = Numbers.load_number_image("score_num") local difficultyNumbers = Numbers.load_number_image("diff_num") @@ -110,49 +120,58 @@ local LEADERBOARD_PLACE_NAMES = { '4th', } -local songPlateHeight = 172; +local songPlateHeight = 172 -local selectedIndex = 1; -local selectedDifficulty = 1; +local selectedIndex = 1 +local selectedDifficulty = 1 local jacketCache = {} local top50diffs = {} -local irRequestStatus = 1; -- 0=unused, 1=not requested, 2=loading, others are status codes +local irRequestStatus = 1 -- 0=unused, 1=not requested, 2=loading, others are status codes local irRequestTimeout = 2 local irLeaderboard = {} local irLeaderboardsCache = {} -local transitionScrollScale = 0; -local transitionScrollOffsetY = 0; -local scrollingUp = false; +local transitionScrollScale = 0 +local transitionScrollOffsetY = 0 +local scrollingUp = false -local transitionAfterscrollScale = 0; -local transitionAfterscrollDataOverlayAlpha = 0; -local transitionAfterscrollGradeAlpha = 0; -local transitionAfterscrollBadgeAlpha = 0; -local transitionAfterscrollTextSongTitle = 0; -local transitionAfterscrollTextSongArtist = 0; -local transitionAfterscrollDifficultiesAlpha = 0; +local transitionAfterscrollScale = 0 +local transitionAfterscrollDataOverlayAlpha = 0 +local transitionAfterscrollGradeAlpha = 0 +local transitionAfterscrollBadgeAlpha = 0 +local transitionAfterscrollTextSongTitle = 0 +local transitionAfterscrollTextSongArtist = 0 +local transitionAfterscrollDifficultiesAlpha = 0 -local transitionJacketBgScrollScale = 0; -local transitionJacketBgScrollAlpha = 0; -local transitionJacketBgScrollPosX = 0; +local transitionJacketBgScrollScale = 0 +local transitionJacketBgScrollAlpha = 0 +local transitionJacketBgScrollPosX = 0 -local transitionLaserScale = 0; -local transitionLaserY = 0; +--search +local searchPreviousActiveState = false +local searchInfoPreviousActiveState = false +local transitionSearchEnterScale = 0 +local transitionSearchInfoEnterScale = 0 +local transitionSearchBackgroundAlpha = 0 +local transitionSearchbarOffsetY = 0 +local transitionSearchInfoOffsetY = 0 + +local transitionLaserScale = 0 +local transitionLaserY = 0 -- Flash transition (animation) -- Used for flashing the badges -- 0 = minimum brightness; 0.5 = maximum brightness; 1 = minimum brightness again -local transitionFlashScale = 0; -local transitionFlashAlpha = 1; +local transitionFlashScale = 0 +local transitionFlashAlpha = 1 -local isFilterWheelActive = false; -local transitionLeaveScale = 0; -local transitionLeaveReappearTimer = 0; -local TRANSITION_LEAVE_DURATION = 0.1; +local isFilterWheelActive = false +local transitionLeaveScale = 0 +local transitionLeaveReappearTimer = 0 +local TRANSITION_LEAVE_DURATION = 0.1 -- Window variables local resX, resY @@ -173,93 +192,93 @@ local resolutionChange = function(x, y) fullX = portraitWidescreenRatio * y fullY = y - game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR); + game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR) end function getCorrectedIndex(from, offset) - total = #songwheel.songs; + total = #songwheel.songs if (math.abs(offset) > total) then if (offset < 0) then - offset = offset + total*math.floor(math.abs(offset)/total); + offset = offset + total*math.floor(math.abs(offset)/total) else - offset = offset - total*math.floor(math.abs(offset)/total); + offset = offset - total*math.floor(math.abs(offset)/total) end end - index = from + offset; + index = from + offset if index < 1 then index = total + (from+offset) -- this only happens if the offset is negative - end; + end if index > total then - indexesUntilEnd = total - from; + indexesUntilEnd = total - from index = offset - indexesUntilEnd -- this only happens if the offset is positive - end; + end - return index; -end; + return index +end function getJacketImage(song) if not jacketCache[song.id] or jacketCache[song.id]==defaultJacketImage then jacketCache[song.id] = gfx.LoadImageJob(song.difficulties[ math.min(selectedDifficulty, #song.difficulties) - ].jacketPath, defaultJacketImage, 500, 500); + ].jacketPath, defaultJacketImage, 500, 500) end - return jacketCache[song.id]; + return jacketCache[song.id] end function getGradeImageForScore(score) - local gradeImage = gradeImages.none; - local bestGradeCutoff = 0; + local gradeImage = gradeImages.none + local bestGradeCutoff = 0 for gradeName, scoreCutoff in pairs(gradeCutoffs) do if scoreCutoff <= score then if scoreCutoff > bestGradeCutoff then - gradeImage = gradeImages[gradeName]; - bestGradeCutoff = scoreCutoff; + gradeImage = gradeImages[gradeName] + bestGradeCutoff = scoreCutoff end end end - return gradeImage; + return gradeImage end function drawLaserAnim() - gfx.Save(); + gfx.Save() gfx.BeginPath() - gfx.Scissor(0, transitionLaserY, desw, 100); + gfx.Scissor(0, transitionLaserY, desw, 100) gfx.ImageRect(0, 0, desw, desh, laserAnimBaseImage, 1, 0) - gfx.Restore(); + gfx.Restore() end function drawBackground(deltaTime) Background.draw(deltaTime) - local song = songwheel.songs[selectedIndex]; - local diff = song and song.difficulties[selectedDifficulty] or false; + local song = songwheel.songs[selectedIndex] + local diff = song and song.difficulties[selectedDifficulty] or false if (not isFilterWheelActive and transitionLeaveReappearTimer == 0) then -- If the score for song exists if song and diff then - local jacketImage = getJacketImage(song); + local jacketImage = getJacketImage(song) gfx.BeginPath() gfx.ImageRect(transitionJacketBgScrollPosX, 0, 900, 900, jacketImage or defaultJacketImage, transitionJacketBgScrollAlpha, 0) - gfx.BeginPath(); - gfx.FillColor(0,0,0,math.floor(transitionJacketBgScrollAlpha*64)); - gfx.Rect(0,0,900,900); - gfx.Fill(); - gfx.ClosePath(); + gfx.BeginPath() + gfx.FillColor(0,0,0,math.floor(transitionJacketBgScrollAlpha*64)) + gfx.Rect(0,0,900,900) + gfx.Fill() + gfx.ClosePath() end end - gfx.BeginPath(); + gfx.BeginPath() gfx.ImageRect(0, 0, desw, desh, dataPanelImage, 1, 0) drawLaserAnim() @@ -282,18 +301,18 @@ function drawBackground(deltaTime) end function drawSong(song, y) - if (not song) then return end; + if (not song) then return end local songX = desw/2+28 local selectedSongDifficulty = song.difficulties[math.min(selectedDifficulty, #song.difficulties)] -- Limit selecting difficulty that is above the number that the song has if not selectedSongDifficulty then - return; + return end - local bestScore; + local bestScore if selectedSongDifficulty.scores then - bestScore = selectedSongDifficulty.scores[1]; + bestScore = selectedSongDifficulty.scores[1] end -- Draw the bg for the song plate @@ -301,7 +320,7 @@ function drawSong(song, y) gfx.ImageRect(songX, y, 515, 172, songPlateBg, 1, 0) -- Draw jacket - local jacketImage = getJacketImage(song); + local jacketImage = getJacketImage(song) gfx.BeginPath() gfx.ImageRect(songX+4, y+4, 163, 163, jacketImage or defaultJacketImage, 1, 0) @@ -311,7 +330,7 @@ function drawSong(song, y) -- Draw the difficulty notch background gfx.BeginPath() - local diffIndex = GetDisplayDifficulty(selectedSongDifficulty.jacketPath, selectedSongDifficulty.difficulty) + local diffIndex = Charting.GetDisplayDifficulty(selectedSongDifficulty.jacketPath, selectedSongDifficulty.difficulty) gfx.ImageRect(songX, y+95, 83, 74, difficultyLabelImages[diffIndex], 1, 0) -- Draw the difficulty level number @@ -321,82 +340,82 @@ function drawSong(song, y) -- Draw song title gfx.FontSize(24) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.Text(song.title, songX+90, y+155); + gfx.Text(song.title, songX+90, y+155) -- Draw score badge - local badgeImage = badgeImages[1]; + local badgeImage = badgeImages[1] if selectedSongDifficulty.topBadge then - badgeImage = badgeImages[selectedSongDifficulty.topBadge+1]; + badgeImage = badgeImages[selectedSongDifficulty.topBadge+1] end - local badgeAlpha = 1; + local badgeAlpha = 1 if (selectedSongDifficulty.topBadge >= 3) then - badgeAlpha = transitionFlashAlpha; -- If hard clear or above, flash the badge + badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge end gfx.BeginPath() gfx.ImageRect(songX+282, y+44, 79, 69, badgeImage, badgeAlpha, 0) -- Draw grade - local gradeImage = gradeImages.none; - local gradeAlpha = 1; + local gradeImage = gradeImages.none + local gradeAlpha = 1 if bestScore then gradeImage = getGradeImageForScore(bestScore.score) if (bestScore.score >= gradeCutoffs.S) then - gradeAlpha = transitionFlashAlpha; -- If S, flash the badge + gradeAlpha = transitionFlashAlpha -- If S, flash the badge end end - gfx.BeginPath(); - gfx.ImageRect(songX+391, y+47, 60, 60, gradeImage, gradeAlpha, 0); + gfx.BeginPath() + gfx.ImageRect(songX+391, y+47, 60, 60, gradeImage, gradeAlpha, 0) -- Draw top 50 label if applicable if (top50diffs[selectedSongDifficulty.id]) then - gfx.BeginPath(); - gfx.ImageRect(songX+82, y+109, 506*0.85, 26*0.85, top50OverlayImage, 1, 0); + gfx.BeginPath() + gfx.ImageRect(songX+82, y+109, 506*0.85, 26*0.85, top50OverlayImage, 1, 0) end end function drawSongList() - gfx.GlobalAlpha(1-transitionLeaveScale); + gfx.GlobalAlpha(1-transitionLeaveScale) - local numOfSongsAround = 7; -- How many songs should be up and how many should be down of the selected one + local numOfSongsAround = 7 -- How many songs should be up and how many should be down of the selected one - local yOffset = transitionScrollOffsetY; + local yOffset = transitionScrollOffsetY - local i=1; + local i=1 while (i <= numOfSongsAround) do local songIndex = getCorrectedIndex(selectedIndex, -i) drawSong(songwheel.songs[songIndex], desh/2-songPlateHeight/2-songPlateHeight*i + yOffset) - i=i+1; - end; + i=i+1 + end -- Draw the selected song drawSong(songwheel.songs[selectedIndex], desh/2-songPlateHeight/2 + yOffset) - i=1; + i=1 while (i <= numOfSongsAround) do local songIndex = getCorrectedIndex(selectedIndex, i) drawSong(songwheel.songs[songIndex], desh/2-songPlateHeight/2+songPlateHeight*i + yOffset) - i=i+1; - end; + i=i+1 + end - gfx.GlobalAlpha(1); + gfx.GlobalAlpha(1) end function drawData() -- Draws the song data on the left panel - if isFilterWheelActive or transitionLeaveReappearTimer ~= 0 then return false end; + if isFilterWheelActive or transitionLeaveReappearTimer ~= 0 then return false end - local song = songwheel.songs[selectedIndex]; - local diff = song and song.difficulties[selectedDifficulty] or false; - local bestScore = diff and diff.scores[1]; + local song = songwheel.songs[selectedIndex] + local diff = song and song.difficulties[selectedDifficulty] or false + local bestScore = diff and diff.scores[1] if not song then return false end - local jacketImage = getJacketImage(song); + local jacketImage = getJacketImage(song) gfx.BeginPath() gfx.ImageRect(96, 324, 348, 348, jacketImage or defaultJacketImage, 1, 0) @@ -409,7 +428,7 @@ function drawData() -- Draws the song data on the left panel -- Draw best score gfx.BeginPath() - local scoreNumber = 0; + local scoreNumber = 0 if bestScore then scoreNumber = bestScore.score end @@ -418,25 +437,25 @@ function drawData() -- Draws the song data on the left panel Numbers.draw_number(253, 798, 1.0, scoreNumber, 4, scoreNumbers, true, 0.22, 1.12) -- Draw grade - local gradeImage = gradeImages.none; - local gradeAlpha = transitionAfterscrollGradeAlpha; + local gradeImage = gradeImages.none + local gradeAlpha = transitionAfterscrollGradeAlpha if bestScore then gradeImage = getGradeImageForScore(bestScore.score) if (transitionAfterscrollGradeAlpha == 1 and bestScore.score >= gradeCutoffs.S) then - gradeAlpha = transitionFlashAlpha; -- If S, flash the badge + gradeAlpha = transitionFlashAlpha -- If S, flash the badge end end - gfx.BeginPath(); - gfx.ImageRect(360, 773, 45, 45, gradeImage, gradeAlpha, 0); + gfx.BeginPath() + gfx.ImageRect(360, 773, 45, 45, gradeImage, gradeAlpha, 0) -- Draw badge - badgeImage = badgeImages[diff.topBadge+1]; + badgeImage = badgeImages[diff.topBadge+1] - local badgeAlpha = transitionAfterscrollBadgeAlpha; + local badgeAlpha = transitionAfterscrollBadgeAlpha if (transitionAfterscrollBadgeAlpha == 1 and diff.topBadge >= 3) then - badgeAlpha = transitionFlashAlpha; -- If hard clear or above, flash the badge, but only after the initial transition + badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge, but only after the initial transition end gfx.BeginPath() @@ -445,30 +464,30 @@ function drawData() -- Draws the song data on the left panel gfx.Restore() -- Draw BPM - gfx.BeginPath(); + gfx.BeginPath() gfx.FontSize(24) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) gfx.Save() gfx.LoadSkinFont('Digital-Serial-Bold.ttf') gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha) -- TODO: split this out - gfx.Text(song.bpm, 85, 920); + gfx.Text(song.bpm, 85, 920) gfx.Restore() -- Draw song title gfx.FontSize(28) - gfx.GlobalAlpha(transitionAfterscrollTextSongTitle); - gfx.Text(song.title, 30+(1-transitionAfterscrollTextSongTitle)*20, 955); + gfx.GlobalAlpha(transitionAfterscrollTextSongTitle) + gfx.Text(song.title, 30+(1-transitionAfterscrollTextSongTitle)*20, 955) -- Draw artist - gfx.GlobalAlpha(transitionAfterscrollTextSongArtist); - gfx.Text(song.artist, 30+(1-transitionAfterscrollTextSongArtist)*30, 997); + gfx.GlobalAlpha(transitionAfterscrollTextSongArtist) + gfx.Text(song.artist, 30+(1-transitionAfterscrollTextSongArtist)*30, 997) - gfx.GlobalAlpha(1); + gfx.GlobalAlpha(1) -- Draw difficulties local DIFF_X_START = 98.5 - local DIFF_GAP = 114.8; - gfx.GlobalAlpha(transitionAfterscrollDifficultiesAlpha); + local DIFF_GAP = 114.8 + gfx.GlobalAlpha(transitionAfterscrollDifficultiesAlpha) for i, diff in ipairs(song.difficulties) do gfx.BeginPath() @@ -481,15 +500,15 @@ function drawData() -- Draws the song data on the left panel Numbers.draw_number(85+(index-1)*DIFF_GAP, 1085, 1.0, diff.level, 2, difficultyNumbers, false, 0.8, 1) local diffLabelImage = difficultyLabelUnderImages[ - GetDisplayDifficulty(diff.jacketPath, diff.difficulty) - ]; + Charting.GetDisplayDifficulty(diff.jacketPath, diff.difficulty) + ] local tw, th = gfx.ImageSize(diffLabelImage) tw=tw*0.9 th=th*0.9 gfx.BeginPath() gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-tw/2, 1050, tw, th, diffLabelImage, 1, 0) end - gfx.GlobalAlpha(1); + gfx.GlobalAlpha(1) -- Scoreboard @@ -499,10 +518,10 @@ function drawData() -- Draws the song data on the left panel gfx.FontSize(22) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha); - gfx.Text(diff.effector, 270, 1180); -- effected by - gfx.Text(diff.illustrator, 270, 1210); -- illustrated by - gfx.GlobalAlpha(1); + gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha) + gfx.Text(diff.effector, 270, 1180) -- effected by + gfx.Text(diff.illustrator, 270, 1210) -- illustrated by + gfx.GlobalAlpha(1) end @@ -510,110 +529,110 @@ function drawLocalLeaderboard(diff) gfx.LoadSkinFont('Digital-Serial-Bold.ttf') gfx.FontSize(26) - local scoreBoardX = 75; - local scoreBoardY = 1250; + local scoreBoardX = 75 + local scoreBoardY = 1250 - local sbBarWidth = 336*1.2; - local sbBarHeight = 33; + local sbBarWidth = 336*1.2 + local sbBarHeight = 33 - local sbBarContentLeftX = scoreBoardX + 80; - local sbBarContentRightX = scoreBoardX + sbBarWidth/2 + 30; + local sbBarContentLeftX = scoreBoardX + 80 + local sbBarContentRightX = scoreBoardX + sbBarWidth/2 + 30 -- Draw the header - gfx.BeginPath(); - gfx.ImageRect(scoreBoardX, scoreBoardY, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0); + gfx.BeginPath() + gfx.ImageRect(scoreBoardX, scoreBoardY, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0) - gfx.BeginPath(); - gfx.ImageRect(205, 1252.5, 800*0.045, 600*0.045, crownImage, 1, 0); + gfx.BeginPath() + gfx.ImageRect(205, 1252.5, 800*0.045, 600*0.045, crownImage, 1, 0) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.BeginPath(); - gfx.Text("LOCAL TOP", sbBarContentRightX, scoreBoardY + sbBarHeight/2); + gfx.BeginPath() + gfx.Text("LOCAL TOP", sbBarContentRightX, scoreBoardY + sbBarHeight/2) for i = 1, 5, 1 do - gfx.BeginPath(); - gfx.ImageRect(scoreBoardX, scoreBoardY + i*sbBarHeight, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0); + gfx.BeginPath() + gfx.ImageRect(scoreBoardX, scoreBoardY + i*sbBarHeight, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.BeginPath(); - gfx.Text(game.GetSkinSetting("username"), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight); + gfx.BeginPath() + gfx.Text(game.GetSkinSetting("username"), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight) - gfx.BeginPath(); - gfx.Text((diff.scores[i]) and diff.scores[i].score or "- - - - - - - -", sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight); + gfx.BeginPath() + gfx.Text((diff.scores[i]) and diff.scores[i].score or "- - - - - - - -", sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight) end end function drawIrLeaderboard() if not IRData.Active then - return; + return end gfx.LoadSkinFont('Digital-Serial-Bold.ttf') gfx.FontSize(26) - local scoreBoardX = 75; - local scoreBoardY = 1500; + local scoreBoardX = 75 + local scoreBoardY = 1500 - local sbBarWidth = 336*1.2; - local sbBarHeight = 33; + local sbBarWidth = 336*1.2 + local sbBarHeight = 33 - local sbBarContentLeftX = scoreBoardX + 80; - local sbBarContentRightX = scoreBoardX + sbBarWidth/2 + 30; + local sbBarContentLeftX = scoreBoardX + 80 + local sbBarContentRightX = scoreBoardX + sbBarWidth/2 + 30 -- Draw the header - gfx.BeginPath(); - gfx.ImageRect(scoreBoardX, scoreBoardY, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0); + gfx.BeginPath() + gfx.ImageRect(scoreBoardX, scoreBoardY, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.BeginPath(); + gfx.BeginPath() if irRequestStatus == 1 or irRequestStatus == 2 then - gfx.Text("Loading ranking...", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2); - return; + gfx.Text("Loading ranking...", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2) + return end if irRequestStatus == IRData.States.ChartRefused then - gfx.Text("This chart is blacklisted", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2); - return; + gfx.Text("This chart is blacklisted", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2) + return end if irRequestStatus == IRData.States.NotFound then - gfx.Text("This chart is not tracked", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2); - return; + gfx.Text("This chart is not tracked", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2) + return end if #irLeaderboard == 0 then - gfx.Text("This chart has no scores", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2); - return; + gfx.Text("This chart has no scores", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2) + return end gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.BeginPath(); - gfx.Text("IR TOP", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2); + gfx.BeginPath() + gfx.Text("IR TOP", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2) for i = 1, 4, 1 do - gfx.BeginPath(); - gfx.ImageRect(scoreBoardX, scoreBoardY + i*sbBarHeight, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0); + gfx.BeginPath() + gfx.ImageRect(scoreBoardX, scoreBoardY + i*sbBarHeight, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0) end -- Becuase the scores are in "random order", we have to do this for index, irScore in ipairs(irLeaderboard) do - -- local irScore = irLeaderboard[i]; + -- local irScore = irLeaderboard[i] if irScore then - local rank = index; + local rank = index gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.BeginPath(); - gfx.Text(LEADERBOARD_PLACE_NAMES[rank], sbBarContentLeftX-40, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight); + gfx.BeginPath() + gfx.Text(LEADERBOARD_PLACE_NAMES[rank], sbBarContentLeftX-40, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.BeginPath(); - gfx.Text(string.upper(irScore.username), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight); + gfx.BeginPath() + gfx.Text(string.upper(irScore.username), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight) - gfx.BeginPath(); - gfx.Text(string.format("%d", irScore.score), sbBarContentRightX, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight); + gfx.BeginPath() + gfx.Text(string.format("%d", irScore.score), sbBarContentRightX, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight) - local badgeImage = badgeImages[irScore.lamp+1]; + local badgeImage = badgeImages[irScore.lamp+1] gfx.BeginPath() gfx.ImageRect(scoreBoardX + sbBarWidth - 50, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight - 12.5, 31.6, 27.6, badgeImage, 1, 0) end @@ -624,7 +643,7 @@ function drawFilterInfo(deltatime) gfx.LoadSkinFont('NotoSans-Regular.ttf') if (songwheel.searchInputActive) then - return; + --return end gfx.BeginPath() @@ -638,16 +657,16 @@ function drawFilterInfo(deltatime) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) gfx.BeginPath() - gfx.Text(folderLabel or '', 167, 131); + gfx.Text(folderLabel or '', 167, 131) gfx.BeginPath() - gfx.Text(subFolderLabel or '', 195, 166); + gfx.Text(subFolderLabel or '', 195, 166) gfx.BeginPath() gfx.ImageRect(desw - 310 - 5, 108, 310, 75, sortInfoBgImage, 1, 0) gfx.BeginPath() - gfx.Text(sortOptionLabel or '', desw-150, 130); + gfx.Text(sortOptionLabel or '', desw-150, 130) end function drawCursor() @@ -656,33 +675,127 @@ function drawCursor() gfx.BeginPath() local cursorImageIndex = game.GetSkinSetting('_gaugeType') - local cursorImage = cursorImages[cursorImageIndex or 1]; + local cursorImage = cursorImages[cursorImageIndex or 1] gfx.ImageRect(desw / 2 - 14, desh / 2 - 213 / 2, 555, 213, cursorImage, 1, 0) end function drawSearch() - if (not songwheel.searchInputActive) then - return; + if (not songwheel.searchInputActive and searchPreviousActiveState) then + searchPreviousActiveState = false + game.PlaySample('sort_wheel/enter.wav') + elseif (songwheel.searchInputActive and not searchPreviousActiveState) then + searchPreviousActiveState = true + game.PlaySample('sort_wheel/leave.wav') + end + + if (songwheel.searchText ~= '' and searchInfoPreviousActiveState == true) then + searchInfoPreviousActiveState = false + elseif (songwheel.searchText == '' and searchInfoPreviousActiveState == false) then + searchInfoPreviousActiveState = true end - - gfx.BeginPath(); + if (transitionSearchEnterScale == 0) then + return + end + + -- Draw dark overlay over Songwheel + gfx.BeginPath() + gfx.FillColor(0, 0, 0, math.floor(transitionSearchBackgroundAlpha * 192)) + gfx.Rect(0, 0, 1080, 1920) + gfx.Fill() + + -- Draw search info panel + gfx.BeginPath() + local infoResize = 0.855 + local sw, sh = gfx.ImageSize(searchInfoPanelImage) + sw = sw * infoResize + sh = sh * infoResize + local infoXPos = 0 + local infoYStartPos = desh - sh - 772 + 242 + local infoYPos = infoYStartPos + transitionSearchInfoOffsetY + + if (game.GetSkinSetting('gameplay_showSearchControls')) then + gfx.ImageRect(infoXPos, infoYPos, sw, sh, searchInfoPanelImage, transitionSearchBackgroundInfoAlpha, 0) + end + + -- Draw Search is Active text + gfx.BeginPath() + local activeResize = 0.855 + local activew, activeh = gfx.ImageSize(searchActiveImage) + activew = activew * activeResize + activeh = activeh * activeResize + local activeXPos = 0 + local activeYStartPos = desh - sh - 722 + + local activeYPos = activeYStartPos + transitionSearchInfoOffsetY + gfx.ImageRect(activeXPos, activeYPos, activew, activeh, searchActiveImage, 1, 0) + + -- Draw Searchbox + gfx.BeginPath() + local searchResize = 0.8 local tw, th = gfx.ImageSize(searchBgImage) - local xPos = desw-tw/2; - local yPos = 90; + tw = tw * searchResize + th = th * searchResize + local xPos = (desw-tw)/2 + local yStartPos = 170 - gfx.ImageRect(xPos, yPos, tw/2, th/2, searchBgImage, 1, 0) + local yPos = yStartPos - transitionSearchbarOffsetY - gfx.FontSize(32); + gfx.ImageRect(xPos, yPos, tw, th, searchBgImage, 1, 0) + + gfx.FontSize(48) gfx.LoadSkinFont('Digital-Serial-Bold.ttf') gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.Text(songwheel.searchText, xPos+100, yPos+52); + gfx.Text(songwheel.searchText, xPos + 160, yPos + 83.2) +end + +function drawScrollbar() + if isFilterWheelActive or transitionLeaveScale ~= 0 then return end + + -- Scrollbar Background + gfx.BeginPath() + local resize = 0.85 + local lw, lh = gfx.ImageSize(scrollBarBackgroundImage) + local lw = lw * resize + local lh = lh * resize + local xPos = desw-20 + local backgroundYPos = desh/2 - lh/2 + gfx.ImageRect(xPos, backgroundYPos, lw, lh, scrollBarBackgroundImage, 1, 0) + + -- Scrollbar Fill + gfx.BeginPath() + local sw, sh = gfx.ImageSize(scrollBarFillImage) + local sw = sw * resize + local sh = sh * resize + local fillXPos = xPos - 6 + + local minScrollYPos = backgroundYPos + local maxScrollYPos = backgroundYPos + lh - sh + local scrollStep = (maxScrollYPos - minScrollYPos) / (#songwheel.songs - 1) + local scrollbarYOffset = (selectedIndex - 1) * scrollStep + local scrollbarYPos = minScrollYPos + scrollbarYOffset + gfx.ImageRect(fillXPos, scrollbarYPos, sw, sh, scrollBarFillImage, 1, 0) + + -- 1st letter of song title on scroll + gfx.BeginPath() + gfx.FontSize(16) + gfx.LoadSkinFont('Digital-Serial-Bold.ttf') + gfx.Rect(fillXPos-18, scrollbarYPos - 5, 16, 16) + gfx.FillColor(0,0,0,170) + gfx.Fill() + gfx.FillColor(255,255,255) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER) + if (songwheel.songs[selectedIndex] ~= nil) then + local title = songwheel.songs[selectedIndex].title; + local letter = string.upper(common.firstAlphaNum(title)) + gfx.Text(letter, fillXPos-10, scrollbarYPos + 5) + end end function refreshIrLeaderboard(deltaTime) if not IRData.Active then - return; + return end if irRequestStatus ~= 1 then -- Only continue if the leaderboard is requesteded, but not loading or loaded. @@ -690,25 +803,25 @@ function refreshIrLeaderboard(deltaTime) end irLeaderboard = {} - local song = songwheel.songs[selectedIndex]; - local diff = song and song.difficulties[selectedDifficulty] or false; + local song = songwheel.songs[selectedIndex] + local diff = song and song.difficulties[selectedDifficulty] or false if (not diff) then - return; + return end if (irLeaderboardsCache[diff.hash]) then - irLeaderboard = irLeaderboardsCache[diff.hash]; - irRequestStatus = 20; - return; + irLeaderboard = irLeaderboardsCache[diff.hash] + irRequestStatus = 20 + return end if (irRequestTimeout > 0) then irRequestTimeout = irRequestTimeout - deltaTime - return; + return end - irRequestStatus = 2; -- Loading + irRequestStatus = 2 -- Loading -- onIrLeaderboardFetched({ -- statusCode = 20, -- body = {} @@ -730,15 +843,15 @@ function dump(o) end function onIrLeaderboardFetched(res) - irRequestStatus = res.statusCode; + irRequestStatus = res.statusCode - local song = songwheel.songs[selectedIndex]; - local diff = song and song.difficulties[selectedDifficulty] or false; + local song = songwheel.songs[selectedIndex] + local diff = song and song.difficulties[selectedDifficulty] or false game.Log(diff.hash, game.LOGGER_ERROR) if res.statusCode == IRData.States.Success then game.Log('Raw IR reposonse: ' .. dump(res.body), game.LOGGER_ERROR) - local tempIrLB = res.body; + local tempIrLB = res.body table.sort(tempIrLB, function (a,b) -- game.Log(a.score .. ' ?? ' .. b.score, game.LOGGER_ERROR) @@ -749,8 +862,8 @@ function onIrLeaderboardFetched(res) -- irLeaderboard[tempScore.ranking] = tempScore -- end - irLeaderboard = tempIrLB; - irLeaderboardsCache[diff.hash] = irLeaderboard; + irLeaderboard = tempIrLB + irLeaderboardsCache[diff.hash] = irLeaderboard game.Log(dump(irLeaderboard), game.LOGGER_ERROR) else @@ -771,48 +884,83 @@ function tickTransitions(deltaTime) transitionAfterscrollScale = transitionAfterscrollScale + deltaTime / 15 end else - transitionAfterscrollScale = 1; + transitionAfterscrollScale = 1 end if scrollingUp then - transitionScrollOffsetY = Easing.inQuad(1-transitionScrollScale) * songPlateHeight; + transitionScrollOffsetY = Easing.inQuad(1-transitionScrollScale) * songPlateHeight else - transitionScrollOffsetY = Easing.inQuad(1-transitionScrollScale) * -songPlateHeight; + transitionScrollOffsetY = Easing.inQuad(1-transitionScrollScale) * -songPlateHeight end if transitionAfterscrollScale < 0.02 then transitionAfterscrollDataOverlayAlpha = math.min(1, transitionAfterscrollScale / 0.02) else - transitionAfterscrollDataOverlayAlpha = 1; + transitionAfterscrollDataOverlayAlpha = 1 end + -- Searchbar offsets and alpha + if not searchPreviousActiveState then + if transitionSearchEnterScale > 0 then + transitionSearchEnterScale = transitionSearchEnterScale - deltaTime / 0.5 -- transition should last for that time in seconds + else + transitionSearchEnterScale = 0 + end + else + if transitionSearchEnterScale < 1 then + transitionSearchEnterScale = transitionSearchEnterScale + deltaTime / 0.5 -- transition should last for that time in seconds + else + transitionSearchEnterScale = 1 + end + end + + transitionSearchInfoOffsetY = Easing.inOutQuad(1 - transitionSearchEnterScale) * 1680 + transitionSearchbarOffsetY = Easing.inOutQuad(1 - transitionSearchEnterScale) * 300 + transitionSearchBackgroundAlpha = Easing.inOutQuad(transitionSearchEnterScale) + + if not searchInfoPreviousActiveState then + if transitionSearchInfoEnterScale > 0 then + transitionSearchInfoEnterScale = transitionSearchInfoEnterScale - deltaTime / 0.25 -- transition should last for that time in seconds + else + transitionSearchInfoEnterScale = 0 + end + else + if transitionSearchInfoEnterScale < 1 then + transitionSearchInfoEnterScale = transitionSearchInfoEnterScale + deltaTime / 0.25 -- transition should last for that time in seconds + else + transitionSearchInfoEnterScale = 1 + end + end + + transitionSearchBackgroundInfoAlpha = Easing.inOutQuad(transitionSearchInfoEnterScale) + -- Grade alpha if transitionAfterscrollScale >= 0.03 and transitionAfterscrollScale < 0.033 then - transitionAfterscrollGradeAlpha = 0.5; + transitionAfterscrollGradeAlpha = 0.5 elseif transitionAfterscrollScale >= 0.04 then - transitionAfterscrollGradeAlpha = 1; + transitionAfterscrollGradeAlpha = 1 else - transitionAfterscrollGradeAlpha = 0; + transitionAfterscrollGradeAlpha = 0 end -- Badge alpha if transitionAfterscrollScale >= 0.032 and transitionAfterscrollScale < 0.035 then - transitionAfterscrollBadgeAlpha = 0.5; + transitionAfterscrollBadgeAlpha = 0.5 elseif transitionAfterscrollScale >= 0.042 then - transitionAfterscrollBadgeAlpha = 1; + transitionAfterscrollBadgeAlpha = 1 else - transitionAfterscrollBadgeAlpha = 0; + transitionAfterscrollBadgeAlpha = 0 end -- Song title alpha and pos if transitionAfterscrollScale < 0.025 then - transitionAfterscrollTextSongTitle = Easing.outQuad(math.min(1, (transitionAfterscrollScale) / 0.025)); + transitionAfterscrollTextSongTitle = Easing.outQuad(math.min(1, (transitionAfterscrollScale) / 0.025)) else transitionAfterscrollTextSongTitle = 1 end -- Song artist alpha and pos if transitionAfterscrollScale < 0.025 then - transitionAfterscrollTextSongArtist = Easing.outQuad(math.min(1, (transitionAfterscrollScale) / 0.025)); + transitionAfterscrollTextSongArtist = Easing.outQuad(math.min(1, (transitionAfterscrollScale) / 0.025)) else transitionAfterscrollTextSongArtist = 1 end @@ -821,7 +969,7 @@ function tickTransitions(deltaTime) if transitionAfterscrollScale < 0.025 then transitionAfterscrollDifficultiesAlpha = math.min(1, transitionAfterscrollScale / 0.025) else - transitionAfterscrollDifficultiesAlpha = 1; + transitionAfterscrollDifficultiesAlpha = 1 end -- Jacket bg animation @@ -832,18 +980,18 @@ function tickTransitions(deltaTime) end if transitionJacketBgScrollScale < 0.05 or transitionJacketBgScrollScale >= 1 then - transitionJacketBgScrollAlpha = 0; + transitionJacketBgScrollAlpha = 0 elseif transitionJacketBgScrollScale >= 0.05 and transitionJacketBgScrollScale < 0.1 then - transitionJacketBgScrollAlpha = math.min(1, (transitionJacketBgScrollScale-0.05) / 0.05); + transitionJacketBgScrollAlpha = math.min(1, (transitionJacketBgScrollScale-0.05) / 0.05) elseif transitionJacketBgScrollScale >= 0.8 and transitionJacketBgScrollScale < 1 then transitionJacketBgScrollAlpha = math.max(0, math.min(1, 1-((transitionJacketBgScrollScale-0.8) / 0.05)) - ); + ) else - transitionJacketBgScrollAlpha = 1; + transitionJacketBgScrollAlpha = 1 end - transitionJacketBgScrollPosX = 0+(transitionJacketBgScrollScale*(0.8/1))*-300; + transitionJacketBgScrollPosX = 0+(transitionJacketBgScrollScale*(0.8/1))*-300 -- Laser anim if transitionLaserScale < 1 then @@ -852,18 +1000,18 @@ function tickTransitions(deltaTime) transitionLaserScale = 0 end - transitionLaserY = desh - math.min(transitionLaserScale * 2 * desh, desh); + transitionLaserY = desh - math.min(transitionLaserScale * 2 * desh, desh) -- Flash transition if transitionFlashScale < 1 then - local songBpm = 120; + local songBpm = 120 if (songwheel.songs[selectedIndex] and game.GetSkinSetting('animations_affectWithBPM')) then - songBpm = songwheel.songs[selectedIndex].bpm; + songBpm = songwheel.songs[selectedIndex].bpm -- Is a variable BPM if (type(songBpm) == "string") then - local s = split(songBpm, '-'); - songBpm = tonumber(s[1]); -- Lowest bpm value + local s = common.split(songBpm, '-') + songBpm = tonumber(s[1]) -- Lowest bpm value end end @@ -871,7 +1019,7 @@ function tickTransitions(deltaTime) -- since it cannot parse the number out of that string. Here we implement a fallback, to not crash -- USC on whacky charts. Whacky charters, quit using batshit insane bpm values. It makes me angery >:( if (songBpm == nil) then - songBpm = 120; + songBpm = 120 end transitionFlashScale = transitionFlashScale + deltaTime / (60/songBpm) -- transition should last for that time in seconds @@ -880,9 +1028,9 @@ function tickTransitions(deltaTime) end if transitionFlashScale < 0.5 then - transitionFlashAlpha = transitionFlashScale * 2; + transitionFlashAlpha = transitionFlashScale * 2 else - transitionFlashAlpha = 1-((transitionFlashScale-0.5) * 2); + transitionFlashAlpha = 1-((transitionFlashScale-0.5) * 2) end transitionFlashAlpha = 1+transitionFlashAlpha*0.5 @@ -893,79 +1041,69 @@ function tickTransitions(deltaTime) else transitionLeaveScale = 1 end - transitionLeaveReappearTimer = 1; - transitionAfterscrollScale = 0; -- Keep songwheel in the "afterscroll" state while the filterwheel is active - transitionJacketBgScrollScale = 0; -- Same thing here, just with the jacket bg + transitionLeaveReappearTimer = 1 + transitionAfterscrollScale = 0 -- Keep songwheel in the "afterscroll" state while the filterwheel is active + transitionJacketBgScrollScale = 0 -- Same thing here, just with the jacket bg else if (transitionLeaveReappearTimer ~= 0) then - transitionAfterscrollScale = 0; -- Keep songwheel in the "afterscroll" state while we're waiting on filter wheel to fade out - transitionJacketBgScrollScale = 0; -- Same thing here, just with the jacket bg + transitionAfterscrollScale = 0 -- Keep songwheel in the "afterscroll" state while we're waiting on filter wheel to fade out + transitionJacketBgScrollScale = 0 -- Same thing here, just with the jacket bg end transitionLeaveReappearTimer = transitionLeaveReappearTimer - deltaTime / (TRANSITION_LEAVE_DURATION + 0.05) -- 0.05s is a few frames between the completetion of the fade out and songs reappearing in the AC if (transitionLeaveReappearTimer <= 0) then - transitionLeaveScale = 0; - transitionLeaveReappearTimer = 0; + transitionLeaveScale = 0 + transitionLeaveReappearTimer = 0 end end end -draw_songwheel = function(x,y,w,h, deltaTime) - - gfx.Translate(x,y); - gfx.Scale(w/1080, h/1920); - gfx.Scissor(0,0,1080,1920); - - - drawBackground(deltaTime); +draw_songwheel = function(deltaTime) + drawBackground(deltaTime) drawSongList() - isFilterWheelActive = game.GetSkinSetting('_songWheelOverlayActive') == 1; + isFilterWheelActive = game.GetSkinSetting('_songWheelOverlayActive') == 1 drawData() drawCursor() drawFilterInfo(deltaTime) - drawSearch(); + drawSearch() - gfx.BeginPath(); + drawScrollbar() + + gfx.BeginPath() gfx.FontSize(18) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) local debugScrollingUp= "FALSE" - if scrollingUp then debugScrollingUp = "TRUE" end; + if scrollingUp then debugScrollingUp = "TRUE" end if game.GetSkinSetting('debug_showInformation') then - gfx.Text('S_I: ' .. selectedIndex .. ' // S_D: ' .. selectedDifficulty .. ' // S_UP: ' .. debugScrollingUp .. ' // AC_TS: ' .. transitionAfterscrollScale .. ' // L_TS: ' .. transitionLeaveScale .. ' // IR_CODE: ' .. irRequestStatus .. ' // IR_T: ' .. irRequestTimeout, 8, 8); + gfx.Text('S_I: ' .. selectedIndex .. ' // S_D: ' .. selectedDifficulty .. ' // S_UP: ' .. debugScrollingUp .. ' // AC_TS: ' .. transitionAfterscrollScale .. ' // L_TS: ' .. transitionLeaveScale .. ' // IR_CODE: ' .. irRequestStatus .. ' // IR_T: ' .. irRequestTimeout, 8, 8) end - gfx.ResetTransform(); + gfx.ResetTransform() end render = function (deltaTime) - tickTransitions(deltaTime); + tickTransitions(deltaTime) game.SetSkinSetting('_currentScreen', 'songwheel') - common.stopMusic(); + Sound.stopMusic() - -- detect resolution change - local resx, resy = game.GetResolution(); - if resx ~= resX or resy ~= resY then - resolutionChange(resx, resy) - end + Dim.updateResolution() - gfx.BeginPath() - 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() + Wallpaper.render() - draw_songwheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime); + Dim.transformToScreenSpace() - refreshIrLeaderboard(deltaTime); + draw_songwheel(deltaTime) + + refreshIrLeaderboard(deltaTime) end songs_changed = function (withAll) @@ -992,7 +1130,7 @@ songs_changed = function (withAll) totalForce = 0 for i = 1, 50 do if diffs[i] then - top50diffs[diffs[i].id] = true; + top50diffs[diffs[i].id] = true totalForce = totalForce + diffs[i].force end end @@ -1001,33 +1139,33 @@ songs_changed = function (withAll) end set_index = function(newIndex) - transitionScrollScale = 0; - transitionAfterscrollScale = 0; - transitionJacketBgScrollScale = 0; + transitionScrollScale = 0 + transitionAfterscrollScale = 0 + transitionJacketBgScrollScale = 0 game.SetSkinSetting('_songWheelScrollbarTotal', #songwheel.songs) game.SetSkinSetting('_songWheelScrollbarIndex', newIndex) - scrollingUp = false; + scrollingUp = false if ((newIndex > selectedIndex and not (newIndex == #songwheel.songs and selectedIndex == 1)) or (newIndex == 1 and selectedIndex == #songwheel.songs)) then - scrollingUp = true; - end; + scrollingUp = true + end - game.PlaySample('song_wheel/cursor_change.wav'); + game.PlaySample('song_wheel/cursor_change.wav') - selectedIndex = newIndex; -end; + selectedIndex = newIndex +end set_diff = function(newDiff) if newDiff ~= selectedDifficulty then - jacketCache = {}; -- Clear the jacket cache for the new diff jackets + jacketCache = {} -- Clear the jacket cache for the new diff jackets - game.PlaySample('song_wheel/diff_change.wav'); + game.PlaySample('song_wheel/diff_change.wav') end - selectedDifficulty = newDiff; + selectedDifficulty = newDiff irLeaderboard = {} - irRequestStatus = 1; + irRequestStatus = 1 irRequestTimeout = 2 -end; \ No newline at end of file +end diff --git a/scripts/songselect/songwheel.old.but.newer.lua b/scripts/songselect/songwheel.old.but.newer.lua deleted file mode 100644 index 22f91d4..0000000 --- a/scripts/songselect/songwheel.old.but.newer.lua +++ /dev/null @@ -1,894 +0,0 @@ ---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 foreground = gfx.CreateSkinImage("song_select/fg.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, 14, 0) - end - - if not songCache[song.id]["bpm"] then - songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("%s",song.bpm), 12, 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"] then - songCache[song.id]["jacket"] = { } - end - - for i = 1, #song.difficulties do - songCache[song.id]["jacket"][i] = gfx.LoadImageJob(song.difficulties[i].jacketPath, jacketFallback, 200, 200) - 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.BeginPath() - 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_BOTTOM + gfx.TEXT_ALIGN_LEFT); - gfx.DrawLabel(scoreLabel, x/11,y/1.48,w*2) - end -end - -draw_song = function(song, x, y, w, h, selected) - local diffIndex = math.min(selectedDiff, #song.difficulties) - local difficulty = song.difficulties[diffIndex] - 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 songCache[song.id]["jacket"][diffIndex] then - gfx.BeginPath() - gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id]["jacket"][diffIndex], 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 - 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.FontSize(28) - 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 - local diffSpacingOffset = (diffWidth*0.82)*(selectedDiff-1) - for i = math.max(selectedDiff - 3, 1), math.max(selectedDiff - 1,1) do - local diff = diffs[i] - local xpos = (x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))) - ((diffWidth*0.82)*(i-selectedDiff+1)) - if i ~= selectedDiff then - draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) - end - end - - --after selected - for i = math.min(selectedDiff + 3, diffCount), selectedDiff + 1,-1 do - local diff = diffs[i] - local xpos = (x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))) + ((diffWidth*0.82)*(i-1)) - 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) + (-0.8*diffWidth)) - draw_diff_icon(diff, (xpos*0.9)+diffSpacingOffset, y, diffWidth, diffHeight, true) - gfx.BeginPath() - gfx.FillColor(0,128,255) - gfx.Fill() - gfx.BeginPath() - gfx.Fill() - gfx.ResetScissor() - draw_cursor((x + (w/5.7))*(selectedDiff^1.085), 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)*1.58) - imageXPos = (x+w)/16+(xMargin*0.8) - 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*4.45+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/1.5,(ypos*10.3+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/2, y+yMargin*15+yPadding, width) - gfx.FontSize(40) - gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding/2, y+yMargin*15.8+yPadding, width) - gfx.FontSize(10) - gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding*2, y+yMargin*14.42+yPadding, width-imageSize) - gfx.FastText(string.format("%s", diff.effector), xpos+xPadding*7.5, y+yMargin*18.87+yPadding) - 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("%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 - gfx.BeginPath(); - gfx.TextAlign(TEXT_ALIGN_CENTER + TEXT_ALIGN_MIDDLE); - gfx.ImageRect(0, 0, resx, resy, foreground, 1, 0); - - --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/songwheel3.lua b/scripts/songselect/songwheel3.lua deleted file mode 100644 index f6beb3f..0000000 --- a/scripts/songselect/songwheel3.lua +++ /dev/null @@ -1,897 +0,0 @@ ---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/songwheel_before_rewrite.lua b/scripts/songselect/songwheel_before_rewrite.lua deleted file mode 100644 index 584639f..0000000 --- a/scripts/songselect/songwheel_before_rewrite.lua +++ /dev/null @@ -1,945 +0,0 @@ --- game.Log("Something went wrong!", game.LOGGER_ERROR) - ---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 difficultyNumbers = { - [0] = gfx.CreateSkinImage("diff_num/0.png", 0), - [1] = gfx.CreateSkinImage("diff_num/1.png", 0), - [2] = gfx.CreateSkinImage("diff_num/2.png", 0), - [3] = gfx.CreateSkinImage("diff_num/3.png", 0), - [4] = gfx.CreateSkinImage("diff_num/4.png", 0), - [5] = gfx.CreateSkinImage("diff_num/5.png", 0), - [6] = gfx.CreateSkinImage("diff_num/6.png", 0), - [7] = gfx.CreateSkinImage("diff_num/7.png", 0), - [8] = gfx.CreateSkinImage("diff_num/8.png", 0), - [9] = gfx.CreateSkinImage("diff_num/9.png", 0), -}; - -local difficultyNameOverlays = { - [0] = gfx.CreateSkinImage("song_select/level/novice.png", 0), - [1] = gfx.CreateSkinImage("song_select/level/advanced.png", 0), - [2] = gfx.CreateSkinImage("song_select/level/exhaust.png", 0), - [3] = gfx.CreateSkinImage("song_select/level/maximum.png", 0), - [4] = gfx.CreateSkinImage("song_select/level/maximum.png", 0), - [5] = gfx.CreateSkinImage("song_select/level/maximum.png", 0), - [6] = gfx.CreateSkinImage("song_select/level/maximum.png", 0), - [7] = gfx.CreateSkinImage("song_select/level/maximum.png", 0), -} - -local difficultyLevelCursor = gfx.CreateSkinImage("song_select/level_cursor.png", 0); - -local foreground = gfx.CreateSkinImage("song_select/fg.png", 0); - -local datapanel = gfx.CreateSkinImage("song_select/data_bg.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, 14, 0) - end - - if not songCache[song.id]["bpm"] then - songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("%s",song.bpm), 12, 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"] then - songCache[song.id]["jacket"] = { } - end - - for i = 1, #song.difficulties do - songCache[song.id]["jacket"][i] = gfx.LoadImageJob(song.difficulties[i].jacketPath, jacketFallback, 400, 400) - 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.BeginPath() - 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_BOTTOM + gfx.TEXT_ALIGN_LEFT); - gfx.DrawLabel(scoreLabel, x/11,y/1.48,w*2) - end -end - -function deep_to_string(t) - local tType = type(t); - if (tType ~= "table") then - return tostring(t); - end - - local result = "{"; - - for k, v in next, t do - local kType = type(k); - local vType = type(v); - - local keyString = deep_to_string(k); - local valueString = deep_to_string(v); - - if (#result > 1) then - result = result .. ";"; - end - - result = result .. keyString .. "=" .. valueString; - end - - return result .. "}"; -end - -draw_song = function(song, x, y, w, h, selected) - -- game.Log("draw_song", game.LOGGER_ERROR); - - local diffIndex = math.min(selectedDiff, #song.difficulties) - local difficulty = song.difficulties[diffIndex] - local clearLampR = 255 - local clearLampG = 255 - local clearLampB = 255 - local clearLampA = 100 - - if difficulty ~= nil then - -- game.Log(deep_to_string(difficulty), game.LOGGER_ERROR); - 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 - -- game.Log(" past difficulty check", game.LOGGER_ERROR); - - 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 songCache[song.id]["jacket"][diffIndex] then - gfx.BeginPath() - gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id]["jacket"][diffIndex], 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 = "MAXIMUM" - diff_short = "MXM" - 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) - - if (false) then - - local seldiff = nil - if song.difficulties[selectedDiff] ~= nil then - seldiff = selectedDiff - else - seldiff = selectedDiff - 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 -end - -draw_diff_icon = function(diff, x, y, w, h, selected) - local difficultyIndex = diff.difficulty; - - local image = difficultyNameOverlays[difficultyIndex]; - - local imgx, imgy = gfx.ImageSize(image); - local aspect = imgx / imgy; - - h = h * 98 / 112; - - local wa = h * aspect; - - gfx.BeginPath(); - gfx.ImageRect(x - wa / 2, y - h / 2, wa, h, image, 1, 0); - - local level = diff.level; - - local firstDigit = difficultyNumbers[math.max(0, math.floor(level / 10))]; - local secondDigit = difficultyNumbers[level % 10]; - - h = h * 0.475; - - imgx, imgy = gfx.ImageSize(firstDigit); - aspect = imgx / imgy; - - wa = h * aspect; - - gfx.BeginPath(); - gfx.ImageRect(x - wa, y - h / 2, wa, h, firstDigit, 1, 0); - - gfx.BeginPath(); - gfx.ImageRect(x, y - h / 2, wa, h, secondDigit, 1, 0); -end - -draw_cursor = function(x, y, h) - local imgx, imgy = gfx.ImageSize(difficultyLevelCursor); - local aspect = imgx / imgy; - - local w = h * aspect; - - gfx.BeginPath(); - gfx.ImageRect(x - w / 2, y - h / 2, w, h, difficultyLevelCursor, 1, 0); -end - -draw_diffs = function(diffs, x, y, w, h) - local diffWidth = w / 5 - local diffHeight = diffWidth - - for i = 1, #diffs do - local diff = diffs[i] - - local xPos = x + w * (i - 0.5) / 4; - local yPos = y + h / 2; - - if (i == selectedDiff) then - draw_cursor(xPos, yPos, diffHeight); - end - - draw_diff_icon(diff, xPos, yPos, diffWidth, diffHeight, i == selectedDiff); - end -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)*1.58) - imageXPos = (x+w)/16+(xMargin*0.8) - 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*4.45+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 - local diffPanelWidth = w * 0.4275; - local diffPanelHeight = diffPanelWidth / 4; - draw_diffs(song.difficulties, (w / 2 - diffPanelWidth) / 2, y + 0.5687583444592 * h - diffPanelHeight / 2, diffPanelWidth, diffPanelHeight) - 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/2, y+yMargin*15+yPadding, width) - gfx.FontSize(40) - gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding/2, y+yMargin*15.8+yPadding, width) - gfx.FontSize(10) - gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding*2, y+yMargin*14.42+yPadding, width-imageSize) - gfx.FastText(string.format("%s", diff.effector), xpos+xPadding*7.5, y+yMargin*18.87+yPadding) - 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("%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.0) - 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.0) - 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) - gfx.ResetTransform() - timer = (timer + deltaTime) - timer = timer % 2 - resx,resy = game.GetResolution(); - -- game.Log("res :: " .. resx .. "," .. resy, game.LOGGER_ERROR); - adjustScreen(resx,resy); - gfx.BeginPath(); - gfx.LoadSkinFont("dfmarugoth.ttf"); - gfx.FontSize(40); - gfx.FillColor(255,255,255); - gfx.ImageRect(0, 0, resx, resy, datapanel, 1, 0); - 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(0,0,fullX,fullY) - --render selected song information - draw_selected(song, 0,0,fullX,resy) - 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 - gfx.BeginPath(); - gfx.TextAlign(TEXT_ALIGN_CENTER + TEXT_ALIGN_MIDDLE); - gfx.ImageRect(0, 0, resx, resy, foreground, 1, 0); - - --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..f76dc7c 100644 --- a/scripts/songselect/sortwheel.lua +++ b/scripts/songselect/sortwheel.lua @@ -1,243 +1,242 @@ -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 +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..98087c3 100644 --- a/scripts/songtransition.lua +++ b/scripts/songtransition.lua @@ -1,214 +1,215 @@ - -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.util'); +local Sound = require("common.sound") +local Numbers = require('components.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 + Sound.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 deleted file mode 100644 index adcfbe4..0000000 --- a/scripts/titlescreen OLD.lua +++ /dev/null @@ -1,215 +0,0 @@ -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/scripts/titlescreen.lua b/scripts/titlescreen.lua index 1edc430..b50be37 100644 --- a/scripts/titlescreen.lua +++ b/scripts/titlescreen.lua @@ -1,452 +1,97 @@ +require("common.globals") +local Common = require("common.util") -require('common') -local Footer = require('components.footer'); -local Background = require('components.background'); +local bootScreen = require('titlescreen.boot') +local splashScreen = require('titlescreen.splash') +local titleScreen = require('titlescreen.title') +local modeSelectScreen = require('titlescreen.modeselect') +local serviceScreen = require('titlescreen.service') -local lang = require("language.call") - -local cursorIndex = 3; -local buttonHeight = 128 + 16; - -local SELECTOR_BAR_OFFSET_FROM_CENTER = 128; - -local BAR_ALPHA = 191; -local HEADER_HEIGHT = 100 - -local buttons = nil -local resx, resy = game.GetResolution() -local desw = 1080 -local desh = 1920 -local scale; - -local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY) -local headerTitleImage = gfx.CreateSkinImage('titlescreen/title.png', 0); -local selectorBgImage = gfx.CreateSkinImage('titlescreen/selector_bg.png', 0); -local selectorArrowsImage = gfx.CreateSkinImage( - 'titlescreen/selector_arrows.png', 0); - -local unselectedButtonImage = gfx.CreateSkinImage( - 'titlescreen/unselected_button.png', 0); - -local selectedButtonBgImage = gfx.CreateSkinImage( - 'titlescreen/selected_button_bg.png', 0); -local selectedButtonOverImage = gfx.CreateSkinImage( - 'titlescreen/selected_button_over.png', 0); - -local skillLabelImage = gfx.CreateSkinImage('titlescreen/labels/skill.png', 0); -local friendLabelImage = gfx.CreateSkinImage('titlescreen/labels/friend.png', 0); -local normalLabelImage = gfx.CreateSkinImage('titlescreen/labels/normal.png', 0); -local nauticaLabelImage = gfx.CreateSkinImage('titlescreen/labels/nautica.png', - 0); -local settingsLabelImage = gfx.CreateSkinImage( - 'titlescreen/labels/settings.png', 0); -local exitLabelImage = gfx.CreateSkinImage('titlescreen/labels/exit.png', 0); - -local creww = game.GetSkinSetting("single_idol") - --- ANIMS -local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..creww, 1 / 30, 0, true); - -if not idolAnimation then - game.Log("Crew folder crew/anim/"..creww.." does not exist.", game.LOGGER_WARNING) -end - --- AUDIO -game.LoadSkinSample('titlescreen/bgm.wav'); -game.LoadSkinSample('titlescreen/cursor_change.wav'); -game.LoadSkinSample('titlescreen/cursor_select.wav'); - -local selectorDescriptionLabel = gfx.CreateLabel(lang.Start.desc , 22, 0); - -local selectorLegendScrollLabel = gfx.CreateLabel(lang.Start.sc , 20, 0); -local selectorLegendSelectLabel = gfx.CreateLabel(lang.Start.st3 , 20, 0); - -local scrollTransitionScale = 1; -- Goes from 0 to 1 when transition is happening, sits at 1 when it's not. -local buttonsMovementScale = 0; -- Basically same as `scrollTransitionScale` but with a +/- sign for the scroll direction and goes from 1 to 0 - -local idolAnimTransitionScale = 0; - -local oldCursorIndex = 3; -local scrollingUp = false; -local playedBgm = false; - --- 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 resolutionChange = function(x, y) - resX = x - resY = y - fullX = portraitWidescreenRatio * y - fullY = y -end - -function resetLayoutInformation() - resx, resy = game.GetResolution() - desw = 1080 - desh = 1920 - scale = resx / desw -end - -draw_button = function(button, x, y, selected, index) - local labelImage = button[1]; - local labelWidth = button[2]; - local descriptionText = button[4]; - - if (selected) then - -- Draw button background - gfx.BeginPath(); - gfx.ImageRect(x, y + (196 / 2 * (1 - scrollTransitionScale)), 505, - 196 * scrollTransitionScale, selectedButtonBgImage, 1, 0); - -- Draw button main label - gfx.BeginPath(); - gfx.ImageRect(x + 256 - (labelWidth / 2), - (y + 58) + (64 / 2 * (1 - scrollTransitionScale)), - labelWidth, 64 * scrollTransitionScale, labelImage, 1, 0); - - -- Draw description - - gfx.GlobalAlpha((scrollTransitionScale - 0.8) * 5) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FontSize(40); - gfx.BeginPath(); - gfx.Text(descriptionText, x + 256, y + 28); - gfx.GlobalAlpha(1) - - -- Draw the glow overlay - gfx.BeginPath(); - gfx.ImageRect(x + 2, (y - 42) + (277 / 2 * (1 - scrollTransitionScale)), - 501, 277 * scrollTransitionScale, selectedButtonOverImage, - 1, 0); - else - if scrollingUp then - if (index == 3 or index == 0) then - gfx.GlobalAlpha(1 - scrollTransitionScale); - end - if (index == 2 or index == 5) then - gfx.GlobalAlpha(scrollTransitionScale); - end - else - if (index == 3 or index == 6) then - gfx.GlobalAlpha(1 - scrollTransitionScale); - end - if (index == 1 or index == 4) then - gfx.GlobalAlpha(scrollTransitionScale); - end - end - -- Draw button background - gfx.BeginPath(); - gfx.ImageRect(x, y + buttonsMovementScale * buttonHeight, 1026 / 2, - 257 / 2, unselectedButtonImage, 1, 0); - - -- Draw button main label - gfx.BeginPath(); - gfx.ImageRect(x + 64, y + 28 + buttonsMovementScale * buttonHeight, - labelWidth, 64, labelImage, 1, 0); - -- Draw description - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) - gfx.FontSize(28); - gfx.BeginPath(); - gfx.Text(descriptionText, x + 64, - y + 18 + buttonsMovementScale * buttonHeight); - - gfx.GlobalAlpha(1) - end -end; - -draw_buttons = function() - indexes = { - getCorrectedButtonIndex(cursorIndex, -2), - getCorrectedButtonIndex(cursorIndex, -1), cursorIndex, - getCorrectedButtonIndex(cursorIndex, 1), - getCorrectedButtonIndex(cursorIndex, 2) +local screens = { + boot = { + screen = bootScreen + }, + splash = { + screen = splashScreen + }, + title = { + screen = titleScreen + }, + mode_select = { + screen = modeSelectScreen + }, + service = { + screen = serviceScreen } +} - local yBase = desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER; +local currentScreen = game.GetSkinSetting("animations_skipIntro") and screens.title or screens.boot -- show boot screen if skipIntro is not set - centerButtonY = yBase - buttonHeight / 2 - 28; -- to fit with the selector bg - marginFromDesHCenter = 128; - - if scrollingUp then - draw_button(buttons[indexes[5]], desw - 512, - yBase - marginFromDesHCenter - buttonHeight * 3, false, 0); -- Placeholder for fadeout transition - end - - draw_button(buttons[indexes[1]], desw - 512, - yBase - marginFromDesHCenter - buttonHeight * 2, false, 1); - draw_button(buttons[indexes[2]], desw - 512, - yBase - marginFromDesHCenter - buttonHeight, false, 2); - - draw_button(buttons[indexes[3]], desw - 512, centerButtonY, true); -- The main selected center button - - if scrollingUp then - draw_button(buttons[indexes[3]], desw - 512, - yBase + marginFromDesHCenter - buttonHeight, false, 3); -- Placeholder for transition that goes to the bottom - else - draw_button(buttons[indexes[3]], desw - 512, centerButtonY, false, 3); -- Placeholder for transition that goes to the top - end - - draw_button(buttons[indexes[4]], desw - 512, - yBase + marginFromDesHCenter + 10, false, 4); - draw_button(buttons[indexes[5]], desw - 512, - yBase + marginFromDesHCenter + buttonHeight + 10, false, 5); - - if not scrollingUp then - draw_button(buttons[indexes[1]], desw - 512, - yBase + marginFromDesHCenter + buttonHeight * 2, false, 6); - end -end; - -function getCorrectedButtonIndex(from, offset) - buttonsNum = #buttons; - - index = from + offset; - - if index < 1 then - index = buttonsNum + (from + offset) -- this only happens if the offset is negative - end - - if index > buttonsNum then - indexesUntilEnd = buttonsNum - from; - index = offset - indexesUntilEnd -- this only happens if the offset is positive - end - - return index; -end - -function drawTexts() - - currentFullDescriptionText = buttons[cursorIndex][5]; - gfx.BeginPath(); - gfx.UpdateLabel(selectorDescriptionLabel, currentFullDescriptionText, 22) - - gfx.BeginPath(); --- gfx.UpdateLabel(selectorLegendScrollLabel, 'SCROLL', 20); - - -- descriptionAlpha = math.abs(selectedButtonScaleY - 0.5) * 2; - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); - - -- Description - gfx.FillColor(255, 255, 255, math.floor(scrollTransitionScale * 255)); - gfx.BeginPath(); - gfx.DrawLabel(selectorDescriptionLabel, 64, - desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER - 52); - - -- Legend on the selector - gfx.FillColor(217, 177, 126); - - gfx.BeginPath(); - gfx.DrawLabel(selectorLegendScrollLabel, 118, - desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER + 56); - - gfx.BeginPath(); - gfx.DrawLabel(selectorLegendSelectLabel, 360, - desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER + 56); - - gfx.FillColor(255, 255, 255); -end - -function setButtons() - if buttons == nil then - buttons = {} - buttons[1] = { - skillLabelImage, 412, Menu.Challenges, - lang.Challanges.ch, lang.Challanges.ch1 - } - buttons[2] = { - friendLabelImage, 169, Menu.Multiplayer, - lang.Multiplayer.mp, lang.Multiplayer.mp2 - } - buttons[3] = { - normalLabelImage, 210, Menu.Start, - lang.Start.st, lang.Start.st2 - } - buttons[4] = { - nauticaLabelImage, 230, Menu.DLScreen, - lang.Nautica.dls, lang.Nautica.dls2 - } - buttons[5] = { - settingsLabelImage, 247, Menu.Settings, - lang.Settings.se, lang.Settings.se1 - } - buttons[6] = { - exitLabelImage, 110, Menu.Exit, - lang.Exit.ex, lang.Exit.ex2 - } - end -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 - 200, HEADER_HEIGHT/2 - 20, 400, 40, headerTitleImage, 1, 0) -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) +local function deltaKnob(delta) if math.abs(delta) > 1.5 * math.pi then - return delta + 2 * math.pi * sign(delta) * -1 + return delta + 2 * math.pi * Common.sign(delta) * -1 end return delta end local lastKnobs = nil local knobProgress = 0 -function handle_controller() +local function handleKnobs() + if not currentScreen.screen.onKnobsChange then + return + end + 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 + 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 - - scrollTransitionScale = 0; -- Reset transitions and play them - - scrollingUp = false; - if ((cursorIndex > oldCursorIndex and - not (cursorIndex == 6 and oldCursorIndex == 1)) or - (cursorIndex == 1 and oldCursorIndex == 6)) then - scrollingUp = true; + if (knobProgress < 0) then + -- Negative + currentScreen.screen.onKnobsChange(-1) + else + -- Positive + currentScreen.screen.onKnobsChange(1) end - - game.PlaySample('titlescreen/cursor_change.wav'); - - oldCursorIndex = cursorIndex; - - knobProgress = knobProgress - roundToZero(knobProgress) + knobProgress = knobProgress - Common.roundToZero(knobProgress) end end end -draw_titlescreen = function (x, y, w, h, deltaTime) - gfx.Scissor(x,y,w,h); - gfx.Translate(x,y); - gfx.Scale(w/1080, h/1920); - - gfx.LoadSkinFont("segoeui.ttf") - - -- Draw background - gfx.BeginPath(); - Background.draw(deltaTime) - - if idolAnimation then - local idolAnimTickRes = gfx.TickAnimation(idolAnimation, deltaTime); - if idolAnimTickRes == 1 then - gfx.GlobalAlpha(idolAnimTransitionScale); - - idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60; - if (idolAnimTransitionScale > 1) then - idolAnimTransitionScale = 1; - end - - gfx.BeginPath(); - gfx.ImageRect(0, 0, desw, desh, idolAnimation, 1, 0); - gfx.GlobalAlpha(1); +local function handleScreenResponse(res) + if res and res.eventType == 'switch' then + if not screens[res.toScreen] then + game.Log('Undefined screen ' .. res.toScreen, game.LOGGER_ERROR) + return + end + currentScreen = screens[res.toScreen] + if currentScreen.screen.reset then + currentScreen.screen.reset() end end - - -- Draw selector background - gfx.BeginPath(); - gfx.ImageRect(0, (desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER) - 280 / 2, - 1079, 280, selectorBgImage, 1, 0); - - setButtons() - - buttonY = (desh / 2) - 2 * (257 + 5); - - draw_buttons(); - drawTexts(); - - -- Draw the arrows around the selected button - gfx.BeginPath(); - gfx.ImageRect(desw - 512, desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER - - buttonHeight - 8, 501, 300, selectorArrowsImage, 1, 0); - - -- Draw top and bottom bars - drawHeader(); - Footer.draw(deltaTime); - - gfx.ResetTransform(); end -render = function(deltaTime) - if not playedBgm then - game.PlaySample('titlescreen/bgm.wav', true); - playedBgm = true; +function render(deltaTime) + handleKnobs() + + handleScreenResponse(currentScreen.screen.render(deltaTime)) +end + +function mouse_pressed(button) + if (currentScreen.screen.onMousePressed) then + currentScreen.screen.onMousePressed(button) end - - game.SetSkinSetting('_currentScreen', 'title') - - -- 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() - - draw_titlescreen((resX - fullX) / 2, 0, fullX, fullY, deltaTime); - - handle_controller() - - scrollTransitionScale = scrollTransitionScale + 1 / 60 * 5; - if (scrollTransitionScale > 1) then scrollTransitionScale = 1; end - - if scrollingUp then - buttonsMovementScale = 1 - scrollTransitionScale - else - buttonsMovementScale = -1 + scrollTransitionScale - end - - gfx.BeginPath(); -end; - -mouse_pressed = function(button) return 0 end + return 0 +end function button_pressed(button) - if button == game.BUTTON_STA then - game.PlaySample('titlescreen/cursor_select.wav'); - game.StopSample('titlescreen/bgm.wav'); - buttons[cursorIndex][3]() - elseif button == game.BUTTON_BCK then - Menu.Exit() + if (currentScreen.screen.onButtonPressed) then + currentScreen.screen.onButtonPressed(button) end end - --- the thing is... titlescreen script does not have a call to reset function... WHYYYYY -function reset() playedBgm = false; end diff --git a/scripts/titlescreen/boot.lua b/scripts/titlescreen/boot.lua new file mode 100644 index 0000000..ce94260 --- /dev/null +++ b/scripts/titlescreen/boot.lua @@ -0,0 +1,28 @@ +local Dim = require("common.dimensions") +local Wallpaper = require("components.wallpaper") +local BootPage = require("titlescreen.pages.boot.bootpage") +local PageView = require("components.pager.pageview") + +local bootpage = BootPage.new() +local pageview = PageView.new(bootpage) + +local function render(deltaTime) + Dim.updateResolution() + + Wallpaper.render() + + Dim.transformToScreenSpace() + + pageview:render(deltaTime) + + --pageview will be empty when you `back()` out of the root page + if not pageview:get() then + return {eventType = "switch", toScreen = "splash"} + end +end + +local function onButtonPressed(button) + pageview:get():handleButtonInput(button) +end + +return {render = render, onButtonPressed = onButtonPressed} \ No newline at end of file diff --git a/scripts/titlescreen/fields/boot/checkupdatefield.lua b/scripts/titlescreen/fields/boot/checkupdatefield.lua new file mode 100644 index 0000000..13b84e1 --- /dev/null +++ b/scripts/titlescreen/fields/boot/checkupdatefield.lua @@ -0,0 +1,71 @@ +require("common.class") +local Util = require("common.util") +local ServiceField = require("titlescreen.fields.service.servicefield") + +---@class CheckUpdateField: ServiceField +---@field onUpdateAvailable nil|fun(url: string, version: string) +---@field _timer number +local CheckUpdateField = { + __tostring = function() return "CheckUpdateField" end, + PROGRESS_FREQ = 1 / 5, -- 5Hz + CHECK_UPDATE_TIMEOUT = 5, -- seconds +} + +---Create a new CheckUpdateField instance +---@param o? table # initial parameters +---@return CheckUpdateField +function CheckUpdateField.new(o) + o = o or {} + + o._timer = o._timer or 0 + o.onUpdateAvailable = o.onUpdateAvailable or nil + + local this = CreateInstance(CheckUpdateField, o, ServiceField) + + this._url = nil + this._version = nil + this._onUpdateAvailableFired = false + + return this +end + +function CheckUpdateField:drawLabel(deltaTime) + local text = self.label + local progress = math.ceil(Util.lerp(self._timer % self.PROGRESS_FREQ, + 0, 0, self.PROGRESS_FREQ, 4 + )) + text = text .. string.rep(".", progress) + + gfx.FontSize(self.FONT_SIZE) + gfx.LoadSkinFont(self.FONT_FACE) + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT | gfx.TEXT_ALIGN_TOP) + gfx.FillColor(table.unpack(self.FONT_COLOR)) + gfx.Text(text, 0, 0) +end + +function CheckUpdateField:drawValue(deltaTime) + +end + +function CheckUpdateField:tick(deltaTime) + if not self._onUpdateAvailableFired then + if self._timer > self.CHECK_UPDATE_TIMEOUT then + self._url, self._version = game.UpdateAvailable() + -- self._url = "" -- debug code to force onUpdateAvailable() + if self._url then + self.onUpdateAvailable(self._url, self._version) + self._onUpdateAvailableFired = true + else + self:getParentPage().viewHandler:clear() -- Exit out of bootscreen + end + end + end + self._timer = self._timer + deltaTime +end + +function CheckUpdateField:render(deltaTime) + self:tick(deltaTime) + ServiceField.render(self, deltaTime) +end + +return CheckUpdateField diff --git a/scripts/titlescreen/fields/boot/dialogfield.lua b/scripts/titlescreen/fields/boot/dialogfield.lua new file mode 100644 index 0000000..6d1bc99 --- /dev/null +++ b/scripts/titlescreen/fields/boot/dialogfield.lua @@ -0,0 +1,149 @@ +require("common.class") +local ContainerField = require("components.pager.containerfield") + +---@class DialogField: ContainerField +---@field _symbolMargin number +---@field _symbolSize number +local DialogField = { + __tostring = function() return "ContainerField" end, + BGCOLOR = {0, 0, 0, 255}, --{r, g, b, a} + DEFAULT_WIDTH = 400, + DEFAULT_HEIGHT = 200, + FONT_SIZE = 16, + FONT_FACE = "dfmarugoth.ttf", + FONT_COLOR = {255, 255, 255, 255}, + BORDERCOLOR = {255, 255, 255, 255}, + BORDERRADII = 12, + BORDERWIDTH = 2, + HEADER = { + title = "Title", + code = "0-0000-0000" + }, + TEXT = { + "Top text,", + "Sample text,", + "Bottom text." + }, + LEGEND = { + { + label = "BUTTON", + text = "DESCRIPTION" + }, + }, +} + +---Create a new DialogField instance +--- +---Inherits from ContainerField +---@param o ContainerField +---@return DialogField +function DialogField.new(o) + o = o or {} + + o.aabbW = o.aabbW or DialogField.DEFAULT_WIDTH + o.aabbH = o.aabbH or DialogField.DEFAULT_HEIGHT + + local this = CreateInstance(DialogField, o, ContainerField) + + this._symbolMargin = 8 + this._symbolSize = 48 + + return this +end + +---Draw the dialog symbol +--- +---Default implementation is a yellow triangle with an exclamation mark +---@param deltaTime number # frametime in seconds +function DialogField:drawSymbol(deltaTime) + local symbolColor = {255, 255, 0, 255} + gfx.Save() + gfx.Translate(self._symbolMargin, self._symbolMargin) + gfx.FillColor(table.unpack(symbolColor)) + gfx.BeginPath() + local symbolBottomY = math.sqrt(3) / 2 * self._symbolSize + gfx.MoveTo(0, symbolBottomY) + gfx.LineTo(self._symbolSize / 2, 0) + gfx.LineTo(self._symbolSize, symbolBottomY) + gfx.Fill() + -- exclamation mark + local excTopMargin = 10 + local excBottomMargin = 4 + local excThickness = 5 + local excColor = {0, 0, 0, 255} + gfx.FillColor(table.unpack(excColor)) + gfx.BeginPath() + gfx.Rect( + self._symbolSize / 2 - excThickness / 2, -- x + excTopMargin, -- y + excThickness, -- w + symbolBottomY - excTopMargin - excBottomMargin - 3 / 2 * excThickness -- h + ) + gfx.Rect( + self._symbolSize / 2 - excThickness / 2, -- x + symbolBottomY - excBottomMargin - excThickness, -- y + excThickness, excThickness -- w, h + ) + gfx.Fill() + gfx.Restore() +end + +---@param deltaTime number # frametime in seconds +function DialogField:drawBackground(deltaTime) + local textMargin = 4 + -- border + local borderH = self.aabbH - #self.LEGEND * self.FONT_SIZE - textMargin + gfx.BeginPath() + gfx.StrokeColor(table.unpack(self.BORDERCOLOR)) + gfx.StrokeWidth(self.BORDERWIDTH) + gfx.FillColor(table.unpack(self.BGCOLOR)) + gfx.RoundedRect(0, 0, self.aabbW, borderH, self.BORDERRADII) + gfx.Fill() + gfx.Stroke() + + gfx.FontSize(self.FONT_SIZE) + gfx.LoadSkinFont(self.FONT_FACE) + + -- draw symbol + self:drawSymbol(deltaTime) + + -- legend + local legendX = 0 + local legendY = borderH + textMargin + gfx.TextAlign(gfx.TEXT_ALIGN_TOP | gfx.TEXT_ALIGN_LEFT) + gfx.FillColor(table.unpack(self.FONT_COLOR)) + for _, legend in ipairs(self.LEGEND) do + gfx.Text(legend.label .. " = " .. legend.text, legendX, legendY) + legendY = legendY + self.FONT_SIZE + end + + -- header + local headerX = self._symbolSize + self._symbolMargin + 16 + local headerY = self._symbolMargin + gfx.Save() + gfx.Translate(headerX, headerY) + gfx.Text(self.HEADER.title, 0, 0) + local separatorY = self.FONT_SIZE + textMargin + local separatorThickness = 1 + gfx.StrokeWidth(separatorThickness) + gfx.BeginPath() + gfx.MoveTo(0, separatorY) + gfx.LineTo(self.aabbW - headerX - self._symbolMargin, separatorY) + gfx.Stroke() + local codeY = separatorY + textMargin + gfx.Text(self.HEADER.code, 0, codeY) + gfx.Restore() +end + +---@param deltaTime number # frametime in seconds +function DialogField:drawForeground(deltaTime) + local textX = 12 + local textY = 64 + local lineHeight = self.FONT_SIZE + 4 + for _, line in ipairs(self.TEXT) do + gfx.Text(line, textX, textY) + textY = textY + lineHeight + end +end + +return DialogField diff --git a/scripts/titlescreen/fields/boot/selftestfield.lua b/scripts/titlescreen/fields/boot/selftestfield.lua new file mode 100644 index 0000000..3c955d1 --- /dev/null +++ b/scripts/titlescreen/fields/boot/selftestfield.lua @@ -0,0 +1,134 @@ +require("common.class") +local Util = require("common.util") +local ServiceField = require("titlescreen.fields.service.servicefield") + +---@class SelfTestStatusEnum +SelfTestStatusEnum = { + IDLE = 1, + INPROGRESS = 2, + OK = 3, + PASS = 4, + ERROR = 5 +} + +local function statusToString(status) + local statusName = {"IDLE", "INPROGRESS", "OK", "PASS", "ERROR"} + return statusName[status] +end + +---@class SelfTestField: ServiceField +---@field checkTask nil|fun(obj: any): SelfTestStatusEnum # a function that will run asynchronously on activating the Field +---@field status SelfTestStatusEnum +---@field onStatusChange nil|fun(status) # a callback function on finishing the checkTask +---@field _thread thread +---@field _timer number +local SelfTestField = { + __tostring = function () return "SelfTestField" end, + COLOR_INPROGRESS = {255, 255, 255, 255}, + COLOR_OK = {0, 255, 0, 255}, + COLOR_PASS = {255, 255, 0, 255}, + COLOR_ERROR = {255, 0, 0, 255}, + INPROGRESS_FREQ = 1 / 20, --20Hz +} + +---Create a new SelfTestField instance +---@param o? table +---@return SelfTestField +function SelfTestField.new(o) + o = o or {} + + o.status = o.status or SelfTestStatusEnum.IDLE + o._timer = 0 + o._thread = nil + + assert((not o.onStatusChange) or (o.checkTask and o.onStatusChange), + "Failed to construct SelfTestField, checkTask is mandatory when onStatusChange is defined!\n" .. debug.traceback() + ) + + return CreateInstance(SelfTestField, o, ServiceField) +end + +function SelfTestField:_closeThread() + if self._thread and coroutine.status(self._thread) ~= "dead" then + coroutine.close(self._thread) + end +end + +function SelfTestField:_resumeThread() + if self._thread and coroutine.status(self._thread) == "suspended" then + local success, status = coroutine.resume(self._thread) + game.Log(self.label .. ": success: " .. tostring(success) .. + ", status: " .. status .. " (" .. statusToString(status) .. ")", + game.LOGGER_DEBUG + ) + if success and status ~= self.status then + self.status = status + if self.onStatusChange then + game.Log("SKIN CONFIG: onStatusChange(" .. status .. ") (" .. + statusToString(status) .. ")", + game.LOGGER_DEBUG + ) + self.onStatusChange(status) + end + end + end +end + +function SelfTestField:activate(obj) + self:_closeThread() + + if self.checkTask then + self._thread = coroutine.create(self.checkTask) + self:_resumeThread() + end +end + +function SelfTestField:deactivate(obj) + self:_closeThread() +end + +function SelfTestField:tick(deltaTime) + self:_resumeThread() + + self._timer = self._timer + deltaTime +end + +function SelfTestField:drawValue(deltaTime) + gfx.Translate(self.VALUE_OFFSETX, 0) + + gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT | gfx.TEXT_ALIGN_TOP) + gfx.FillColor(table.unpack(self.FONT_COLOR)) + gfx.Text(": ", 0, 0) + + local color, text + if self.status == SelfTestStatusEnum.IDLE then + color = self.FONT_COLOR + text = "" + elseif self.status == SelfTestStatusEnum.INPROGRESS then + local progress = math.ceil(Util.lerp(self._timer % self.INPROGRESS_FREQ, + 0, 0, self.INPROGRESS_FREQ, 4 + )) + color = self.COLOR_INPROGRESS + text = string.rep(".", progress) + elseif self.status == SelfTestStatusEnum.OK then + color = self.COLOR_OK + text = "OK" + elseif self.status == SelfTestStatusEnum.PASS then + color = self.COLOR_PASS + text = "PASS" + elseif self.status == SelfTestStatusEnum.ERROR then + color = self.COLOR_ERROR + text = "ERROR" + end + + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT | gfx.TEXT_ALIGN_TOP) + gfx.FillColor(table.unpack(color)) + gfx.Text(text, 0, 0) +end + +function SelfTestField:render(deltaTime) + self:tick(deltaTime) + ServiceField.render(self, deltaTime) +end + +return SelfTestField \ No newline at end of file diff --git a/scripts/titlescreen/fields/service/colorgradientfield.lua b/scripts/titlescreen/fields/service/colorgradientfield.lua new file mode 100644 index 0000000..97302eb --- /dev/null +++ b/scripts/titlescreen/fields/service/colorgradientfield.lua @@ -0,0 +1,46 @@ +require("common.class") +local Util = require("common.util") +local ServiceField = require("titlescreen.fields.service.servicefield") + +---@class ColorGradientField: ServiceField +local ColorGradientField = { + __tostring = function() return "ColorGradientField" end, + GRADIENT_X_OFFSET = 128, + GRADIENT_WIDTH = 576, + GRADIENT_STEPS = 32 +} + +---Create a new ColorGradientField instance +---@param o? table # initial parameters +---@return ColorGradientField +function ColorGradientField.new(o) + o = o or {} + + o.value = o.value or {0, 0, 0, 255} + + return CreateInstance(ColorGradientField, o, ServiceField) +end + +---@param obj? any # message object for the field +function ColorGradientField:activate(obj) end + +---@param obj? any # message object for the field +function ColorGradientField:focus(obj) end + +---@param obj? any # message object for the field +function ColorGradientField:deactivate(obj) end + +---@param deltaTime number # frametime in seconds +function ColorGradientField:drawValue(deltaTime) + local stepW = self.GRADIENT_WIDTH / self.GRADIENT_STEPS + for i = 0, self.GRADIENT_STEPS - 1 do + local posX = self.GRADIENT_X_OFFSET + i * stepW + local colorA = math.ceil(Util.lerp(i, 0, 0, self.GRADIENT_STEPS - 1, self.value[4])) + gfx.BeginPath() + gfx.Rect(posX, 0, stepW, self.aabbH) + gfx.FillColor(self.value[1], self.value[2], self.value[3], colorA) + gfx.Fill() + end +end + +return ColorGradientField diff --git a/scripts/titlescreen/fields/service/inputbuttonfield.lua b/scripts/titlescreen/fields/service/inputbuttonfield.lua new file mode 100644 index 0000000..294a455 --- /dev/null +++ b/scripts/titlescreen/fields/service/inputbuttonfield.lua @@ -0,0 +1,43 @@ +require("common.class") +local ServiceField = require("titlescreen.fields.service.servicefield") + +---@class InputButtonField: ServiceField +---@field button integer +local InputButtonField = { + __tostring = function() return "InputButtonField" end, +} + +---Create a new InputButtonField instance +---@param o? table # initial parameters +---@return InputButtonField +function InputButtonField.new(o) + o = o or {} + + o.button = o.button or nil + + return CreateInstance(InputButtonField, o, ServiceField) +end + +---@param obj? any # message object for the field +function InputButtonField:activate(obj) end + +---@param obj? any # message object for the field +function InputButtonField:focus(obj) end + +---@param obj? any # message object for the field +function InputButtonField:deactivate(obj) end + +---@param deltaTime number # frametime in seconds +function InputButtonField:drawValue(deltaTime) + gfx.Translate(self.VALUE_OFFSETX, 0) + + if not self.button then + gfx.Text("