From 1038fc85b9f03b5caa540e10cedccc8f0aafcfd1 Mon Sep 17 00:00:00 2001 From: AnthonyDuan Date: Thu, 15 Jan 2026 23:51:01 +0800 Subject: [PATCH] Add auto-submit to leaderboard and argparse support for app.py --- public/src/js/scoresheet.js | 502 +++++++++++++++++++----------------- 1 file changed, 264 insertions(+), 238 deletions(-) diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index a220be9..e2affbf 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -1,8 +1,8 @@ -class Scoresheet{ - constructor(...args){ +class Scoresheet { + constructor(...args) { this.init(...args) } - init(controller, results, multiplayer, touchEnabled){ + init(controller, results, multiplayer, touchEnabled) { this.controller = controller this.resultsObj = results this.player = [multiplayer ? (p2.player === 1 ? 0 : 1) : 0] @@ -11,33 +11,33 @@ class Scoresheet{ this.results[player0] = {} this.rules = [] this.rules[player0] = this.controller.game.rules - if(multiplayer){ + if (multiplayer) { this.player.push(p2.player === 2 ? 0 : 1) this.results[this.player[1]] = p2.results this.rules[this.player[1]] = this.controller.syncWith.game.rules } - for(var i in results){ + for (var i in results) { this.results[player0][i] = results[i] === null ? null : results[i].toString() } this.multiplayer = multiplayer this.touchEnabled = touchEnabled - + this.canvas = document.getElementById("canvas") this.ctx = this.canvas.getContext("2d") var resolution = settings.getItem("resolution") var noSmoothing = resolution === "low" || resolution === "lowest" - if(noSmoothing){ + if (noSmoothing) { this.ctx.imageSmoothingEnabled = false } - if(resolution === "lowest"){ + if (resolution === "lowest") { this.canvas.style.imageRendering = "pixelated" } this.game = document.getElementById("game") - + this.fadeScreen = document.createElement("div") this.fadeScreen.id = "fade-screen" this.game.appendChild(this.fadeScreen) - + this.font = strings.font this.numbersFont = "TnT, Meiryo, sans-serif" this.state = { @@ -49,18 +49,18 @@ class Scoresheet{ } this.frame = 1000 / 60 this.numbers = "001122334455667788900112233445".split("") - + this.draw = new CanvasDraw(noSmoothing) this.canvasCache = new CanvasCache(noSmoothing) this.nameplateCache = new CanvasCache(noSmoothing) - + this.keyboard = new Keyboard({ confirm: ["enter", "space", "esc", "don_l", "don_r"] }, this.keyDown.bind(this)) this.gamepad = new Gamepad({ confirm: ["a", "b", "start", "ls", "rs"] }, this.keyDown.bind(this)) - + this.difficulty = { "easy": 0, "normal": 1, @@ -68,22 +68,22 @@ class Scoresheet{ "oni": 3, "ura": 4 } - + this.scoreSaved = false this.redrawRunning = true this.redrawBind = this.redraw.bind(this) this.redraw() - + assets.sounds["v_results"].play() assets.sounds["bgm_result"].playLoop(3, false, 0, 0.847, 17.689) - + this.session = p2.session - if(this.session){ - if(p2.getMessage("songsel")){ + if (this.session) { + if (p2.getMessage("songsel")) { this.toSongsel(true) } pageEvents.add(p2, "message", response => { - if(response.type === "songsel"){ + if (response.type === "songsel") { this.toSongsel(true) } }) @@ -100,59 +100,59 @@ class Scoresheet{ touchEvents: controller.view.touchEvents }) } - keyDown(pressed){ - if(pressed && this.redrawing){ + keyDown(pressed) { + if (pressed && this.redrawing) { this.toNext() } } - mouseDown(event){ - if(event.type === "touchstart"){ + mouseDown(event) { + if (event.type === "touchstart") { event.preventDefault() this.canvas.style.cursor = "" this.state.pointerLocked = true - }else{ + } else { this.state.pointerLocked = false - if(event.which !== 1){ + if (event.which !== 1) { return } } this.toNext() } - toNext(){ + toNext() { var elapsed = this.getMS() - this.state.screenMS - if(this.state.screen === "fadeIn" && elapsed >= this.state.startDelay){ + if (this.state.screen === "fadeIn" && elapsed >= this.state.startDelay) { this.toScoresShown() - }else if(this.state.screen === "scoresShown" && elapsed >= 1000){ + } else if (this.state.screen === "scoresShown" && elapsed >= 1000) { this.toSongsel() } } - toScoresShown(){ - if(!p2.session){ + toScoresShown() { + if (!p2.session) { this.state.screen = "scoresShown" this.state.screenMS = this.getMS() this.controller.playSound("neiro_1_don", 0, true) } } - toSongsel(fromP2){ - if(!p2.session || fromP2){ + toSongsel(fromP2) { + if (!p2.session || fromP2) { snd.musicGain.fadeOut(0.5) this.state.screen = "fadeOut" this.state.screenMS = this.getMS() - if(!fromP2){ + if (!fromP2) { this.controller.playSound("neiro_1_don", 0, true) } } } - - startRedraw(){ + + startRedraw() { this.redrawing = true requestAnimationFrame(this.redrawBind) this.winW = null this.winH = null - + pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.mouseDown.bind(this)) - - if(!this.multiplayer){ + + if (!this.multiplayer) { this.tetsuoHana = document.createElement("div") this.tetsuoHana.id = "tetsuohana" var flowersBg = "url('" + assets.image["results_flowers"].src + "')" @@ -160,12 +160,12 @@ class Scoresheet{ var tetsuoHanaBg = "url('" + assets.image["results_tetsuohana" + (debugObj.state === "closed" ? "" : "2")].src + "')" var id = ["flowers1", "flowers2", "mikoshi", "tetsuo", "hana"] var bg = [flowersBg, flowersBg, mikoshiBg, tetsuoHanaBg, tetsuoHanaBg] - for(var i = 0; i < id.length; i++){ - if(id[i] === "mikoshi"){ + for (var i = 0; i < id.length; i++) { + if (id[i] === "mikoshi") { var divOut = document.createElement("div") divOut.id = id[i] + "-out" this.tetsuoHana.appendChild(divOut) - }else{ + } else { var divOut = this.tetsuoHana } var div = document.createElement("div") @@ -179,32 +179,32 @@ class Scoresheet{ this.game.appendChild(this.tetsuoHana) } } - - redraw(){ - if(!this.redrawRunning){ + + redraw() { + if (!this.redrawRunning) { return } - if(this.redrawing){ + if (this.redrawing) { requestAnimationFrame(this.redrawBind) } var ms = this.getMS() - - if(!this.redrawRunning){ + + if (!this.redrawRunning) { return } - + var ctx = this.ctx ctx.save() - + var winW = innerWidth var winH = lastHeight this.pixelRatio = window.devicePixelRatio || 1 var resolution = settings.getItem("resolution") - if(resolution === "medium"){ + if (resolution === "medium") { this.pixelRatio *= 0.75 - }else if(resolution === "low"){ + } else if (resolution === "low") { this.pixelRatio *= 0.5 - }else if(resolution === "lowest"){ + } else if (resolution === "lowest") { this.pixelRatio *= 0.25 } winW *= this.pixelRatio @@ -212,49 +212,49 @@ class Scoresheet{ var ratioX = winW / 1280 var ratioY = winH / 720 var ratio = (ratioX < ratioY ? ratioX : ratioY) - - if(this.redrawing){ - if(this.winW !== winW || this.winH !== winH){ + + if (this.redrawing) { + if (this.winW !== winW || this.winH !== winH) { this.canvas.width = Math.max(1, winW) this.canvas.height = Math.max(1, winH) ctx.scale(ratio, ratio) this.canvas.style.width = (winW / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px" - + this.canvasCache.resize(winW / ratio, 80 + 1, ratio) this.nameplateCache.resize(274, 134, ratio + 0.2) - - if(!this.multiplayer){ + + if (!this.multiplayer) { this.tetsuoHana.style.setProperty("--scale", ratio / this.pixelRatio) - if(this.tetsuoHanaClass === "dance"){ + if (this.tetsuoHanaClass === "dance") { this.tetsuoHana.classList.remove("dance", "dance2") - setTimeout(()=>{ + setTimeout(() => { this.tetsuoHana.classList.add("dance2") - },50) - }else if(this.tetsuoHanaClass === "failed"){ + }, 50) + } else if (this.tetsuoHanaClass === "failed") { this.tetsuoHana.classList.remove("failed") - setTimeout(()=>{ + setTimeout(() => { this.tetsuoHana.classList.add("failed") - },50) + }, 50) } } - }else if(!document.hasFocus() && this.state.screen === "scoresShown"){ - if(this.state["countup0"]){ + } else if (!document.hasFocus() && this.state.screen === "scoresShown") { + if (this.state["countup0"]) { this.stopSound("se_results_countup", 0) } - if(this.state["countup1"]){ + if (this.state["countup1"]) { this.stopSound("se_results_countup", 1) } return - }else{ + } else { ctx.clearRect(0, 0, winW / ratio, winH / ratio) } - }else{ + } else { ctx.scale(ratio, ratio) - if(!this.canvasCache.canvas){ + if (!this.canvasCache.canvas) { this.canvasCache.resize(winW / ratio, 80 + 1, ratio) } - if(!this.nameplateCache.canvas){ + if (!this.nameplateCache.canvas) { this.nameplateCache.resize(274, 67, ratio + 0.2) } } @@ -263,23 +263,23 @@ class Scoresheet{ this.ratio = ratio winW /= ratio winH /= ratio - + var frameTop = winH / 2 - 720 / 2 var frameLeft = winW / 2 - 1280 / 2 - + var players = this.multiplayer ? 2 : 1 var p2Offset = 298 - + var bgOffset = 0 var elapsed = ms - this.state.screenMS - if(this.state.screen === "fadeIn" && elapsed < 1000){ + if (this.state.screen === "fadeIn" && elapsed < 1000) { bgOffset = Math.min(1, this.draw.easeIn(1 - elapsed / 1000)) * (winH / 2) } - if((this.state.screen !== "fadeIn" || elapsed >= 1000) && !this.scoreSaved){ + if ((this.state.screen !== "fadeIn" || elapsed >= 1000) && !this.scoreSaved) { this.saveScore() } - - if(bgOffset){ + + if (bgOffset) { ctx.save() ctx.translate(0, -bgOffset) } @@ -297,7 +297,7 @@ class Scoresheet{ ctx.fillRect(0, winH / 2 - 12, winW, 12) ctx.fillStyle = "rgba(0, 0, 0, 0.25)" ctx.fillRect(0, winH / 2, winW, 20) - if(bgOffset !== 0){ + if (bgOffset !== 0) { ctx.fillStyle = "#000" ctx.fillRect(0, winH / 2 - 2, winW, 2) } @@ -305,13 +305,13 @@ class Scoresheet{ ctx.fillRect(0, 0, winW, frameTop + 64) ctx.fillStyle = "#bf2900" ctx.fillRect(0, frameTop + 64, winW, 8) - - if(bgOffset){ + + if (bgOffset) { ctx.restore() ctx.save() ctx.translate(0, bgOffset) } - + this.draw.pattern({ ctx: ctx, img: assets.image[this.multiplayer ? "bg_score_p2" : "bg_score_p1"], @@ -325,9 +325,9 @@ class Scoresheet{ ctx.fillStyle = this.multiplayer ? "rgba(138, 245, 247, 0.5)" : "rgba(249, 163, 149, 0.5)" ctx.fillRect(0, winH / 2, winW, 12) ctx.fillStyle = "#000" - if(bgOffset === 0){ + if (bgOffset === 0) { ctx.fillRect(0, winH / 2 - 2, winW, 4) - }else{ + } else { ctx.fillRect(0, winH / 2, winW, 2) } ctx.fillStyle = this.multiplayer ? "#6bbec0" : "#fa4529" @@ -336,47 +336,47 @@ class Scoresheet{ ctx.fillRect(0, winH - frameTop - 72, winW, 7) ctx.fillStyle = this.multiplayer ? "#a8e0e0" : "#ff9b7a" ctx.fillRect(0, winH - frameTop - 66, winW, 2) - - if(bgOffset){ + + if (bgOffset) { ctx.restore() } - - if(this.state.screen === "scoresShown" || this.state.screen === "fadeOut"){ + + if (this.state.screen === "scoresShown" || this.state.screen === "fadeOut") { var elapsed = Infinity - }else if(this.redrawing){ + } else if (this.redrawing) { var elapsed = ms - this.state.screenMS - this.state.startDelay - }else{ + } else { var elapsed = 0 } - + var rules = this.controller.game.rules var failedOffset = rules.clearReached(this.results[this.player[0]].gauge) ? 0 : -2000 - if(players === 2 && failedOffset !== 0){ + if (players === 2 && failedOffset !== 0) { var p2results = this.results[this.player[1]] - if(p2results && this.controller.syncWith.game.rules.clearReached(p2results.gauge)){ + if (p2results && this.controller.syncWith.game.rules.clearReached(p2results.gauge)) { failedOffset = 0 } } - if(elapsed >= 3100 + failedOffset){ - for(var p = 0; p < players; p++){ + if (elapsed >= 3100 + failedOffset) { + for (var p = 0; p < players; p++) { ctx.save() var results = this.results[p] - if(!results){ + if (!results) { continue } var clear = this.rules[p].clearReached(results.gauge) - if(p === 1 || !this.multiplayer && clear){ + if (p === 1 || !this.multiplayer && clear) { ctx.translate(0, 290) } - if(clear){ + if (clear) { ctx.globalCompositeOperation = "lighter" } ctx.globalAlpha = Math.min(1, Math.max(0, (elapsed - (3100 + failedOffset)) / 500)) * 0.5 var grd = ctx.createLinearGradient(0, frameTop + 72, 0, frameTop + 368) grd.addColorStop(0, "#000") - if(clear){ + if (clear) { grd.addColorStop(1, "#ffffba") - }else{ + } else { grd.addColorStop(1, "transparent") } ctx.fillStyle = grd @@ -384,11 +384,11 @@ class Scoresheet{ ctx.restore() } } - - if(elapsed >= 0){ - if(this.state.hasPointer === 0){ + + if (elapsed >= 0) { + if (this.state.hasPointer === 0) { this.state.hasPointer = 1 - if(!this.state.pointerLocked){ + if (!this.state.pointerLocked) { this.canvas.style.cursor = this.session ? "" : "pointer" } } @@ -397,7 +397,7 @@ class Scoresheet{ this.draw.alpha(Math.min(1, elapsed / 400), ctx, ctx => { ctx.scale(ratio, ratio) ctx.translate(frameLeft, frameTop) - + this.canvasCache.get({ ctx: ctx, x: 0, @@ -416,15 +416,15 @@ class Scoresheet{ letterSpacing: strings.id === "en" ? 0 : 3, forceShadow: true }, [ - {x: -2, y: -2, outline: "#000", letterBorder: 22}, + { x: -2, y: -2, outline: "#000", letterBorder: 22 }, {}, - {x: 2, y: 2, shadow: [2, 2, 7]}, - {x: 2, y: 2, outline: "#ad1516", letterBorder: 10}, - {x: -2, y: -2, outline: "#ff797b"}, - {outline: "#f70808"}, - {fill: "#fff", shadow: [-1, 1, 3, 1.5]} + { x: 2, y: 2, shadow: [2, 2, 7] }, + { x: 2, y: 2, outline: "#ad1516", letterBorder: 10 }, + { x: -2, y: -2, outline: "#ff797b" }, + { outline: "#f70808" }, + { fill: "#fff", shadow: [-1, 1, 3, 1.5] } ]) - + this.draw.layeredText({ ctx: ctx, text: this.results[this.player[0]].title, @@ -436,21 +436,21 @@ class Scoresheet{ align: "right", forceShadow: true }, [ - {outline: "#000", letterBorder: 10, shadow: [1, 1, 3]}, - {fill: "#fff"} + { outline: "#000", letterBorder: 10, shadow: [1, 1, 3] }, + { fill: "#fff" } ]) }) - + ctx.save() - for(var p = 0; p < players; p++){ + for (var p = 0; p < players; p++) { var results = this.results[p] - if(!results){ + if (!results) { continue } - if(p === 1){ + if (p === 1) { ctx.translate(0, p2Offset) } - + ctx.drawImage(assets.image["difficulty"], 0, 144 * this.difficulty[results.difficulty], 168, 143, @@ -468,11 +468,11 @@ class Scoresheet{ ctx.strokeText(text, 395, 308) ctx.fillText(text, 395, 308) ctx.miterLimit = 10 - + var defaultName = p === 0 ? strings.defaultName : strings.default2PName - if(p === this.player[0]){ + if (p === this.player[0]) { var name = account.loggedIn ? account.displayName : defaultName - }else{ + } else { var name = results.name || defaultName } this.nameplateCache.get({ @@ -492,13 +492,13 @@ class Scoresheet{ blue: p === 1 }) }) - - if(this.controller.autoPlayEnabled){ + + if (this.controller.autoPlayEnabled) { ctx.drawImage(assets.image["badge_auto"], 431, 311, 34, 34 ) } - + this.draw.roundedRect({ ctx: ctx, x: 532, @@ -549,10 +549,10 @@ class Scoresheet{ align: "right", width: 36 }, [ - {fill: "#fff"}, - {outline: "#000", letterBorder: 0.5} + { fill: "#fff" }, + { outline: "#000", letterBorder: 0.5 } ]) - + this.draw.score({ ctx: ctx, score: "good", @@ -574,7 +574,7 @@ class Scoresheet{ y: 273, results: true }) - + ctx.textAlign = "right" var grd = ctx.createLinearGradient(0, 0, 0, 30) grd.addColorStop(0.2, "#ff4900") @@ -590,8 +590,8 @@ class Scoresheet{ width: 154, letterSpacing: strings.id === "ja" ? 1 : 0 }, [ - {outline: "#000", letterBorder: 8}, - {fill: grd} + { outline: "#000", letterBorder: 8 }, + { fill: grd } ]) this.draw.layeredText({ ctx: ctx, @@ -604,24 +604,24 @@ class Scoresheet{ width: 154, letterSpacing: strings.id === "ja" ? 4 : 0 }, [ - {outline: "#000", letterBorder: 8}, - {fill: "#ffc700"} + { outline: "#000", letterBorder: 8 }, + { fill: "#ffc700" } ]) } ctx.restore() }) ctx.restore() } - - if(!this.multiplayer){ - if(elapsed >= 400 && elapsed < 3100 + failedOffset){ - if(this.tetsuoHanaClass !== "fadein"){ + + if (!this.multiplayer) { + if (elapsed >= 400 && elapsed < 3100 + failedOffset) { + if (this.tetsuoHanaClass !== "fadein") { this.tetsuoHana.classList.add("fadein") this.tetsuoHanaClass = "fadein" } - }else if(elapsed >= 3100 + failedOffset){ - if(this.tetsuoHanaClass !== "dance" && this.tetsuoHanaClass !== "failed"){ - if(this.tetsuoHanaClass){ + } else if (elapsed >= 3100 + failedOffset) { + if (this.tetsuoHanaClass !== "dance" && this.tetsuoHanaClass !== "failed") { + if (this.tetsuoHanaClass) { this.tetsuoHana.classList.remove(this.tetsuoHanaClass) } this.tetsuoHanaClass = this.rules[this.player[0]].clearReached(this.results[this.player[0]].gauge) ? "dance" : "failed" @@ -629,20 +629,20 @@ class Scoresheet{ } } } - - if(elapsed >= 800){ + + if (elapsed >= 800) { ctx.save() ctx.setTransform(1, 0, 0, 1, 0, 0) this.draw.alpha(Math.min(1, (elapsed - 800) / 500), ctx, ctx => { ctx.scale(ratio, ratio) ctx.translate(frameLeft, frameTop) - - for(var p = 0; p < players; p++){ + + for (var p = 0; p < players; p++) { var results = this.results[p] - if(!results){ + if (!results) { continue } - if(p === 1){ + if (p === 1) { ctx.translate(0, p2Offset) } var w = 712 @@ -669,51 +669,51 @@ class Scoresheet{ }) ctx.restore() } - - if(elapsed >= 1200){ + + if (elapsed >= 1200) { ctx.save() ctx.setTransform(1, 0, 0, 1, 0, 0) var noCrownResultWait = -2000; - for(var p = 0; p < players; p++){ + for (var p = 0; p < players; p++) { var results = this.results[p] - if(!results){ + if (!results) { continue } var crownType = null - if(this.rules[p].clearReached(results.gauge)){ + if (this.rules[p].clearReached(results.gauge)) { crownType = results.bad === "0" ? "gold" : "silver" } - if(crownType !== null){ + if (crownType !== null) { noCrownResultWait = 0; var amount = Math.min(1, (elapsed - 1200) / 450) this.draw.alpha(this.draw.easeIn(amount), ctx, ctx => { ctx.save() ctx.scale(ratio, ratio) ctx.translate(frameLeft, frameTop) - if(p === 1){ + if (p === 1) { ctx.translate(0, p2Offset) } - + var crownScale = 1 var shine = 0 - if(amount < 1){ + if (amount < 1) { crownScale = 2.8 * (1 - amount) + 0.9 - }else if(elapsed < 1850){ + } else if (elapsed < 1850) { crownScale = 0.9 + (elapsed - 1650) / 2000 - }else if(elapsed < 2200){ + } else if (elapsed < 2200) { shine = (elapsed - 1850) / 175 - if(shine > 1){ + if (shine > 1) { shine = 2 - shine } } - if(this.state.screen === "fadeIn" && elapsed >= 1200 && !this.state["fullcomboPlayed" + p]){ + if (this.state.screen === "fadeIn" && elapsed >= 1200 && !this.state["fullcomboPlayed" + p]) { this.state["fullcomboPlayed" + p] = true - if(crownType === "gold"){ + if (crownType === "gold") { this.playSound("v_results_fullcombo" + (p === 1 ? "2" : ""), p) } } - if(this.state.screen === "fadeIn" && elapsed >= 1650 && !this.state["crownPlayed" + p]){ + if (this.state.screen === "fadeIn" && elapsed >= 1650 && !this.state["crownPlayed" + p]) { this.state["crownPlayed" + p] = true this.playSound("se_results_crown", p) } @@ -727,65 +727,65 @@ class Scoresheet{ whiteOutline: true, ratio: ratio }) - + ctx.restore() }) } } ctx.restore() } - - if(elapsed >= 2400 + noCrownResultWait){ + + if (elapsed >= 2400 + noCrownResultWait) { ctx.save() ctx.translate(frameLeft, frameTop) - + var printNumbers = ["good", "ok", "bad", "maxCombo", "drumroll"] - if(!this.state["countupTime0"]){ + if (!this.state["countupTime0"]) { var times = {} var lastTime = 0 - for(var p = 0; p < players; p++){ + for (var p = 0; p < players; p++) { var results = this.results[p] - if(!results){ + if (!results) { continue } var currentTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame - if(currentTime > lastTime){ + if (currentTime > lastTime) { lastTime = currentTime } } - for(var i in printNumbers){ + for (var i in printNumbers) { var largestTime = 0 - for(var p = 0; p < players; p++){ + for (var p = 0; p < players; p++) { var results = this.results[p] - if(!results){ + if (!results) { continue } times[printNumbers[i]] = lastTime + 500 var currentTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame - if(currentTime > largestTime){ + if (currentTime > largestTime) { largestTime = currentTime } } lastTime = largestTime } this.state.fadeInEnd = lastTime - for(var p = 0; p < players; p++){ + for (var p = 0; p < players; p++) { this.state["countupTime" + p] = times } } - - for(var p = 0; p < players; p++){ + + for (var p = 0; p < players; p++) { var results = this.results[p] - if(!results){ + if (!results) { continue } - if(p === 1){ + if (p === 1) { ctx.translate(0, p2Offset) } ctx.save() - + this.state.countupShown = false - + var points = this.getNumber(results.points, 3100 + noCrownResultWait, elapsed) var scale = 1.3 ctx.font = "35px " + this.numbersFont @@ -795,24 +795,24 @@ class Scoresheet{ ctx.fillStyle = "#fff" ctx.strokeStyle = "#fff" ctx.lineWidth = 0.5 - for(var i = 0; i < points.length; i++){ + for (var i = 0; i < points.length; i++) { ctx.translate(-23.3 * scale, 0) ctx.fillText(points[points.length - i - 1], 0, 0) ctx.strokeText(points[points.length - i - 1], 0, 0) } ctx.restore() - - if(!this.state["countupTime" + p]){ + + if (!this.state["countupTime" + p]) { var times = {} var lastTime = 3100 + noCrownResultWait + results.points.length * 30 * this.frame + 1000 - for(var i in printNumbers){ + for (var i in printNumbers) { times[printNumbers[i]] = lastTime + 500 lastTime = lastTime + 500 + results[printNumbers[i]].length * 30 * this.frame } this.state["countupTime" + p] = times } - - for(var i in printNumbers){ + + for (var i in printNumbers) { var start = this.state["countupTime" + p][printNumbers[i]] this.draw.layeredText({ ctx: ctx, @@ -824,136 +824,162 @@ class Scoresheet{ letterSpacing: 1, align: "right" }, [ - {outline: "#000", letterBorder: 9}, - {fill: "#fff"} + { outline: "#000", letterBorder: 9 }, + { fill: "#fff" } ]) } - - if(this.state.countupShown){ - if(!this.state["countup" + p]){ + + if (this.state.countupShown) { + if (!this.state["countup" + p]) { this.state["countup" + p] = true this.loopSound("se_results_countup", p, [0.1, false, 0, 0, 0.07]) } - }else if(this.state["countup" + p]){ + } else if (this.state["countup" + p]) { this.state["countup" + p] = false this.stopSound("se_results_countup", p) - if(this.state.screen === "fadeIn"){ + if (this.state.screen === "fadeIn") { this.playSound("neiro_1_don", p) } } - - if(this.state.screen === "fadeIn" && elapsed >= this.state.fadeInEnd){ + + if (this.state.screen === "fadeIn" && elapsed >= this.state.fadeInEnd) { this.state.screen = "scoresShown" this.state.screenMS = this.getMS() } } ctx.restore() } - - if(this.session && !this.state.scoreNext && this.state.screen === "scoresShown" && ms - this.state.screenMS >= 10000){ + + if (this.session && !this.state.scoreNext && this.state.screen === "scoresShown" && ms - this.state.screenMS >= 10000) { this.state.scoreNext = true - if(p2.session){ + if (p2.session) { p2.send("songsel") - }else{ + } else { this.toSongsel(true) } } - - if(this.state.screen === "fadeOut"){ - if(this.state.hasPointer === 1){ + + if (this.state.screen === "fadeOut") { + if (this.state.hasPointer === 1) { this.state.hasPointer = 2 this.canvas.style.cursor = "" } - - if(!this.fadeScreenBlack){ + + if (!this.fadeScreenBlack) { this.fadeScreenBlack = true this.fadeScreen.style.backgroundColor = "#000" } var elapsed = ms - this.state.screenMS - - if(elapsed >= 1000){ + + if (elapsed >= 1000) { this.clean() this.controller.songSelection(true, this.showWarning) } } - + ctx.restore() } - - getNumber(score, start, elapsed){ + + getNumber(score, start, elapsed) { var numberPos = Math.floor((elapsed - start) / this.frame) - if(numberPos < 0){ + if (numberPos < 0) { return "" } var output = "" - for(var i = 0; i < score.length; i++){ - if(numberPos < 30 * (i + 1)){ + for (var i = 0; i < score.length; i++) { + if (numberPos < 30 * (i + 1)) { this.state.countupShown = true return this.numbers[numberPos % 30] + output - }else{ + } else { output = score[score.length - i - 1] + output } } return output } - - getSound(id, p){ + + getSound(id, p) { return assets.sounds[id + (this.multiplayer ? "_p" + (p + 1) : "")] } - playSound(id, p){ + playSound(id, p) { this.getSound(id, p).play() } - loopSound(id, p, args){ + loopSound(id, p, args) { this.getSound(id, p).playLoop(...args) } - stopSound(id, p){ + stopSound(id, p) { this.getSound(id, p).stop() } - - mod(length, index){ + + mod(length, index) { return ((index % length) + length) % length } - - getMS(){ + + getMS() { return Date.now() } - - saveScore(){ - if(this.controller.saveScore){ - if(this.resultsObj.points < 0){ + + saveScore() { + if (this.controller.saveScore) { + if (this.resultsObj.points < 0) { this.resultsObj.points = 0 } var title = this.controller.selectedSong.originalTitle var hash = this.controller.selectedSong.hash var difficulty = this.resultsObj.difficulty + var songId = this.controller.selectedSong.id var oldScore = scoreStorage.get(hash, difficulty, true) var clearReached = this.controller.game.rules.clearReached(this.resultsObj.gauge) var crown = "" - if(clearReached){ + if (clearReached) { crown = this.resultsObj.bad === 0 ? "gold" : "silver" } - if(!oldScore || oldScore.points <= this.resultsObj.points){ - if(oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)){ + if (!oldScore || oldScore.points <= this.resultsObj.points) { + if (oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)) { crown = oldScore.crown } this.resultsObj.crown = crown delete this.resultsObj.title delete this.resultsObj.difficulty delete this.resultsObj.gauge - scoreStorage.add(hash, difficulty, this.resultsObj, true, title).catch(() => { - this.showWarning = {name: "scoreSaveFailed"} + scoreStorage.add(hash, difficulty, this.resultsObj, true, title).then(() => { + // Auto-submit to leaderboard if logged in and has song ID + this.submitToLeaderboard(songId, difficulty, this.resultsObj) + }).catch(() => { + this.showWarning = { name: "scoreSaveFailed" } }) - }else if(oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)){ + } else if (oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)) { oldScore.crown = crown scoreStorage.add(hash, difficulty, oldScore, true, title).catch(() => { - this.showWarning = {name: "scoreSaveFailed"} + this.showWarning = { name: "scoreSaveFailed" } }) } } this.scoreSaved = true } - - clean(){ + + submitToLeaderboard(songId, difficulty, scoreObj) { + // Only submit if user is logged in and song has valid ID + if (!account.loggedIn || !songId) { + return + } + + loader.getCsrfToken().then(token => { + var request = new XMLHttpRequest() + request.open("POST", "api/leaderboard/submit") + request.setRequestHeader("Content-Type", "application/json;charset=UTF-8") + request.setRequestHeader("X-CSRFToken", token) + request.send(JSON.stringify({ + song_id: songId, + difficulty: difficulty, + score: scoreObj + })) + }).catch(() => { + // Silently fail - leaderboard submission is optional + console.log("Leaderboard submission failed") + }) + } + + clean() { this.keyboard.clean() this.gamepad.clean() this.draw.clean() @@ -962,13 +988,13 @@ class Scoresheet{ snd.buffer.loadSettings() this.redrawRunning = false pageEvents.remove(this.canvas, ["mousedown", "touchstart"]) - if(this.touchEnabled){ + if (this.touchEnabled) { pageEvents.remove(document.getElementById("touch-full-btn"), "touchend") } - if(this.session){ + if (this.session) { pageEvents.remove(p2, "message") } - if(!this.multiplayer){ + if (!this.multiplayer) { delete this.tetsuoHana } delete this.ctx