diff --git a/public/src/css/view.css b/public/src/css/view.css index 76ed3f4..c1f254e 100644 --- a/public/src/css/view.css +++ b/public/src/css/view.css @@ -291,3 +291,80 @@ kbd{ .left-buttons .taibtn{ z-index: 1; } +.accountpass-form, +.accountdel-form, +.login-form{ + text-align: center; + width: 80%; + margin: auto; +} +.accountpass-form .accountpass-div, +.accountdel-form .accountdel-div, +.login-form .password2-div{ + display: none; +} +.account-view .displayname, +.accountpass-form input[type=password], +.accountdel-form input[type=password], +.login-form input[type=text], +.login-form input[type=password]{ + width: 100%; + font-size: 1.4em; + margin: 0.1em 0; + padding: 0.3em; + box-sizing: border-box; +} +.accountpass-form input[type=password]{ + width: calc(100% / 3); +} +.accountpass-form input[type=password]::placeholder{ + font-size: 0.8em; +} +.login-form input[type=checkbox]{ + transform: scale(1.4); +} +.account-view .displayname-hint, +.login-form .username-hint, +.login-form .password-hint, +.login-form .remember-label{ + display: block; + font-size: 1.1em; + padding: 0.5em; +} +.login-form .remember-label{ + padding: 0.85em; +} +.account-view .save-btn{ + float: right; + padding: 0.4em 1.5em; + font-weight: bold; + border-color: #000; + color: #000; + z-index: 1; +} +.account-view .view-end-button{ + margin-right: 0.4em; + font-weight: normal; + border-color: #dacdb2; + color: #555; +} +.account-view .save-btn:hover, +.account-view .save-btn.selected, +.account-view .view-end-button:hover, +.account-view .view-end-button.selected{ + color: #fff; + border-color: #fff; +} +.account-view .displayname-div{ + width: 80%; + margin: 0 auto; +} +.accountpass-form .accountpass-btn, +.accountdel-form .accountdel-btn, +.login-form .login-btn{ + z-index: 1; +} +.accountpass-form, +.accountdel-form{ + margin: 0.3em auto; +} diff --git a/public/src/js/account.js b/public/src/js/account.js new file mode 100644 index 0000000..35ea69d --- /dev/null +++ b/public/src/js/account.js @@ -0,0 +1,482 @@ +class Account{ + constructor(touchEnabled){ + this.touchEnabled = touchEnabled + cancelTouch = false + this.locked = false + + if(account.loggedIn){ + this.accountForm() + }else{ + this.loginForm() + } + this.selected = this.items.length - 1 + + this.keyboard = new Keyboard({ + confirm: ["enter", "space", "don_l", "don_r"], + previous: ["left", "up", "ka_l"], + next: ["right", "down", "ka_r"], + back: ["escape"] + }, this.keyPressed.bind(this)) + this.gamepad = new Gamepad({ + "confirm": ["b", "ls", "rs"], + "previous": ["u", "l", "lb", "lt", "lsu", "lsl"], + "next": ["d", "r", "rb", "rt", "lsd", "lsr"], + "back": ["start", "a"] + }, this.keyPressed.bind(this)) + + pageEvents.send("account", account.loggedIn) + } + accountForm(){ + loader.changePage("account", true) + this.mode = "account" + + this.setAltText(this.getElement("view-title"), account.username) + this.items = [] + this.inputForms = [] + this.shownDiv = "" + + this.getElement("displayname-hint").innerText = strings.account.displayName + this.displayname = this.getElement("displayname") + this.displayname.placeholder = strings.account.displayName + this.displayname.value = account.displayName + this.inputForms.push(this.displayname) + + this.accountPassButton = this.getElement("accountpass-btn") + this.setAltText(this.accountPassButton, strings.account.changePassword) + pageEvents.add(this.accountPassButton, ["click", "touchstart"], event => { + this.showDiv(event, "pass") + }) + this.accountPass = this.getElement("accountpass-form") + for(var i = 0; i < this.accountPass.length; i++){ + this.accountPass[i].placeholder = strings.account.currentNewRepeat[i] + this.inputForms.push(this.accountPass[i]) + } + this.accountPassDiv = this.getElement("accountpass-div") + + this.accountDelButton = this.getElement("accountdel-btn") + this.setAltText(this.accountDelButton, strings.account.deleteAccount) + pageEvents.add(this.accountDelButton, ["click", "touchstart"], event => { + this.showDiv(event, "del") + }) + this.accountDel = this.getElement("accountdel-form") + this.accountDel.password.placeholder = strings.account.verifyPassword + this.inputForms.push(this.accountDel.password) + this.accountDelDiv = this.getElement("accountdel-div") + + this.logoutButton = this.getElement("logout-btn") + this.setAltText(this.logoutButton, strings.account.logout) + pageEvents.add(this.logoutButton, ["mousedown", "touchstart"], this.onLogout.bind(this)) + this.items.push(this.logoutButton) + + this.endButton = this.getElement("view-end-button") + this.setAltText(this.endButton, strings.account.cancel) + pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) + this.items.push(this.endButton) + + this.saveButton = this.getElement("save-btn") + this.setAltText(this.saveButton, strings.account.save) + pageEvents.add(this.saveButton, ["mousedown", "touchstart"], this.onSave.bind(this)) + this.items.push(this.saveButton) + + for(var i = 0; i < this.inputForms.length; i++){ + pageEvents.add(this.inputForms[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this)) + } + } + showDiv(event, div){ + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + }else if(event.which !== 1){ + return + } + } + if(this.locked){ + return + } + var otherDiv = this.shownDiv && this.shownDiv !== div + var display = this.shownDiv === div ? "" : "block" + this.shownDiv = display ? div : "" + switch(div){ + case "pass": + if(otherDiv){ + this.accountDelDiv.style.display = "" + } + this.accountPassDiv.style.display = display + break + case "del": + if(otherDiv){ + this.accountPassDiv.style.display = "" + } + this.accountDelDiv.style.display = display + break + } + } + loginForm(register, fromSwitch){ + loader.changePage("login", true) + this.mode = register ? "register" : "login" + + this.setAltText(this.getElement("view-title"), strings.account[this.mode]) + this.items = [] + this.form = this.getElement("login-form") + this.getElement("username-hint").innerText = strings.account.username + this.form.username.placeholder = strings.account.enterUsername + this.getElement("password-hint").innerText = strings.account.password + this.form.password.placeholder = strings.account.enterPassword + this.password2 = this.getElement("password2-div") + this.remember = this.getElement("remember-div") + this.getElement("remember-label").appendChild(document.createTextNode(strings.account.remember)) + this.loginButton = this.getElement("login-btn") + this.registerButton = this.getElement("register-btn") + + if(register){ + var pass2 = document.createElement("input") + pass2.type = "password" + pass2.name = "password2" + pass2.required = true + pass2.placeholder = strings.account.repeatPassword + this.password2.appendChild(pass2) + this.password2.style.display = "block" + this.remember.style.display = "none" + this.setAltText(this.loginButton, strings.account.registerAccount) + this.setAltText(this.registerButton, strings.account.login) + }else{ + this.setAltText(this.loginButton, strings.account.login) + this.setAltText(this.registerButton, strings.account.register) + } + + pageEvents.add(this.form, "submit", this.onLogin.bind(this)) + pageEvents.add(this.loginButton, ["mousedown", "touchstart"], this.onLogin.bind(this)) + + pageEvents.add(this.registerButton, ["mousedown", "touchstart"], this.onSwitchMode.bind(this)) + this.items.push(this.registerButton) + if(!register){ + this.items.push(this.loginButton) + } + + for(var i = 0; i < this.form.length; i++){ + pageEvents.add(this.form[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this)) + } + + this.endButton = this.getElement("view-end-button") + this.setAltText(this.endButton, strings.account.back) + pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) + this.items.push(this.endButton) + if(fromSwitch){ + this.selected = 0 + this.endButton.classList.remove("selected") + this.registerButton.classList.add("selected") + } + } + getElement(name){ + return loader.screen.getElementsByClassName(name)[0] + } + setAltText(element, text){ + element.innerText = text + element.setAttribute("alt", text) + } + keyPressed(pressed, name){ + if(!pressed || this.locked){ + return + } + var selected = this.items[this.selected] + if(name === "confirm"){ + if(selected === this.endButton){ + this.onEnd() + }else if(selected === this.registerButton){ + this.onSwitchMode() + }else if(selected === this.loginButton){ + this.onLogin() + } + }else if(name === "previous" || name === "next"){ + selected.classList.remove("selected") + this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1)) + this.items[this.selected].classList.add("selected") + assets.sounds["se_ka"].play() + }else if(name === "back"){ + this.onEnd() + } + } + mod(length, index){ + return ((index % length) + length) % length + } + onFormPress(event){ + event.stopPropagation() + if(event.type === "keypress" && event.keyCode === 13){ + if(this.mode === "account"){ + this.onSave() + }else{ + this.onLogin() + } + } + } + onSwitchMode(event){ + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + }else if(event.which !== 1){ + return + } + } + if(this.locked){ + return + } + this.clean(true) + this.loginForm(this.mode === "login", true) + } + onLogin(event){ + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + }else if(event.which !== 1){ + return + } + } + if(this.locked){ + return + } + var obj = { + username: this.form.username.value, + password: this.form.password.value + } + if(!obj.username || !obj.password){ + alert(strings.account.cannotBeEmpty.replace("%s", strings.account[!obj.username ? "username" : "password"])) + return + } + if(this.mode === "login"){ + obj.remember = this.form.remember.checked + }else{ + if(obj.password !== this.form.password2.value){ + alert(strings.account.passwordsDoNotMatch) + return + } + } + this.request(this.mode, obj).then(response => { + account.loggedIn = true + account.username = response.username + account.displayName = response.display_name + var loadScores = scores => { + scoreStorage.load(scores) + this.onEnd(false, true) + pageEvents.send("login", account.username) + } + if(this.mode === "login"){ + this.request("scores/get").then(response => { + loadScores(response.scores) + }, () => { + loadScores({}) + }) + }else{ + scoreStorage.save().catch(() => {}).finally(() => { + this.onEnd(false, true) + pageEvents.send("login", account.username) + }) + } + }, response => { + if(response && response.status === "error" && response.message){ + alert(response.message) + }else{ + alert(strings.account.error) + } + }) + } + onLogout(){ + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + }else if(event.which !== 1){ + return + } + } + if(this.locked){ + return + } + account.loggedIn = false + delete account.username + delete account.displayName + var loadScores = scores => { + Cookies.remove("token") + scoreStorage.load() + this.onEnd(false, true) + pageEvents.send("logout") + } + this.request("logout").then(response => { + loadScores() + }, () => { + loadScores() + }) + } + onSave(event){ + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + }else if(event.which !== 1){ + return + } + } + if(this.locked){ + return + } + var promises = [] + var noNameChange = false + if(this.shownDiv === "pass"){ + var passwords = [] + for(var i = 0; i < this.accountPass.length; i++){ + passwords.push(this.accountPass[i].value) + } + if(passwords[1] === passwords[2]){ + promises.push(this.request("account/password", { + current_password: passwords[0], + new_password: passwords[1] + })) + }else{ + alert(strings.account.passwordsDoNotMatch) + return + } + } + if(this.shownDiv === "del" && this.accountDel.password.value){ + noNameChange = true + promises.push(this.request("account/remove", { + password: this.accountDel.password.value + }).then(() => { + account.loggedIn = false + delete account.username + delete account.displayName + Cookies.remove("token") + scoreStorage.load() + pageEvents.send("logout") + return Promise.resolve + })) + } + var newName = this.displayname.value.trim() + if(!noNameChange && newName !== account.displayName){ + promises.push(this.request("account/display_name", { + display_name: newName + })) + } + var error = false + var errorFunc = response => { + if(error){ + return + } + if(response && response.message){ + alert(response.message) + }else{ + alert(strings.account.error) + } + } + Promise.all(promises).then(() => { + this.onEnd(false, true) + }, errorFunc).catch(errorFunc) + } + onEnd(event, noSound){ + var touched = false + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + touched = true + }else if(event.which !== 1){ + return + } + } + if(this.locked){ + return + } + this.clean() + assets.sounds["se_don"].play() + setTimeout(() => { + new SongSelect(false, false, touched) + }, 500) + } + request(url, obj){ + this.lock(true) + return new Promise((resolve, reject) => { + var request = new XMLHttpRequest() + request.open(obj ? "POST" : "GET", "api/" + url) + pageEvents.load(request).then(() => { + this.lock(false) + if(request.status !== 200){ + reject() + return + } + try{ + var json = JSON.parse(request.response) + }catch(e){ + reject() + return + } + if(json.status === "ok"){ + resolve(json) + }else{ + reject(json) + } + }, () => { + this.lock(false) + reject() + }) + if(obj){ + request.setRequestHeader("Content-Type", "application/json;charset=UTF-8") + request.send(JSON.stringify(obj)) + }else{ + request.send() + } + }) + } + lock(isLocked){ + this.locked = isLocked + if(this.mode === "login" || this.mode === "register"){ + for(var i = 0; i < this.form.length; i++){ + this.form[i].disabled = isLocked + } + }else if(this.mode === "account"){ + for(var i = 0; i < this.inputForms.length; i++){ + this.inputForms[i].disabled = isLocked + } + } + } + clean(eventsOnly){ + if(!eventsOnly){ + cancelTouch = true + this.keyboard.clean() + this.gamepad.clean() + } + if(this.mode === "account"){ + pageEvents.remove(this.accounPassButton, ["click", "touchstart"]) + pageEvents.remove(this.accountDelButton, ["click", "touchstart"]) + pageEvents.remove(this.logoutButton, ["mousedown", "touchstart"]) + pageEvents.remove(this.saveButton, ["mousedown", "touchstart"]) + for(var i = 0; i < this.inputForms.length; i++){ + pageEvents.remove(this.inputForms[i], ["keydown", "keyup", "keypress"]) + } + this.accountPass.reset() + this.accountDel.reset() + delete this.displayname + delete this.accountPassButton + delete this.accountPass + delete this.accountPassDiv + delete this.accountDelButton + delete this.accountDel + delete this.accountDelDiv + delete this.logoutButton + delete this.saveButton + delete this.inputForms + }else if(this.mode === "login" || this.mode === "register"){ + if(!eventsOnly){ + this.form.reset() + } + pageEvents.remove(this.form, "submit") + pageEvents.remove(this.loginButton, ["mousedown", "touchstart"]) + pageEvents.remove(this.registerButton, ["mousedown", "touchstart"]) + for(var i = 0; i < this.form.length; i++){ + pageEvents.remove(this.registerButton, ["keydown", "keyup", "keypress"]) + } + delete this.form + delete this.password2 + delete this.remember + delete this.loginButton + delete this.registerButton + } + pageEvents.remove(this.endButton, ["mousedown", "touchstart"]) + delete this.endButton + delete this.items + } +} diff --git a/public/src/js/assets.js b/public/src/js/assets.js index 31e4d8d..f7e3ee7 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -1,6 +1,7 @@ var assets = { "js": [ "lib/md5.min.js", + "lib/js.cookie.min.js", "loadsong.js", "parseosu.js", "titlescreen.js", @@ -31,7 +32,8 @@ var assets = { "importsongs.js", "logo.js", "settings.js", - "scorestorage.js" + "scorestorage.js", + "account.js" ], "css": [ "main.css", @@ -137,7 +139,9 @@ var assets = { "about.html", "debug.html", "session.html", - "settings.html" + "settings.html", + "account.html", + "login.html" ], "songs": [], diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index ca984ad..6da8266 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -706,12 +706,12 @@ }) }else if(r.smallHiragana.test(symbol)){ // Small hiragana, small katakana - drawn.push({text: symbol, x: 0, y: 0, w: 30}) + drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 30}) }else if(r.hiragana.test(symbol)){ // Hiragana, katakana - drawn.push({text: symbol, x: 0, y: 0, w: 35}) + drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 35}) }else{ - drawn.push({text: symbol, x: 0, y: 0, w: 39}) + drawn.push({text: symbol, kana: true, x: 0, y: 0, w: 39}) } } @@ -720,6 +720,9 @@ if(config.letterSpacing){ symbol.w += config.letterSpacing } + if(config.kanaSpacing && symbol.kana){ + symbol.w += config.kanaSpacing + } drawnWidth += symbol.w * mul } @@ -1549,6 +1552,99 @@ ctx.restore() } + nameplate(config){ + var ctx = config.ctx + var w = 264 + var h = 57 + var r = h / 2 + var pi = Math.PI + + ctx.save() + + ctx.translate(config.x, config.y) + if(config.scale){ + ctx.scale(config.scale, config.scale) + } + + ctx.fillStyle="rgba(0, 0, 0, 0.25)" + ctx.beginPath() + ctx.arc(r + 4, r + 5, r, pi / 2, pi / -2) + ctx.arc(w - r + 4, r + 5, r, pi / -2, pi / 2) + ctx.fill() + ctx.beginPath() + ctx.moveTo(r, 0) + this.roundedCorner(ctx, w, 0, r, 1) + ctx.lineTo(r, r) + ctx.fillStyle = config.blue ? "#67cecb" : "#ff421d" + ctx.fill() + ctx.beginPath() + ctx.moveTo(r, r) + this.roundedCorner(ctx, w, h, r, 2) + ctx.lineTo(r, h) + ctx.fillStyle = "rgba(255, 255, 255, 0.8)" + ctx.fill() + ctx.strokeStyle = "#000" + ctx.lineWidth = 4 + ctx.beginPath() + ctx.moveTo(r, 0) + ctx.arc(w - r, r, r, pi / -2, pi / 2) + ctx.lineTo(r, h) + ctx.stroke() + ctx.beginPath() + ctx.moveTo(r, r - 1) + ctx.lineTo(w, r - 1) + ctx.lineWidth = 2 + ctx.stroke() + ctx.beginPath() + ctx.arc(r, r, r, 0, pi * 2) + ctx.fillStyle = config.blue ? "#67cecb" : "#ff421d" + ctx.fill() + ctx.lineWidth = 4 + ctx.stroke() + ctx.font = this.bold(config.font) + "28px " + config.font + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.lineWidth = 5 + ctx.miterLimit = 1 + ctx.strokeStyle = "#fff" + ctx.fillStyle = "#000" + var text = config.blue ? "2P" : "1P" + ctx.strokeText(text, r + 2, r + 1) + ctx.fillText(text, r + 2, r + 1) + if(config.rank){ + this.layeredText({ + ctx: ctx, + text: config.rank, + fontSize: 20, + fontFamily: config.font, + x: w / 2 + r * 0.7, + y: r * 0.5, + width: 180, + align: "center", + baseline: "middle" + }, [ + {fill: "#000"} + ]) + } + this.layeredText({ + ctx: ctx, + text: config.name || "", + fontSize: 21, + fontFamily: config.font, + x: w / 2 + r * 0.7, + y: r * 1.5 - 0.5, + width: 180, + kanaSpacing: 10, + align: "center", + baseline: "middle" + }, [ + {outline: "#000", letterBorder: 6}, + {fill: "#fff"} + ]) + + ctx.restore() + } + alpha(amount, ctx, callback, winW, winH){ if(amount >= 1){ return callback(ctx) diff --git a/public/src/js/game.js b/public/src/js/game.js index 8fcdd99..da9534e 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -505,7 +505,9 @@ class Game{ var musicDuration = duration * 1000 - this.controller.offset if(this.musicFadeOut === 0){ if(this.controller.multiplayer === 1){ - p2.send("gameresults", this.getGlobalScore()) + var obj = this.getGlobalScore() + obj.name = account.loggedIn ? account.displayName : strings.defaultName + p2.send("gameresults", obj) } this.musicFadeOut++ }else if(this.musicFadeOut === 1 && ms >= started + 1600){ diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index 20a431a..992e96d 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -202,12 +202,16 @@ var tja = new ParseTja(data, "oni", 0, 0, true) var songObj = { id: index + 1, + order: index + 1, type: "tja", chart: file, - stars: [], + stars: {}, music: "muted" } + var coursesAdded = false var titleLang = {} + var titleLangAdded = false + var subtitleLangAdded = false var subtitleLang = {} var dir = file.webkitRelativePath.toLowerCase() dir = dir.slice(0, dir.lastIndexOf("/") + 1) @@ -221,7 +225,11 @@ } songObj.subtitle = subtitle songObj.preview = meta.demostart || 0 - songObj.stars[this.courseTypes[diff]] = (meta.level || "0") + (meta.branch ? " B" : "") + songObj.courses[diff] = { + stars: meta.level || 0, + branch: !!meta.branch + } + coursesAdded = true if(meta.wave){ songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] || songObj.music } @@ -264,32 +272,27 @@ } if(meta["title" + id]){ titleLang[id] = meta["title" + id] + titleLangAdded = true }else if(songTitle in this.songTitle && this.songTitle[songTitle][id]){ titleLang[id] = this.songTitle[songTitle][id] + ura + titleLangAdded = true } if(meta["subtitle" + id]){ subtitleLang[id] = meta["subtitle" + id] + subtitleLangAdded = true } } } - var titleLangArray = [] - for(var id in titleLang){ - titleLangArray.push(id + " " + titleLang[id]) + if(titleLangAdded){ + songObj.title_lang = titleLang } - if(titleLangArray.length !== 0){ - songObj.title_lang = titleLangArray.join("\n") - } - var subtitleLangArray = [] - for(var id in subtitleLang){ - subtitleLangArray.push(id + " " + subtitleLang[id]) - } - if(subtitleLangArray.length !== 0){ - songObj.subtitle_lang = subtitleLangArray.join("\n") + if(subtitleLangAdded){ + songObj.subtitle_lang = subtitleLang } if(!songObj.category){ songObj.category = category || this.getCategory(file, [songTitle || songObj.title, file.name.slice(0, file.name.lastIndexOf("."))]) } - if(songObj.stars.length !== 0){ + if(coursesAdded){ this.songs[index] = songObj } var hash = md5.base64(event.target.result).slice(0, -2) @@ -316,12 +319,20 @@ dir = dir.slice(0, dir.lastIndexOf("/") + 1) var songObj = { id: index + 1, + order: index + 1, type: "osu", chart: file, subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist, - subtitle_lang: osu.metadata.Artist || osu.metadata.ArtistUnicode, + subtitle_lang: { + en: osu.metadata.Artist || osu.metadata.ArtistUnicode + }, preview: osu.generalInfo.PreviewTime / 1000, - stars: [null, null, null, parseInt(osu.difficulty.overallDifficulty) || 1], + courses: { + oni:{ + stars: parseInt(osu.difficulty.overallDifficulty) || 0, + branch: false + } + }, music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted" } var filename = file.name.slice(0, file.name.lastIndexOf(".")) @@ -333,7 +344,9 @@ suffix = " " + matches[0] } songObj.title = title + suffix - songObj.title_lang = (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix + songObj.title_lang = { + en: (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix + } }else{ songObj.title = filename } diff --git a/public/src/js/lib/js.cookie.min.js b/public/src/js/lib/js.cookie.min.js new file mode 100644 index 0000000..a0e6820 --- /dev/null +++ b/public/src/js/lib/js.cookie.min.js @@ -0,0 +1,2 @@ +/*! js-cookie v3.0.0-rc.0 | MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self,function(){var r=e.Cookies,n=e.Cookies=t();n.noConflict=function(){return e.Cookies=r,n}}())}(this,function(){"use strict";function e(e){for(var t=1;t { @@ -155,65 +156,92 @@ class Loader{ } })) - var readyEvent = "normal" - var songId - var hashLower = location.hash.toLowerCase() - p2 = new P2Connection() - if(hashLower.startsWith("#song=")){ - var number = parseInt(location.hash.slice(6)) - if(number > 0){ - songId = number - readyEvent = "song-id" + if(gameConfig._accounts){ + var token = Cookies.get("token") + if(token){ + this.addPromise(this.ajax("/api/scores/get").then(response => { + response = JSON.parse(response) + if(response.status === "ok"){ + account.loggedIn = true + account.username = response.username + account.displayName = response.display_name + scoreStorage.load(response.scores) + pageEvents.send("login", account.username) + } + })) + }else{ + this.assetLoaded() } - }else if(location.hash.length === 6){ - p2.hashLock = true - this.addPromise(new Promise(resolve => { - p2.open() - pageEvents.add(p2, "message", response => { - if(response.type === "session"){ - pageEvents.send("session-start", "invited") - readyEvent = "session-start" - resolve() - }else if(response.type === "gameend"){ - p2.hash("") - p2.hashLock = false - readyEvent = "session-expired" - resolve() - } - }) - p2.send("invite", location.hash.slice(1).toLowerCase()) - setTimeout(() => { - if(p2.socket.readyState !== 1){ - p2.hash("") - p2.hashLock = false - resolve() - } - }, 10000) - }).then(() => { - pageEvents.remove(p2, "message") - })) - }else{ - p2.hash("") } settings = new Settings() 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, false, true) - if(score){ - score.title = song.title - } - } Promise.all(this.promises).then(() => { - this.canvasTest.drawAllImages().then(result => { + if(!account.loggedIn){ + scoreStorage.load() + } + 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, false, true) + if(score){ + score.title = song.title + } + } + var promises = [] + + var readyEvent = "normal" + var songId + var hashLower = location.hash.toLowerCase() + p2 = new P2Connection() + if(hashLower.startsWith("#song=")){ + var number = parseInt(location.hash.slice(6)) + if(number > 0){ + songId = number + readyEvent = "song-id" + } + }else if(location.hash.length === 6){ + p2.hashLock = true + promises.push(new Promise(resolve => { + p2.open() + pageEvents.add(p2, "message", response => { + if(response.type === "session"){ + pageEvents.send("session-start", "invited") + readyEvent = "session-start" + resolve() + }else if(response.type === "gameend"){ + p2.hash("") + p2.hashLock = false + readyEvent = "session-expired" + resolve() + } + }) + p2.send("invite", { + id: location.hash.slice(1).toLowerCase(), + name: account.loggedIn ? account.displayName : null + }) + setTimeout(() => { + if(p2.socket.readyState !== 1){ + p2.hash("") + p2.hashLock = false + resolve() + } + }, 10000) + }).then(() => { + pageEvents.remove(p2, "message") + })) + }else{ + p2.hash("") + } + + promises.push(this.canvasTest.drawAllImages()) + + Promise.all(promises).then(result => { perf.allImg = result perf.load = Date.now() - this.startTime this.canvasTest.clean() diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index b40461b..3e5b460 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -297,7 +297,8 @@ class LoadSong{ }) p2.send("join", { id: song.folder, - diff: song.difficulty + diff: song.difficulty, + name: account.loggedIn ? account.displayName : null }) }else{ this.clean() diff --git a/public/src/js/main.js b/public/src/js/main.js index 3a393a4..c01209c 100644 --- a/public/src/js/main.js +++ b/public/src/js/main.js @@ -84,6 +84,7 @@ var strings var vectors var settings var scoreStorage +var account = {} pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => { if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){ diff --git a/public/src/js/p2.js b/public/src/js/p2.js index a0c266d..0857381 100644 --- a/public/src/js/p2.js +++ b/public/src/js/p2.js @@ -3,6 +3,7 @@ class P2Connection{ this.closed = true this.lastMessages = {} this.otherConnected = false + this.name = null this.allEvents = new Map() this.addEventListener("message", this.message.bind(this)) this.currentHash = "" @@ -123,6 +124,7 @@ class P2Connection{ this.hash("") this.hashLock = false } + this.name = null break case "gameresults": this.results = {} @@ -151,6 +153,9 @@ class P2Connection{ this.otherConnected = true this.session = true break + case "name": + this.name = (response.value || "").toString() || null + break } } onhashchange(){ diff --git a/public/src/js/pageevents.js b/public/src/js/pageevents.js index 56c0d1a..46c0a12 100644 --- a/public/src/js/pageevents.js +++ b/public/src/js/pageevents.js @@ -86,6 +86,9 @@ class PageEvents{ }) } keyEvent(event){ + if(!("key" in event)){ + return + } if(this.kbd.indexOf(event.key.toLowerCase()) !== -1){ this.lastKeyEvent = Date.now() event.preventDefault() diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 4e5cca6..a2a62c4 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -39,6 +39,7 @@ class Scoresheet{ 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"] @@ -208,6 +209,7 @@ class Scoresheet{ 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){ this.tetsuoHana.style.setProperty("--scale", ratio / this.pixelRatio) @@ -233,6 +235,9 @@ class Scoresheet{ if(!this.canvasCache.canvas){ this.canvasCache.resize(winW / ratio, 80 + 1, ratio) } + if(!this.nameplateCache.canvas){ + this.nameplateCache.resize(274, 67, ratio + 0.2) + } } this.winW = winW this.winH = winH @@ -450,6 +455,29 @@ class Scoresheet{ ctx.fillText(text, 395, 308) ctx.miterLimit = 10 + if(p === 0){ + var name = account.loggedIn ? account.displayName : strings.defaultName + }else{ + var name = results.name + } + this.nameplateCache.get({ + ctx: ctx, + x: 259, + y: 92, + w: 273, + h: 66, + id: p.toString() + "p", + }, ctx => { + this.draw.nameplate({ + ctx: ctx, + x: 3, + y: 3, + name: name, + font: this.font, + blue: p === 1 + }) + }) + if(this.controller.autoPlayEnabled){ ctx.drawImage(assets.image["badge_auto"], 431, 311, 34, 34 diff --git a/public/src/js/scorestorage.js b/public/src/js/scorestorage.js index 87820f5..55dfe02 100644 --- a/public/src/js/scorestorage.js +++ b/public/src/js/scorestorage.js @@ -5,17 +5,22 @@ class ScoreStorage{ this.difficulty = ["oni", "ura", "hard", "normal", "easy"] this.scoreKeys = ["points", "good", "ok", "bad", "maxCombo", "drumroll"] this.crownValue = ["", "silver", "gold"] - this.load() } - load(){ + load(strings){ this.scores = {} - this.scoreStrings = {} - try{ - var localScores = localStorage.getItem("scoreStorage") - if(localScores){ - this.scoreStrings = JSON.parse(localScores) - } - }catch(e){} + if(strings){ + this.scoreStrings = strings + }else if(account.loggedIn){ + return + }else{ + this.scoreStrings = {} + try{ + var localScores = localStorage.getItem("scoreStorage") + if(localScores){ + this.scoreStrings = JSON.parse(localScores) + } + }catch(e){} + } for(var hash in this.scoreStrings){ var scoreString = this.scoreStrings[hash] var songAdded = false @@ -46,16 +51,22 @@ class ScoreStorage{ } } } - save(){ + save(localOnly){ for(var hash in this.scores){ this.writeString(hash) } this.write() + return this.sendToServer({ + scores: this.scoreStrings, + is_import: true + }) } write(){ - try{ - localStorage.setItem("scoreStorage", JSON.stringify(this.scoreStrings)) - }catch(e){} + if(!account.loggedIn){ + try{ + localStorage.setItem("scoreStorage", JSON.stringify(this.scoreStrings)) + }catch(e){} + } } writeString(hash){ var score = this.scores[hash] @@ -112,6 +123,11 @@ class ScoreStorage{ this.scores[hash][difficulty] = scoreObject this.writeString(hash) this.write() + var obj = {} + obj[hash] = this.scoreStrings[hash] + this.sendToServer({ + scores: obj + }).catch(() => this.add.apply(this, arguments)) } template(){ var template = {crown: ""} @@ -146,6 +162,42 @@ class ScoreStorage{ delete this.scoreStrings[hash] } this.write() + this.sendToServer({ + scores: this.scoreStrings, + is_import: true + }) + } + } + sendToServer(obj, retry){ + if(account.loggedIn){ + var request = new XMLHttpRequest() + request.open("POST", "api/scores/save") + var promise = pageEvents.load(request).then(response => { + if(request.status !== 200){ + return Promise.reject() + } + }).catch(() => { + if(retry){ + account.loggedIn = false + delete account.username + delete account.displayName + Cookies.remove("token") + this.load() + pageEvents.send("logout") + return Promise.reject() + }else{ + return new Promise(resolve => { + setTimeout(() => { + resolve() + }, 3000) + }).then(() => this.sendToServer(obj, true)) + } + }) + request.setRequestHeader("Content-Type", "application/json;charset=UTF-8") + request.send(JSON.stringify(obj)) + return promise + }else{ + return Promise.resolve() } } } diff --git a/public/src/js/session.js b/public/src/js/session.js index 0f08c67..f9d267e 100644 --- a/public/src/js/session.js +++ b/public/src/js/session.js @@ -34,7 +34,10 @@ class Session{ pageEvents.send("session-start", "host") } }) - p2.send("invite") + p2.send("invite", { + id: null, + name: account.loggedIn ? account.displayName : null + }) pageEvents.send("session") } getElement(name){ diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 85df8db..64b65de 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -116,7 +116,7 @@ class SongSelect{ originalTitle: song.title, subtitle: subtitle, skin: song.category in this.songSkin ? this.songSkin[song.category] : this.songSkin.default, - stars: song.stars, + courses: song.courses, category: song.category, preview: song.preview || 0, type: song.type, @@ -126,14 +126,19 @@ class SongSelect{ volume: song.volume, maker: song.maker, canJump: true, - hash: song.hash || song.title + hash: song.hash || song.title, + order: song.order }) } this.songs.sort((a, b) => { var catA = a.category in this.songSkin ? this.songSkin[a.category] : this.songSkin.default var catB = b.category in this.songSkin ? this.songSkin[b.category] : this.songSkin.default if(catA.sort === catB.sort){ - return a.id > b.id ? 1 : -1 + if(a.order === b.order){ + return a.id > b.id ? 1 : -1 + }else{ + return a.order > b.order ? 1 : -1 + } }else{ return catA.sort > catB.sort ? 1 : -1 } @@ -226,6 +231,7 @@ class SongSelect{ this.difficultyCache = new CanvasCache(noSmoothing) 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"] @@ -450,7 +456,11 @@ class SongSelect{ 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(mouse.x > 641 && mouse.y > 603){ + }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){ + this.toSession() + }else if(!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket.readyState === 1 && !assets.customSongs){ this.toSession() }else{ var moveBy = this.songSelMouse(mouse.x, mouse.y) @@ -501,11 +511,15 @@ class SongSelect{ if(this.state.screen === "song"){ 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(mouse.x > 641 && mouse.y > 603 && p2.socket.readyState === 1 && !assets.customSongs){ + }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){ + moveTo = "session" + }else if(!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket.readyState === 1 && !assets.customSongs){ moveTo = "session" }else{ var moveTo = this.songSelMouse(mouse.x, mouse.y) - if(moveTo === null && this.state.moveHover === 0 && !this.songs[this.selectedSong].stars){ + if(moveTo === null && this.state.moveHover === 0 && !this.songs[this.selectedSong].courses){ this.state.moveMS = this.getMS() - this.songSelecting.speed } } @@ -544,7 +558,7 @@ class SongSelect{ var dir = x > 0 ? 1 : -1 x = Math.abs(x) var selectedWidth = this.songAsset.selectedWidth - if(!this.songs[this.selectedSong].stars){ + 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 @@ -565,7 +579,13 @@ class SongSelect{ }else if(550 < x && x < 1050 && 95 < y && y < 524){ var moveBy = Math.floor((x - 550) / ((1050 - 550) / 5)) + this.diffOptions.length var currentSong = this.songs[this.selectedSong] - if(this.state.ura && moveBy === this.diffOptions.length + 3 || currentSong.stars[moveBy - this.diffOptions.length]){ + if( + this.state.ura + && moveBy === this.diffOptions.length + 3 + || currentSong.courses[ + this.difficultyId[moveBy - this.diffOptions.length] + ] + ){ return moveBy } } @@ -583,7 +603,7 @@ class SongSelect{ }) } }else if(this.state.locked !== 1 || fromP2){ - if(this.songs[this.selectedSong].stars && (this.state.locked === 0 || fromP2)){ + if(this.songs[this.selectedSong].courses && (this.state.locked === 0 || fromP2)){ this.state.moveMS = ms }else{ this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize @@ -645,7 +665,7 @@ class SongSelect{ toSelectDifficulty(fromP2){ var currentSong = this.songs[this.selectedSong] if(p2.session && !fromP2 && currentSong.action !== "random"){ - if(this.songs[this.selectedSong].stars){ + if(this.songs[this.selectedSong].courses){ if(!this.state.selLock){ this.state.selLock = true p2.send("songsel", { @@ -655,7 +675,7 @@ class SongSelect{ } } }else if(this.state.locked === 0 || fromP2){ - if(currentSong.stars){ + if(currentSong.courses){ this.state.screen = "difficulty" this.state.screenMS = this.getMS() this.state.locked = true @@ -677,7 +697,7 @@ class SongSelect{ this.state.locked = true do{ var i = Math.floor(Math.random() * this.songs.length) - }while(!this.songs[i].stars) + }while(!this.songs[i].courses) var moveBy = i - this.selectedSong setTimeout(() => { this.moveToSong(moveBy) @@ -744,17 +764,18 @@ class SongSelect{ }else if(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": this.difficultyId[difficulty], + "difficulty": diff, "category": selectedSong.category, "type": selectedSong.type, "offset": selectedSong.offset, "songSkin": selectedSong.songSkin, - "stars": selectedSong.stars[difficulty], + "stars": selectedSong.courses[diff].stars, "hash": selectedSong.hash }, autoplay, multiplayer, touch) } @@ -797,6 +818,13 @@ class SongSelect{ new SettingsView(this.touchEnabled) }, 500) } + toAccount(){ + this.playSound("se_don") + this.clean() + setTimeout(() => { + new Account(this.touchEnabled) + }, 500) + } toSession(){ if(p2.socket.readyState !== 1 || assets.customSongs){ return @@ -893,6 +921,8 @@ class SongSelect{ 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 categories = 0 var lastCategory this.songs.forEach(song => { @@ -921,7 +951,7 @@ class SongSelect{ fontFamily: this.font, x: w / 2, y: 38 / 2, - width: w - 30, + width: id === "sessionend" ? 385 : w - 30, align: "center", baseline: "middle" }, [ @@ -969,7 +999,7 @@ class SongSelect{ } if(screen === "song"){ - if(this.songs[this.selectedSong].stars){ + if(this.songs[this.selectedSong].courses){ selectedWidth = this.songAsset.selectedWidth } @@ -1054,7 +1084,7 @@ class SongSelect{ if(elapsed < resize){ selectedWidth = this.songAsset.width + (((resize - elapsed) / resize) * (selectedWidth - this.songAsset.width)) }else if(elapsed > resize2){ - this.playBgm(!this.songs[this.selectedSong].stars) + this.playBgm(!this.songs[this.selectedSong].courses) this.state.locked = 1 selectedWidth = this.songAsset.width + ((elapsed - resize2) / resize * (selectedWidth - this.songAsset.width)) }else{ @@ -1062,7 +1092,7 @@ class SongSelect{ selectedWidth = this.songAsset.width } }else{ - this.playBgm(!this.songs[this.selectedSong].stars) + this.playBgm(!this.songs[this.selectedSong].courses) this.state.locked = 0 } }else if(screen === "difficulty"){ @@ -1071,7 +1101,7 @@ class SongSelect{ this.state.locked = 0 } if(this.state.move){ - var hasUra = currentSong.stars[4] + var hasUra = currentSong.courses.ura var previousSelection = this.selectedDiff do{ if(hasUra && this.state.move > 0){ @@ -1089,12 +1119,12 @@ class SongSelect{ this.selectedDiff = this.mod(this.diffOptions.length + 5, this.selectedDiff + this.state.move) } }while( - this.selectedDiff >= this.diffOptions.length && !currentSong.stars[this.selectedDiff - this.diffOptions.length] + 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.stars[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 } } @@ -1164,7 +1194,7 @@ class SongSelect{ var currentSong = this.songs[this.selectedSong] var highlight = 0 - if(!currentSong.stars){ + if(!currentSong.courses){ highlight = 2 } if(this.state.moveHover === 0){ @@ -1418,7 +1448,7 @@ class SongSelect{ } } var drawDifficulty = (ctx, i, currentUra) => { - if(currentSong.stars[i] || currentUra){ + if(currentSong.courses[this.difficultyId[i]] || currentUra){ var score = scoreStorage.get(currentSong.hash, false, true) var crownDiff = currentUra ? "ura" : this.difficultyId[i] var crownType = "" @@ -1502,9 +1532,9 @@ class SongSelect{ outlineSize: currentUra ? this.songAsset.letterBorder : 0 }) }) - var songStarsArray = (currentUra ? currentSong.stars[4] : currentSong.stars[i]).toString().split(" ") - var songStars = songStarsArray[0] - var songBranch = songStarsArray[1] === "B" + var songStarsObj = (currentUra ? currentSong.courses.ura : currentSong.courses[this.difficultyId[i]]) + var songStars = songStarsObj.stars + var songBranch = songStarsObj.branch var elapsedMS = this.state.screenMS > this.state.moveMS || !songSel ? this.state.screenMS : this.state.moveMS var fade = ((ms - elapsedMS) % 2000) / 2000 if(songBranch && fade > 0.25 && fade < 0.75){ @@ -1591,8 +1621,8 @@ class SongSelect{ } } } - for(var i = 0; currentSong.stars && i < 4; i++){ - var currentUra = i === 3 && (this.state.ura && !songSel || currentSong.stars[4] && songSel) + for(var i = 0; currentSong.courses && i < 4; i++){ + var currentUra = i === 3 && (this.state.ura && !songSel || currentSong.courses.ura && songSel) if(songSel && currentUra){ drawDifficulty(ctx, i, false) var elapsedMS = this.state.screenMS > this.state.moveMS ? this.state.screenMS : this.state.moveMS @@ -1753,7 +1783,7 @@ class SongSelect{ } } - if(!songSel && currentSong.stars[4]){ + 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 @@ -1842,7 +1872,7 @@ class SongSelect{ ctx.fillRect(0, frameTop + 595, 1280 + frameLeft * 2, 125 + frameTop) var x = 0 var y = frameTop + 603 - var w = frameLeft + 638 + var w = p2.session ? frameLeft + 638 - 200 : frameLeft + 638 var h = 117 + frameTop this.draw.pattern({ ctx: ctx, @@ -1869,7 +1899,81 @@ class SongSelect{ ctx.lineTo(x + w - 4, y + h) ctx.lineTo(x + w - 4, y + 4) ctx.fill() - x = frameLeft + 642 + + this.nameplateCache.get({ + ctx: ctx, + x: frameLeft + 60, + y: frameTop + 640, + w: 273, + h: 66, + id: "1p", + }, ctx => { + this.draw.nameplate({ + ctx: ctx, + x: 3, + y: 3, + name: account.loggedIn ? account.displayName : strings.defaultName, + rank: account.loggedIn || !gameConfig._accounts || p2.session ? false : strings.notLoggedIn, + font: this.font + }) + }) + if(this.state.moveHover === "account"){ + this.draw.highlight({ + ctx: ctx, + x: frameLeft + 59.5, + y: frameTop + 639.5, + w: 271, + h: 64, + radius: 28.5, + opacity: 0.8, + size: 10 + }) + } + + if(p2.session){ + x = x + w + 4 + w = 396 + this.draw.pattern({ + ctx: ctx, + img: assets.image["bg_settings"], + x: x, + y: y, + w: w, + h: h, + dx: frameLeft + 11, + dy: frameTop + 45, + scale: 3.1 + }) + ctx.fillStyle = "rgba(255, 255, 255, 0.5)" + ctx.beginPath() + ctx.moveTo(x, y + h) + ctx.lineTo(x, y) + ctx.lineTo(x + w, y) + ctx.lineTo(x + w, y + 4) + ctx.lineTo(x + 4, y + 4) + ctx.lineTo(x + 4, y + h) + ctx.fill() + ctx.fillStyle = "rgba(0, 0, 0, 0.25)" + ctx.beginPath() + ctx.moveTo(x + w, y) + ctx.lineTo(x + w, y + h) + ctx.lineTo(x + w - 4, y + h) + ctx.lineTo(x + w - 4, y + 4) + ctx.fill() + if(this.state.moveHover === "session"){ + this.draw.highlight({ + ctx: ctx, + x: x, + y: y, + w: w, + h: h, + opacity: 0.8 + }) + } + } + + x = p2.session ? frameLeft + 642 + 200 : frameLeft + 642 + w = p2.session ? frameLeft + 638 - 200 : frameLeft + 638 if(p2.session){ this.draw.pattern({ ctx: ctx, @@ -1925,7 +2029,7 @@ class SongSelect{ } this.sessionCache.get({ ctx: ctx, - x: winW / 2, + x: p2.session ? winW / 4 : winW / 2, y: y + (h - 32) / 2, w: winW / 2, h: 38, @@ -1933,7 +2037,7 @@ class SongSelect{ }) ctx.globalAlpha = 1 } - if(this.state.moveHover === "session"){ + if(!p2.session && this.state.moveHover === "session"){ this.draw.highlight({ ctx: ctx, x: x, @@ -1944,6 +2048,25 @@ class SongSelect{ }) } } + if(p2.session){ + this.nameplateCache.get({ + ctx: ctx, + x: frameLeft + 949, + y: frameTop + 640, + w: 273, + h: 66, + id: "2p", + }, ctx => { + this.draw.nameplate({ + ctx: ctx, + x: 3, + y: 3, + name: p2.name, + font: this.font, + blue: true + }) + }) + } if(screen === "titleFadeIn"){ ctx.save() @@ -2019,7 +2142,7 @@ class SongSelect{ if(!score){ break } - if(config.song.stars[i] && score[diff] && score[diff].crown){ + if(config.song.courses[this.difficultyId[i]] && score[diff] && score[diff].crown){ this.draw.crown({ ctx: ctx, type: score[diff].crown, @@ -2148,7 +2271,7 @@ class SongSelect{ }) if(currentSong){ currentSong.p2Cursor = diffId - if(p2.session && currentSong.stars){ + if(p2.session && currentSong.courses){ this.selectedSong = index this.state.move = 0 if(this.state.screen !== "difficulty"){ @@ -2192,7 +2315,7 @@ class SongSelect{ } this.moveToSong(moveBy, true) } - }else if(this.songs[song].stars){ + }else if(this.songs[song].courses){ this.selectedSong = song this.state.move = 0 if(this.state.screen !== "difficulty"){ @@ -2238,16 +2361,11 @@ class SongSelect{ getLocalTitle(title, titleLang){ if(titleLang){ - titleLang = titleLang.split("\n") - titleLang.forEach(line => { - var space = line.indexOf(" ") - var id = line.slice(0, space) - if(id === strings.id){ - title = line.slice(space + 1) - }else if(titleLang.length === 1 && strings.id === "en" && !(id in allStrings)){ - title = line + for(var id in titleLang){ + if(id === strings.id && titleLang[id]){ + return titleLang[id] } - }) + } } return title } diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 17986fc..9a6c499 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -36,6 +36,8 @@ this.hard = "むずかしい" this.oni = "おに" this.songBranch = "譜面分岐あり" + this.defaultName = "どんちゃん" + this.notLoggedIn = "ログインしていない" this.sessionStart = "オンラインセッションを開始する!" this.sessionEnd = "オンラインセッションを終了する" this.loading = "ロード中..." @@ -184,6 +186,24 @@ content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings." } } + this.account = { + username: "ユーザー名", + enterUsername: "ユーザー名を入力", + password: "パスワード", + enterPassword: "パスワードを入力", + repeatPassword: "パスワードを再入力", + remember: "ログイン状態を保持する", + login: "ログイン", + register: "登録", + registerAccount: "アカウントを登録", + passwordsDoNotMatch: "パスワードが一致しません", + cannotBeEmpty: "%sは空にできません", + error: "リクエストの処理中にエラーが発生しました", + logout: "ログアウト", + back: "もどる", + cancel: "Cancel", + save: "Save" + } this.browserSupport = { browserWarning: "サポートされていないブラウザを実行しています (%s)", details: "詳しく", @@ -233,6 +253,8 @@ function StringsEn(){ this.hard = "Hard" this.oni = "Extreme" this.songBranch = "Diverge Notes" + this.defaultName = "Don-chan" + this.notLoggedIn = "Not logged in" this.sessionStart = "Begin an Online Session!" this.sessionEnd = "End Online Session" this.loading = "Loading..." @@ -381,6 +403,33 @@ function StringsEn(){ content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings." } } + this.account = { + username: "Username", + enterUsername: "Enter Username", + password: "Password", + enterPassword: "Enter Password", + repeatPassword: "Repeat Password", + remember: "Remember me", + login: "Log In", + register: "Register", + registerAccount: "Register account", + passwordsDoNotMatch: "Passwords do not match", + cannotBeEmpty: "%s cannot be empty", + error: "An error occurred while processing your request", + logout: "Log Out", + back: "Back", + cancel: "Cancel", + save: "Save", + displayName: "Displayed Name", + changePassword: "Change Password", + currentNewRepeat: [ + "Current Password", + "New Password", + "Repeat New Password" + ], + deleteAccount: "Delete Account", + verifyPassword: "Verify password to delete this account" + } this.browserSupport = { browserWarning: "You are running an unsupported browser (%s)", details: "Details...", @@ -430,6 +479,8 @@ function StringsCn(){ this.hard = "困难" this.oni = "魔王" this.songBranch = "有谱面分歧" + this.defaultName = "小咚" + this.notLoggedIn = "未登录" this.sessionStart = "开始在线会话!" this.sessionEnd = "结束在线会话" this.loading = "加载中..." @@ -578,6 +629,24 @@ function StringsCn(){ content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings." } } + this.account = { + username: "登录名", + enterUsername: "输入用户名", + password: "密码", + enterPassword: "输入密码", + repeatPassword: "重新输入密码", + remember: "记住登录", + login: "登录", + register: "注册", + registerAccount: "注册帐号", + passwordsDoNotMatch: "密码不匹配", + cannotBeEmpty: "%s不能为空", + error: "处理您的请求时发生错误", + logout: "登出", + back: "返回", + cancel: "Cancel", + save: "Save" + } this.browserSupport = { browserWarning: "You are running an unsupported browser (%s)", details: "Details...", @@ -627,6 +696,8 @@ function StringsTw(){ this.hard = "困難" this.oni = "魔王" this.songBranch = "有譜面分歧" + this.defaultName = "小咚" + this.notLoggedIn = "未登錄" this.sessionStart = "開始多人模式!" this.sessionEnd = "結束多人模式" this.loading = "讀取中..." @@ -775,6 +846,24 @@ function StringsTw(){ content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings." } } + this.account = { + username: "使用者名稱", + enterUsername: "輸入用戶名", + password: "密碼", + enterPassword: "輸入密碼", + repeatPassword: "再次輸入密碼", + remember: "記住登錄", + login: "登入", + register: "註冊", + registerAccount: "註冊帳號", + passwordsDoNotMatch: "密碼不匹配", + cannotBeEmpty: "%s不能為空", + error: "處理您的請求時發生錯誤", + logout: "登出", + back: "返回", + cancel: "Cancel", + save: "Save" + } this.browserSupport = { browserWarning: "You are running an unsupported browser (%s)", details: "Details...", @@ -824,6 +913,8 @@ function StringsKo(){ this.hard = "어려움" this.oni = "귀신" this.songBranch = "악보 분기 있습니다" + this.defaultName = "동이" + this.notLoggedIn = "로그인하지 않았습니다" this.sessionStart = "온라인 세션 시작!" this.sessionEnd = "온라인 세션 끝내기" this.loading = "로딩 중..." @@ -972,6 +1063,24 @@ function StringsKo(){ content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings." } } + this.account = { + username: "사용자 이름", + enterUsername: "사용자 이름을 입력하십시오", + password: "비밀번호", + enterPassword: "비밀번호 입력", + repeatPassword: "비밀번호 재입력", + remember: "자동 로그인", + login: "로그인", + register: "가입하기", + registerAccount: "계정 등록", + passwordsDoNotMatch: "비밀번호가 일치하지 않습니다", + cannotBeEmpty: "%s 비어 있을 수 없습니다", + error: "요청을 처리하는 동안 오류가 발생했습니다", + logout: "로그 아웃", + back: "돌아간다", + cancel: "Cancel", + save: "Save" + } this.browserSupport = { browserWarning: "You are running an unsupported browser (%s)", details: "Details...", diff --git a/public/src/js/view.js b/public/src/js/view.js index 9964c83..a165b09 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -126,6 +126,7 @@ this.comboCache = new CanvasCache(noSmoothing) this.pauseCache = new CanvasCache(noSmoothing) this.branchCache = new CanvasCache(noSmoothing) + this.nameplateCache = new CanvasCache(noSmoothing) this.multiplayer = this.controller.multiplayer @@ -235,6 +236,11 @@ if(!this.multiplayer){ this.pauseCache.resize(81 * this.pauseOptions.length * 2, 464, ratio) } + if(this.portrait){ + this.nameplateCache.resize(220, 54, ratio + 0.2) + }else{ + this.nameplateCache.resize(274, 67, ratio + 0.2) + } this.fillComboCache() this.setDonBgHeight() resized = true @@ -388,6 +394,32 @@ h: 130 } + if(this.multiplayer !== 2){ + this.nameplateCache.get({ + ctx: ctx, + x: 167, + y: 160, + w: 219, + h: 53, + id: "1p", + }, ctx => { + if(this.multiplayer === 2){ + var name = p2.name || strings.defaultName + }else{ + var name = account.loggedIn ? account.displayName : strings.defaultName + } + this.draw.nameplate({ + ctx: ctx, + x: 3, + y: 3, + scale: 0.8, + name: name, + font: this.font, + blue: this.multiplayer === 2 + }) + }) + } + ctx.fillStyle = "#000" ctx.fillRect( 0, @@ -547,6 +579,29 @@ } var taikoPos = {x: 179, y: frameTop + 190, w: 138, h: 162} + this.nameplateCache.get({ + ctx: ctx, + x: 320, + y: this.multiplayer === 2 ? frameTop + 305 : frameTop + 20, + w: 273, + h: 66, + id: "1p", + }, ctx => { + if(this.multiplayer === 2){ + var name = p2.name || strings.defaultName + }else{ + var name = account.loggedIn ? account.displayName : strings.defaultName + } + this.draw.nameplate({ + ctx: ctx, + x: 3, + y: 3, + name: name, + font: this.font, + blue: this.multiplayer === 2 + }) + }) + ctx.fillStyle = "#000" ctx.fillRect( 0, diff --git a/public/src/views/account.html b/public/src/views/account.html new file mode 100644 index 0000000..1271156 --- /dev/null +++ b/public/src/views/account.html @@ -0,0 +1,33 @@ +
+ +
diff --git a/public/src/views/login.html b/public/src/views/login.html new file mode 100644 index 0000000..c9a58c5 --- /dev/null +++ b/public/src/views/login.html @@ -0,0 +1,24 @@ +
+
+
+
+ +
+
+ +
+
+
+
diff --git a/server.py b/server.py index 04e802d..eb8e73c 100644 --- a/server.py +++ b/server.py @@ -42,7 +42,8 @@ async def connection(ws, path): user = { "ws": ws, "action": "ready", - "session": False + "session": False, + "name": None } server_status["users"].append(user) try: @@ -79,6 +80,7 @@ async def connection(ws, path): waiting = server_status["waiting"] id = value["id"] if "id" in value else None diff = value["diff"] if "diff" in value else None + user["name"] = value["name"] if "name" in value else None if not id or not diff: continue if id not in waiting: @@ -92,6 +94,7 @@ async def connection(ws, path): await ws.send(msgobj("waiting")) else: # Join the other user and start game + user["name"] = value["name"] if "name" in value else None user["other_user"] = waiting[id]["user"] waiting_diff = waiting[id]["diff"] del waiting[id] @@ -101,7 +104,9 @@ async def connection(ws, path): user["other_user"]["other_user"] = user await asyncio.wait([ ws.send(msgobj("gameload", waiting_diff)), - user["other_user"]["ws"].send(msgobj("gameload", diff)) + user["other_user"]["ws"].send(msgobj("gameload", diff)), + ws.send(msgobj("name", user["other_user"]["name"])), + user["other_user"]["ws"].send(msgobj("name", user["name"])) ]) else: # Wait for another user @@ -116,27 +121,31 @@ async def connection(ws, path): # Update others on waiting players await notify_status() elif type == "invite": - if value == None: + if value and "id" in value and value["id"] == None: # Session invite link requested invite = get_invite() server_status["invites"][invite] = user user["action"] = "invite" user["session"] = invite + user["name"] = value["name"] if "name" in value else None await ws.send(msgobj("invite", invite)) - elif value in server_status["invites"]: + elif value and "id" in value and value["id"] in server_status["invites"]: # Join a session with the other user - user["other_user"] = server_status["invites"][value] - del server_status["invites"][value] + user["name"] = value["name"] if "name" in value else None + user["other_user"] = server_status["invites"][value["id"]] + del server_status["invites"][value["id"]] if "ws" in user["other_user"]: user["other_user"]["other_user"] = user user["action"] = "invite" - user["session"] = value + user["session"] = value["id"] sent_msg = msgobj("session") await asyncio.wait([ ws.send(sent_msg), - user["other_user"]["ws"].send(sent_msg) + user["other_user"]["ws"].send(sent_msg), + ws.send(msgobj("invite")), + ws.send(msgobj("name", user["other_user"]["name"])), + user["other_user"]["ws"].send(msgobj("name", user["name"])) ]) - await ws.send(msgobj("invite")) else: del user["other_user"] await ws.send(msgobj("gameend"))