From 41afc2a9051e8c68edc404ec6df97544912fc8df Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Thu, 5 Mar 2020 18:58:49 +0300 Subject: [PATCH 1/8] Game: Improve gauge --- public/src/js/canvasdraw.js | 4 ++-- public/src/js/controller.js | 2 +- public/src/js/game.js | 18 +++++++-------- public/src/js/gamerules.js | 44 +++++++++++++++++++++++++++++++++++++ public/src/js/scoresheet.js | 31 +++++++++++++++----------- public/src/js/view.js | 15 +++++++------ public/src/js/viewassets.js | 22 +++++++++++-------- 7 files changed, 94 insertions(+), 42 deletions(-) diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index df8a25c..0568d72 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -1347,10 +1347,10 @@ var secondTop = config.multiplayer ? 0 : 8 config.percentage = Math.max(0, Math.min(1, config.percentage)) - var cleared = config.percentage - 1 / 50 >= config.clear + var cleared = config.percentage >= config.clear var gaugeW = 14 * 50 - var gaugeClear = gaugeW * config.clear + var gaugeClear = gaugeW * (config.clear - 1 / 50) var gaugeFilled = gaugeW * config.percentage ctx.fillStyle = "#000" diff --git a/public/src/js/controller.js b/public/src/js/controller.js index cb20acd..f49d495 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -171,7 +171,7 @@ class Controller{ gameEnded(){ var score = this.getGlobalScore() var vp - if(Math.round(score.gauge / 2) - 1 >= 25){ + if(this.game.rules.clearReached(score.gauge)){ if(score.bad === 0){ vp = "fullcombo" this.playSound("v_fullcombo", 1.350) diff --git a/public/src/js/game.js b/public/src/js/game.js index bb790ba..8d214a9 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -18,10 +18,11 @@ class Game{ title: selectedSong.title, difficulty: this.rules.difficulty } - this.HPGain = 100 / this.songData.circles.filter(circle => { + var combo = this.songData.circles.filter(circle => { var type = circle.type return (type === "don" || type === "ka" || type === "daiDon" || type === "daiKa") && (!circle.branch || circle.branch.active) }).length + this.soulPoints = this.rules.soulPoints(combo) this.paused = false this.started = false this.mainMusicPlaying = false @@ -628,12 +629,15 @@ class Game{ switch(score){ case 450: this.globalScore.good++ + this.globalScore.gauge += this.soulPoints.good break case 230: this.globalScore.ok++ + this.globalScore.gauge += this.soulPoints.ok break case 0: this.globalScore.bad++ + this.globalScore.gauge += this.soulPoints.bad break } if (this.songData.scoremode) { @@ -647,12 +651,10 @@ class Game{ } } // Gauge update - if(score !== 0){ - this.globalScore.gauge += this.HPGain - }else if(this.globalScore.gauge - this.HPGain > 0){ - this.globalScore.gauge -= this.HPGain - }else{ + if(this.globalScore.gauge < 0){ this.globalScore.gauge = 0 + }else if(this.globalScore.gauge > 10000){ + this.globalScore.gauge = 10000 } // Points update if (this.songData.scoremode == 2) { @@ -719,10 +721,6 @@ class Game{ this.currentCircle = closestCircle } } - this.HPGain = 100 / this.songData.circles.filter(circle => { - var type = circle.type - return (type === "don" || type === "ka" || type === "daiDon" || type === "daiKa") && (!circle.branch || circle.branch.active) - }).length if(this.controller.multiplayer === 1){ p2.send("branch", activeName) } diff --git a/public/src/js/gamerules.js b/public/src/js/gamerules.js index 35818bd..9dc1f11 100644 --- a/public/src/js/gamerules.js +++ b/public/src/js/gamerules.js @@ -18,7 +18,51 @@ class GameRules{ this.bad = 13 / 2 * frame break } + switch(this.difficulty){ + case "easy": + this.gaugeClear = 30 / 50 + break + case "normal": + case "hard": + this.gaugeClear = 35 / 50 + break + case "oni": + case "ura": + this.gaugeClear = 40 / 50 + break + } this.daiLeniency = 2 * frame } + soulPoints(combo){ + var good, ok, bad + switch(this.difficulty){ + case "easy": + good = Math.floor(10000 / combo * 1.575) + ok = Math.floor(good * 0.75) + bad = Math.ceil(good * -2) + break + case "normal": + good = Math.floor(10000 / combo / 0.7) + ok = Math.floor(good * 0.75) + bad = Math.ceil(good / -0.75) + break + case "hard": + good = Math.floor(10000 / combo * 1.5) + ok = Math.floor(good * 0.75) + bad = Math.ceil(good / -0.8) + break + case "oni": + case "ura": + good = Math.floor(10000 / combo / 0.7) + ok = Math.floor(good * 0.5) + bad = Math.ceil(good * -1.6) + break + } + return {good: good, ok: ok, bad: bad} + } + clearReached(gauge){ + var gaugePercent = Math.round(gauge / 200) / 50 + return gaugePercent >= this.gaugeClear + } } diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index a42ecb9..54b9710 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -319,15 +319,18 @@ class Scoresheet{ var elapsed = 0 } - var gaugePercent = Math.round(this.results.gauge / 2) / 50 + var gaugePercent = Math.round(this.results.gauge / 200) / 50 + var gaugeClear = [this.controller.game.rules.gaugeClear] if(players === 2){ - var gauge2 = Math.round(p2.results.gauge / 2) / 50 - if(gauge2 > gaugePercent){ - gaugePercent = gauge2 + gaugeClear.push(this.controller.syncWith.game.rules.gaugeClear) + } + var failedOffset = gaugePercent >= gaugeClear[0] ? 0 : -2000 + if(players === 2){ + var gauge2 = Math.round(p2.results.gauge / 200) / 50 + if(gauge2 > gaugePercent && failedOffset !== 0 && gauge2 >= gaugeClear[1]){ + failedOffset = 0 } } - var gaugeClear = 25 / 50 - var failedOffset = gaugePercent >= gaugeClear ? 0 : -2000 if(elapsed >= 3100 + failedOffset){ for(var p = 0; p < players; p++){ ctx.save() @@ -335,8 +338,8 @@ class Scoresheet{ if(p === 1){ results = p2.results } - var resultGauge = Math.round(results.gauge / 2) / 50 - var clear = resultGauge >= gaugeClear + var resultGauge = Math.round(results.gauge / 200) / 50 + var clear = resultGauge >= gaugeClear[p] if(p === 1 || !this.multiplayer && clear){ ctx.translate(0, 290) } @@ -570,7 +573,7 @@ class Scoresheet{ if(this.tetsuoHanaClass){ this.tetsuoHana.classList.remove(this.tetsuoHanaClass) } - this.tetsuoHanaClass = gaugePercent >= gaugeClear ? "dance" : "failed" + this.tetsuoHanaClass = this.controller.game.rules.clearReached(this.results.gauge) ? "dance" : "failed" this.tetsuoHana.classList.add(this.tetsuoHanaClass) } } @@ -589,25 +592,26 @@ class Scoresheet{ results = p2.results ctx.translate(0, p2Offset) } - var gaugePercent = Math.round(results.gauge / 2) / 50 + var gaugePercent = Math.round(results.gauge / 200) / 50 var w = 712 this.draw.gauge({ ctx: ctx, x: 558 + w, y: 116, - clear: 25 / 50, + clear: gaugeClear[p], percentage: gaugePercent, font: this.font, scale: w / 788, scoresheet: true, blue: p === 1 }) + var rules = p === 0 ? this.controller.game.rules : this.controller.syncWith.game.rules this.draw.soul({ ctx: ctx, x: 1215, y: 144, scale: 36 / 42, - cleared: gaugePercent - 1 / 50 >= 25 / 50 + cleared: rules.clearReached(results.gauge) }) } }) @@ -625,7 +629,8 @@ class Scoresheet{ results = p2.results } var crownType = null - if(Math.round(results.gauge / 2) - 1 >= 25){ + var rules = p === 0 ? this.controller.game.rules : this.controller.syncWith.game.rules + if(rules.clearReached(results.gauge)){ crownType = results.bad === "0" ? "gold" : "silver" } if(crownType !== null){ diff --git a/public/src/js/view.js b/public/src/js/view.js index 332cac8..8c1e4d1 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -17,6 +17,7 @@ this.songBg = document.getElementById("songbg") this.songStage = document.getElementById("song-stage") + this.rules = this.controller.game.rules this.portraitClass = false this.touchp2Class = false this.darkDonBg = false @@ -347,7 +348,7 @@ } var score = this.controller.getGlobalScore() - var gaugePercent = Math.round(score.gauge / 2) / 50 + var gaugePercent = Math.round(score.gauge / 200) / 50 if(this.multiplayer === 2){ var scoreImg = "bg_score_p2" @@ -497,7 +498,7 @@ ctx: ctx, x: winW, y: this.multiplayer === 2 ? 468 : 273, - clear: 25 / 50, + clear: this.rules.gaugeClear, percentage: gaugePercent, font: this.font, scale: 0.7, @@ -509,7 +510,7 @@ x: winW - 40, y: this.multiplayer === 2 ? 484 : 293, scale: 0.75, - cleared: gaugePercent - 1 / 50 >= 25 / 50 + cleared: this.rules.clearReached(score.gauge) }) // Note bar @@ -572,7 +573,7 @@ ctx: ctx, x: winW, y: this.multiplayer === 2 ? 357 : 135, - clear: 25 / 50, + clear: this.rules.gaugeClear, percentage: gaugePercent, font: this.font, multiplayer: this.multiplayer === 2, @@ -582,7 +583,7 @@ ctx: ctx, x: winW - 57, y: this.multiplayer === 2 ? 378 : 165, - cleared: gaugePercent - 1 / 50 >= 25 / 50 + cleared: this.rules.clearReached(score.gauge) }) // Note bar @@ -1881,8 +1882,8 @@ } }else{ var animation = this.assets.don.getAnimation() - var gauge = this.controller.getGlobalScore().gauge - var cleared = Math.round(gauge / 2) - 1 >= 25 + var score = this.controller.getGlobalScore() + var cleared = this.rules.clearReached(score.gauge) if(animation === "gogo" || cleared && animation === "normal" || !cleared && animation === "clear"){ this.assets.don.normalAnimation() } diff --git a/public/src/js/viewassets.js b/public/src/js/viewassets.js index 425fcf3..dae06c6 100644 --- a/public/src/js/viewassets.js +++ b/public/src/js/viewassets.js @@ -42,16 +42,20 @@ class ViewAssets{ var length = this.don.getAnimationLength("gogo") this.don.setUpdateSpeed(4 / length) this.don.setAnimation("gogo") - }else if(Math.round(this.controller.getGlobalScore().gauge / 2) - 1 >= 25){ - this.don.setAnimationStart(0) - var length = this.don.getAnimationLength("clear") - this.don.setUpdateSpeed(2 / length) - this.don.setAnimation("clear") }else{ - this.don.setAnimationStart(0) - var length = this.don.getAnimationLength("normal") - this.don.setUpdateSpeed(4 / length) - this.don.setAnimation("normal") + var score = this.controller.getGlobalScore() + var cleared = this.controller.game.rules.clearReached(score.gauge) + if(cleared){ + this.don.setAnimationStart(0) + var length = this.don.getAnimationLength("clear") + this.don.setUpdateSpeed(2 / length) + this.don.setAnimation("clear") + }else{ + this.don.setAnimationStart(0) + var length = this.don.getAnimationLength("normal") + this.don.setUpdateSpeed(4 / length) + this.don.setAnimation("normal") + } } } this.don.addFrames("clear", 30, "don_anim_clear") From 21259abdda488ce0b41bd153c28e95bfd4fd6835 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Fri, 6 Mar 2020 03:02:07 +0300 Subject: [PATCH 2/8] Scoresheet: Save results to localstorage --- public/src/js/assets.js | 3 +- public/src/js/loader.js | 1 + public/src/js/main.js | 1 + public/src/js/scoresheet.js | 24 ++++++ public/src/js/scorestorage.js | 135 ++++++++++++++++++++++++++++++++++ public/src/js/songselect.js | 2 + 6 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 public/src/js/scorestorage.js diff --git a/public/src/js/assets.js b/public/src/js/assets.js index 39a32b8..45c5e93 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -30,7 +30,8 @@ var assets = { "session.js", "importsongs.js", "logo.js", - "settings.js" + "settings.js", + "scorestorage.js" ], "css": [ "main.css", diff --git a/public/src/js/loader.js b/public/src/js/loader.js index a3ba450..ec19352 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -204,6 +204,7 @@ class Loader{ } settings = new Settings() + scoreStorage = new ScoreStorage() pageEvents.setKbd() Promise.all(this.promises).then(() => { diff --git a/public/src/js/main.js b/public/src/js/main.js index e9067f1..25dea20 100644 --- a/public/src/js/main.js +++ b/public/src/js/main.js @@ -83,6 +83,7 @@ var perf = { var strings var vectors var settings +var scoreStorage pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => { if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){ diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 54b9710..c2cd2cd 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -1,6 +1,7 @@ class Scoresheet{ constructor(controller, results, multiplayer, touchEnabled){ this.controller = controller + this.resultsObj = results this.results = {} for(var i in results){ this.results[i] = results[i].toString() @@ -54,6 +55,7 @@ class Scoresheet{ "ura": 4 } + this.scoreSaved = false this.redrawRunning = true this.redrawBind = this.redraw.bind(this) this.redraw() @@ -248,6 +250,9 @@ class Scoresheet{ 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){ + this.saveScore() + } if(bgOffset){ ctx.save() @@ -854,6 +859,25 @@ class Scoresheet{ return Date.now() } + saveScore(){ + if(!this.controller.autoPlayEnabled && this.resultsObj.points > 0){ + var title = this.controller.selectedSong.originalTitle + var difficulty = this.resultsObj.difficulty + var oldScore = scoreStorage.get(title, difficulty) + if(!oldScore || oldScore.points <= this.resultsObj.points){ + this.resultsObj.crown = "" + if(this.controller.game.rules.clearReached(this.resultsObj.gauge)){ + this.resultsObj.crown = this.resultsObj.bad === 0 ? "gold" : "silver" + } + delete this.resultsObj.title + delete this.resultsObj.difficulty + delete this.resultsObj.gauge + scoreStorage.add(title, difficulty, this.resultsObj) + } + } + this.scoreSaved = true + } + clean(){ this.keyboard.clean() this.gamepad.clean() diff --git a/public/src/js/scorestorage.js b/public/src/js/scorestorage.js new file mode 100644 index 0000000..f06e729 --- /dev/null +++ b/public/src/js/scorestorage.js @@ -0,0 +1,135 @@ +class ScoreStorage{ + constructor(){ + this.scores = {} + this.difficulty = ["oni", "ura", "hard", "normal", "easy"] + this.scoreKeys = ["points", "good", "ok", "bad", "maxCombo", "drumroll"] + this.crownValue = ["", "silver", "gold"] + this.load() + } + load(){ + this.scores = {} + this.scoreStrings = {} + try{ + var localScores = localStorage.getItem("scoreStorage") + if(localScores){ + this.scoreStrings = JSON.parse(localScores) + } + }catch(e){} + for(var song in this.scoreStrings){ + var scoreString = this.scoreStrings[song] + var songAdded = false + if(typeof scoreString === "string" && scoreString){ + var diffArray = scoreString.split(";") + for(var i in this.difficulty){ + if(diffArray[i]){ + var crown = parseInt(diffArray[i].slice(0, 1)) || 0 + var score = { + crown: this.crownValue[crown] || "" + } + var scoreArray = diffArray[i].slice(1).split(",") + for(var j in this.scoreKeys){ + var name = this.scoreKeys[j] + var value = parseInt(scoreArray[j], 36) || 0 + if(value < 0){ + value = 0 + } + score[name] = value + } + if(!songAdded){ + this.scores[song] = [] + songAdded = true + } + this.scores[song][this.difficulty[i]] = score + } + } + } + } + } + save(){ + for(var song in this.scores){ + this.writeString(song) + } + this.write() + } + write(){ + try{ + localStorage.setItem("scoreStorage", JSON.stringify(this.scoreStrings)) + }catch(e){} + } + writeString(song){ + var score = this.scores[song] + var diffArray = [] + var notEmpty = false + for(var i = this.difficulty.length; i--;){ + var diff = this.difficulty[i] + if(score[diff]){ + var scoreArray = [] + var crown = this.crownValue.indexOf(score[diff].crown).toString() + for(var j in this.scoreKeys){ + var name = this.scoreKeys[j] + var value = score[diff][name] + value = Math.floor(value).toString(36) + scoreArray.push(value) + } + diffArray.unshift(crown + scoreArray.join(",")) + notEmpty = true + }else if(notEmpty){ + diffArray.unshift("") + } + } + this.scoreStrings[song] = diffArray.join(";") + } + get(song, difficulty){ + if(!song){ + return this.scores + }else if(song in this.scores){ + if(difficulty){ + return this.scores[song][difficulty] + }else{ + return this.scores[song] + } + } + } + add(song, difficulty, scoreObject){ + if(!(song in this.scores)){ + this.scores[song] = {} + } + this.scores[song][difficulty] = scoreObject + this.writeString(song) + this.write() + } + template(){ + var template = {crown: ""} + for(var i in this.scoreKeys){ + var name = this.scoreKeys[i] + template[name] = 0 + } + return template + } + remove(song, difficulty){ + if(song in this.scores){ + if(difficulty){ + if(difficulty in this.scores[song]){ + delete this.scores[song][difficulty] + var noDiff = true + for(var i in this.difficulty){ + if(this.scores[song][this.difficulty[i]]){ + noDiff = false + break + } + } + if(noDiff){ + delete this.scores[song] + delete this.scoreStrings[song] + }else{ + this.writeString(song) + } + } + }else{ + delete this.scores[song] + delete this.scoreStrings[song] + } + this.write() + } + } +} diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 926d1e1..97a7f13 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -113,6 +113,7 @@ class SongSelect{ this.songs.push({ id: song.id, title: title, + originalTitle: song.title, subtitle: subtitle, skin: song.category in this.songSkin ? this.songSkin[song.category] : this.songSkin.default, stars: song.stars, @@ -738,6 +739,7 @@ class SongSelect{ new LoadSong({ "title": selectedSong.title, + "originalTitle": selectedSong.originalTitle, "folder": selectedSong.id, "difficulty": this.difficultyId[difficulty], "category": selectedSong.category, From 0221c977c8c9a686b7854e258a8accc3dd657eb5 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Fri, 6 Mar 2020 20:52:22 +0300 Subject: [PATCH 3/8] SongSelect: Add crowns - Improve the soul gauge to fill properly. The algorithm is different for each difficulty. - Saves score to localStorage, the whole score is correct now. - Adds crowns to song selection screen. The scores would take a lot of space if stored as readable objects so they are stored compressed. If you need to edit your scores, you can do so by opening dev console and entering `scoreStorage.get()`. Expand to the song and double click on values that you need to edit. When you are done editing, do not forget to save your scores with `scoreStorage.save()`. Adding new scores can be done with `scoreStorage.add`, first get a template with `obj=scoreStorage.template(),obj` and after editing, add it with `scoreStorage.add("song name", "oni", obj)`. To remove a score use `scoreStorage.remove("song name"[, "oni"])`. --- public/src/js/canvasdraw.js | 76 +++++++------- public/src/js/scoresheet.js | 9 +- public/src/js/songselect.js | 203 ++++++++++++++++++++++-------------- 3 files changed, 171 insertions(+), 117 deletions(-) diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index 0568d72..f31a174 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -1273,27 +1273,29 @@ ctx.translate(-47, -39) ctx.miterLimit = 1.7 - if(!this.crownCache.w){ - this.crownCache.resize(140, 140, config.ratio) + if(config.whiteOutline){ + if(!this.crownCache.w){ + this.crownCache.resize(140, 140, config.ratio) + } + var offset = 140 / 2 - 94 / 2 + this.crownCache.get({ + ctx: ctx, + x: -offset, + y: -offset, + w: 140, + h: 140, + id: "crown" + }, ctx => { + ctx.save() + ctx.translate(offset, offset) + ctx.strokeStyle = "#fff" + ctx.lineWidth = 35 + ctx.miterLimit = 1.7 + ctx.filter = "blur(1.5px)" + ctx.stroke(this.crownPath) + ctx.restore() + }) } - var offset = 140 / 2 - 94 / 2 - this.crownCache.get({ - ctx: ctx, - x: -offset, - y: -offset, - w: 140, - h: 140, - id: "crown" - }, ctx => { - ctx.save() - ctx.translate(offset, offset) - ctx.strokeStyle = "#fff" - ctx.lineWidth = 35 - ctx.miterLimit = 1.7 - ctx.filter = "blur(1.5px)" - ctx.stroke(this.crownPath) - ctx.restore() - }) if(config.shine){ ctx.strokeStyle = "#fff" @@ -1302,7 +1304,7 @@ ctx.globalAlpha = 1 - config.shine } - ctx.strokeStyle = "#000" + ctx.strokeStyle = config.type ? "#000" : "rgba(255, 193, 0, 0.5)" ctx.lineWidth = 18 ctx.stroke(this.crownPath) @@ -1313,21 +1315,25 @@ ctx.globalAlpha = 1 - config.shine } - var grd = ctx.createLinearGradient(0, 0, 94, 0) - if(config.type === "gold"){ - grd.addColorStop(0, "#ffffc5") - grd.addColorStop(0.23, "#ffff44") - grd.addColorStop(0.53, "#efbd12") - grd.addColorStop(0.83, "#ffff44") - grd.addColorStop(1, "#efbd12") - }else if(config.type === "silver"){ - grd.addColorStop(0, "#d6efef") - grd.addColorStop(0.23, "#bddfde") - grd.addColorStop(0.53, "#97c1c0") - grd.addColorStop(0.83, "#bddfde") - grd.addColorStop(1, "#97c1c0") + if(config.type){ + var grd = ctx.createLinearGradient(0, 0, 94, 0) + if(config.type === "gold"){ + grd.addColorStop(0, "#ffffc5") + grd.addColorStop(0.23, "#ffff44") + grd.addColorStop(0.53, "#efbd12") + grd.addColorStop(0.83, "#ffff44") + grd.addColorStop(1, "#efbd12") + }else if(config.type === "silver"){ + grd.addColorStop(0, "#d6efef") + grd.addColorStop(0.23, "#bddfde") + grd.addColorStop(0.53, "#97c1c0") + grd.addColorStop(0.83, "#bddfde") + grd.addColorStop(1, "#97c1c0") + } + ctx.fillStyle = grd + }else{ + ctx.fillStyle = "#ffdb2c" } - ctx.fillStyle = grd ctx.fill(this.crownPath) ctx.restore() diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index c2cd2cd..3e62440 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -678,6 +678,7 @@ class Scoresheet{ y: 218, scale: crownScale, shine: shine, + whiteOutline: true, ratio: ratio }) @@ -865,10 +866,14 @@ class Scoresheet{ var difficulty = this.resultsObj.difficulty var oldScore = scoreStorage.get(title, difficulty) if(!oldScore || oldScore.points <= this.resultsObj.points){ - this.resultsObj.crown = "" + var crown = "" if(this.controller.game.rules.clearReached(this.resultsObj.gauge)){ - this.resultsObj.crown = this.resultsObj.bad === 0 ? "gold" : "silver" + crown = this.resultsObj.bad === 0 ? "gold" : "silver" } + if(oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)){ + crown = oldScore.crown + } + this.resultsObj.crown = crown delete this.resultsObj.title delete this.resultsObj.difficulty delete this.resultsObj.gauge diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 97a7f13..cb320da 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -959,86 +959,6 @@ class SongSelect{ } } - if(screen === "title" || screen === "titleFadeIn" || screen === "song"){ - var textW = strings.id === "en" ? 350 : 280 - this.selectTextCache.get({ - ctx: ctx, - x: frameLeft, - y: frameTop, - w: textW + 53 + 60, - h: this.songAsset.marginTop + 15, - id: "song" - }, ctx => { - this.draw.layeredText({ - ctx: ctx, - text: strings.selectSong, - fontSize: 48, - fontFamily: this.font, - x: 53, - y: 30, - width: textW, - letterSpacing: strings.id === "en" ? 0 : 2, - forceShadow: true - }, [ - {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]} - ]) - }) - - var category = this.songs[this.selectedSong].category - var selectedSong = this.songs[this.selectedSong] - this.draw.category({ - ctx: ctx, - x: winW / 2 - 280 / 2 - 30, - y: frameTop + 60, - fill: selectedSong.skin.background, - highlight: this.state.moveHover === "categoryPrev" - }) - this.draw.category({ - ctx: ctx, - x: winW / 2 + 280 / 2 + 30, - y: frameTop + 60, - right: true, - fill: selectedSong.skin.background, - highlight: this.state.moveHover === "categoryNext" - }) - this.categoryCache.get({ - ctx: ctx, - x: winW / 2 - 280 / 2, - y: frameTop, - w: 280, - h: this.songAsset.marginTop, - id: category + selectedSong.skin.outline - }, ctx => { - if(category){ - if(category in strings.categories){ - var categoryName = strings.categories[category] - }else{ - var categoryName = category - } - this.draw.layeredText({ - ctx: ctx, - text: categoryName, - fontSize: 40, - fontFamily: this.font, - x: 280 / 2, - y: 38, - width: 255, - align: "center", - forceShadow: true - }, [ - {outline: selectedSong.skin.outline, letterBorder: 12, shadow: [3, 3, 3]}, - {fill: "#fff"} - ]) - } - }) - } - if(screen === "song"){ if(this.songs[this.selectedSong].stars){ selectedWidth = this.songAsset.selectedWidth @@ -1228,6 +1148,86 @@ class SongSelect{ } } + if(screen === "title" || screen === "titleFadeIn" || screen === "song"){ + var textW = strings.id === "en" ? 350 : 280 + this.selectTextCache.get({ + ctx: ctx, + x: frameLeft, + y: frameTop, + w: textW + 53 + 60, + h: this.songAsset.marginTop + 15, + id: "song" + }, ctx => { + this.draw.layeredText({ + ctx: ctx, + text: strings.selectSong, + fontSize: 48, + fontFamily: this.font, + x: 53, + y: 30, + width: textW, + letterSpacing: strings.id === "en" ? 0 : 2, + forceShadow: true + }, [ + {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]} + ]) + }) + + var category = this.songs[this.selectedSong].category + var selectedSong = this.songs[this.selectedSong] + this.draw.category({ + ctx: ctx, + x: winW / 2 - 280 / 2 - 30, + y: frameTop + 60, + fill: selectedSong.skin.background, + highlight: this.state.moveHover === "categoryPrev" + }) + this.draw.category({ + ctx: ctx, + x: winW / 2 + 280 / 2 + 30, + y: frameTop + 60, + right: true, + fill: selectedSong.skin.background, + highlight: this.state.moveHover === "categoryNext" + }) + this.categoryCache.get({ + ctx: ctx, + x: winW / 2 - 280 / 2, + y: frameTop, + w: 280, + h: this.songAsset.marginTop, + id: category + selectedSong.skin.outline + }, ctx => { + if(category){ + if(category in strings.categories){ + var categoryName = strings.categories[category] + }else{ + var categoryName = category + } + this.draw.layeredText({ + ctx: ctx, + text: categoryName, + fontSize: 40, + fontFamily: this.font, + x: 280 / 2, + y: 38, + width: 255, + align: "center", + forceShadow: true + }, [ + {outline: selectedSong.skin.outline, letterBorder: 12, shadow: [3, 3, 3]}, + {fill: "#fff"} + ]) + } + }) + } + var currentSong = this.songs[this.selectedSong] var highlight = 0 if(!currentSong.stars){ @@ -1387,6 +1387,20 @@ class SongSelect{ } var drawDifficulty = (ctx, i, currentUra) => { if(currentSong.stars[i] || currentUra){ + var score = scoreStorage.get(currentSong.originalTitle) + var crownDiff = currentUra ? "ura" : this.difficultyId[i] + var crownType = "" + if(score && score[crownDiff]){ + crownType = score[crownDiff].crown + } + this.draw.crown({ + ctx: ctx, + type: crownType, + x: songSel ? x + 33 + i * 60 : x + 402 + i * 100, + y: songSel ? y + 75 : y + 30, + scale: 0.25, + ratio: this.ratio / this.pixelRatio + }) if(songSel){ var _x = x + 33 + i * 60 var _y = y + 120 @@ -1912,6 +1926,35 @@ class SongSelect{ drawClosedSong(config){ var ctx = config.ctx + if(!config.song.action && config.song.originalTitle){ + var score = scoreStorage.get(config.song.originalTitle) + for(var i = this.difficultyId.length; i--;){ + var diff = this.difficultyId[i] + if(!score){ + break + } + if(config.song.stars[i] && score[diff] && score[diff].crown){ + this.draw.crown({ + ctx: ctx, + type: score[diff].crown, + x: config.x + this.songAsset.width / 2, + y: config.y - 13, + scale: 0.3, + ratio: this.ratio / this.pixelRatio + }) + this.draw.diffIcon({ + ctx: ctx, + diff: i, + x: config.x + this.songAsset.width / 2 + 8, + y: config.y - 8, + scale: diff === "hard" || diff === "normal" ? 0.45 : 0.5, + border: 6.5, + small: true + }) + break + } + } + } config.width = this.songAsset.width config.height = this.songAsset.height config.border = this.songAsset.border From 1759772831e8a7269b5e7e8aa19d8ae96ad9da3a Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 7 Mar 2020 04:48:30 +0300 Subject: [PATCH 4/8] ScoreStorage: Use hashes instead of song titles --- app.py | 42 +++++++++--------- public/src/js/assets.js | 1 + public/src/js/importsongs.js | 10 ++++- public/src/js/lib/md5.min.js | 10 +++++ public/src/js/loader.js | 14 +++++- public/src/js/scoresheet.js | 5 ++- public/src/js/scorestorage.js | 72 +++++++++++++++++++------------ public/src/js/songselect.js | 80 +++++++++++++++++++++-------------- 8 files changed, 150 insertions(+), 84 deletions(-) create mode 100644 public/src/js/lib/md5.min.js diff --git a/app.py b/app.py index d288d98..d1ecbe3 100644 --- a/app.py +++ b/app.py @@ -25,6 +25,7 @@ def get_db(): db = getattr(g, '_database', None) if db is None: db = g._database = sqlite3.connect(DATABASE) + db.row_factory = sqlite3.Row return db @@ -96,8 +97,8 @@ def route_api_preview(): if not song_row: abort(400) - song_type = song_row[0][12] - prev_path = make_preview(song_id, song_type, song_row[0][15]) + song_type = song_row[0]['type'] + prev_path = make_preview(song_id, song_type, song_row[0]['preview']) if not prev_path: return redirect(get_config()['songs_baseurl'] + '%s/main.mp3' % song_id) @@ -112,43 +113,44 @@ def route_api_songs(): raw_categories = query_db('select * from categories') categories = {} for cat in raw_categories: - categories[cat[0]] = cat[1] + categories[cat['id']] = cat['title'] raw_song_skins = query_db('select * from song_skins') song_skins = {} for skin in raw_song_skins: - song_skins[skin[0]] = {'name': skin[1], 'song': skin[2], 'stage': skin[3], 'don': skin[4]} + song_skins[skin[0]] = {'name': skin['name'], 'song': skin['song'], 'stage': skin['stage'], 'don': skin['don']} songs_out = [] for song in songs: - song_id = song[0] - song_type = song[12] - preview = song[15] + song_id = song['id'] + song_type = song['type'] + preview = song['preview'] - category_out = categories[song[11]] if song[11] in categories else "" - song_skin_out = song_skins[song[14]] if song[14] in song_skins else None + category_out = categories[song['category']] if song['category'] in categories else '' + song_skin_out = song_skins[song['skin_id']] if song['skin_id'] in song_skins else None maker = None - if song[17] == 0: + if song['maker_id'] == 0: maker = 0 - elif song[17] and song[17] > 0: - maker = {'name': song[18], 'url': song[19], 'id': song[17]} + elif song['maker_id'] and song['maker_id'] > 0: + maker = {'name': song['name'], 'url': song['url'], 'id': song['maker_id']} songs_out.append({ 'id': song_id, - 'title': song[1], - 'title_lang': song[2], - 'subtitle': song[3], - 'subtitle_lang': song[4], + 'title': song['title'], + 'title_lang': song['title_lang'], + 'subtitle': song['subtitle'], + 'subtitle_lang': song['subtitle_lang'], 'stars': [ - song[5], song[6], song[7], song[8], song[9] + song['easy'], song['normal'], song['hard'], song['oni'], song['ura'] ], 'preview': preview, 'category': category_out, 'type': song_type, - 'offset': song[13], + 'offset': song['offset'], 'song_skin': song_skin_out, - 'volume': song[16], - 'maker': maker + 'volume': song['volume'], + 'maker': maker, + 'hash': song['hash'] }) return jsonify(songs_out) diff --git a/public/src/js/assets.js b/public/src/js/assets.js index 45c5e93..b202a13 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -1,6 +1,7 @@ var assets = { "js": [ "lib/fontdetect.min.js", + "lib/md5.min.js", "loadsong.js", "parseosu.js", "titlescreen.js", diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index 6fb7ea1..d9dba60 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -274,6 +274,13 @@ if(songObj.stars.length !== 0){ this.songs[index] = songObj } + var hash = md5.base64(event.target.result).slice(0, -2) + songObj.hash = hash + scoreStorage.songTitles[songObj.title] = hash + var score = scoreStorage.get(hash) + if(score){ + score.title = songObj.title + } }).catch(() => {}) reader.readAsText(file, "sjis") return promise @@ -297,7 +304,8 @@ subtitle_lang: osu.metadata.Artist || osu.metadata.ArtistUnicode, preview: osu.generalInfo.PreviewTime / 1000, stars: [null, null, null, parseInt(osu.difficulty.overallDifficulty) || 1], - music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted" + music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted", + hash: md5.base64(event.target.result).slice(0, -2) } var filename = file.name.slice(0, file.name.lastIndexOf(".")) var title = osu.metadata.TitleUnicode || osu.metadata.Title diff --git a/public/src/js/lib/md5.min.js b/public/src/js/lib/md5.min.js new file mode 100644 index 0000000..7418ff8 --- /dev/null +++ b/public/src/js/lib/md5.min.js @@ -0,0 +1,10 @@ +/** + * [js-md5]{@link https://github.com/emn178/js-md5} + * + * @namespace md5 + * @version 0.7.3 + * @author Chen, Yi-Cyuan [emn178@gmail.com] + * @copyright Chen, Yi-Cyuan 2014-2017 + * @license MIT + */ +!function(){"use strict";function t(t){if(t)d[0]=d[16]=d[1]=d[2]=d[3]=d[4]=d[5]=d[6]=d[7]=d[8]=d[9]=d[10]=d[11]=d[12]=d[13]=d[14]=d[15]=0,this.blocks=d,this.buffer8=l;else if(a){var r=new ArrayBuffer(68);this.buffer8=new Uint8Array(r),this.blocks=new Uint32Array(r)}else this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];this.h0=this.h1=this.h2=this.h3=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0}var r="input is invalid type",e="object"==typeof window,i=e?window:{};i.JS_MD5_NO_WINDOW&&(e=!1);var s=!e&&"object"==typeof self,h=!i.JS_MD5_NO_NODE_JS&&"object"==typeof process&&process.versions&&process.versions.node;h?i=global:s&&(i=self);var f=!i.JS_MD5_NO_COMMON_JS&&"object"==typeof module&&module.exports,o="function"==typeof define&&define.amd,a=!i.JS_MD5_NO_ARRAY_BUFFER&&"undefined"!=typeof ArrayBuffer,n="0123456789abcdef".split(""),u=[128,32768,8388608,-2147483648],y=[0,8,16,24],c=["hex","array","digest","buffer","arrayBuffer","base64"],p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""),d=[],l;if(a){var A=new ArrayBuffer(68);l=new Uint8Array(A),d=new Uint32Array(A)}!i.JS_MD5_NO_NODE_JS&&Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),!a||!i.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW&&ArrayBuffer.isView||(ArrayBuffer.isView=function(t){return"object"==typeof t&&t.buffer&&t.buffer.constructor===ArrayBuffer});var b=function(r){return function(e){return new t(!0).update(e)[r]()}},v=function(){var r=b("hex");h&&(r=w(r)),r.create=function(){return new t},r.update=function(t){return r.create().update(t)};for(var e=0;e>2]|=t[f]<>6,u[h++]=128|63&s):s<55296||s>=57344?(u[h++]=224|s>>12,u[h++]=128|s>>6&63,u[h++]=128|63&s):(s=65536+((1023&s)<<10|1023&t.charCodeAt(++f)),u[h++]=240|s>>18,u[h++]=128|s>>12&63,u[h++]=128|s>>6&63,u[h++]=128|63&s);else for(h=this.start;f>2]|=s<>2]|=(192|s>>6)<>2]|=(128|63&s)<=57344?(n[h>>2]|=(224|s>>12)<>2]|=(128|s>>6&63)<>2]|=(128|63&s)<>2]|=(240|s>>18)<>2]|=(128|s>>12&63)<>2]|=(128|s>>6&63)<>2]|=(128|63&s)<=64?(this.start=h-64,this.hash(),this.hashed=!0):this.start=h}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%4294967296),this}},t.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var t=this.blocks,r=this.lastByteIndex;t[r>>2]|=u[3&r],r>=56&&(this.hashed||this.hash(),t[0]=t[16],t[16]=t[1]=t[2]=t[3]=t[4]=t[5]=t[6]=t[7]=t[8]=t[9]=t[10]=t[11]=t[12]=t[13]=t[14]=t[15]=0),t[14]=this.bytes<<3,t[15]=this.hBytes<<3|this.bytes>>>29,this.hash()}},t.prototype.hash=function(){var t,r,e,i,s,h,f=this.blocks;this.first?r=((r=((t=((t=f[0]-680876937)<<7|t>>>25)-271733879<<0)^(e=((e=(-271733879^(i=((i=(-1732584194^2004318071&t)+f[1]-117830708)<<12|i>>>20)+t<<0)&(-271733879^t))+f[2]-1126478375)<<17|e>>>15)+i<<0)&(i^t))+f[3]-1316259209)<<22|r>>>10)+e<<0:(t=this.h0,r=this.h1,e=this.h2,r=((r+=((t=((t+=((i=this.h3)^r&(e^i))+f[0]-680876936)<<7|t>>>25)+r<<0)^(e=((e+=(r^(i=((i+=(e^t&(r^e))+f[1]-389564586)<<12|i>>>20)+t<<0)&(t^r))+f[2]+606105819)<<17|e>>>15)+i<<0)&(i^t))+f[3]-1044525330)<<22|r>>>10)+e<<0),r=((r+=((t=((t+=(i^r&(e^i))+f[4]-176418897)<<7|t>>>25)+r<<0)^(e=((e+=(r^(i=((i+=(e^t&(r^e))+f[5]+1200080426)<<12|i>>>20)+t<<0)&(t^r))+f[6]-1473231341)<<17|e>>>15)+i<<0)&(i^t))+f[7]-45705983)<<22|r>>>10)+e<<0,r=((r+=((t=((t+=(i^r&(e^i))+f[8]+1770035416)<<7|t>>>25)+r<<0)^(e=((e+=(r^(i=((i+=(e^t&(r^e))+f[9]-1958414417)<<12|i>>>20)+t<<0)&(t^r))+f[10]-42063)<<17|e>>>15)+i<<0)&(i^t))+f[11]-1990404162)<<22|r>>>10)+e<<0,r=((r+=((t=((t+=(i^r&(e^i))+f[12]+1804603682)<<7|t>>>25)+r<<0)^(e=((e+=(r^(i=((i+=(e^t&(r^e))+f[13]-40341101)<<12|i>>>20)+t<<0)&(t^r))+f[14]-1502002290)<<17|e>>>15)+i<<0)&(i^t))+f[15]+1236535329)<<22|r>>>10)+e<<0,r=((r+=((i=((i+=(r^e&((t=((t+=(e^i&(r^e))+f[1]-165796510)<<5|t>>>27)+r<<0)^r))+f[6]-1069501632)<<9|i>>>23)+t<<0)^t&((e=((e+=(t^r&(i^t))+f[11]+643717713)<<14|e>>>18)+i<<0)^i))+f[0]-373897302)<<20|r>>>12)+e<<0,r=((r+=((i=((i+=(r^e&((t=((t+=(e^i&(r^e))+f[5]-701558691)<<5|t>>>27)+r<<0)^r))+f[10]+38016083)<<9|i>>>23)+t<<0)^t&((e=((e+=(t^r&(i^t))+f[15]-660478335)<<14|e>>>18)+i<<0)^i))+f[4]-405537848)<<20|r>>>12)+e<<0,r=((r+=((i=((i+=(r^e&((t=((t+=(e^i&(r^e))+f[9]+568446438)<<5|t>>>27)+r<<0)^r))+f[14]-1019803690)<<9|i>>>23)+t<<0)^t&((e=((e+=(t^r&(i^t))+f[3]-187363961)<<14|e>>>18)+i<<0)^i))+f[8]+1163531501)<<20|r>>>12)+e<<0,r=((r+=((i=((i+=(r^e&((t=((t+=(e^i&(r^e))+f[13]-1444681467)<<5|t>>>27)+r<<0)^r))+f[2]-51403784)<<9|i>>>23)+t<<0)^t&((e=((e+=(t^r&(i^t))+f[7]+1735328473)<<14|e>>>18)+i<<0)^i))+f[12]-1926607734)<<20|r>>>12)+e<<0,r=((r+=((h=(i=((i+=((s=r^e)^(t=((t+=(s^i)+f[5]-378558)<<4|t>>>28)+r<<0))+f[8]-2022574463)<<11|i>>>21)+t<<0)^t)^(e=((e+=(h^r)+f[11]+1839030562)<<16|e>>>16)+i<<0))+f[14]-35309556)<<23|r>>>9)+e<<0,r=((r+=((h=(i=((i+=((s=r^e)^(t=((t+=(s^i)+f[1]-1530992060)<<4|t>>>28)+r<<0))+f[4]+1272893353)<<11|i>>>21)+t<<0)^t)^(e=((e+=(h^r)+f[7]-155497632)<<16|e>>>16)+i<<0))+f[10]-1094730640)<<23|r>>>9)+e<<0,r=((r+=((h=(i=((i+=((s=r^e)^(t=((t+=(s^i)+f[13]+681279174)<<4|t>>>28)+r<<0))+f[0]-358537222)<<11|i>>>21)+t<<0)^t)^(e=((e+=(h^r)+f[3]-722521979)<<16|e>>>16)+i<<0))+f[6]+76029189)<<23|r>>>9)+e<<0,r=((r+=((h=(i=((i+=((s=r^e)^(t=((t+=(s^i)+f[9]-640364487)<<4|t>>>28)+r<<0))+f[12]-421815835)<<11|i>>>21)+t<<0)^t)^(e=((e+=(h^r)+f[15]+530742520)<<16|e>>>16)+i<<0))+f[2]-995338651)<<23|r>>>9)+e<<0,r=((r+=((i=((i+=(r^((t=((t+=(e^(r|~i))+f[0]-198630844)<<6|t>>>26)+r<<0)|~e))+f[7]+1126891415)<<10|i>>>22)+t<<0)^((e=((e+=(t^(i|~r))+f[14]-1416354905)<<15|e>>>17)+i<<0)|~t))+f[5]-57434055)<<21|r>>>11)+e<<0,r=((r+=((i=((i+=(r^((t=((t+=(e^(r|~i))+f[12]+1700485571)<<6|t>>>26)+r<<0)|~e))+f[3]-1894986606)<<10|i>>>22)+t<<0)^((e=((e+=(t^(i|~r))+f[10]-1051523)<<15|e>>>17)+i<<0)|~t))+f[1]-2054922799)<<21|r>>>11)+e<<0,r=((r+=((i=((i+=(r^((t=((t+=(e^(r|~i))+f[8]+1873313359)<<6|t>>>26)+r<<0)|~e))+f[15]-30611744)<<10|i>>>22)+t<<0)^((e=((e+=(t^(i|~r))+f[6]-1560198380)<<15|e>>>17)+i<<0)|~t))+f[13]+1309151649)<<21|r>>>11)+e<<0,r=((r+=((i=((i+=(r^((t=((t+=(e^(r|~i))+f[4]-145523070)<<6|t>>>26)+r<<0)|~e))+f[11]-1120210379)<<10|i>>>22)+t<<0)^((e=((e+=(t^(i|~r))+f[2]+718787259)<<15|e>>>17)+i<<0)|~t))+f[9]-343485551)<<21|r>>>11)+e<<0,this.first?(this.h0=t+1732584193<<0,this.h1=r-271733879<<0,this.h2=e-1732584194<<0,this.h3=i+271733878<<0,this.first=!1):(this.h0=this.h0+t<<0,this.h1=this.h1+r<<0,this.h2=this.h2+e<<0,this.h3=this.h3+i<<0)},t.prototype.hex=function(){this.finalize();var t=this.h0,r=this.h1,e=this.h2,i=this.h3;return n[t>>4&15]+n[15&t]+n[t>>12&15]+n[t>>8&15]+n[t>>20&15]+n[t>>16&15]+n[t>>28&15]+n[t>>24&15]+n[r>>4&15]+n[15&r]+n[r>>12&15]+n[r>>8&15]+n[r>>20&15]+n[r>>16&15]+n[r>>28&15]+n[r>>24&15]+n[e>>4&15]+n[15&e]+n[e>>12&15]+n[e>>8&15]+n[e>>20&15]+n[e>>16&15]+n[e>>28&15]+n[e>>24&15]+n[i>>4&15]+n[15&i]+n[i>>12&15]+n[i>>8&15]+n[i>>20&15]+n[i>>16&15]+n[i>>28&15]+n[i>>24&15]},t.prototype.toString=t.prototype.hex,t.prototype.digest=function(){this.finalize();var t=this.h0,r=this.h1,e=this.h2,i=this.h3;return[255&t,t>>8&255,t>>16&255,t>>24&255,255&r,r>>8&255,r>>16&255,r>>24&255,255&e,e>>8&255,e>>16&255,e>>24&255,255&i,i>>8&255,i>>16&255,i>>24&255]},t.prototype.array=t.prototype.digest,t.prototype.arrayBuffer=function(){this.finalize();var t=new ArrayBuffer(16),r=new Uint32Array(t);return r[0]=this.h0,r[1]=this.h1,r[2]=this.h2,r[3]=this.h3,t},t.prototype.buffer=t.prototype.arrayBuffer,t.prototype.base64=function(){for(var t,r,e,i="",s=this.array(),h=0;h<15;)t=s[h++],r=s[h++],e=s[h++],i+=p[t>>>2]+p[63&(t<<4|r>>>4)]+p[63&(r<<2|e>>>6)]+p[63&e];return t=s[h],i+=p[t>>>2]+p[t<<4&63]+"=="};var _=v();f?module.exports=_:(i.md5=_,o&&define(function(){return _}))}(); \ No newline at end of file diff --git a/public/src/js/loader.js b/public/src/js/loader.js index ec19352..3a5cd75 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -204,9 +204,21 @@ class Loader{ } settings = new Settings() - scoreStorage = new ScoreStorage() pageEvents.setKbd() + scoreStorage = new ScoreStorage() + for(var i in assets.songsDefault){ + var song = assets.songsDefault[i] + if(!song.hash){ + song.hash = song.title + } + scoreStorage.songTitles[song.title] = song.hash + var score = scoreStorage.get(song.hash) + if(score){ + score.title = song.title + } + } + Promise.all(this.promises).then(() => { this.canvasTest.drawAllImages().then(result => { perf.allImg = result diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 3e62440..cf9887b 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -863,8 +863,9 @@ class Scoresheet{ saveScore(){ if(!this.controller.autoPlayEnabled && this.resultsObj.points > 0){ var title = this.controller.selectedSong.originalTitle + var hash = this.controller.selectedSong.hash var difficulty = this.resultsObj.difficulty - var oldScore = scoreStorage.get(title, difficulty) + var oldScore = scoreStorage.get(hash, difficulty, true) if(!oldScore || oldScore.points <= this.resultsObj.points){ var crown = "" if(this.controller.game.rules.clearReached(this.resultsObj.gauge)){ @@ -877,7 +878,7 @@ class Scoresheet{ delete this.resultsObj.title delete this.resultsObj.difficulty delete this.resultsObj.gauge - scoreStorage.add(title, difficulty, this.resultsObj) + scoreStorage.add(hash, difficulty, this.resultsObj, true, title) } } this.scoreSaved = true diff --git a/public/src/js/scorestorage.js b/public/src/js/scorestorage.js index f06e729..87820f5 100644 --- a/public/src/js/scorestorage.js +++ b/public/src/js/scorestorage.js @@ -1,6 +1,7 @@ class ScoreStorage{ constructor(){ this.scores = {} + this.songTitles = {} this.difficulty = ["oni", "ura", "hard", "normal", "easy"] this.scoreKeys = ["points", "good", "ok", "bad", "maxCombo", "drumroll"] this.crownValue = ["", "silver", "gold"] @@ -15,8 +16,8 @@ class ScoreStorage{ this.scoreStrings = JSON.parse(localScores) } }catch(e){} - for(var song in this.scoreStrings){ - var scoreString = this.scoreStrings[song] + for(var hash in this.scoreStrings){ + var scoreString = this.scoreStrings[hash] var songAdded = false if(typeof scoreString === "string" && scoreString){ var diffArray = scoreString.split(";") @@ -36,18 +37,18 @@ class ScoreStorage{ score[name] = value } if(!songAdded){ - this.scores[song] = [] + this.scores[hash] = {title: null} songAdded = true } - this.scores[song][this.difficulty[i]] = score + this.scores[hash][this.difficulty[i]] = score } } } } } save(){ - for(var song in this.scores){ - this.writeString(song) + for(var hash in this.scores){ + this.writeString(hash) } this.write() } @@ -56,8 +57,8 @@ class ScoreStorage{ localStorage.setItem("scoreStorage", JSON.stringify(this.scoreStrings)) }catch(e){} } - writeString(song){ - var score = this.scores[song] + writeString(hash){ + var score = this.scores[hash] var diffArray = [] var notEmpty = false for(var i = this.difficulty.length; i--;){ @@ -77,25 +78,39 @@ class ScoreStorage{ diffArray.unshift("") } } - this.scoreStrings[song] = diffArray.join(";") + this.scoreStrings[hash] = diffArray.join(";") } - get(song, difficulty){ + titleHash(song){ + if(song in this.songTitles){ + return this.songTitles[song] + }else{ + return song + } + } + get(song, difficulty, isHash){ if(!song){ return this.scores - }else if(song in this.scores){ + }else{ + var hash = isHash ? song : this.titleHash(song) if(difficulty){ - return this.scores[song][difficulty] + if(hash in this.scores){ + return this.scores[hash][difficulty] + } }else{ - return this.scores[song] + return this.scores[hash] } } } - add(song, difficulty, scoreObject){ - if(!(song in this.scores)){ - this.scores[song] = {} + add(song, difficulty, scoreObject, isHash, setTitle){ + var hash = isHash ? song : this.titleHash(song) + if(!(hash in this.scores)){ + this.scores[hash] = {} } - this.scores[song][difficulty] = scoreObject - this.writeString(song) + if(setTitle){ + this.scores[hash].title = setTitle + } + this.scores[hash][difficulty] = scoreObject + this.writeString(hash) this.write() } template(){ @@ -106,28 +121,29 @@ class ScoreStorage{ } return template } - remove(song, difficulty){ - if(song in this.scores){ + remove(song, difficulty, isHash){ + var hash = isHash ? song : this.titleHash(song) + if(hash in this.scores){ if(difficulty){ - if(difficulty in this.scores[song]){ - delete this.scores[song][difficulty] + if(difficulty in this.scores[hash]){ + delete this.scores[hash][difficulty] var noDiff = true for(var i in this.difficulty){ - if(this.scores[song][this.difficulty[i]]){ + if(this.scores[hash][this.difficulty[i]]){ noDiff = false break } } if(noDiff){ - delete this.scores[song] - delete this.scoreStrings[song] + delete this.scores[hash] + delete this.scoreStrings[hash] }else{ - this.writeString(song) + this.writeString(hash) } } }else{ - delete this.scores[song] - delete this.scoreStrings[song] + delete this.scores[hash] + delete this.scoreStrings[hash] } this.write() } diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index cb320da..6bc9d16 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -125,7 +125,8 @@ class SongSelect{ music: song.music, volume: song.volume, maker: song.maker, - canJump: true + canJump: true, + hash: song.hash || song.title }) } this.songs.sort((a, b) => { @@ -746,7 +747,8 @@ class SongSelect{ "type": selectedSong.type, "offset": selectedSong.offset, "songSkin": selectedSong.songSkin, - "stars": selectedSong.stars[difficulty] + "stars": selectedSong.stars[difficulty], + "hash": selectedSong.hash }, autoplay, multiplayer, touch) } toOptions(moveBy){ @@ -1256,6 +1258,15 @@ class SongSelect{ this.currentSongCache.clear() } + if(selectedWidth === this.songAsset.width){ + this.drawSongCrown({ + ctx: ctx, + song: currentSong, + x: winW / 2 - selectedWidth / 2 + xOffset, + y: songTop + this.songAsset.height - selectedHeight + }) + } + this.draw.songFrame({ ctx: ctx, x: winW / 2 - selectedWidth / 2 + xOffset, @@ -1387,7 +1398,7 @@ class SongSelect{ } var drawDifficulty = (ctx, i, currentUra) => { if(currentSong.stars[i] || currentUra){ - var score = scoreStorage.get(currentSong.originalTitle) + var score = scoreStorage.get(currentSong.hash) var crownDiff = currentUra ? "ura" : this.difficultyId[i] var crownType = "" if(score && score[crownDiff]){ @@ -1926,35 +1937,7 @@ class SongSelect{ drawClosedSong(config){ var ctx = config.ctx - if(!config.song.action && config.song.originalTitle){ - var score = scoreStorage.get(config.song.originalTitle) - for(var i = this.difficultyId.length; i--;){ - var diff = this.difficultyId[i] - if(!score){ - break - } - if(config.song.stars[i] && score[diff] && score[diff].crown){ - this.draw.crown({ - ctx: ctx, - type: score[diff].crown, - x: config.x + this.songAsset.width / 2, - y: config.y - 13, - scale: 0.3, - ratio: this.ratio / this.pixelRatio - }) - this.draw.diffIcon({ - ctx: ctx, - diff: i, - x: config.x + this.songAsset.width / 2 + 8, - y: config.y - 8, - scale: diff === "hard" || diff === "normal" ? 0.45 : 0.5, - border: 6.5, - small: true - }) - break - } - } - } + this.drawSongCrown(config) config.width = this.songAsset.width config.height = this.songAsset.height config.border = this.songAsset.border @@ -2004,6 +1987,39 @@ class SongSelect{ } } + drawSongCrown(config){ + if(!config.song.action && config.song.hash){ + var ctx = config.ctx + var score = scoreStorage.get(config.song.hash) + for(var i = this.difficultyId.length; i--;){ + var diff = this.difficultyId[i] + if(!score){ + break + } + if(config.song.stars[i] && score[diff] && score[diff].crown){ + this.draw.crown({ + ctx: ctx, + type: score[diff].crown, + x: config.x + this.songAsset.width / 2, + y: config.y - 13, + scale: 0.3, + ratio: this.ratio / this.pixelRatio + }) + this.draw.diffIcon({ + ctx: ctx, + diff: i, + x: config.x + this.songAsset.width / 2 + 8, + y: config.y - 8, + scale: diff === "hard" || diff === "normal" ? 0.45 : 0.5, + border: 6.5, + small: true + }) + break + } + } + } + } + startPreview(loadOnly){ var currentSong = this.songs[this.selectedSong] var id = currentSong.id From 0d13f14f45492ba4491110aaa403f8056ca34a8d Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 7 Mar 2020 05:39:48 +0300 Subject: [PATCH 5/8] Add various tools --- tools/get_version.bat | 4 + tools/get_version.sh | 1 + tools/merge_image.htm | 196 ++++++++++++++++++++++++++++++++++++++++++ tools/set_previews.py | 86 ++++++++++++++++++ tools/taikodb_hash.py | 49 +++++++++++ 5 files changed, 336 insertions(+) create mode 100644 tools/get_version.bat create mode 100644 tools/get_version.sh create mode 100644 tools/merge_image.htm create mode 100644 tools/set_previews.py create mode 100644 tools/taikodb_hash.py diff --git a/tools/get_version.bat b/tools/get_version.bat new file mode 100644 index 0000000..3d2737d --- /dev/null +++ b/tools/get_version.bat @@ -0,0 +1,4 @@ +@echo off +( +git log -1 --pretty="format:{\"commit\": \"%%H\", \"commit_short\": \"%%h\", \"version\": \"%%ad\", \"url\": \"https://github.com/bui/taiko-web/\"}" --date="format:%%y.%%m.%%d" +) > ../version.json diff --git a/tools/get_version.sh b/tools/get_version.sh new file mode 100644 index 0000000..8d46c64 --- /dev/null +++ b/tools/get_version.sh @@ -0,0 +1 @@ +git log -1 --pretty="format:{\"commit\": \"%H\", \"commit_short\": \"%h\", \"version\": \"%ad\", \"url\": \"https://github.com/bui/taiko-web/\"}" --date="format:%y.%m.%d" > ../version.json diff --git a/tools/merge_image.htm b/tools/merge_image.htm new file mode 100644 index 0000000..fd4726f --- /dev/null +++ b/tools/merge_image.htm @@ -0,0 +1,196 @@ + + +Merge Image + + + +
+ + + +
+
Drag and drop your images here...
+ + + + diff --git a/tools/set_previews.py b/tools/set_previews.py new file mode 100644 index 0000000..835884e --- /dev/null +++ b/tools/set_previews.py @@ -0,0 +1,86 @@ +from __future__ import division +import os +import sqlite3 +import re +DATABASE = 'taiko.db' + +conn = sqlite3.connect(DATABASE) +curs = conn.cursor() + +def parse_osu(osu): + osu_lines = open(osu, 'r').read().replace('\x00', '').split('\n') + sections = {} + current_section = (None, []) + + for line in osu_lines: + line = line.strip() + secm = re.match('^\[(\w+)\]$', line) + if secm: + if current_section: + sections[current_section[0]] = current_section[1] + current_section = (secm.group(1), []) + else: + if current_section: + current_section[1].append(line) + else: + current_section = ('Default', [line]) + + if current_section: + sections[current_section[0]] = current_section[1] + + return sections + + +def get_osu_key(osu, section, key, default=None): + sec = osu[section] + for line in sec: + ok = line.split(':', 1)[0].strip() + ov = line.split(':', 1)[1].strip() + + if ok.lower() == key.lower(): + return ov + + return default + + +def get_preview(song_id, song_type): + preview = 0 + + if song_type == "tja": + if os.path.isfile('public/songs/%s/main.tja' % song_id): + preview = get_tja_preview('public/songs/%s/main.tja' % song_id) + else: + osus = [osu for osu in os.listdir('public/songs/%s' % song_id) if osu in ['easy.osu', 'normal.osu', 'hard.osu', 'oni.osu']] + if osus: + osud = parse_osu('public/songs/%s/%s' % (song_id, osus[0])) + preview = int(get_osu_key(osud, 'General', 'PreviewTime', 0)) + + return preview + + +def get_tja_preview(tja): + tja_lines = open(tja, 'r').read().replace('\x00', '').split('\n') + + for line in tja_lines: + line = line.strip() + if ':' in line: + name, value = line.split(':', 1) + if name.lower() == 'demostart': + value = value.strip() + try: + value = float(value) + except ValueError: + pass + else: + return int(value * 1000) + elif line.lower() == '#start': + break + return 0 + + +if __name__ == '__main__': + songs = curs.execute('select id, type from songs').fetchall() + for song in songs: + preview = get_preview(song[0], song[1]) / 1000 + curs.execute('update songs set preview = ? where id = ?', (preview, song[0])) + conn.commit() diff --git a/tools/taikodb_hash.py b/tools/taikodb_hash.py new file mode 100644 index 0000000..7c6955f --- /dev/null +++ b/tools/taikodb_hash.py @@ -0,0 +1,49 @@ +import os +import sys +import hashlib +import base64 +import sqlite3 + +def md5(md5hash, filename): + with open(filename, "rb") as file: + for chunk in iter(lambda: file.read(64 * 1024), b""): + md5hash.update(chunk) + +def get_hashes(root): + hashes = {} + diffs = ["easy", "normal", "hard", "oni", "ura"] + dirs = os.listdir(root) + for dir in dirs: + dir_path = os.path.join(root, dir) + if dir.isdigit() and os.path.isdir(dir_path): + files = os.listdir(dir_path) + md5hash = hashlib.md5() + if "main.tja" in files: + md5(md5hash, os.path.join(dir_path, "main.tja")) + else: + for diff in diffs: + if diff + ".osu" in files: + md5(md5hash, os.path.join(dir_path, diff + ".osu")) + hashes[dir] = base64.b64encode(md5hash.digest())[:-2] + return hashes + +def write_db(database, songs): + db = sqlite3.connect(database) + hashes = get_hashes(songs) + added = 0 + for id in hashes: + added += 1 + cur = db.cursor() + cur.execute("update songs set hash = ? where id = ?", (hashes[id].decode(), int(id))) + cur.close() + db.commit() + db.close() + if added: + print("{0} hashes have been added to the database.".format(added)) + else: + print("Error: No songs were found in the given directory.") + +if len(sys.argv) >= 3: + write_db(sys.argv[1], sys.argv[2]) +else: + print("Usage: taikodb_hash.py ../taiko.db ../public/songs") From ac0483b3898f4d2f7049776f3c58c2f64d10ca2f Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 7 Mar 2020 10:13:50 +0300 Subject: [PATCH 6/8] SongStorage: Fix imported osu songs not getting titles --- public/src/js/importsongs.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index d9dba60..793e006 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -304,8 +304,7 @@ subtitle_lang: osu.metadata.Artist || osu.metadata.ArtistUnicode, preview: osu.generalInfo.PreviewTime / 1000, stars: [null, null, null, parseInt(osu.difficulty.overallDifficulty) || 1], - music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted", - hash: md5.base64(event.target.result).slice(0, -2) + music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted" } var filename = file.name.slice(0, file.name.lastIndexOf(".")) var title = osu.metadata.TitleUnicode || osu.metadata.Title @@ -322,6 +321,13 @@ } this.songs[index] = songObj songObj.category = category || this.getCategory(file) + var hash = md5.base64(event.target.result).slice(0, -2) + songObj.hash = hash + scoreStorage.songTitles[songObj.title] = hash + var score = scoreStorage.get(hash) + if(score){ + score.title = songObj.title + } }).catch(() => {}) reader.readAsText(file) return promise From 40577beadce46fa0044043fbd4d37ebc72aaf828 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 7 Mar 2020 10:34:01 +0300 Subject: [PATCH 7/8] ScoreStorage: Fix full combos with a worse score not saving --- public/src/js/scoresheet.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index cf9887b..8a12ac7 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -861,16 +861,20 @@ class Scoresheet{ } saveScore(){ - if(!this.controller.autoPlayEnabled && this.resultsObj.points > 0){ + if(!this.controller.autoPlayEnabled){ + if(this.resultsObj.points < 0){ + this.resultsObj.points = 0 + } var title = this.controller.selectedSong.originalTitle var hash = this.controller.selectedSong.hash var difficulty = this.resultsObj.difficulty var oldScore = scoreStorage.get(hash, difficulty, true) + var clearReached = this.controller.game.rules.clearReached(this.resultsObj.gauge) + var crown = "" + if(clearReached){ + crown = this.resultsObj.bad === 0 ? "gold" : "silver" + } if(!oldScore || oldScore.points <= this.resultsObj.points){ - var crown = "" - if(this.controller.game.rules.clearReached(this.resultsObj.gauge)){ - crown = this.resultsObj.bad === 0 ? "gold" : "silver" - } if(oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)){ crown = oldScore.crown } @@ -879,6 +883,9 @@ class Scoresheet{ delete this.resultsObj.difficulty delete this.resultsObj.gauge scoreStorage.add(hash, difficulty, this.resultsObj, true, title) + }else if(oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)){ + oldScore.crown = crown + scoreStorage.add(hash, difficulty, oldScore, true, title) } } this.scoreSaved = true From 5316f0f371f18989c6ab70ce3720c68255edc439 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 7 Mar 2020 11:14:51 +0300 Subject: [PATCH 8/8] get_version.sh: Grant execute permission --- tools/get_version.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tools/get_version.sh diff --git a/tools/get_version.sh b/tools/get_version.sh old mode 100644 new mode 100755