From 6686f5ae157addfe498bf78a5099692070c8ffbf Mon Sep 17 00:00:00 2001 From: AnthonyDuan Date: Sun, 18 Jan 2026 07:42:58 +0800 Subject: [PATCH] feat: Add song difficulty leaderboard feature --- app.py | 135 +++ public/src/js/assets.js | 9 +- public/src/js/leaderboard_ui.js | 287 +++++ public/src/js/scoresheet.js | 533 +++++---- public/src/js/songselect.js | 1791 +++++++++++++++++-------------- public/src/js/strings.js | 93 +- schema.py | 26 + tools/reset_leaderboard.py | 43 + 8 files changed, 1831 insertions(+), 1086 deletions(-) create mode 100644 public/src/js/leaderboard_ui.js create mode 100644 tools/reset_leaderboard.py diff --git a/app.py b/app.py index 0af8fcc..c66e3e9 100644 --- a/app.py +++ b/app.py @@ -13,6 +13,7 @@ import requests import schema import os import time +from datetime import datetime # -- カスタム -- import traceback @@ -105,6 +106,13 @@ db.users.create_index('username', unique=True) db.songs.create_index('id', unique=True) db.songs.create_index('song_type') db.scores.create_index('username') +db.leaderboard.create_index([ + ('song_id', 1), + ('difficulty', 1), + ('month', 1), + ('score', -1) +]) +db.leaderboard.create_index('username') class HashException(Exception): @@ -746,6 +754,133 @@ def route_api_scores_get(): return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name'], 'don': don}) +@app.route(basedir + 'api/leaderboard/submit', methods=['POST']) +@login_required +def route_api_leaderboard_submit(): + data = request.get_json() + if not schema.validate(data, schema.leaderboard_submit): + return abort(400) + + username = session.get('username') + user = db.users.find_one({'username': username}) + + current_month = datetime.now().strftime('%Y-%m') + + # Handle song_id type + song_id = data['song_id'] + + query = { + 'song_id': song_id, + 'difficulty': data['difficulty'], + 'month': current_month, + 'username': username + } + + existing = db.leaderboard.find_one(query) + + new_record = False + rank_info = None + + if not existing or data['score'] > existing['score']: + entry = { + 'song_id': song_id, + 'difficulty': data['difficulty'], + 'month': current_month, + 'username': username, + 'display_name': user['display_name'], + 'don': get_db_don(user), + 'score': data['score'], + 'good': data.get('good', 0), + 'ok': data.get('ok', 0), + 'bad': data.get('bad', 0), + 'maxCombo': data.get('maxCombo', 0), + 'timestamp': datetime.now() + } + db.leaderboard.update_one(query, {'$set': entry}, upsert=True) + new_record = True + + # Get rank + count = db.leaderboard.count_documents({ + 'song_id': song_id, + 'difficulty': data['difficulty'], + 'month': current_month, + 'score': {'$gt': data['score']} + }) + rank = count + 1 + rank_info = rank + + return jsonify({ + 'status': 'ok', + 'new_record': new_record, + 'rank': rank_info + }) + + +@app.route(basedir + 'api/leaderboard/get') +def route_api_leaderboard_get(): + song_id = request.args.get('song_id') + difficulty = request.args.get('difficulty') + + if not song_id or not difficulty: + return abort(400) + + # Try convert song_id to int if it looks like one + if re.match('^[0-9]+$', str(song_id)): + song_id = int(song_id) + + current_month = datetime.now().strftime('%Y-%m') + + query = { + 'song_id': song_id, + 'difficulty': difficulty, + 'month': current_month + } + + # Get top 50 + leaderboard = list(db.leaderboard.find(query, {'_id': False}).sort('score', -1).limit(50)) + + user_rank = None + if session.get('username'): + username = session.get('username') + user_entry = db.leaderboard.find_one({ + 'song_id': song_id, + 'difficulty': difficulty, + 'month': current_month, + 'username': username + }, {'_id': False}) + + if user_entry: + # Calculate rank + count = db.leaderboard.count_documents({ + 'song_id': song_id, + 'difficulty': difficulty, + 'month': current_month, + 'score': {'$gt': user_entry['score']} + }) + user_rank = { + 'rank': count + 1, + 'entry': user_entry + } + + return jsonify({ + 'status': 'ok', + 'leaderboard': leaderboard, + 'user_rank': user_rank + }) + + +@app.route(basedir + 'api/leaderboard/reset', methods=['POST']) +@admin_required(level=100) +def route_api_leaderboard_reset(): + current_month = datetime.now().strftime('%Y-%m') + result = db.leaderboard.delete_many({'month': {'$ne': current_month}}) + return jsonify({ + 'status': 'ok', + 'deleted_count': result.deleted_count, + 'current_month': current_month + }) + + @app.route(basedir + 'privacy') def route_api_privacy(): last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt'))) diff --git a/public/src/js/assets.js b/public/src/js/assets.js index 24d4232..b505274 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -6,6 +6,7 @@ var assets = { "parseosu.js", "titlescreen.js", "scoresheet.js", + "leaderboard_ui.js", "songselect.js", "keyboard.js", "gameinput.js", @@ -98,7 +99,7 @@ var assets = { "audioSfx": [ "se_pause.ogg", "se_calibration.ogg", - + "v_results.ogg", "v_sanka.ogg", "v_songsel.ogg", @@ -112,14 +113,14 @@ var assets = { "se_don.ogg", "se_ka.ogg", "se_jump.ogg", - + "se_balloon.ogg", "se_gameclear.ogg", "se_gamefail.ogg", "se_gamefullcombo.ogg", "se_results_countup.ogg", "se_results_crown.ogg", - + "v_fullcombo.ogg", "v_renda.ogg", "v_results_fullcombo.ogg", @@ -153,7 +154,7 @@ var assets = { "customsongs.html", "search.html" ], - + "songs": [], "sounds": {}, "image": {}, diff --git a/public/src/js/leaderboard_ui.js b/public/src/js/leaderboard_ui.js new file mode 100644 index 0000000..74e8003 --- /dev/null +++ b/public/src/js/leaderboard_ui.js @@ -0,0 +1,287 @@ +class LeaderboardUI { + constructor(songSelect) { + this.songSelect = songSelect + this.canvasCache = new CanvasCache() + this.nameplateCache = new CanvasCache() + this.visible = false + this.loading = false + this.leaderboardData = [] + this.userRank = null + this.scroll = 0 + this.maxScroll = 0 + this.songId = null + this.difficulty = null + this.font = strings.font + this.numbersFont = "TnT, Meiryo, sans-serif" + + // UI Constants + this.width = 800 + this.height = 500 + this.itemHeight = 60 + this.headerHeight = 80 + this.padding = 20 + + this.closeBtn = { + x: 0, y: 0, w: 60, h: 60 + } + } + + show(songId, difficulty) { + this.visible = true + this.loading = true + this.songId = songId + this.difficulty = difficulty + this.leaderboardData = [] + this.userRank = null + this.scroll = 0 + + this.fetchData(songId, difficulty) + } + + hide() { + this.visible = false + this.songSelect.leaderboardActive = false + } + + async fetchData(songId, difficulty) { + try { + const response = await loader.ajax(`api/leaderboard/get?song_id=${songId}&difficulty=${difficulty}`) + const data = JSON.parse(response) + if (data.status === 'ok') { + this.leaderboardData = data.leaderboard + this.userRank = data.userRank + this.loading = false + + // Calculate max scroll + const totalHeight = this.leaderboardData.length * this.itemHeight + const viewHeight = this.height - this.headerHeight - this.padding * 2 + this.maxScroll = Math.max(0, totalHeight - viewHeight) + } + } catch (e) { + console.error(e) + this.loading = false + } + } + + draw(ctx, winW, winH, pixelRatio) { + if (!this.visible) return + + const x = (winW - this.width) / 2 + const y = (winH - this.height) / 2 + + // Draw Overlay + ctx.fillStyle = "rgba(0, 0, 0, 0.7)" + ctx.fillRect(0, 0, winW, winH) + + // Draw Window + this.songSelect.draw.roundedRect({ + ctx: ctx, + x: x, + y: y, + w: this.width, + h: this.height, + radius: 20 + }) + ctx.fillStyle = "#fff" + ctx.fill() + + // Header + ctx.fillStyle = "#ff6600" + ctx.beginPath() + ctx.moveTo(x + 20, y) + ctx.lineTo(x + this.width - 20, y) + ctx.quadraticCurveTo(x + this.width, y, x + this.width, y + 20) + ctx.lineTo(x + this.width, y + this.headerHeight) + ctx.lineTo(x, y + this.headerHeight) + ctx.lineTo(x, y + 20) + ctx.quadraticCurveTo(x, y, x + 20, y) + ctx.fill() + + // Title + const diffText = strings[this.difficulty === "ura" ? "oni" : this.difficulty] + const titleText = strings.leaderboardTitle.replace("%s", diffText) + + this.songSelect.draw.layeredText({ + ctx: ctx, + text: titleText, + fontSize: 40, + fontFamily: this.font, + x: x + this.width / 2, + y: y + 54, + align: "center", + width: this.width - 100 + }, [ + { outline: "#fff", letterBorder: 2 }, + { fill: "#000" } + ]) + + // Close Button + this.closeBtn.x = x + this.width - 50 + this.closeBtn.y = y - 10 + ctx.fillStyle = "#ff0000" + ctx.beginPath() + ctx.arc(this.closeBtn.x, this.closeBtn.y, 20, 0, Math.PI * 2) + ctx.fill() + ctx.strokeStyle = "#fff" + ctx.lineWidth = 3 + ctx.stroke() + ctx.fillStyle = "#fff" + ctx.font = "bold 24px Arial" + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.fillText("X", this.closeBtn.x, this.closeBtn.y) + + // Content Area + const contentX = x + this.padding + const contentY = y + this.headerHeight + this.padding + const contentW = this.width - this.padding * 2 + const contentH = this.height - this.headerHeight - this.padding * 2 + + ctx.save() + ctx.beginPath() + ctx.rect(contentX, contentY, contentW, contentH) + ctx.clip() + + if (this.loading) { + ctx.fillStyle = "#000" + ctx.font = `30px ${this.font}` + ctx.textAlign = "center" + ctx.fillText(strings.loadingLeaderboard, x + this.width / 2, y + this.height / 2) + } else if (this.leaderboardData.length === 0) { + ctx.fillStyle = "#666" + ctx.font = `30px ${this.font}` + ctx.textAlign = "center" + ctx.fillText(strings.noScores, x + this.width / 2, y + this.height / 2) + } else { + // Draw List + let currentY = contentY - this.scroll + + // Header Row + /* + ctx.fillStyle = "#eee" + ctx.fillRect(contentX, contentY, contentW, 40) + ctx.fillStyle = "#000" + ctx.font = `bold 20px ${this.font}` + ctx.textAlign = "left" + ctx.fillText(strings.rank, contentX + 20, contentY + 28) + ctx.fillText(strings.playerName, contentX + 100, contentY + 28) + ctx.textAlign = "right" + ctx.fillText(strings.score, contentX + contentW - 20, contentY + 28) + currentY += 40 + */ + + this.leaderboardData.forEach((entry, index) => { + if (currentY + this.itemHeight < contentY) { + currentY += this.itemHeight + return + } + if (currentY > contentY + contentH) return + + const isUser = this.userRank && this.userRank.entry.username === entry.username + + // Background + if (index % 2 === 0) { + ctx.fillStyle = isUser ? "#fff8e1" : "#f9f9f9" + } else { + ctx.fillStyle = isUser ? "#fff3cd" : "#fff" + } + ctx.fillRect(contentX, currentY, contentW, this.itemHeight) + + // Rank + const rank = index + 1 + let rankColor = "#000" + if (rank === 1) rankColor = "#ffd700" + else if (rank === 2) rankColor = "#c0c0c0" + else if (rank === 3) rankColor = "#cd7f32" + + if (rank <= 3) { + ctx.fillStyle = rankColor + ctx.beginPath() + ctx.arc(contentX + 40, currentY + this.itemHeight / 2, 18, 0, Math.PI * 2) + ctx.fill() + ctx.fillStyle = "#fff" + ctx.font = "bold 20px Arial" + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.fillText(rank, contentX + 40, currentY + this.itemHeight / 2) + } else { + ctx.fillStyle = "#000" + ctx.font = "20px Arial" + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.fillText(rank, contentX + 40, currentY + this.itemHeight / 2) + } + + // Name + ctx.fillStyle = "#000" + ctx.font = `24px ${this.font}` + ctx.textAlign = "left" + ctx.fillText(entry.display_name, contentX + 90, currentY + this.itemHeight / 2) + + // Score + ctx.font = `bold 28px ${this.numbersFont}` + ctx.textAlign = "right" + ctx.fillStyle = "#ff4500" + ctx.fillText(entry.score.toLocaleString(), contentX + contentW - 20, currentY + this.itemHeight / 2 + 2) + + // Max Combo (small) + ctx.font = `16px ${this.font}` + ctx.fillStyle = "#666" + ctx.fillText(`${strings.maxCombo}: ${entry.maxCombo}`, contentX + contentW - 180, currentY + this.itemHeight / 2 + 5) + + currentY += this.itemHeight + }) + } + ctx.restore() + + // Draw User Rank at Bottom if exists + if (this.userRank) { + const footerY = y + this.height - 50 + ctx.fillStyle = "#333" + ctx.fillRect(x, footerY, this.width, 50) + + ctx.fillStyle = "#fff" + ctx.font = `20px ${this.font}` + ctx.textAlign = "left" + ctx.textBaseline = "middle" + + const rankText = strings.yourRank.replace("%s", this.userRank.rank) + ctx.fillText(rankText, x + 20, footerY + 25) + + ctx.textAlign = "right" + ctx.font = `bold 24px ${this.numbersFont}` + ctx.fillText(this.userRank.entry.score.toLocaleString(), x + this.width - 20, footerY + 25) + } + } + + mouseMove(x, y) { + // Handle hover if needed + } + + mouseDown(x, y) { + if (!this.visible) return false + + // Check Close Button + const dx = x - this.closeBtn.x + const dy = y - this.closeBtn.y + if (dx * dx + dy * dy < 20 * 20) { + this.hide() + assets.sounds["se_cancel"].play() + return true + } + + // Check window click (consume event) + // Adjust coordinates + const winX = (innerWidth * (window.devicePixelRatio || 1) - this.width) / 2 + /* Note: simple check logic here, might need more precise logic mapping similar to mouseWheel */ + + return true + } + + wheel(delta) { + if (!this.visible) return + + this.scroll += delta * 50 + this.scroll = Math.max(0, Math.min(this.scroll, this.maxScroll)) + } +} diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index a220be9..967d020 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,103 +824,126 @@ 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.leaderboardResult) { + ctx.save() + var text = strings.recordBroken.replace("%s", this.leaderboardResult.rank) + ctx.textAlign = "center" + var y = 130 + if (this.multiplayer) { + y = 400 + } + this.draw.layeredText({ + ctx: ctx, + text: text, + x: 640, + y: y, + fontSize: 40, + fontFamily: strings.font, + align: "center" + }, [ + { outline: "#fff", letterBorder: 5 }, + { fill: "#ff0000" } + ]) + ctx.restore() + } + ctx.restore() + + 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 @@ -929,11 +952,11 @@ class Scoresheet{ 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 @@ -941,19 +964,55 @@ class Scoresheet{ delete this.resultsObj.difficulty delete this.resultsObj.gauge scoreStorage.add(hash, difficulty, this.resultsObj, true, title).catch(() => { - this.showWarning = {name: "scoreSaveFailed"} + 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" } }) } } + if (!this.controller.autoPlayEnabled && account.loggedIn && !this.multiplayer) { + this.submitToLeaderboard() + } this.scoreSaved = true } - - clean(){ + + submitToLeaderboard() { + if (!this.resultsObj || !this.controller.selectedSong) { + return + } + var song = this.controller.selectedSong + var body = { + song_id: song.id, + difficulty: this.resultsObj.difficulty, + score: this.resultsObj.points, + good: this.resultsObj.good, + ok: this.resultsObj.ok, + bad: this.resultsObj.bad, + maxCombo: this.resultsObj.maxCombo, + hash: song.hash + } + + loader.ajax("api/leaderboard/submit", request => { + request.open("POST", "api/leaderboard/submit") + request.setRequestHeader("Content-Type", "application/json") + request.send(JSON.stringify(body)) + }).then(response => { + var data = JSON.parse(response) + if (data.status === "ok" && data.new_record && data.rank) { + this.leaderboardResult = { + rank: data.rank + } + assets.sounds["se_results_crown"].play() + } + }).catch(e => { + console.error("Leaderboard submit failed", e) + }) + } + + clean() { this.keyboard.clean() this.gamepad.clean() this.draw.clean() @@ -962,13 +1021,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 diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index edec6bc..bdb5c4b 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -1,19 +1,19 @@ -class SongSelect{ - constructor(...args){ +class SongSelect { + constructor(...args) { this.init(...args) } - init(fromTutorial, fadeIn, touchEnabled, songId, showWarning){ + init(fromTutorial, fadeIn, touchEnabled, songId, showWarning) { this.touchEnabled = touchEnabled - + loader.changePage("songselect", false) this.canvas = document.getElementById("song-sel-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" } @@ -21,7 +21,7 @@ class SongSelect{ let color = Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0"); return `#${color}`; } - + this.songSkin = { "selected": { background: "#ffdb2c", @@ -75,25 +75,25 @@ class SongSelect{ border: ["#fde9df", "#ce7553"], outline: "#ce7553" }, - // カスタム曲スキン - "upload": { - sort: 0, - background: "#ffe57f", - border: ["#ffd54f", "#ff9800"], - outline: "#ffab40", - }, - "keijiban": { - sort: 0, - background: "#1c1c1c", - border: ["#000000", "#333333"], - outline: "#222222", - }, - "customSettings": { - sort: 0, - background: "#a5d6a7", // 緑色の背景 - border: ["#81c784", "#66bb6a"], // 緑色の境界線 - outline: "#388e3c" // 緑色のアウトライン - }, + // カスタム曲スキン + "upload": { + sort: 0, + background: "#ffe57f", + border: ["#ffd54f", "#ff9800"], + outline: "#ffab40", + }, + "keijiban": { + sort: 0, + background: "#1c1c1c", + border: ["#000000", "#333333"], + outline: "#222222", + }, + "customSettings": { + sort: 0, + background: "#a5d6a7", // 緑色の背景 + border: ["#81c784", "#66bb6a"], // 緑色の境界線 + outline: "#388e3c" // 緑色のアウトライン + }, "default": { sort: null, background: `${rand()}`, @@ -102,12 +102,12 @@ class SongSelect{ infoFill: `${rand()}` } } - + var songSkinLength = Object.keys(this.songSkin).length - for(var i in assets.categories){ + for (var i in assets.categories) { var category = assets.categories[i] - if(!this.songSkin[category.title] && category.songSkin){ - if(category.songSkin.sort === null){ + if (!this.songSkin[category.title] && category.songSkin) { + if (category.songSkin.sort === null) { category.songSkin.sort = songSkinLength + 1 } category.songSkin.id = category.id @@ -115,13 +115,14 @@ class SongSelect{ } } this.songSkin["default"].sort = songSkinLength + 1 - + this.font = strings.font - + this.search = new Search(this) + this.leaderboard = new LeaderboardUI(this) this.songs = [] - for(let song of assets.songs){ + for (let song of assets.songs) { var title = this.getLocalTitle(song.title, song.title_lang) song.titlePrepared = title ? fuzzysort.prepare(this.search.normalizeString(title)) : null var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang) @@ -131,13 +132,13 @@ class SongSelect{ this.songs.sort((a, b) => { var catA = a.originalCategory in this.songSkin ? this.songSkin[a.originalCategory] : this.songSkin.default var catB = b.originalCategory in this.songSkin ? this.songSkin[b.originalCategory] : this.songSkin.default - if(catA.sort !== catB.sort){ + if (catA.sort !== catB.sort) { return catA.sort > catB.sort ? 1 : -1 - }else if(a.originalCategory !== b.originalCategory){ + } else if (a.originalCategory !== b.originalCategory) { return a.originalCategory > b.originalCategory ? 1 : -1 - }else if(a.order !== b.order){ + } else if (a.order !== b.order) { return a.order > b.order ? 1 : -1 - }else{ + } else { return a.id > b.id ? 1 : -1 } }) @@ -145,7 +146,7 @@ class SongSelect{ if (titlesort === "true") { this.songs.sort((a, b) => a.title.localeCompare(b.title)); } - if(assets.songs.length){ + if (assets.songs.length) { this.songs.push({ title: strings.back, skin: this.songSkin.back, @@ -167,11 +168,11 @@ class SongSelect{ p2Enabled: true }) } - if(touchEnabled){ - if(fromTutorial === "tutorial"){ + if (touchEnabled) { + if (fromTutorial === "tutorial") { fromTutorial = false } - }else{ + } else { this.songs.push({ title: strings.howToPlay, skin: this.songSkin.tutorial, @@ -180,7 +181,7 @@ class SongSelect{ }) } this.showWarning = showWarning - if(showWarning && showWarning.name === "scoreSaveFailed"){ + if (showWarning && showWarning.name === "scoreSaveFailed") { scoreStorage.scoreSaveFailed = true } this.songs.push({ @@ -195,14 +196,14 @@ class SongSelect{ action: "settings", category: strings.random }) - + var showCustom = false - if(gameConfig.google_credentials.gdrive_enabled){ + if (gameConfig.google_credentials.gdrive_enabled) { showCustom = true - }else if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){ + } else if ("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))) { showCustom = true } - if(showCustom){ + if (showCustom) { this.songs.push({ title: assets.customSongs ? strings.customSongs.default : strings.customSongs.title, skin: this.songSkin.customSongs, @@ -217,68 +218,68 @@ class SongSelect{ category: strings.random }) - // カスタムメニュー - // this.songs.push({ - // title: "ソースコード", - // skin: this.songSkin.sourceCode, - // action: "sourceCode", - // }); - // for (let i = 0; i < 10; i++) { - this.songs.push({ - title: "曲を投稿!", - skin: this.songSkin.upload, - action: "upload", - }); - // } - this.songs.push({ - title: "掲示板", - skin: this.songSkin.keijiban, - action: "keijiban", - }); + // カスタムメニュー + // this.songs.push({ + // title: "ソースコード", + // skin: this.songSkin.sourceCode, + // action: "sourceCode", + // }); + // for (let i = 0; i < 10; i++) { + this.songs.push({ + title: "曲を投稿!", + skin: this.songSkin.upload, + action: "upload", + }); + // } + this.songs.push({ + title: "掲示板", + skin: this.songSkin.keijiban, + action: "keijiban", + }); - this.songs.push({ - title: "曲選択速度", - skin: this.songSkin.customSettings, - action: "songSelectingSpeed", - }); - - this.songs.push({ - title: "ばいそく", - skin: this.songSkin.customSettings, - action: "baisoku", - }); + this.songs.push({ + title: "曲選択速度", + skin: this.songSkin.customSettings, + action: "songSelectingSpeed", + }); - this.songs.push({ - title: "ドロン", - skin: this.songSkin.customSettings, - action: "doron", - }); + this.songs.push({ + title: "ばいそく", + skin: this.songSkin.customSettings, + action: "baisoku", + }); - this.songs.push({ - title: "あべこべ", - skin: this.songSkin.customSettings, - action: "abekobe", - }); + this.songs.push({ + title: "ドロン", + skin: this.songSkin.customSettings, + action: "doron", + }); - this.songs.push({ - title: "でたらめ", - skin: this.songSkin.customSettings, - action: "detarame", - }); + this.songs.push({ + title: "あべこべ", + skin: this.songSkin.customSettings, + action: "abekobe", + }); + + this.songs.push({ + title: "でたらめ", + skin: this.songSkin.customSettings, + action: "detarame", + }); - this.songs.push({ - title: "タイトル順で並べ替え", - skin: this.songSkin.customSettings, - action: "titlesort", - }); + this.songs.push({ + title: "タイトル順で並べ替え", + skin: this.songSkin.customSettings, + action: "titlesort", + }); this.songs.push({ title: strings.back, skin: this.songSkin.back, action: "back" }) - + this.songAsset = { marginTop: 104, marginLeft: 18, @@ -291,13 +292,19 @@ class SongSelect{ innerBorder: 8, letterBorder: 12 } - + this.diffOptions = [{ text: strings.back, fill: "#efb058", iconName: "back", iconFill: "#f7d39c", letterSpacing: 4 + }, { + text: strings.leaderboard, + fill: "#42c0d2", + iconName: "crown", + iconFill: "#8ee7f2", + letterSpacing: 0 }, { text: strings.songOptions, fill: "#b2e442", @@ -312,7 +319,7 @@ class SongSelect{ letterSpacing: 4 }] this.optionsList = [strings.none, strings.auto, strings.netplay] - + this.draw = new CanvasDraw(noSmoothing) this.songTitleCache = new CanvasCache(noSmoothing) this.selectTextCache = new CanvasCache(noSmoothing) @@ -321,64 +328,64 @@ class SongSelect{ this.sessionCache = new CanvasCache(noSmoothing) this.currentSongCache = new CanvasCache(noSmoothing) this.nameplateCache = new CanvasCache(noSmoothing) - - + + this.difficulty = [strings.easy, strings.normal, strings.hard, strings.oni] this.difficultyId = ["easy", "normal", "hard", "oni", "ura"] - + this.sessionText = { "sessionstart": strings.sessionStart, "sessionend": strings.sessionEnd } - + this.selectedSong = 0 this.selectedDiff = 0 this.lastCurrentSong = {} this.lastRandom = false assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506) - - if(!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage) && !songId){ + + if (!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage) && !songId) { fromTutorial = touchEnabled ? "about" : "tutorial" } - if(p2.session || assets.customSongs && "customSelected" in localStorage){ + if (p2.session || assets.customSongs && "customSelected" in localStorage) { fromTutorial = false } - + this.drumSounds = settings.getItem("latency").drumSounds this.playedSounds = {} - + var songIdIndex = -1 var newSelected = -1 - if(fromTutorial){ + if (fromTutorial) { newSelected = this.songs.findIndex(song => song.action === fromTutorial) } - if(newSelected !== -1){ + if (newSelected !== -1) { this.setSelectedSong(newSelected, false) this.playBgm(true) - }else{ - if(songId){ + } else { + if (songId) { songIdIndex = this.songs.findIndex(song => song.id === songId) - if(songIdIndex === -1){ + if (songIdIndex === -1) { this.clearHash() } } - if(songIdIndex !== -1){ + if (songIdIndex !== -1) { this.setSelectedSong(songIdIndex, false) - }else if(assets.customSongs){ + } else if (assets.customSongs) { this.setSelectedSong(Math.min(Math.max(0, assets.customSelected), this.songs.length - 1), false) - }else if((!p2.session || fadeIn) && "selectedSong" in localStorage){ - this.setSelectedSong(Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length - 1), false) + } else if ((!p2.session || fadeIn) && "selectedSong" in localStorage) { + this.setSelectedSong(Math.min(Math.max(0, localStorage["selectedSong"] | 0), this.songs.length - 1), false) } - if(!this.showWarning){ + if (!this.showWarning) { this.playSound(songIdIndex !== -1 ? "v_diffsel" : "v_songsel") } snd.musicGain.fadeOut() this.playBgm(false) } - if("selectedDiff" in localStorage){ - this.selectedDiff = Math.min(Math.max(0, localStorage["selectedDiff"] |0), this.diffOptions.length + 3) + if ("selectedDiff" in localStorage) { + this.selectedDiff = Math.min(Math.max(0, localStorage["selectedDiff"] | 0), this.diffOptions.length + 3) } - + this.songSelect = document.getElementById("song-select") this.songTypes = [ "01 Pop", @@ -408,10 +415,10 @@ class SongSelect{ this.updateTypeLabel() var cat = this.songs[this.selectedSong].originalCategory this.drawBackground(cat) - + this.previewId = 0 this.previewList = Array(5) - + var skipStart = fromTutorial || p2.session this.state = { screen: songIdIndex !== -1 ? "difficulty" : (fadeIn ? "titleFadeIn" : (skipStart ? "song" : "title")), @@ -436,9 +443,9 @@ class SongSelect{ } this.wheelScrolls = 0 this.wheelTimer = 0 - + this.startPreview(true) - + this.pressedKeys = {} this.keyboard = new Keyboard({ confirm: ["enter", "space", "don_l", "don_r"], @@ -466,18 +473,18 @@ class SongSelect{ jump_left: ["lb"], jump_right: ["rb"] }, this.keyPress.bind(this)) - - if(!assets.customSongs){ + + if (!assets.customSongs) { this.startP2() } - + pageEvents.add(loader.screen, "mousemove", this.mouseMove.bind(this)) pageEvents.add(loader.screen, "mouseleave", () => { this.state.moveHover = null }) pageEvents.add(loader.screen, ["mousedown", "touchstart"], this.mouseDown.bind(this)) pageEvents.add(this.canvas, "touchend", this.touchEnd.bind(this)) - if(touchEnabled && fullScreenSupported){ + if (touchEnabled && fullScreenSupported) { this.touchFullBtn = document.getElementById("touch-full-btn") this.touchFullBtn.style.display = "block" pageEvents.add(this.touchFullBtn, "touchend", toggleFullscreen) @@ -487,32 +494,32 @@ class SongSelect{ this.selectable = document.getElementById("song-sel-selectable") this.selectableText = "" - + this.redrawRunning = true this.redrawBind = this.redraw.bind(this) this.redraw() pageEvents.send("song-select") pageEvents.send("song-select-move", this.songs[this.selectedSong]) - if(songIdIndex !== -1){ + if (songIdIndex !== -1) { pageEvents.send("song-select-difficulty", this.songs[this.selectedSong]) } } - setAltText(element, text){ + setAltText(element, text) { element.innerText = text element.setAttribute("alt", text) } - setSelectedSong(songIdx, drawBg=true){ + setSelectedSong(songIdx, drawBg = true) { if (songIdx < 0) { return; } - if(drawBg){ + if (drawBg) { var cat = this.songs[songIdx].originalCategory - if(cat){ + if (cat) { this.drawBackground(cat) - }else{ + } else { this.drawBackground(false) } } @@ -520,105 +527,107 @@ class SongSelect{ this.selectedSong = songIdx } - keyPress(pressed, name, event, repeat){ - if(pressed){ - if(!this.pressedKeys[name]){ + keyPress(pressed, name, event, repeat) { + if (pressed) { + if (!this.pressedKeys[name]) { this.pressedKeys[name] = this.getMS() + (name === "left" || name === "right" ? 150 : 300) } - }else{ + } else { this.pressedKeys[name] = 0 return } - if(name === "ctrl" || name === "shift" || !this.redrawRunning){ + if (name === "ctrl" || name === "shift" || !this.redrawRunning) { return } var ctrl = event ? event.ctrlKey : (this.pressedKeys["ctrl"] || this.pressedKeys["ctrlGamepad"]) var shift = event ? event.shiftKey : this.pressedKeys["shift"] - if(this.state.showWarning){ - if(name === "confirm"){ + if (this.state.showWarning) { + if (name === "confirm") { this.playSound("se_don") this.state.showWarning = false this.showWarning = false } - }else if(this.search.opened){ + } else if (this.search.opened) { this.search.keyPress(pressed, name, event, repeat, ctrl) - }else if(this.state.screen === "song"){ - if(event && event.keyCode && event.keyCode === 70 && ctrl){ + } else if (this.state.screen === "song") { + if (event && event.keyCode && event.keyCode === 70 && ctrl) { this.search.display() - if(event){ + if (event) { event.preventDefault() } - }else if(name === "confirm"){ + } else if (name === "confirm") { this.toSelectDifficulty() - }else if(name === "back"){ + } else if (name === "back") { this.toTitleScreen() - }else if(name === "session"){ + } else if (name === "session") { this.toSession() - }else if(name === "left"){ - if(shift){ - if(!repeat){ this.changeType(-1) } - }else{ + } else if (name === "left") { + if (shift) { + if (!repeat) { this.changeType(-1) } + } else { this.moveToSong(-1) } - }else if(name === "right"){ - if(shift){ - if(!repeat){ this.changeType(1) } - }else{ + } else if (name === "right") { + if (shift) { + if (!repeat) { this.changeType(1) } + } else { this.moveToSong(1) } - }else if(name === "jump_left" && !repeat){ + } else if (name === "jump_left" && !repeat) { this.changeType(-1) - }else if(name === "jump_right" && !repeat){ + } else if (name === "jump_right" && !repeat) { this.changeType(1) - }else if(name === "mute" || name === "ctrlGamepad"){ + } else if (name === "mute" || name === "ctrlGamepad") { this.endPreview(true) this.playBgm(false) } - }else if(this.state.screen === "difficulty"){ - if(event && event.keyCode && event.keyCode === 70 && ctrl){ + } else if (this.state.screen === "difficulty") { + if (event && event.keyCode && event.keyCode === 70 && ctrl) { this.search.display() - if(event){ + if (event) { event.preventDefault() } - }else if(name === "confirm"){ - if(this.selectedDiff === 0){ + } else if (name === "confirm") { + if (this.selectedDiff === 0) { this.toSongSelect() - }else if(this.selectedDiff === 2){ + } else if (this.selectedDiff === 1) { + this.toLeaderboard() + } else if (this.selectedDiff === 3) { this.toDownload() - }else if(this.selectedDiff === 3){ + } else if (this.selectedDiff === 4) { this.toDelete() - }else if(this.selectedDiff === 1){ + } else if (this.selectedDiff === 2) { this.toOptions(1) - }else{ + } else { this.toLoadSong(this.selectedDiff - this.diffOptions.length, shift, ctrl) } - }else if(name === "back" || name === "session"){ + } else if (name === "back" || name === "session") { this.toSongSelect() - }else if(name === "left"){ + } else if (name === "left") { this.moveToDiff(-1) - }else if(name === "right"){ + } else if (name === "right") { this.moveToDiff(1) - }else if(this.selectedDiff === 1 && (name === "up" || name === "down")){ + } else if (this.selectedDiff === 1 && (name === "up" || name === "down")) { this.toOptions(name === "up" ? -1 : 1) - }else if(name === "mute" || name === "ctrlGamepad"){ + } else if (name === "mute" || name === "ctrlGamepad") { this.endPreview(true) this.playBgm(false) } - }else if(this.state.screen === "title" || this.state.screen === "titleFadeIn"){ - if(event && event.keyCode && event.keyCode === 70 && ctrl){ + } else if (this.state.screen === "title" || this.state.screen === "titleFadeIn") { + if (event && event.keyCode && event.keyCode === 70 && ctrl) { this.search.display() - if(event){ + if (event) { event.preventDefault() } } } } - updateTypeLabel(){ + updateTypeLabel() { this.setAltText(this.typeLabel, this.songTypes[this.songTypeIndex]) } - changeType(delta){ + changeType(delta) { this.songTypeIndex = (this.songTypeIndex + delta + this.songTypes.length) % this.songTypes.length localStorage.setItem("songTypeIndex", this.songTypeIndex) this.updateTypeLabel() @@ -628,28 +637,36 @@ class SongSelect{ assets.songsDefault = songs assets.songs = assets.songsDefault new SongSelect(false, false, this.touchEnabled) - }).catch(() => {}) + }).catch(() => { }) } - - mouseDown(event){ - if(event.target === this.selectable || event.target.parentNode === this.selectable){ + + mouseDown(event) { + if (event.target === this.selectable || event.target.parentNode === this.selectable) { this.selectable.focus() - }else if(event.target.tagName !== "INPUT"){ + } else if (event.target.tagName !== "INPUT") { getSelection().removeAllRanges() this.selectable.blur() } - if(event.target !== this.canvas || !this.redrawRunning){ + if (event.target !== this.canvas || !this.redrawRunning) { return } - if(event.type === "mousedown"){ - if(event.which !== 1){ + if (this.leaderboard && this.leaderboard.visible) { + if (event.type === "mousedown") { + var x = event.offsetX + var y = event.offsetY + this.leaderboard.mouseDown(x, y) + } + return + } + if (event.type === "mousedown") { + if (event.which !== 1) { return } var mouse = this.mouseOffset(event.offsetX, event.offsetY) var shift = event.shiftKey var ctrl = event.ctrlKey var touch = false - }else{ + } else { event.preventDefault() var x = event.touches[0].pageX - this.canvas.offsetLeft var y = event.touches[0].pageY - this.canvas.offsetTop @@ -658,232 +675,234 @@ class SongSelect{ var ctrl = false var touch = true } - if(this.state.showWarning){ - if(408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550){ + if (this.state.showWarning) { + if (408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550) { this.playSound("se_don") this.state.showWarning = false this.showWarning = false } - }else if(this.state.screen === "song"){ - if(20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)){ + } else if (this.state.screen === "song") { + if (20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)) { this.categoryJump(mouse.x < 640 ? -1 : 1) - }else if(!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts){ + } else if (!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts) { this.toAccount() - }else if(p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603){ + } else if (p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603) { this.toSession() - }else if(!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs){ + } else if (!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs) { this.toSession() - }else{ + } else { var moveBy = this.songSelMouse(mouse.x, mouse.y) - if(moveBy === 0){ + if (moveBy === 0) { this.toSelectDifficulty() - }else if(moveBy !== null){ + } else if (moveBy !== null) { this.moveToSong(moveBy) } } - }else if(this.state.screen === "difficulty"){ + } else if (this.state.screen === "difficulty") { var moveBy = this.diffSelMouse(mouse.x, mouse.y) - if(mouse.x < 183 || mouse.x > 1095 || mouse.y < 54 || mouse.y > 554){ + if (mouse.x < 183 || mouse.x > 1095 || mouse.y < 54 || mouse.y > 554) { this.toSongSelect() - }else if(moveBy === 0){ + } else if (moveBy === 0) { this.selectedDiff = 0 this.toSongSelect() - }else if(moveBy === 2){ + } else if (moveBy === 1) { + this.toLeaderboard() + } else if (moveBy === 3) { this.toDownload() - }else if(moveBy === 3){ + } else if (moveBy === 4) { this.toDelete() - }else if(moveBy === 1){ + } else if (moveBy === 2) { this.toOptions(1) - }else if(moveBy === "maker"){ + } else if (moveBy === "maker") { window.open(this.songs[this.selectedSong].maker.url) - }else if(moveBy === this.diffOptions.length + 4){ + } else if (moveBy === this.diffOptions.length + 4) { this.state.ura = !this.state.ura this.playSound("se_ka", 0, p2.session ? p2.player : false) - if(this.selectedDiff === this.diffOptions.length + 4 && !this.state.ura){ + if (this.selectedDiff === this.diffOptions.length + 4 && !this.state.ura) { this.state.move = -1 } - }else if(moveBy !== null){ + } else if (moveBy !== null) { this.toLoadSong(moveBy - this.diffOptions.length, shift, ctrl, touch) } } } - touchEnd(event){ + touchEnd(event) { event.preventDefault() } - mouseWheel(event){ - if(this.state.screen === "song" && this.state.focused){ + mouseWheel(event) { + if (this.state.screen === "song" && this.state.focused) { this.wheelTimer = this.getMS() - if(event.deltaY < 0) { + if (event.deltaY < 0) { this.wheelScrolls-- - }else if(event.deltaY > 0){ + } else if (event.deltaY > 0) { this.wheelScrolls++ } } } - mouseMove(event){ + mouseMove(event) { var mouse = this.mouseOffset(event.offsetX, event.offsetY) var moveTo = null - if(this.state.showWarning){ - if(408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550){ + if (this.state.showWarning) { + if (408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550) { moveTo = "showWarning" } - }else if(this.state.screen === "song" && !this.search.opened){ - if(20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)){ + } else if (this.state.screen === "song" && !this.search.opened) { + if (20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)) { moveTo = mouse.x < 640 ? "categoryPrev" : "categoryNext" - }else if(!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts){ + } else if (!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts) { moveTo = "account" - }else if(p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603){ + } else if (p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603) { moveTo = "session" - }else if(!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs){ + } else if (!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs) { moveTo = "session" - }else{ + } else { var moveTo = this.songSelMouse(mouse.x, mouse.y) - if(moveTo === null && this.state.moveHover === 0 && !this.songs[this.selectedSong].courses){ + if (moveTo === null && this.state.moveHover === 0 && !this.songs[this.selectedSong].courses) { this.state.mouseMoveMS = this.getMS() - this.songSelecting.speed } } this.state.moveHover = moveTo - }else if(this.state.screen === "difficulty"){ + } else if (this.state.screen === "difficulty") { var moveTo = this.diffSelMouse(mouse.x, mouse.y) - if(moveTo === null && this.state.moveHover === this.selectedDiff){ + if (moveTo === null && this.state.moveHover === this.selectedDiff) { this.state.mouseMoveMS = this.getMS() - 1000 } this.state.moveHover = moveTo } this.pointer(moveTo !== null) } - mouseOffset(offsetX, offsetY){ + mouseOffset(offsetX, offsetY) { return { x: (offsetX * this.pixelRatio - this.winW / 2) / this.ratio + 1280 / 2, y: (offsetY * this.pixelRatio - this.winH / 2) / this.ratio + 720 / 2 } } - pointer(enabled){ - if(!this.canvas){ + pointer(enabled) { + if (!this.canvas) { return } - if(enabled && this.state.hasPointer === false){ + if (enabled && this.state.hasPointer === false) { this.canvas.style.cursor = "pointer" this.state.hasPointer = true - }else if(!enabled && this.state.hasPointer === true){ + } else if (!enabled && this.state.hasPointer === true) { this.canvas.style.cursor = "" this.state.hasPointer = false } } - - songSelMouse(x, y){ - if(this.state.locked === 0 && this.songAsset.marginTop <= y && y <= this.songAsset.marginTop + this.songAsset.height){ + + songSelMouse(x, y) { + if (this.state.locked === 0 && this.songAsset.marginTop <= y && y <= this.songAsset.marginTop + this.songAsset.height) { x -= 1280 / 2 var dir = x > 0 ? 1 : -1 x = Math.abs(x) var selectedWidth = this.songAsset.selectedWidth - if(!this.songs[this.selectedSong].courses){ + if (!this.songs[this.selectedSong].courses) { selectedWidth = this.songAsset.width } var moveBy = Math.ceil((x - selectedWidth / 2 - this.songAsset.marginLeft / 2) / (this.songAsset.width + this.songAsset.marginLeft)) * dir - if(moveBy / dir > 0){ + if (moveBy / dir > 0) { return moveBy - }else{ + } else { return 0 } } return null } - diffSelMouse(x, y){ - if(this.state.locked === 0){ - if(223 < x && x < 223 + 72 * this.diffOptions.length && 132 < y && y < 436){ + diffSelMouse(x, y) { + if (this.state.locked === 0) { + if (223 < x && x < 223 + 72 * this.diffOptions.length && 132 < y && y < 436) { return Math.floor((x - 223) / 72) - }else if(this.songs[this.selectedSong].maker && this.songs[this.selectedSong].maker.id > 0 && this.songs[this.selectedSong].maker.url && x > 230 && x < 485 && y > 446 && y < 533) { + } else if (this.songs[this.selectedSong].maker && this.songs[this.selectedSong].maker.id > 0 && this.songs[this.selectedSong].maker.url && x > 230 && x < 485 && y > 446 && y < 533) { return "maker" - }else if(550 < x && x < 1050 && 109 < y && y < 538){ + } else if (550 < x && x < 1050 && 109 < y && y < 538) { var moveBy = Math.floor((x - 550) / ((1050 - 550) / 5)) + this.diffOptions.length var currentSong = this.songs[this.selectedSong] - if( + if ( this.state.ura && moveBy === this.diffOptions.length + 3 || currentSong.courses[ - this.difficultyId[moveBy - this.diffOptions.length] + this.difficultyId[moveBy - this.diffOptions.length] ] - ){ + ) { return moveBy } } } return null } - - moveToSong(moveBy, fromP2){ + + moveToSong(moveBy, fromP2) { var ms = this.getMS() - if(p2.session && !fromP2){ - if(!this.state.selLock && ms > this.state.moveMS + 800){ + if (p2.session && !fromP2) { + if (!this.state.selLock && ms > this.state.moveMS + 800) { this.state.selLock = true p2.send("songsel", { song: this.mod(this.songs.length, this.selectedSong + moveBy) }) } - }else if(this.state.locked !== 1 || fromP2){ - if(this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded && (this.state.locked === 0 || fromP2)){ + } else if (this.state.locked !== 1 || fromP2) { + if (this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded && (this.state.locked === 0 || fromP2)) { this.state.moveMS = ms - }else{ + } else { this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize } this.state.move = moveBy this.state.lastMove = moveBy this.state.locked = 1 this.state.moveHover = null - + var lastMoveMul = Math.pow(Math.abs(moveBy), 1 / 4) var changeSpeed = this.songSelecting.speed * lastMoveMul var resize = changeSpeed * this.songSelecting.resize / lastMoveMul var scrollDelay = changeSpeed * this.songSelecting.scrollDelay var resize2 = changeSpeed - resize var scroll = resize2 - resize - scrollDelay * 2 - + var soundsDelay = Math.abs((scroll + resize) / moveBy) this.lastMoveBy = fromP2 ? fromP2.player : false - - for(var i = 0; i < Math.abs(moveBy) - 1; i++){ + + for (var i = 0; i < Math.abs(moveBy) - 1; i++) { this.playSound("se_ka", (resize + i * soundsDelay) / 1000, fromP2 ? fromP2.player : false) } this.pointer(false) } } - - categoryJump(moveBy, fromP2){ - if(p2.session && !fromP2){ + + categoryJump(moveBy, fromP2) { + if (p2.session && !fromP2) { var ms = this.getMS() - if(!this.state.selLock && ms > this.state.moveMS + 800){ + if (!this.state.selLock && ms > this.state.moveMS + 800) { this.state.selLock = true p2.send("catjump", { song: this.selectedSong, move: moveBy }) } - }else if(this.state.locked !== 1 || fromP2){ + } else if (this.state.locked !== 1 || fromP2) { this.state.catJump = true this.state.move = moveBy; this.state.locked = 1 - + this.endPreview() this.playSound("se_jump", 0, fromP2 ? fromP2.player : false) } } - moveToDiff(moveBy){ - if(this.state.locked !== 1){ + moveToDiff(moveBy) { + if (this.state.locked !== 1) { this.state.move = moveBy this.state.moveMS = this.getMS() - 500 this.state.locked = 1 this.playSound("se_ka", 0, p2.session ? p2.player : false) } } - - toSelectDifficulty(fromP2, playVoice=true){ + + toSelectDifficulty(fromP2, playVoice = true) { var currentSong = this.songs[this.selectedSong] - if(p2.session && !fromP2 && (!currentSong.action || !currentSong.p2Enabled)){ - if(this.songs[this.selectedSong].courses){ - if(!this.state.selLock){ + if (p2.session && !fromP2 && (!currentSong.action || !currentSong.p2Enabled)) { + if (this.songs[this.selectedSong].courses) { + if (!this.state.selLock) { this.state.selLock = true p2.send("songsel", { song: this.selectedSong, @@ -892,10 +911,10 @@ class SongSelect{ }) } } - }else if(this.state.locked === 0 || fromP2){ + } else if (this.state.locked === 0 || fromP2) { this.search.remove() - if(currentSong.courses){ - if(currentSong.unloaded){ + if (currentSong.courses) { + if (currentSong.unloaded) { return } @@ -905,168 +924,168 @@ class SongSelect{ this.state.locked = true this.state.moveHover = null this.state.ura = 0 - if(this.selectedDiff === this.diffOptions.length + 4){ + if (this.selectedDiff === this.diffOptions.length + 4) { this.selectedDiff = this.diffOptions.length + 3 } - + this.playSound("se_don", 0, fromP2 ? fromP2.player : false) assets.sounds["v_songsel"].stop() - if(!this.showWarning && prevScreen !== "difficulty" && playVoice){ + if (!this.showWarning && prevScreen !== "difficulty" && playVoice) { this.playSound("v_diffsel", 0.3) } pageEvents.send("song-select-difficulty", currentSong) - }else if(currentSong.action === "back"){ + } else if (currentSong.action === "back") { this.toTitleScreen() - }else if(currentSong.action === "random"){ - do{ + } else if (currentSong.action === "random") { + do { var i = Math.floor(Math.random() * this.songs.length) - }while(!this.songs[i].courses) + } while (!this.songs[i].courses) this.setSelectedSong(i) this.lastRandom = true this.playBgm(false) - this.toSelectDifficulty(false, playVoice=false) + this.toSelectDifficulty(false, playVoice = false) pageEvents.send("song-select-random") - }else if(currentSong.action === "search"){ + } else if (currentSong.action === "search") { this.search.display(true) - }else if(currentSong.action === "tutorial"){ + } else if (currentSong.action === "tutorial") { this.toTutorial() - }else if(currentSong.action === "about"){ + } else if (currentSong.action === "about") { this.toAbout() - }else if(currentSong.action === "settings"){ + } else if (currentSong.action === "settings") { this.toSettings() - }else if(currentSong.action === "customSongs"){ + } else if (currentSong.action === "customSongs") { this.toCustomSongs() - }else if(currentSong.action === "plugins"){ + } else if (currentSong.action === "plugins") { this.toPlugins() } - // カスタムメニューの実行処理 - else if (currentSong.action === "sourceCode") { - this.playSound("se_don"); - setTimeout(() => { - open("https://github.com/yuukialpha/taiko-web","_blank"); - }, 500); - } else if (currentSong.action === "upload") { - this.playSound("se_don"); - setTimeout(() => { - window.location.href = "https://zizhipu.taiko.asia"; - }, 100); - } else if (currentSong.action === "keijiban") { - this.playSound("se_don"); - setTimeout(() => { - window.location.href = "https://litey.trade/"; - }, 100); - } else if (currentSong.action === "songSelectingSpeed") { - this.playSound("se_don"); - setTimeout(() => { - let songSelectingSpeed = localStorage.getItem("sss") ?? "400"; - const pro = prompt("曲選択速度を入力してね!", songSelectingSpeed); - if (pro === null) { - // キャンセル - } else if (pro === "") { - songSelectingSpeed = "400"; - } else { - songSelectingSpeed = pro; - } - const preValue = localStorage.getItem("sss") ?? "400"; - localStorage.setItem("sss", songSelectingSpeed.toString()); - if (preValue !== songSelectingSpeed) { - location.reload(); - } - }, 100); - } else if (currentSong.action === "baisoku") { - this.playSound("se_don"); - setTimeout(() => { - let baisoku = localStorage.getItem("baisoku") ?? "1"; - const input = prompt("ばいそくの倍率を入力してね!", baisoku); - if (input === null) { - // キャンセル - } else if (input === "") { - baisoku = "1"; - } else { - baisoku = input; - } - localStorage.setItem("baisoku", baisoku.toString()); - }, 100); - } else if (currentSong.action === "doron") { - this.playSound("se_don"); - setTimeout(() => { - let doron = localStorage.getItem("doron") ?? "false"; - const input = prompt("ドロンを有効にするには\"true\"を入力してね!", doron); - if (input === null) { - // キャンセル - } else if (input === "") { - doron = "false"; - } else { - doron = input; - } - localStorage.setItem("doron", doron); - }, 100); - } else if (currentSong.action === "abekobe") { - this.playSound("se_don"); - setTimeout(() => { - let abekobe = localStorage.getItem("abekobe") ?? "false"; - const input = prompt("あべこべを有効にするには\"true\"を入力してね!", abekobe); - if (input === null) { - // キャンセル - } else if (input === "") { - abekobe = "false"; - } else { - abekobe = input; - } - localStorage.setItem("abekobe", abekobe); - }, 100); - } else if (currentSong.action === "detarame") { - this.playSound("se_don"); - setTimeout(() => { - let detarame = localStorage.getItem("detarame") ?? "0"; - const input = prompt("でたらめになる確率をパーセントで入力してね!", detarame); - if (input === null) { - // キャンセル - } else if (input === "") { - detarame = "0"; - } else { - detarame = input; - } - localStorage.setItem("detarame", detarame); - }, 100); - } else if (currentSong.action === "titlesort") { - this.playSound("se_don"); - setTimeout(() => { - let titlesort = localStorage.getItem("titlesort") ?? "false"; - const input = prompt("タイトル順で並べ替えするには\"true\"を入力してね!", titlesort); - if (input === null) { - // キャンセル - } else if (input === "") { - titlesort = "false"; - } else { - titlesort = input; - } - const preValue = localStorage.getItem("titlesort") ?? "false"; - localStorage.setItem("titlesort", titlesort); - if (preValue !== titlesort) { - location.reload(); - } - }, 100); - } + // カスタムメニューの実行処理 + else if (currentSong.action === "sourceCode") { + this.playSound("se_don"); + setTimeout(() => { + open("https://github.com/yuukialpha/taiko-web", "_blank"); + }, 500); + } else if (currentSong.action === "upload") { + this.playSound("se_don"); + setTimeout(() => { + window.location.href = "https://zizhipu.taiko.asia"; + }, 100); + } else if (currentSong.action === "keijiban") { + this.playSound("se_don"); + setTimeout(() => { + window.location.href = "https://litey.trade/"; + }, 100); + } else if (currentSong.action === "songSelectingSpeed") { + this.playSound("se_don"); + setTimeout(() => { + let songSelectingSpeed = localStorage.getItem("sss") ?? "400"; + const pro = prompt("曲選択速度を入力してね!", songSelectingSpeed); + if (pro === null) { + // キャンセル + } else if (pro === "") { + songSelectingSpeed = "400"; + } else { + songSelectingSpeed = pro; + } + const preValue = localStorage.getItem("sss") ?? "400"; + localStorage.setItem("sss", songSelectingSpeed.toString()); + if (preValue !== songSelectingSpeed) { + location.reload(); + } + }, 100); + } else if (currentSong.action === "baisoku") { + this.playSound("se_don"); + setTimeout(() => { + let baisoku = localStorage.getItem("baisoku") ?? "1"; + const input = prompt("ばいそくの倍率を入力してね!", baisoku); + if (input === null) { + // キャンセル + } else if (input === "") { + baisoku = "1"; + } else { + baisoku = input; + } + localStorage.setItem("baisoku", baisoku.toString()); + }, 100); + } else if (currentSong.action === "doron") { + this.playSound("se_don"); + setTimeout(() => { + let doron = localStorage.getItem("doron") ?? "false"; + const input = prompt("ドロンを有効にするには\"true\"を入力してね!", doron); + if (input === null) { + // キャンセル + } else if (input === "") { + doron = "false"; + } else { + doron = input; + } + localStorage.setItem("doron", doron); + }, 100); + } else if (currentSong.action === "abekobe") { + this.playSound("se_don"); + setTimeout(() => { + let abekobe = localStorage.getItem("abekobe") ?? "false"; + const input = prompt("あべこべを有効にするには\"true\"を入力してね!", abekobe); + if (input === null) { + // キャンセル + } else if (input === "") { + abekobe = "false"; + } else { + abekobe = input; + } + localStorage.setItem("abekobe", abekobe); + }, 100); + } else if (currentSong.action === "detarame") { + this.playSound("se_don"); + setTimeout(() => { + let detarame = localStorage.getItem("detarame") ?? "0"; + const input = prompt("でたらめになる確率をパーセントで入力してね!", detarame); + if (input === null) { + // キャンセル + } else if (input === "") { + detarame = "0"; + } else { + detarame = input; + } + localStorage.setItem("detarame", detarame); + }, 100); + } else if (currentSong.action === "titlesort") { + this.playSound("se_don"); + setTimeout(() => { + let titlesort = localStorage.getItem("titlesort") ?? "false"; + const input = prompt("タイトル順で並べ替えするには\"true\"を入力してね!", titlesort); + if (input === null) { + // キャンセル + } else if (input === "") { + titlesort = "false"; + } else { + titlesort = input; + } + const preValue = localStorage.getItem("titlesort") ?? "false"; + localStorage.setItem("titlesort", titlesort); + if (preValue !== titlesort) { + location.reload(); + } + }, 100); + } } this.pointer(false) } - toSongSelect(fromP2){ - if(p2.session && !fromP2){ - if(!this.state.selLock){ + toSongSelect(fromP2) { + if (p2.session && !fromP2) { + if (!this.state.selLock) { this.state.selLock = true p2.send("songsel", { song: this.lastRandom ? this.songs.findIndex(song => song.action === "random") : this.selectedSong }) } - - }else if(fromP2 || this.state.locked !== 1){ + + } else if (fromP2 || this.state.locked !== 1) { this.state.screen = "song" this.state.screenMS = this.getMS() this.state.locked = true this.state.moveHover = null - if(this.lastRandom){ + if (this.lastRandom) { this.endPreview(false) this.setSelectedSong(this.songs.findIndex(song => song.action === "random")) this.lastRandom = false @@ -1078,45 +1097,45 @@ class SongSelect{ this.clearHash() pageEvents.send("song-select-back") } - toLoadSong(difficulty, shift, ctrl, touch){ + toLoadSong(difficulty, shift, ctrl, touch) { this.clean() var selectedSong = this.songs[this.selectedSong] assets.sounds["v_diffsel"].stop() this.playSound("se_don", 0, p2.session ? p2.player : false) - - try{ - if(assets.customSongs){ + + try { + if (assets.customSongs) { assets.customSelected = this.selectedSong localStorage["customSelected"] = this.selectedSong - }else{ + } else { localStorage["selectedSong"] = this.selectedSong } localStorage["selectedDiff"] = difficulty + this.diffOptions.length - }catch(e){} - - if(difficulty === 3 && this.state.ura){ + } catch (e) { } + + if (difficulty === 3 && this.state.ura) { difficulty = 4 } var autoplay = false var multiplayer = false - if(p2.session || this.state.options === 2){ + if (p2.session || this.state.options === 2) { multiplayer = true - }else if(this.state.options === 1){ + } else if (this.state.options === 1) { autoplay = true - }else if(shift){ + } else if (shift) { autoplay = shift - }else if(p2.socket && p2.socket.readyState === 1 && !assets.customSongs){ + } else if (p2.socket && p2.socket.readyState === 1 && !assets.customSongs) { multiplayer = ctrl } var diff = this.difficultyId[difficulty] - + new LoadSong({ "title": selectedSong.title, "originalTitle": selectedSong.originalTitle, "folder": selectedSong.id, "difficulty": diff, "category": selectedSong.category, - "category_id":selectedSong.category_id, + "category_id": selectedSong.category_id, "type": selectedSong.type, "offset": selectedSong.offset, "songSkin": selectedSong.songSkin, @@ -1126,17 +1145,17 @@ class SongSelect{ "video": selectedSong.video, }, autoplay, multiplayer, touch) } - toOptions(moveBy){ - if(!p2.session){ + toOptions(moveBy) { + if (!p2.session) { this.playSound("se_ka", 0, p2.session ? p2.player : false) this.selectedDiff = 1 - do{ + do { this.state.options = this.mod(this.optionsList.length, this.state.options + moveBy) - }while((!p2.socket || p2.socket.readyState !== 1 || assets.customSongs) && this.state.options === 2) + } while ((!p2.socket || p2.socket.readyState !== 1 || assets.customSongs) && this.state.options === 2) } } - toTitleScreen(){ - if(!p2.session){ + toTitleScreen() { + if (!p2.session) { this.playSound("se_cancel") this.clean() setTimeout(() => { @@ -1144,45 +1163,45 @@ class SongSelect{ }, 500) } } - toTutorial(){ + toTutorial() { this.playSound("se_don") this.clean() setTimeout(() => { new Tutorial(true) }, 500) } - toAbout(){ + toAbout() { this.playSound("se_don") this.clean() setTimeout(() => { new About(this.touchEnabled) }, 500) } - toSettings(){ + toSettings() { this.playSound("se_don") this.clean() setTimeout(() => { new SettingsView(this.touchEnabled) }, 500) } - toAccount(){ + toAccount() { this.playSound("se_don") this.clean() setTimeout(() => { new Account(this.touchEnabled) }, 500) } - toSession(){ - if(p2.socket.readyState !== 1 || assets.customSongs){ + toSession() { + if (p2.socket.readyState !== 1 || assets.customSongs) { return } - if(p2.session){ + if (p2.session) { this.playSound("se_don") p2.send("gameend") this.state.moveHover = null - }else{ + } else { localStorage["selectedSong"] = this.selectedSong - + this.playSound("se_don") this.clean() setTimeout(() => { @@ -1190,8 +1209,8 @@ class SongSelect{ }, 500) } } - toCustomSongs(){ - if(assets.customSongs){ + toCustomSongs() { + if (assets.customSongs) { assets.customSongs = false assets.songs = assets.songsDefault delete assets.otherFiles @@ -1203,9 +1222,9 @@ class SongSelect{ localStorage.removeItem("customSelected") db.removeItem("customFolder") pageEvents.send("import-songs-default") - }else{ + } else { localStorage["selectedSong"] = this.selectedSong - + this.playSound("se_don") this.clean() setTimeout(() => { @@ -1213,47 +1232,47 @@ class SongSelect{ }, 500) } } - toPlugins(){ + toPlugins() { this.playSound("se_don") this.clean() setTimeout(() => { new SettingsView(this.touchEnabled, false, undefined, undefined, plugins.getSettings()) }, 500) } - - redraw(){ - if(!this.redrawRunning){ + + redraw() { + if (!this.redrawRunning) { return } requestAnimationFrame(this.redrawBind) var ms = this.getMS() - - for(var key in this.pressedKeys){ - if(this.pressedKeys[key]){ - if(ms >= this.pressedKeys[key] + (this.state.screen === "song" && (key === "right" || key === "left") ? 20 : 50)){ + + for (var key in this.pressedKeys) { + if (this.pressedKeys[key]) { + if (ms >= this.pressedKeys[key] + (this.state.screen === "song" && (key === "right" || key === "left") ? 20 : 50)) { this.keyPress(true, key, null, true) this.pressedKeys[key] = ms } } } - - if(!this.redrawRunning){ + + if (!this.redrawRunning) { return } - + var ctx = this.ctx var winW = innerWidth var winH = lastHeight - if(winW / 32 > winH / 9){ + if (winW / 32 > winH / 9) { winW = winH / 9 * 32 } 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 @@ -1261,47 +1280,47 @@ class SongSelect{ var ratioX = winW / 1280 var ratioY = winH / 720 var ratio = (ratioX < ratioY ? ratioX : ratioY) - if(this.winW !== winW || this.winH !== winH){ + 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" - + var borders = (this.songAsset.border + this.songAsset.innerBorder) * 2 var songsLength = Math.ceil(winW / ratio / (this.songAsset.width + this.songAsset.marginLeft)) + 1 - + this.songTitleCache.resize( (this.songAsset.width - borders + 1) * songsLength, this.songAsset.height - borders + 1, ratio + 0.2 ) - + this.currentSongCache.resize( (this.songAsset.width - borders + 1) * 2, this.songAsset.height - borders + 1, ratio + 0.2 ) - + var textW = strings.id === "en" ? 350 : 280 this.selectTextCache.resize((textW + 53 + 60 + 1) * 2, this.songAsset.marginTop + 15, ratio + 0.5) - + this.nameplateCache.resize(274, 134, ratio + 0.2) - + var lastCategory this.songs.forEach(song => { var cat = (song.category || "") + song.skin.outline - if(lastCategory !== cat){ + if (lastCategory !== cat) { lastCategory = cat } }) - this.categoryCache.resize(280, this.songAsset.marginTop + 1 , ratio + 0.5) - + this.categoryCache.resize(280, this.songAsset.marginTop + 1, ratio + 0.5) + this.difficultyCache.resize((44 + 56 + 2) * 5, 135 + 10, ratio + 0.5) - + var w = winW / ratio / 2 this.sessionCache.resize(w, 39 * 2, ratio + 0.5) - for(var id in this.sessionText){ + for (var id in this.sessionText) { this.sessionCache.set({ w: w, h: 38, @@ -1318,28 +1337,28 @@ class SongSelect{ align: "center", baseline: "middle" }, [ - {outline: "#000", letterBorder: 8}, - {fill: "#fff"} + { outline: "#000", letterBorder: 8 }, + { fill: "#fff" } ]) }) } - + this.selectableText = "" - - if(this.search.opened && this.search.container){ + + if (this.search.opened && this.search.container) { this.search.onInput(true) } - }else if(!document.hasFocus() && !p2.session){ - if(this.state.focused){ + } else if (!document.hasFocus() && !p2.session) { + if (this.state.focused) { this.state.focused = false this.songSelect.classList.add("unfocused") this.pressedKeys = {} } return - }else{ + } else { ctx.clearRect(0, 0, winW / ratio, winH / ratio) } - if(!this.state.focused){ + if (!this.state.focused) { this.state.focused = true this.songSelect.classList.remove("unfocused") } @@ -1348,7 +1367,7 @@ class SongSelect{ this.ratio = ratio winW /= ratio winH /= ratio - + var frameTop = winH / 2 - 720 / 2 var frameLeft = winW / 2 - 1280 / 2 var songTop = frameTop + this.songAsset.marginTop @@ -1356,44 +1375,44 @@ class SongSelect{ var songSelMoving = false var screen = this.state.screen var selectedWidth = this.songAsset.width - + this.search.redraw() - - if(this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) { - if(p2.session){ + + if (this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) { + if (p2.session) { this.moveToSong(this.wheelScrolls) - }else{ + } else { this.state.move = this.wheelScrolls this.state.waitPreview = ms + 400 this.endPreview() } this.wheelScrolls = 0 } - - if(screen === "title" || screen === "titleFadeIn"){ - if(ms > this.state.screenMS + 1000){ + + if (screen === "title" || screen === "titleFadeIn") { + if (ms > this.state.screenMS + 1000) { this.state.screen = "song" this.state.screenMS = ms + (ms - this.state.screenMS - 1000) this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize + (ms - this.state.screenMS) this.state.locked = 3 this.state.lastMove = 1 - }else{ + } else { this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize + (ms - this.state.screenMS - 1000) } - if(screen === "titleFadeIn" && ms > this.state.screenMS + 500){ + if (screen === "titleFadeIn" && ms > this.state.screenMS + 500) { this.state.screen = "title" screen = "title" } } - - if((screen === "song" || screen === "difficulty") && (this.showWarning && !this.showWarning.shown || scoreStorage.scoreSaveFailed)){ - if(!this.showWarning){ - this.showWarning = {name: "scoreSaveFailed"} + + if ((screen === "song" || screen === "difficulty") && (this.showWarning && !this.showWarning.shown || scoreStorage.scoreSaveFailed)) { + if (!this.showWarning) { + this.showWarning = { name: "scoreSaveFailed" } } - if(this.bgmEnabled){ + if (this.bgmEnabled) { this.playBgm(false) } - if(this.showWarning.name === "scoreSaveFailed"){ + if (this.showWarning.name === "scoreSaveFailed") { scoreStorage.scoreSaveFailed = false } this.showWarning.shown = true @@ -1401,8 +1420,8 @@ class SongSelect{ this.state.locked = true this.playSound("se_pause") } - - if(screen === "title" || screen === "titleFadeIn" || screen === "song"){ + + if (screen === "title" || screen === "titleFadeIn" || screen === "song") { var textW = strings.id === "en" ? 350 : 280 this.selectTextCache.get({ ctx: ctx, @@ -1423,16 +1442,16 @@ class SongSelect{ letterSpacing: strings.id === "en" ? 0 : 2, forceShadow: true }, [ - {x: -2, y: -2, outline: "#000", letterBorder: 22}, + { x: -2, y: -2, outline: "#000", letterBorder: 22 }, {}, - {x: 2, y: 2, shadow: [3, 3, 3]}, - {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: [3, 3, 3] }, + { x: 2, y: 2, outline: "#ad1516", letterBorder: 10 }, + { x: -2, y: -2, outline: "#ff797b" }, + { outline: "#f70808" }, + { fill: "#fff", shadow: [-1, 1, 3, 1.5] } ]) }) - + var selectedSong = this.songs[this.selectedSong] var category = selectedSong.category this.draw.category({ @@ -1458,11 +1477,11 @@ class SongSelect{ h: this.songAsset.marginTop, id: category + selectedSong.skin.outline }, ctx => { - if(category){ - let cat = assets.categories.find(cat=>cat.title === category) - if(cat){ + if (category) { + let cat = assets.categories.find(cat => cat.title === category) + if (cat) { var categoryName = this.getLocalTitle(cat.title, cat.title_lang) - }else{ + } else { var categoryName = category } this.draw.layeredText({ @@ -1476,18 +1495,18 @@ class SongSelect{ align: "center", forceShadow: true }, [ - {outline: selectedSong.skin.outline, letterBorder: 12, shadow: [3, 3, 3]}, - {fill: "#fff"} + { outline: selectedSong.skin.outline, letterBorder: 12, shadow: [3, 3, 3] }, + { fill: "#fff" } ]) } }) } - - if(screen === "song"){ - if(this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded){ + + if (screen === "song") { + if (this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded) { selectedWidth = this.songAsset.selectedWidth } - + var lastMoveMul = Math.pow(Math.abs(this.state.lastMove || 0), 1 / 4) var changeSpeed = this.songSelecting.speed * lastMoveMul var resize = changeSpeed * (lastMoveMul === 0 ? 0 : this.songSelecting.resize / lastMoveMul) @@ -1495,46 +1514,46 @@ class SongSelect{ var resize2 = changeSpeed - resize var scroll = resize2 - resize - scrollDelay * 2 var elapsed = ms - this.state.moveMS - - if(this.state.catJump || (this.state.move && ms > this.state.moveMS + resize2 - scrollDelay)){ + + if (this.state.catJump || (this.state.move && ms > this.state.moveMS + resize2 - scrollDelay)) { var isJump = this.state.catJump var previousSelectedSong = this.selectedSong - - if(!isJump){ + + if (!isJump) { this.playSound("se_ka", 0, this.lastMoveBy) this.setSelectedSong(this.mod(this.songs.length, this.selectedSong + this.state.move)) - }else{ + } else { var currentCat = this.songs[this.selectedSong].category var currentIdx = this.mod(this.songs.length, this.selectedSong) - if(this.state.move > 0){ + if (this.state.move > 0) { var nextSong = this.songs.find(song => this.mod(this.songs.length, this.songs.indexOf(song)) > currentIdx && song.category !== currentCat && song.canJump) - if(!nextSong){ + if (!nextSong) { nextSong = this.songs[0] } - }else{ + } else { var isFirstInCat = this.songs.findIndex(song => song.category === currentCat) == this.selectedSong - if(!isFirstInCat){ + if (!isFirstInCat) { var nextSong = this.songs.find(song => this.mod(this.songs.length, this.songs.indexOf(song)) < currentIdx && song.category === currentCat && song.canJump) - }else{ + } else { var idx = this.songs.length - 1 var nextSong var lastCat - for(;idx>=0;idx--){ - if(this.songs[idx].category !== lastCat && this.songs[idx].action !== "back"){ + for (; idx >= 0; idx--) { + if (this.songs[idx].category !== lastCat && this.songs[idx].action !== "back") { lastCat = this.songs[idx].category - if(nextSong){ + if (nextSong) { break } } - if(lastCat !== currentCat && idx < currentIdx){ + if (lastCat !== currentCat && idx < currentIdx) { nextSong = idx } } nextSong = this.songs[nextSong] } - if(!nextSong){ + if (!nextSong) { var rev = [...this.songs].reverse() nextSong = rev.find(song => song.canJump) } @@ -1544,98 +1563,98 @@ class SongSelect{ this.state.catJump = false } - if(previousSelectedSong !== this.selectedSong){ + if (previousSelectedSong !== this.selectedSong) { pageEvents.send("song-select-move", this.songs[this.selectedSong]) } this.state.move = 0 this.state.locked = 2 - if(assets.customSongs){ + if (assets.customSongs) { assets.customSelected = this.selectedSong localStorage["customSelected"] = this.selectedSong - }else if(!p2.session){ - try{ + } else if (!p2.session) { + try { localStorage["selectedSong"] = this.selectedSong - }catch(e){} + } catch (e) { } } } - if(this.state.moveMS && ms < this.state.moveMS + changeSpeed){ + if (this.state.moveMS && ms < this.state.moveMS + changeSpeed) { xOffset = Math.min(scroll, Math.max(0, elapsed - resize - scrollDelay)) / scroll * (this.songAsset.width + this.songAsset.marginLeft) xOffset *= -this.state.move - if(elapsed < resize){ + if (elapsed < resize) { selectedWidth = this.songAsset.width + (((resize - elapsed) / resize) * (selectedWidth - this.songAsset.width)) - }else if(elapsed > resize2){ + } else if (elapsed > resize2) { this.playBgm(!this.songs[this.selectedSong].courses) this.state.locked = 1 selectedWidth = this.songAsset.width + ((elapsed - resize2) / resize * (selectedWidth - this.songAsset.width)) - }else{ + } else { songSelMoving = true selectedWidth = this.songAsset.width } - }else{ - if(this.previewing !== "muted"){ + } else { + if (this.previewing !== "muted") { this.playBgm(!this.songs[this.selectedSong].courses) } this.state.locked = 0 } - }else if(screen === "difficulty"){ + } else if (screen === "difficulty") { var currentSong = this.songs[this.selectedSong] - if(this.state.locked){ + if (this.state.locked) { this.state.locked = 0 } - if(this.state.move){ + if (this.state.move) { var hasUra = currentSong.courses.ura var previousSelection = this.selectedDiff - do{ - if(hasUra && this.state.move > 0){ + do { + if (hasUra && this.state.move > 0) { this.selectedDiff += this.state.move - if(this.selectedDiff > this.diffOptions.length + 4){ + if (this.selectedDiff > this.diffOptions.length + 4) { this.state.ura = !this.state.ura - if(this.state.ura){ + if (this.state.ura) { this.selectedDiff = previousSelection === this.diffOptions.length + 3 ? this.diffOptions.length + 4 : previousSelection break - }else{ + } else { this.state.move = -1 } } - }else{ + } else { this.selectedDiff = this.mod(this.diffOptions.length + 5, this.selectedDiff + this.state.move) } - }while( + } while ( this.selectedDiff >= this.diffOptions.length && !currentSong.courses[this.difficultyId[this.selectedDiff - this.diffOptions.length]] || this.selectedDiff === this.diffOptions.length + 3 && this.state.ura || this.selectedDiff === this.diffOptions.length + 4 && !this.state.ura ) this.state.move = 0 - }else if(this.selectedDiff < 0 || this.selectedDiff >= this.diffOptions.length && !currentSong.courses[this.difficultyId[this.selectedDiff - this.diffOptions.length]]){ + } else if (this.selectedDiff < 0 || this.selectedDiff >= this.diffOptions.length && !currentSong.courses[this.difficultyId[this.selectedDiff - this.diffOptions.length]]) { this.selectedDiff = 0 } } - - if(songSelMoving){ - if(this.previewing !== null){ + + if (songSelMoving) { + if (this.previewing !== null) { this.endPreview() } - }else if(screen !== "title" && screen !== "titleFadeIn" && ms > this.state.moveMS + 100){ - if(this.previewing !== "muted" && this.previewing !== this.selectedSong && "id" in this.songs[this.selectedSong]){ + } else if (screen !== "title" && screen !== "titleFadeIn" && ms > this.state.moveMS + 100) { + if (this.previewing !== "muted" && this.previewing !== this.selectedSong && "id" in this.songs[this.selectedSong]) { this.startPreview() } } - + this.songFrameCache = { w: this.songAsset.width + this.songAsset.selectedWidth + this.songAsset.fullWidth + (15 + 1) * 3, h: this.songAsset.fullHeight + 16, ratio: ratio } - - if(screen === "title" || screen === "titleFadeIn" || screen === "song"){ - for(var i = this.selectedSong - 1; ; i--){ + + if (screen === "title" || screen === "titleFadeIn" || screen === "song") { + for (var i = this.selectedSong - 1; ; i--) { var highlight = 0 - if(i - this.selectedSong === this.state.moveHover){ + if (i - this.selectedSong === this.state.moveHover) { highlight = 1 } var index = this.mod(this.songs.length, i) var _x = winW / 2 - (this.selectedSong - i) * (this.songAsset.width + this.songAsset.marginLeft) - selectedWidth / 2 + xOffset - if(_x + this.songAsset.width + this.songAsset.marginLeft < 0){ + if (_x + this.songAsset.width + this.songAsset.marginLeft < 0) { break } this.drawClosedSong({ @@ -1648,16 +1667,16 @@ class SongSelect{ }) } var startFrom - for(var i = this.selectedSong + 1; ; i++){ + for (var i = this.selectedSong + 1; ; i++) { var _x = winW / 2 + (i - this.selectedSong - 1) * (this.songAsset.width + this.songAsset.marginLeft) + this.songAsset.marginLeft + selectedWidth / 2 + xOffset - if(_x > winW){ + if (_x > winW) { startFrom = i - 1 break } } - for(var i = startFrom; i > this.selectedSong ; i--){ + for (var i = startFrom; i > this.selectedSong; i--) { var highlight = 0 - if(i - this.selectedSong === this.state.moveHover){ + if (i - this.selectedSong === this.state.moveHover) { highlight = 1 } var index = this.mod(this.songs.length, i) @@ -1673,37 +1692,37 @@ class SongSelect{ }) } } - + var currentSong = this.songs[this.selectedSong] var highlight = 0 - if(!currentSong.courses){ + if (!currentSong.courses) { highlight = 2 } - if(this.state.moveHover === 0){ + if (this.state.moveHover === 0) { highlight = 1 } var selectedSkin = this.songSkin.selected - if(screen === "title" || screen === "titleFadeIn" || this.state.locked === 3 || currentSong.unloaded){ + if (screen === "title" || screen === "titleFadeIn" || this.state.locked === 3 || currentSong.unloaded) { selectedSkin = currentSong.skin highlight = 2 - }else if(songSelMoving){ + } else if (songSelMoving) { selectedSkin = currentSong.skin highlight = 0 } var selectedHeight = this.songAsset.height - if(screen === "difficulty"){ + if (screen === "difficulty") { selectedWidth = this.songAsset.fullWidth selectedHeight = this.songAsset.fullHeight highlight = 0 } - - if(this.lastCurrentSong.title !== currentSong.title || this.lastCurrentSong.subtitle !== currentSong.subtitle){ + + if (this.lastCurrentSong.title !== currentSong.title || this.lastCurrentSong.subtitle !== currentSong.subtitle) { this.lastCurrentSong.title = currentSong.title this.lastCurrentSong.subtitle = currentSong.subtitle this.currentSongCache.clear() } - - if(selectedWidth === this.songAsset.width){ + + if (selectedWidth === this.songAsset.width) { this.drawSongCrown({ ctx: ctx, song: currentSong, @@ -1711,7 +1730,7 @@ class SongSelect{ y: songTop + this.songAsset.height - selectedHeight }) } - + this.draw.songFrame({ ctx: ctx, x: winW / 2 - selectedWidth / 2 + xOffset, @@ -1730,10 +1749,10 @@ class SongSelect{ disabled: p2.session && currentSong.action && !currentSong.p2Enabled, innerContent: (x, y, w, h) => { ctx.strokeStyle = "#000" - if(screen === "title" || screen === "titleFadeIn" || screen === "song"){ + if (screen === "title" || screen === "titleFadeIn" || screen === "song") { var opened = ((selectedWidth - this.songAsset.width) / (this.songAsset.selectedWidth - this.songAsset.width)) var songSel = true - }else{ + } else { var textW = strings.id === "en" ? 350 : 280 this.selectTextCache.get({ ctx: ctx, @@ -1753,19 +1772,19 @@ class SongSelect{ width: textW, forceShadow: true }, [ - {x: -2, y: -2, outline: "#000", letterBorder: 23}, + { x: -2, y: -2, outline: "#000", letterBorder: 23 }, {}, - {x: 2, y: 2, shadow: [3, 3, 3]}, - {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: [3, 3, 3] }, + { x: 2, y: 2, outline: "#ad1516", letterBorder: 10 }, + { x: -2, y: -2, outline: "#ff797b" }, + { outline: "#f70808" }, + { fill: "#fff", shadow: [-1, 1, 3, 1.5] } ]) }) var opened = 1 var songSel = false - - for(var i = 0; i < this.diffOptions.length; i++){ + + for (var i = 0; i < this.diffOptions.length; i++) { var _x = x + 62 + i * 72 var _y = y + 67 ctx.fillStyle = this.diffOptions[i].fill @@ -1790,12 +1809,12 @@ class SongSelect{ y: _y + 28, iconName: this.diffOptions[i].iconName }) - + var text = this.diffOptions[i].text - if(this.diffOptions[i].iconName === "options" && (this.selectedDiff === i || this.state.options !== 0)){ + if (this.diffOptions[i].iconName === "options" && (this.selectedDiff === i || this.state.options !== 0)) { text = this.optionsList[this.state.options] } - + this.draw.verticalText({ ctx: ctx, text: text, @@ -1811,14 +1830,14 @@ class SongSelect{ fontFamily: this.font, letterSpacing: this.diffOptions[i].letterSpacing }) - + var highlight = 0 - if(this.state.moveHover === i){ + if (this.state.moveHover === i) { highlight = 2 - }else if(this.selectedDiff === i){ + } else if (this.selectedDiff === i) { highlight = 1 } - if(highlight){ + if (highlight) { this.draw.highlight({ ctx: ctx, x: _x - 32, @@ -1830,7 +1849,7 @@ class SongSelect{ opacity: highlight === 2 ? 0.8 : 1, radius: 24 }) - if(this.selectedDiff === i && !this.touchEnabled){ + if (this.selectedDiff === i && !this.touchEnabled) { this.draw.diffCursor({ ctx: ctx, font: this.font, @@ -1843,24 +1862,24 @@ class SongSelect{ } } var drawDifficulty = (ctx, i, currentUra) => { - if(currentSong.courses[this.difficultyId[i]] || currentUra){ + if (currentSong.courses[this.difficultyId[i]] || currentUra) { var crownDiff = currentUra ? "ura" : this.difficultyId[i] var players = p2.session ? 2 : 1 var score = [scoreStorage.get(currentSong.hash, false, true)] - if(p2.session){ + if (p2.session) { score[p2.player === 1 ? "push" : "unshift"](scoreStorage.getP2(currentSong.hash, false, true)) } var reversed = false - for(var a = players; a--;){ + for (var a = players; a--;) { var crownType = "" var p = reversed ? -(a - 1) : a - if(score[p] && score[p][crownDiff]){ + if (score[p] && score[p][crownDiff]) { crownType = score[p][crownDiff].crown } - if(!reversed && players === 2 && p === 1 && crownType){ + if (!reversed && players === 2 && p === 1 && crownType) { reversed = true a++ - }else{ + } else { this.draw.crown({ ctx: ctx, type: crownType, @@ -1871,7 +1890,7 @@ class SongSelect{ }) } } - if(songSel && !this.state.move){ + if (songSel && !this.state.move) { var _x = x + 33 + i * 60 var _y = y + 120 ctx.fillStyle = currentUra ? "#006279" : "#ff9f18" @@ -1887,7 +1906,7 @@ class SongSelect{ scale: 1, border: 6 }) - }else{ + } else { var _x = x + 402 + i * 100 var _y = y + 87 this.draw.diffIcon({ @@ -1946,7 +1965,7 @@ class SongSelect{ var moveMS = Math.max(this.state.moveMS, this.state.mouseMoveMS) var elapsedMS = this.state.screenMS > moveMS || !songSel ? this.state.screenMS : moveMS var fade = ((ms - elapsedMS) % 2000) / 2000 - if(songBranch && fade > 0.25 && fade < 0.75){ + if (songBranch && fade > 0.25 && fade < 0.75) { this.draw.verticalText({ ctx: ctx, text: strings.songBranch, @@ -1960,19 +1979,19 @@ class SongSelect{ outline: songSel ? false : "#f22666", outlineSize: songSel ? 0 : this.songAsset.letterBorder }) - }else{ - for(var j = 0; j < 10; j++){ - if(songSel){ + } else { + for (var j = 0; j < 10; j++) { + if (songSel) { var yPos = _y + 113 + j * 17 - }else{ + } else { var yPos = _y + 178 + j * 19.5 } - if(10 - j > songStars){ + if (10 - j > songStars) { ctx.fillStyle = currentUra ? "#187085" : (songSel ? "#e97526" : "#e7e7e7") ctx.beginPath() ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2) ctx.fill() - }else{ + } else { this.draw.diffStar({ ctx: ctx, songSel: songSel, @@ -1985,17 +2004,17 @@ class SongSelect{ } } var currentDiff = this.selectedDiff - this.diffOptions.length - if(this.selectedDiff === 4 + this.diffOptions.length){ + if (this.selectedDiff === 4 + this.diffOptions.length) { currentDiff = 3 } - if(!songSel){ + if (!songSel) { var highlight = 0 - if(this.state.moveHover - this.diffOptions.length === i){ + if (this.state.moveHover - this.diffOptions.length === i) { highlight = 2 - }else if(currentDiff === i){ + } else if (currentDiff === i) { highlight = 1 } - if(currentDiff === i && !this.touchEnabled){ + if (currentDiff === i && !this.touchEnabled) { this.draw.diffCursor({ ctx: ctx, font: this.font, @@ -2005,7 +2024,7 @@ class SongSelect{ two: p2.session && p2.player === 2 }) } - if(highlight){ + if (highlight) { this.draw.highlight({ ctx: ctx, x: _x - 32, @@ -2020,18 +2039,18 @@ class SongSelect{ } } } - for(var i = 0; currentSong.courses && i < 4; i++){ + for (var i = 0; currentSong.courses && i < 4; i++) { var currentUra = i === 3 && (this.state.ura && !songSel || currentSong.courses.ura && songSel) - if(songSel && currentUra){ + if (songSel && currentUra) { drawDifficulty(ctx, i, false) var elapsedMS = Math.max(this.state.screenMS, this.state.moveMS, this.state.mouseMoveMS) var fade = ((ms - elapsedMS) % 4000) / 4000 var alphaFade = 0 - if(fade > 0.95){ + if (fade > 0.95) { alphaFade = this.draw.easeOut(1 - (fade - 0.95) * 20) - }else if(fade > 0.5){ + } else if (fade > 0.5) { alphaFade = 1 - }else if(fade > 0.45){ + } else if (fade > 0.45) { alphaFade = this.draw.easeIn((fade - 0.45) * 20) } this.draw.alpha(alphaFade, ctx, ctx => { @@ -2039,16 +2058,16 @@ class SongSelect{ ctx.fillRect(x + 7 + i * 60, y + 60, 52, 352) drawDifficulty(ctx, i, true) }, winW, winH) - }else{ + } else { drawDifficulty(ctx, i, currentUra) } } - for(var i = 0; currentSong.courses && i < 4; i++){ - if(!songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1){ + for (var i = 0; currentSong.courses && i < 4; i++) { + if (!songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1) { var _x = x + 402 + i * 100 var _y = y + 87 var currentDiff = this.selectedDiff - this.diffOptions.length - if(this.selectedDiff === 4 + this.diffOptions.length){ + if (this.selectedDiff === 4 + this.diffOptions.length) { currentDiff = 3 } this.draw.diffCursor({ @@ -2062,14 +2081,14 @@ class SongSelect{ }) } } - + var borders = (this.songAsset.border + this.songAsset.innerBorder) * 2 var textW = this.songAsset.width - borders var textH = this.songAsset.height - borders var textX = Math.max(w - 37 - textW / 2, w / 2 - textW / 2) var textY = opened * 12 + (1 - opened) * 7 - - if(currentSong.subtitle){ + + if (currentSong.subtitle) { this.currentSongCache.get({ ctx: ctx, x: x + textX - textW, @@ -2094,22 +2113,22 @@ class SongSelect{ }) }) } - + var hasMaker = currentSong.maker || currentSong.maker === 0 var hasVideo = currentSong.video || currentSong.video === 0 - if(hasMaker || currentSong.lyrics){ + if (hasMaker || currentSong.lyrics) { if (songSel) { var _x = x + 38 var _y = y + 10 ctx.strokeStyle = "#000" ctx.lineWidth = 5 - - if(hasMaker){ + + if (hasMaker) { var grd = ctx.createLinearGradient(_x, _y, _x, _y + 50) grd.addColorStop(0, "#fa251a") grd.addColorStop(1, "#ffdc33") ctx.fillStyle = grd - }else{ + } else { ctx.fillStyle = "#000" } this.draw.roundedRect({ @@ -2122,8 +2141,8 @@ class SongSelect{ }) ctx.fill() ctx.stroke() - - if(hasMaker){ + + if (hasMaker) { this.draw.layeredText({ ctx: ctx, text: strings.creative.creative, @@ -2135,10 +2154,10 @@ class SongSelect{ y: _y + (strings.id === "ja" || strings.id === "en" ? 25 : 28), width: 172 }, [ - {outline: "#fff", letterBorder: 6}, - {fill: "#000"} + { outline: "#fff", letterBorder: 6 }, + { fill: "#000" } ]) - }else{ + } else { this.draw.layeredText({ ctx: ctx, text: strings.withLyrics, @@ -2150,15 +2169,15 @@ class SongSelect{ y: _y + (strings.id === "ja" || strings.id === "en" ? 25 : 28), width: 172 }, [ - {fill: currentSong.skin.border[0]} + { fill: currentSong.skin.border[0] } ]) } - } else if(currentSong.maker && currentSong.maker.id > 0 && currentSong.maker.name){ + } else if (currentSong.maker && currentSong.maker.id > 0 && currentSong.maker.name) { var _x = x + 62 var _y = y + 380 ctx.lineWidth = 5 - var grd = ctx.createLinearGradient(_x, _y, _x, _y+50); + var grd = ctx.createLinearGradient(_x, _y, _x, _y + 50); grd.addColorStop(0, '#fa251a'); grd.addColorStop(1, '#ffdc33'); @@ -2187,8 +2206,8 @@ class SongSelect{ x: _x - 15, y: _y + 23 }, [ - {outline: "#000", letterBorder: 8}, - {fill: "#fff"} + { outline: "#000", letterBorder: 8 }, + { fill: "#fff" } ]) this.draw.layeredText({ @@ -2202,11 +2221,11 @@ class SongSelect{ y: _y + 56, width: 210 }, [ - {outline: "#fff", letterBorder: 8}, - {fill: "#000"} + { outline: "#fff", letterBorder: 8 }, + { fill: "#000" } ]) - if(this.state.moveHover === "maker"){ + if (this.state.moveHover === "maker") { this.draw.highlight({ ctx: ctx, x: _x - 32, @@ -2219,10 +2238,10 @@ class SongSelect{ } } } - - for(var i = 0; currentSong.courses && i < 4; i++){ - if(currentSong.courses[this.difficultyId[i]] || currentUra){ - if(songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1){ + + for (var i = 0; currentSong.courses && i < 4; i++) { + if (currentSong.courses[this.difficultyId[i]] || currentUra) { + if (songSel && i === currentSong.p2Cursor && p2.socket.readyState === 1) { var _x = x + 33 + i * 60 var _y = y + 120 this.draw.diffCursor({ @@ -2237,8 +2256,8 @@ class SongSelect{ } } } - - if(!songSel && currentSong.courses.ura){ + + if (!songSel && currentSong.courses.ura) { var fade = ((ms - this.state.screenMS) % 1200) / 1200 var _x = x + 402 + 4 * 100 + fade * 25 var _y = y + 258 @@ -2316,7 +2335,7 @@ class SongSelect{ fontFamily: this.font }) } - if(selectedSkin.outline === "#000"){ + if (selectedSkin.outline === "#000") { this.currentSongCache.get({ ctx: ctx, x: x + textX, @@ -2325,7 +2344,7 @@ class SongSelect{ h: textH, id: "title", }, verticalTitle) - }else{ + } else { this.songTitleCache.get({ ctx: ctx, x: x + textX, @@ -2335,7 +2354,7 @@ class SongSelect{ id: currentSong.title + selectedSkin.outline, }, verticalTitle) } - if(!songSel && this.selectableText !== currentSong.title){ + if (!songSel && this.selectableText !== currentSong.title) { this.draw.verticalText({ ctx: ctx, text: currentSong.title, @@ -2354,13 +2373,13 @@ class SongSelect{ } } }) - - if(screen !== "difficulty" && this.selectableText){ + + if (screen !== "difficulty" && this.selectableText) { this.selectableText = "" this.selectable.style.display = "none" } - - if(songSelMoving){ + + if (songSelMoving) { this.draw.highlight({ ctx: ctx, x: winW / 2 - selectedWidth / 2, @@ -2370,7 +2389,7 @@ class SongSelect{ opacity: 0.8 }) } - + ctx.fillStyle = "#000" ctx.fillRect(0, frameTop + 595, 1280 + frameLeft * 2, 125 + frameTop) var x = 0 @@ -2402,11 +2421,11 @@ class SongSelect{ ctx.lineTo(x + w - 4, y + h) ctx.lineTo(x + w - 4, y + 4) ctx.fill() - - if(!p2.session || p2.player === 1){ + + if (!p2.session || p2.player === 1) { var name = account.loggedIn ? account.displayName : strings.defaultName var rank = account.loggedIn || !gameConfig.accounts || p2.session ? false : strings.notLoggedIn - }else{ + } else { var name = p2.name || strings.defaultName var rank = false } @@ -2427,7 +2446,7 @@ class SongSelect{ font: this.font }) }) - if(this.state.moveHover === "account"){ + if (this.state.moveHover === "account") { this.draw.highlight({ ctx: ctx, x: frameLeft + 59.5, @@ -2439,8 +2458,8 @@ class SongSelect{ size: 10 }) } - - if(p2.session){ + + if (p2.session) { x = x + w + 4 w = 396 this.draw.pattern({ @@ -2470,7 +2489,7 @@ class SongSelect{ ctx.lineTo(x + w - 4, y + h) ctx.lineTo(x + w - 4, y + 4) ctx.fill() - if(this.state.moveHover === "session"){ + if (this.state.moveHover === "session") { this.draw.highlight({ ctx: ctx, x: x, @@ -2481,10 +2500,10 @@ class SongSelect{ }) } } - + x = p2.session ? frameLeft + 642 + 200 : frameLeft + 642 w = p2.session ? frameLeft + 638 - 200 : frameLeft + 638 - if(p2.session){ + if (p2.session) { this.draw.pattern({ ctx: ctx, img: assets.image["bg_score_p2"], @@ -2497,7 +2516,7 @@ class SongSelect{ scale: 1.55 }) ctx.fillStyle = "rgba(138, 245, 247, 0.5)" - }else{ + } else { this.draw.pattern({ ctx: ctx, img: assets.image["bg_settings"], @@ -2519,22 +2538,22 @@ class SongSelect{ ctx.lineTo(x + 4, y + 4) ctx.lineTo(x + 4, y + h) ctx.fill() - if(screen !== "difficulty" && p2.socket && p2.socket.readyState === 1 && !assets.customSongs){ + if (screen !== "difficulty" && p2.socket && p2.socket.readyState === 1 && !assets.customSongs) { var elapsed = (ms - this.state.screenMS) % 3100 var fade = 1 - if(!p2.session && screen === "song"){ - if(elapsed > 2800){ + if (!p2.session && screen === "song") { + if (elapsed > 2800) { fade = (elapsed - 2800) / 300 - }else if(2000 < elapsed){ - if(elapsed < 2300){ + } else if (2000 < elapsed) { + if (elapsed < 2300) { fade = 1 - (elapsed - 2000) / 300 - }else{ + } else { fade = 0 } } } - if(fade > 0){ - if(fade < 1){ + if (fade > 0) { + if (fade < 1) { ctx.globalAlpha = this.draw.easeIn(fade) } this.sessionCache.get({ @@ -2547,7 +2566,7 @@ class SongSelect{ }) ctx.globalAlpha = 1 } - if(!p2.session && this.state.moveHover === "session"){ + if (!p2.session && this.state.moveHover === "session") { this.draw.highlight({ ctx: ctx, x: x, @@ -2558,10 +2577,10 @@ class SongSelect{ }) } } - if(p2.session){ - if(p2.player === 1){ + if (p2.session) { + if (p2.player === 1) { var name = p2.name || strings.default2PName - }else{ + } else { var name = account.loggedIn ? account.displayName : strings.default2PName } this.nameplateCache.get({ @@ -2582,17 +2601,17 @@ class SongSelect{ }) }) } - - if(this.state.showWarning){ - if(this.preview){ + + if (this.state.showWarning) { + if (this.preview) { this.endPreview() } ctx.fillStyle = "rgba(0, 0, 0, 0.5)" ctx.fillRect(0, 0, winW, winH) - + ctx.save() ctx.translate(frameLeft, frameTop) - + var pauseRect = (ctx, mul) => { this.draw.roundedRect({ ctx: ctx, @@ -2617,15 +2636,15 @@ class SongSelect{ dx: 68, dy: 11 }) - if(this.showWarning.name === "scoreSaveFailed"){ + if (this.showWarning.name === "scoreSaveFailed") { var text = strings.scoreSaveFailed - }else if(this.showWarning.name === "loadSongError"){ + } else if (this.showWarning.name === "loadSongError") { var text = [] var textIndex = 0 var subText = [this.showWarning.title, this.showWarning.id, this.showWarning.error] var textParts = strings.loadSongError.split("%s") textParts.forEach((textPart, i) => { - if(i !== 0){ + if (i !== 0) { text.push(subText[textIndex++]) } text.push(textPart) @@ -2646,7 +2665,7 @@ class SongSelect{ verticalAlign: "middle", textAlign: "center" }) - + var _x = 640 var _y = 470 var _w = 464 @@ -2662,8 +2681,8 @@ class SongSelect{ }) ctx.fill() var layers = [ - {outline: "#000", letterBorder: 10}, - {fill: "#fff"} + { outline: "#000", letterBorder: 10 }, + { fill: "#fff" } ] this.draw.layeredText({ ctx: ctx, @@ -2677,12 +2696,12 @@ class SongSelect{ letterSpacing: -1, align: "center" }, layers) - + var highlight = 1 - if(this.state.moveHover === "showWarning"){ + if (this.state.moveHover === "showWarning") { highlight = 2 } - if(highlight){ + if (highlight) { this.draw.highlight({ ctx: ctx, x: _x - _w / 2 - 3.5, @@ -2695,39 +2714,43 @@ class SongSelect{ radius: 30 }) } - + ctx.restore() } - - if(screen === "titleFadeIn"){ + + if (screen === "titleFadeIn") { ctx.save() - + + if (this.leaderboard && this.leaderboard.visible) { + this.leaderboard.draw(ctx, winW, winH, this.pixelRatio) + } + var elapsed = ms - this.state.screenMS ctx.globalAlpha = Math.max(0, 1 - elapsed / 500) ctx.fillStyle = "#000" ctx.fillRect(0, 0, winW, winH) - + ctx.restore() } - - if(p2.session && (!this.lastScoreMS || ms > this.lastScoreMS + 1000)){ + + if (p2.session && (!this.lastScoreMS || ms > this.lastScoreMS + 1000)) { this.lastScoreMS = ms scoreStorage.eventLoop() } } - drawBackground(cat){ - if(this.songSkin[cat] && this.songSkin[cat].bg_img){ + drawBackground(cat) { + if (this.songSkin[cat] && this.songSkin[cat].bg_img) { let filename = this.songSkin[cat].bg_img.slice(0, this.songSkin[cat].bg_img.lastIndexOf(".")) this.songSelect.style.backgroundImage = "url('" + assets.image[filename].src + "')" - }else{ + } else { this.songSelect.style.backgroundImage = "url('" + assets.image["bg_genre_def"].src + "')" } } - - drawClosedSong(config){ + + drawClosedSong(config) { var ctx = config.ctx - + this.drawSongCrown(config) config.width = this.songAsset.width config.height = this.songAsset.height @@ -2765,7 +2788,7 @@ class SongSelect{ }) } this.draw.songFrame(config) - if("p2Cursor" in config.song && config.song.p2Cursor !== null && p2.socket.readyState === 1){ + if ("p2Cursor" in config.song && config.song.p2Cursor !== null && p2.socket.readyState === 1) { this.draw.diffCursor({ ctx: ctx, font: this.font, @@ -2777,23 +2800,23 @@ class SongSelect{ }) } } - - drawSongCrown(config){ - if(!config.song.action && config.song.hash){ + + drawSongCrown(config) { + if (!config.song.action && config.song.hash) { var ctx = config.ctx var players = p2.session ? 2 : 1 var score = [scoreStorage.get(config.song.hash, false, true)] var scoreDrawn = [] - if(p2.session){ + if (p2.session) { score[p2.player === 1 ? "push" : "unshift"](scoreStorage.getP2(config.song.hash, false, true)) } - for(var i = this.difficultyId.length; i--;){ + for (var i = this.difficultyId.length; i--;) { var diff = this.difficultyId[i] - for(var p = players; p--;){ - if(!score[p] || scoreDrawn[p]){ + for (var p = players; p--;) { + if (!score[p] || scoreDrawn[p]) { continue } - if(config.song.courses[this.difficultyId[i]] && score[p][diff] && score[p][diff].crown){ + if (config.song.courses[this.difficultyId[i]] && score[p][diff] && score[p][diff].crown) { this.draw.crown({ ctx: ctx, type: score[p][diff].crown, @@ -2817,76 +2840,76 @@ class SongSelect{ } } } - - startPreview(loadOnly){ - if(!loadOnly && this.state && this.state.showWarning || this.state.waitPreview > this.getMS()){ + + startPreview(loadOnly) { + if (!loadOnly && this.state && this.state.showWarning || this.state.waitPreview > this.getMS()) { return } var currentSong = this.songs[this.selectedSong] var id = currentSong.id var prvTime = currentSong.preview this.endPreview() - - if("id" in currentSong){ + + if ("id" in currentSong) { var startLoad = this.getMS() - if(loadOnly){ + if (loadOnly) { var currentId = null - }else{ + } else { var currentId = this.previewId this.previewing = this.selectedSong } var songObj = this.previewList.find(song => song && song.id === id) - - if(songObj){ - if(!loadOnly){ + + if (songObj) { + if (!loadOnly) { this.preview = songObj.preview_sound this.preview.gain = snd.previewGain this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume) } - }else{ - songObj = {id: id} - if(currentSong.previewMusic){ + } else { + songObj = { id: id } + if (currentSong.previewMusic) { songObj.preview_time = 0 var promise = snd.previewGain.load(currentSong.previewMusic).catch(() => { songObj.preview_time = prvTime return snd.previewGain.load(currentSong.music) }) - }else if(currentSong.unloaded){ + } else if (currentSong.unloaded) { var promise = this.getUnloaded(this.selectedSong, songObj, currentId) - }else if(currentSong.sound){ + } else if (currentSong.sound) { songObj.preview_time = prvTime currentSong.sound.gain = snd.previewGain var promise = Promise.resolve(currentSong.sound) - }else if(currentSong.music !== "muted"){ + } else if (currentSong.music !== "muted") { songObj.preview_time = prvTime var promise = snd.previewGain.load(currentSong.music) - }else{ + } else { return } promise.then(sound => { - if(currentId === this.previewId || loadOnly){ + if (currentId === this.previewId || loadOnly) { songObj.preview_sound = sound - if(!loadOnly){ + if (!loadOnly) { this.preview = sound this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume) } var oldPreview = this.previewList.shift() - if(oldPreview){ + if (oldPreview) { oldPreview.preview_sound.clean() } this.previewList.push(songObj) - }else{ + } else { sound.clean() } }).catch(e => { - if(e !== "cancel"){ + if (e !== "cancel") { return Promise.reject(e) } }) } } } - previewLoaded(startLoad, prvTime, volume){ + previewLoaded(startLoad, prvTime, volume) { var endLoad = this.getMS() var difference = endLoad - startLoad var minDelay = 300 @@ -2894,26 +2917,26 @@ class SongSelect{ snd.previewGain.setVolumeMul(volume || 1) this.preview.playLoop(delay / 1000, false, prvTime) } - endPreview(force){ + endPreview(force) { this.previewId++ this.previewing = force ? "muted" : null - if(this.preview){ + if (this.preview) { this.preview.stop() } } - playBgm(enabled){ - if(enabled && this.state && this.state.showWarning){ + playBgm(enabled) { + if (enabled && this.state && this.state.showWarning) { return } - if(enabled && !this.bgmEnabled){ + if (enabled && !this.bgmEnabled) { this.bgmEnabled = true snd.musicGain.fadeIn(0.4) - }else if(!enabled && this.bgmEnabled){ + } else if (!enabled && this.bgmEnabled) { this.bgmEnabled = false snd.musicGain.fadeOut(0.4) } } - getUnloaded(selectedSong, songObj, currentId){ + getUnloaded(selectedSong, songObj, currentId) { var currentSong = this.songs[selectedSong] var file = currentSong.chart var importSongs = new ImportSongs(false, assets.otherFiles) @@ -2928,39 +2951,39 @@ class SongSelect{ importSongs.clean() songObj.preview_time = imported.preview var index = assets.songs.findIndex(song => song.id === currentSong.id) - if(index !== -1){ + if (index !== -1) { assets.songs[index] = imported } this.songs[selectedSong] = this.addSong(imported) this.state.moveMS = this.getMS() - this.songSelecting.speed * this.songSelecting.resize - if(imported.music && currentId === this.previewId){ + if (imported.music && currentId === this.previewId) { return snd.previewGain.load(imported.music).then(sound => { imported.sound = sound this.songs[selectedSong].sound = sound return sound.copy() }) - }else{ + } else { return Promise.reject("cancel") } }) } - addSong(song){ + addSong(song) { var title = this.getLocalTitle(song.title, song.title_lang) var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang) var skin = null var categoryName = "" var originalCategory = "" - if(song.category_id !== null && song.category_id !== undefined){ + if (song.category_id !== null && song.category_id !== undefined) { var category = assets.categories.find(cat => cat.id === song.category_id) var categoryName = this.getLocalTitle(category.title, category.title_lang) var originalCategory = category.title var skin = this.songSkin[category.title] - }else if(song.category){ + } else if (song.category) { var categoryName = song.category var originalCategory = song.category } - if(!categoryName){ - if(song.song_type){ + if (!categoryName) { + if (song.song_type) { categoryName = song.song_type originalCategory = song.song_type } @@ -2977,40 +3000,40 @@ class SongSelect{ canJump: true, hash: song.hash || song.title } - for(var i in song){ - if(!(i in addedSong)){ + for (var i in song) { + if (!(i in addedSong)) { addedSong[i] = song[i] } } return addedSong } - - onusers(response){ + + onusers(response) { var p2InSong = false this.songs.forEach(song => { song.p2Cursor = null }) - if(response && response.value){ + if (response && response.value) { response.value.forEach(idDiff => { - var id = idDiff.id |0 + var id = idDiff.id | 0 var diff = idDiff.diff var diffId = this.difficultyId.indexOf(diff) - if(diffId > 3){ + if (diffId > 3) { diffId = 3 } - if(diffId >= 0){ + if (diffId >= 0) { var index = 0 var currentSong = this.songs.find((song, i) => { index = i return song.id === id }) - if(currentSong){ + if (currentSong) { currentSong.p2Cursor = diffId - if(p2.session && currentSong.courses){ + if (p2.session && currentSong.courses) { this.setSelectedSong(index) this.state.move = 0 - if(this.state.screen !== "difficulty"){ - this.toSelectDifficulty({player: response.value.player}) + if (this.state.screen !== "difficulty") { + this.toSelectDifficulty({ player: response.value.player }) } this.search.enabled = false p2InSong = true @@ -3021,132 +3044,232 @@ class SongSelect{ }) } - if(!this.search.enabled && !p2InSong){ + if (!this.search.enabled && !p2InSong) { this.search.enabled = true } } - onsongsel(response){ - if(response && response.value){ + + toLeaderboard() { + var song = this.songs[this.selectedSong] + if (!song) { + return + } + var songId = song.id + if (song.hash && typeof songId === 'string' && isNaN(parseInt(songId))) { + // If key is string and not numeric, it might be hash, or backend expects numeric ID + // Ideally we pass hash if available, or ID + // Our backend implementation handles both, but prefers ID or hash + } + // Actually leaderboard API expects song_id to be what is stored in DB. + // For custom songs it uses hash? No, custom songs are local. + // Let's pass song.id by default. + + // Determine difficulty ID + // difficultyId array: ["easy", "normal", "hard", "oni", "ura"] + // But in menu we select difficulty by pressing right/left which updates selectedDiff + // When we click "Leaderboard" button, we are NOT yet on a specific difficulty? + // Wait, the leaderboard button is in the difficulty selection SCREEN. + // BUT the button itself is an ACTION, like "Download" or "Back". + // It doesn't select a difficulty. + // However, to show a leaderboard, we need a difficulty. + // Should the leaderboard UI allow switching difficulties? + // OR does the leaderboard button show the leaderboard for the CURRENTLY HIGHLIGHTED difficulty? + // In Taiko Web, the difficulty selection screen creates vertical bars for each difficulty. + // User navigates LEFT/RIGHT to select a difficulty or an option (Back, Custom, Download..). + // So if user selects "Leaderboard" option, which difficulty is selected? None! + // "Leaderboard" is a menu item parallel to "Easy", "Normal", "Hard"... + // NO! + // `this.diffOptions` are valid selections in `selectedDiff`. + // `selectedDiff` 0..N are options efficiently. + // The difficulties are further to the right. + // `moveBy = selectedDiff - diffOptions.length`. + // So if I select "Leaderboard" (index 1), I am NOT selecting a song difficulty. + // So `toLeaderboard` needs to know which difficulty to show? + // Maybe default to Oni? Or show a difficulty selector INSIDE the leaderboard UI? + // OR, change the UX: don't make it a separate main menu item. + // Make it a small button accompanying EACH difficulty? + // + // In my plan: "In songselect.js ... add 'Leaderboard' button to diffOptions". + // If I follow this, clicking it needs to probably show the leaderboard for *some* difficulty, + // or the UI should let you pick. + // `LeaderboardUI` supports `show(songId, difficulty)`. + // Let's make it show "Oni" by default if available, or the highest available. + // And add tabs in LeaderboardUI to switch difficulty? + // Or, simpler: Just show Oni for now, or last played. + // + // Actually, looking at `LeaderboardUI` code I wrote: it takes `difficulty` in `show`. + // I did NOT implement tabs in `LeaderboardUI`. + // + // Alternative idea: + // When user hovers/selects a difficulty (e.g. Oni), maybe press a key to see leaderboard? + // But the requirement was "Add a Leaderboard option". + // + // Let's check how "Download" works. It downloads the current song. + // "Song Options" opens a menu. + // + // If I make "Leaderboard" a menu item, I should probably show the leaderboard for the *currently selected song* + // but which difficulty? + // Let's default to Oni. + // Better: The leaderboard UI should have difficulty tabs. + // Since I can't easily redesign `LeaderboardUI` in this tool call (it's already written), + // I will stick to showing *Oni* (or highest available) for now, + // AND maybe I can quickly patch `LeaderboardUI` later to add tabs if needed. + // + // Wait, `songselect.js` logic: + // `this.selectedDiff` tracks what is selected. + // If > `diffOptions.length`, it is a specific difficulty. + // If I am on "Leaderboard" button, I am NOT on a difficulty. + // + // Let's try to pass 'oni' by default. + + var diff = "oni" + if (this.state.ura) { + diff = "ura" + } + // Check if song has oni + if (!song.courses[diff]) { + // Find highest + var diffs = ["ura", "oni", "hard", "normal", "easy"] + for (var d of diffs) { + if (song.courses[d]) { + diff = d + break + } + } + } + + this.leaderboard.show(song.id, diff) + } + + onsongsel(response) { + if (response && response.value) { var selected = false - if(response.type === "songsel" && "selected" in response.value){ + if (response.type === "songsel" && "selected" in response.value) { selected = response.value.selected } - if("fromRandom" in response.value && response.value.fromRandom === true){ + if ("fromRandom" in response.value && response.value.fromRandom === true) { this.lastRandom = true } - if("song" in response.value){ + if ("song" in response.value) { var song = +response.value.song - if(song >= 0 && song < this.songs.length){ - if(response.type === "catjump"){ + if (song >= 0 && song < this.songs.length) { + if (response.type === "catjump") { var moveBy = response.value.move - if(moveBy === -1 || moveBy === 1){ + if (moveBy === -1 || moveBy === 1) { this.setSelectedSong(song) - this.categoryJump(moveBy, {player: response.value.player}) + this.categoryJump(moveBy, { player: response.value.player }) } - }else if(!selected){ + } else if (!selected) { this.state.locked = true - if(this.state.screen === "difficulty"){ + if (this.state.screen === "difficulty") { this.toSongSelect(true) } var moveBy = song - this.selectedSong - if(moveBy){ - if(this.selectedSong < song){ + if (moveBy) { + if (this.selectedSong < song) { var altMoveBy = -this.mod(this.songs.length, this.selectedSong - song) - }else{ + } else { var altMoveBy = this.mod(this.songs.length, moveBy) } - if(Math.abs(altMoveBy) < Math.abs(moveBy)){ + if (Math.abs(altMoveBy) < Math.abs(moveBy)) { moveBy = altMoveBy } - this.moveToSong(moveBy, {player: response.value.player}) + this.moveToSong(moveBy, { player: response.value.player }) } - }else if(this.songs[song].courses){ + } else if (this.songs[song].courses) { this.setSelectedSong(song) this.state.move = 0 - if(this.state.screen !== "difficulty"){ + if (this.state.screen !== "difficulty") { this.playBgm(false) - this.toSelectDifficulty({player: response.value.player}) + this.toSelectDifficulty({ player: response.value.player }) } } } } } } - oncatjump(response){ - if(response && response.value){ - if("song" in response.value){ + oncatjump(response) { + if (response && response.value) { + if ("song" in response.value) { var song = +response.value.song - if(song >= 0 && song < this.songs.length){ + if (song >= 0 && song < this.songs.length) { this.state.locked = true } } } } - startP2(){ + startP2() { this.onusers(p2.getMessage("users")) - if(p2.session){ + if (p2.session) { this.onsongsel(p2.getMessage("songsel")) } pageEvents.add(p2, "message", response => { - if(response.type == "users"){ + if (response.type == "users") { this.onusers(response) } - if(p2.session && (response.type == "songsel" || response.type == "catjump")){ + if (p2.session && (response.type == "songsel" || response.type == "catjump")) { this.onsongsel(response) this.state.selLock = false } }) - if(p2.closed){ + if (p2.closed) { p2.open() } } - - mod(length, index){ + + mod(length, index) { return ((index % length) + length) % length } - - getLocalTitle(title, titleLang){ - if(titleLang){ - if(strings.id === "cn"){ - if(titleLang.cn){ + + getLocalTitle(title, titleLang) { + if (titleLang) { + if (strings.id === "cn") { + if (titleLang.cn) { return titleLang.cn } - if(titleLang.ja){ + if (titleLang.ja) { return titleLang.ja } return title } - for(var id in titleLang){ - if(id === "en" && strings.preferEn && !(strings.id in titleLang) && titleLang.en || id === strings.id && titleLang[id]){ + for (var id in titleLang) { + if (id === "en" && strings.preferEn && !(strings.id in titleLang) && titleLang.en || id === strings.id && titleLang[id]) { return titleLang[id] } } } return title } - - clearHash(){ - if(location.hash.toLowerCase().startsWith("#song=")){ + + clearHash() { + if (location.hash.toLowerCase().startsWith("#song=")) { p2.hash("") } } - - playSound(id, time, snd){ - if(!this.drumSounds && (id === "se_don" || id === "se_ka" || id === "se_cancel")){ + + playSound(id, time, snd) { + if (!this.drumSounds && (id === "se_don" || id === "se_ka" || id === "se_cancel")) { return } var ms = Date.now() + (time || 0) * 1000 - if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){ + if (!(id in this.playedSounds) || ms > this.playedSounds[id] + 30) { assets.sounds[id + (snd ? "_p" + snd : "")].play(time) this.playedSounds[id] = ms } } - - getMS(){ + + getMS() { return Date.now() } - - clean(){ + + wheel(event) { + if (this.leaderboard && this.leaderboard.visible) { + this.leaderboard.wheel(event.deltaY) + return + } + } + + clean() { this.keyboard.clean() this.gamepad.clean() this.clearHash() @@ -3160,7 +3283,7 @@ class SongSelect{ this.nameplateCache.clean() this.search.clean() assets.sounds["bgm_songsel"].stop() - if(!this.bgmEnabled){ + if (!this.bgmEnabled) { snd.musicGain.fadeIn() setTimeout(() => { snd.buffer.loadSettings() @@ -3169,14 +3292,14 @@ class SongSelect{ this.redrawRunning = false this.endPreview() this.previewList.forEach(song => { - if(song){ + if (song) { song.preview_sound.clean() } }) pageEvents.remove(loader.screen, ["mousemove", "mouseleave", "mousedown", "touchstart"]) pageEvents.remove(this.canvas, ["touchend", "wheel"]) pageEvents.remove(p2, "message") - if(this.touchEnabled && fullScreenSupported){ + if (this.touchEnabled && fullScreenSupported) { pageEvents.remove(this.touchFullBtn, "click") delete this.touchFullBtn } @@ -3185,7 +3308,7 @@ class SongSelect{ delete this.canvas } - toDownload(){ + toDownload() { var jsZip = new JSZip() var zip = new jsZip() var song = this.songs[this.selectedSong] @@ -3196,38 +3319,38 @@ class SongSelect{ var musicBlob var lyricsBlob var blobs = [] - if(song.chart){ + if (song.chart) { var charts = [] - if(song.chart.separateDiff){ - for(var i in song.chart){ - if(song.chart[i] && i !== "separateDiff"){ + if (song.chart.separateDiff) { + for (var i in song.chart) { + if (song.chart[i] && i !== "separateDiff") { charts.push(song.chart[i]) } } - }else{ + } else { charts.push(song.chart) } charts.forEach(chart => { promises.push(chart.blob().then(blob => { var promise - if(!chartParsed){ + if (!chartParsed) { chartParsed = true - if(song.type === "tja"){ + if (song.type === "tja") { promise = readFile(blob, false, "utf-8").then(dataRaw => { var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : [] var tja = new ParseTja(data, "oni", 0, 0, true) - for(var diff in tja.metadata){ + for (var diff in tja.metadata) { var meta = tja.metadata[diff] - if(meta.wave){ + if (meta.wave) { musicFilename = meta.wave } } }) - }else if(song.type === "osu"){ + } else if (song.type === "osu") { promise = readFile(blob).then(dataRaw => { var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : [] var osu = new ParseOsu(data, "oni", 0, 0, true) - if(osu.generalInfo.AudioFilename){ + if (osu.generalInfo.AudioFilename) { musicFilename = osu.generalInfo.AudioFilename } }) @@ -3237,7 +3360,7 @@ class SongSelect{ name: chart.name, data: blob } - if(song.type === "tja" && !song.chart.separateDiff){ + if (song.type === "tja" && !song.chart.separateDiff) { chartBlob = outputBlob } blobs.push(outputBlob) @@ -3245,7 +3368,7 @@ class SongSelect{ })) }) } - if(song.music){ + if (song.music) { promises.push(song.music.blob().then(blob => { musicBlob = { name: song.music.name, @@ -3264,30 +3387,30 @@ class SongSelect{ // })) // } Promise.all(promises).then(() => { - if(musicFilename){ - if(musicBlob){ + if (musicFilename) { + if (musicBlob) { musicBlob.name = musicFilename } var filename = musicFilename var index = filename.lastIndexOf(".") - if(index !== -1){ + if (index !== -1) { filename = filename.slice(0, index) } - if(chartBlob){ + if (chartBlob) { chartBlob.name = filename + ".tja" } - if(lyricsBlob){ + if (lyricsBlob) { lyricsBlob.name = filename + ".vtt" } } blobs.forEach(blob => zip.file(blob.name, blob.data)) - }).then(() => zip.generateAsync({type: "blob"})).then(zip => { + }).then(() => zip.generateAsync({ type: "blob" })).then(zip => { var url = URL.createObjectURL(zip) var link = document.createElement("a") link.href = url - if("download" in HTMLAnchorElement.prototype){ + if ("download" in HTMLAnchorElement.prototype) { link.download = song.title + ".zip" - }else{ + } else { link.target = "_blank" } link.innerText = "." diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 6f0729e..e51dbb0 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -28,7 +28,7 @@ var translations = { tw: "zh-Hant", ko: "ko" }, - + taikoWeb: { ja: "たいこウェブ", en: "Taiko Web", @@ -438,7 +438,78 @@ var translations = { tw: "連打數", ko: "연타 횟수" }, - + + leaderboard: { + ja: "ランキング", + en: "Leaderboard", + cn: "排行榜", + tw: "排行榜", + ko: "순위표" + }, + leaderboardTitle: { + ja: "%s の ランキング", + en: "%s Leaderboard", + cn: "%s 排行榜", + tw: "%s 排行榜", + ko: "%s 순위표" + }, + rank: { + ja: "順位", + en: "Rank", + cn: "排名", + tw: "排名", + ko: "순위" + }, + playerName: { + ja: "プレイヤー", + en: "Player", + cn: "玩家", + tw: "玩家", + ko: "플레이어" + }, + score: { + ja: "スコア", + en: "Score", + cn: "分数", + tw: "分數", + ko: "점수" + }, + recordBroken: { + ja: "最高記録を更新!第%s位", + en: "New Record! Rank #%s", + cn: "打破最佳纪录 第%s名", + tw: "打破最佳紀錄 第%s名", + ko: "최고 기록 경신! %s위" + }, + yourRank: { + ja: "あなたの順位: %s位", + en: "Your Rank: #%s", + cn: "您的排名: 第%s名", + tw: "您的排名: 第%s名", + ko: "순위: %s위" + }, + notRanked: { + ja: "ランク外", + en: "Not Ranked", + cn: "未上榜", + tw: "未上榜", + ko: "순위 없음" + }, + loadingLeaderboard: { + ja: "ランキング読み込み中...", + en: "Loading Leaderboard...", + cn: "加载排行榜中...", + tw: "讀取排行榜中...", + ko: "순위표 로딩 중..." + }, + noScores: { + ja: "記録なし", + en: "No Scores Yet", + cn: "暂无记录", + tw: "暫無紀錄", + ko: "기록 없음" + }, + errorOccured: { ja: "エラーが発生しました。再読み込みしてください。", en: "An error occurred, please refresh", @@ -879,7 +950,7 @@ var translations = { en: "Audio Latency Calibration", tw: "聲音延遲校正", ko: "오디오 레이턴시 조절" - + }, content: { ja: "背景で鳴っている音を聴いてみましょう。\n\n音が聞こえたら、太鼓の面(%sまたは%s)をたたこう!", @@ -1472,26 +1543,26 @@ var translations = { } } var allStrings = {} -function separateStrings(){ - for(var j in languageList){ +function separateStrings() { + for (var j in languageList) { var lang = languageList[j] allStrings[lang] = { id: lang } var str = allStrings[lang] - var translateObj = function(obj, name, str){ - if("en" in obj){ - for(var i in obj){ + var translateObj = function (obj, name, str) { + if ("en" in obj) { + for (var i in obj) { str[name] = obj[lang] || obj.en } - }else if(obj){ + } else if (obj) { str[name] = {} - for(var i in obj){ + for (var i in obj) { translateObj(obj[i], i, str[name]) } } } - for(var i in translations){ + for (var i in translations) { translateObj(translations[i], i, str) } } diff --git a/schema.py b/schema.py index a012b02..5c16109 100644 --- a/schema.py +++ b/schema.py @@ -80,3 +80,29 @@ scores_save = { } } } + +leaderboard_submit = { + '$schema': 'http://json-schema.org/schema#', + 'type': 'object', + 'properties': { + 'song_id': {'type': ['integer', 'string']}, + 'difficulty': {'type': 'string', 'enum': ['easy', 'normal', 'hard', 'oni', 'ura']}, + 'score': {'type': 'integer'}, + 'good': {'type': 'integer'}, + 'ok': {'type': 'integer'}, + 'bad': {'type': 'integer'}, + 'maxCombo': {'type': 'integer'}, + 'hash': {'type': 'string'} + }, + 'required': ['difficulty', 'score'] +} + +leaderboard_get = { + '$schema': 'http://json-schema.org/schema#', + 'type': 'object', + 'properties': { + 'song_id': {'type': ['integer', 'string']}, + 'difficulty': {'type': 'string'} + }, + 'required': ['song_id', 'difficulty'] +} diff --git a/tools/reset_leaderboard.py b/tools/reset_leaderboard.py new file mode 100644 index 0000000..ad541f0 --- /dev/null +++ b/tools/reset_leaderboard.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import os +import sys +from datetime import datetime +from pymongo import MongoClient + +# Add project root to path +sys.path.append(os.path.dirname(os.path.dirname(__file__))) +try: + import config +except ImportError: + # Handle case where config might not be in path or environment variables are used + config = None + +def reset_leaderboard(): + """Delete leaderboard entries not from the current month""" + mongo_host = os.environ.get("TAIKO_WEB_MONGO_HOST") + if not mongo_host and config: + mongo_host = config.MONGO['host'] + + db_name = "taiko" + if config: + db_name = config.MONGO['database'] + + if not mongo_host: + print("Error: content not found for MONGO_HOST") + return + + client = MongoClient(host=mongo_host) + db = client[db_name] + + current_month = datetime.now().strftime('%Y-%m') + + # Delete old month data + result = db.leaderboard.delete_many({'month': {'$ne': current_month}}) + + print(f"Deleted {result.deleted_count} old leaderboard entries") + print(f"Current month: {current_month}") + + client.close() + +if __name__ == '__main__': + reset_leaderboard()