From cb64777012a916c22598793b6ad35af30d037493 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Thu, 4 Apr 2019 23:40:11 +0300 Subject: [PATCH] SongSelect: Add Settings - Resolution can be adjusted, as well as touch drum animation on mobile - A translation text file "songtitle.txt" can be imported - Titles and translated titles are each on their own line, if a line begins with a language code, it will translate the song title that is above - An example file can be found here: https://gist.github.com/LoveEevee/65fe66f0b54c0536f96fd2f4862984d4 - The page will fail to load if version on the page does not match /api/config - Disabled Tab key while playing, before hitting it would focus the version link - Fix forcing branches in debug not working - Fixed not being able to click on songs that do not have oni but have ura - Fix unexpected category being used as a fallback - Fix verticalText and layeredText not accepting anything except strings --- app.py | 3 +- public/assets/img/img.css | 15 --- public/src/css/main.css | 44 ++++++- public/src/css/songbg.css | 1 + public/src/js/assets.js | 10 +- public/src/js/canvascache.js | 3 + public/src/js/canvasdraw.js | 4 +- public/src/js/debug.js | 4 + public/src/js/game.js | 9 +- public/src/js/importsongs.js | 35 +++++- public/src/js/keyboard.js | 4 +- public/src/js/loader.js | 18 ++- public/src/js/loadsong.js | 77 +++++++----- public/src/js/main.js | 2 + public/src/js/settings.js | 210 +++++++++++++++++++++++++++++++++ public/src/js/songselect.js | 52 ++++++-- public/src/js/soundbuffer.js | 2 +- public/src/js/strings.js | 80 +++++++++++++ public/src/js/view.js | 33 ++++-- public/src/views/settings.html | 7 ++ 20 files changed, 527 insertions(+), 86 deletions(-) create mode 100644 public/src/js/settings.js create mode 100644 public/src/views/settings.html diff --git a/app.py b/app.py index 91f45b0..e03643d 100644 --- a/app.py +++ b/app.py @@ -111,7 +111,6 @@ def route_api_songs(): raw_categories = query_db('select * from categories') categories = {} - def_category = {'title': None, 'title_en': None} for cat in raw_categories: categories[cat[0]] = cat[1] @@ -126,7 +125,7 @@ def route_api_songs(): song_type = song[12] preview = song[15] - category_out = categories[song[11]] if song[11] in categories else def_category + 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 songs_out.append({ diff --git a/public/assets/img/img.css b/public/assets/img/img.css index 7a9d682..9d4073a 100644 --- a/public/assets/img/img.css +++ b/public/assets/img/img.css @@ -10,24 +10,9 @@ #loading-don{ background-image: url("dancing-don.gif"); } -#touch-drum-img{ - background-image: url("touch_drum.png"); -} #touch-full-btn{ background-image: url("touch_fullscreen.png"); } #touch-pause-btn{ background-image: url("touch_pause.png"); } -.song-stage-1{ - background-image: url("bg_stage_1.png"); - background-size: calc(100vh / 720 * 66); -} -.song-stage-2{ - background-image: url("bg_stage_2.png"); - background-size: calc(100vh / 720 * 254); -} -.song-stage-3{ - background-image: url("bg_stage_3.png"); - background-size: calc(100vh / 720 * 458); -} diff --git a/public/src/css/main.css b/public/src/css/main.css index 2ec0cf7..c31abb0 100644 --- a/public/src/css/main.css +++ b/public/src/css/main.css @@ -92,7 +92,8 @@ kbd{ color: #000; } .taibtn:hover, -#tutorial-end-button:hover{ +#tutorial-end-button:hover, +#tutorial-end-button.selected{ position: relative; z-index: 1; color: #fff; @@ -147,6 +148,47 @@ kbd{ cursor: text; overflow: hidden; } +.setting-box{ + display: flex; + height: 2em; + margin-top: 1.2em; + border: 0.25em solid #000; + border-radius: 0.5em; + padding: 0.3em; + outline: none; + color: #000; + cursor: pointer; +} +.setting-box:first-child{ + margin-top: 0; +} +#tutorial-content:not(:hover) .setting-box.selected, +.setting-box:hover{ + background: #ffb547; + border-color: #21211a; +} +.setting-name{ + position: relative; + width: 50%; + padding: 0.3em; + font-size: 1.3em; + box-sizing: border-box; +} +#tutorial-content:not(:hover) .setting-box.selected .setting-name, +.setting-box:hover .setting-name{ + color: #fff; + z-index: 0; +} +.setting-name::before{ + padding-left: 0.3em; +} +.setting-value{ + background: #fff; + width: 50%; + border-radius: 0.2em; + padding: 0.5em; + box-sizing: border-box; +} @keyframes bgscroll{ from{ background-position: 0 top; diff --git a/public/src/css/songbg.css b/public/src/css/songbg.css index 9374f93..5f6f2ef 100644 --- a/public/src/css/songbg.css +++ b/public/src/css/songbg.css @@ -26,6 +26,7 @@ height: calc(44 / 720 * 100vh); background-position: center bottom; background-repeat-y: no-repeat; + background-size: auto 100%; bottom: 0; } .portrait #songbg{ diff --git a/public/src/js/assets.js b/public/src/js/assets.js index 4f938a1..aaab4b5 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -27,7 +27,8 @@ var assets = { "debug.js", "session.js", "importsongs.js", - "logo.js" + "logo.js", + "settings.js" ], "css": [ "main.css", @@ -71,11 +72,7 @@ var assets = { "bg_score_p2.png", "bg_settings.png", "bg_pause.png", - "bg_stage_1.png", - "bg_stage_2.png", - "bg_stage_3.png", "badge_auto.png", - "touch_drum.png", "touch_pause.png", "touch_fullscreen.png", "mimizu.png", @@ -168,7 +165,8 @@ var assets = { "tutorial.html", "about.html", "debug.html", - "session.html" + "session.html", + "settings.html" ], "songs": [], diff --git a/public/src/js/canvascache.js b/public/src/js/canvascache.js index b8070a8..a731bcb 100644 --- a/public/src/js/canvascache.js +++ b/public/src/js/canvascache.js @@ -106,6 +106,9 @@ class CanvasCache{ this.ctx.clearRect(0, 0, this.w, this.h) } clean(){ + if(!this.canvas){ + return + } this.resize(1, 1, 1) delete this.map delete this.ctx diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index 97d3afd..0e77fbb 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -277,7 +277,7 @@ verticalText(config){ var ctx = config.ctx - var inputText = config.text + var inputText = config.text.toString() var mul = config.fontSize / 40 var ura = false var r = this.regex @@ -601,7 +601,7 @@ layeredText(config, layers){ var ctx = config.ctx - var inputText = config.text + var inputText = config.text.toString() var mul = config.fontSize / 40 var ura = false var r = this.regex diff --git a/public/src/js/debug.js b/public/src/js/debug.js index beeede3..844e8d1 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -141,6 +141,7 @@ class Debug{ if(circles[i].endTime >= measureMS){ break } + game.skipNote(circles[i]) } if(game.mainMusicPlaying){ game.mainMusicPlaying = false @@ -215,6 +216,9 @@ class Debug{ var name = this.branchSelect.value game.branch = name === "auto" ? false : name game.branchSet = name === "auto" + if(noRestart){ + game.branchStatic = true + } var selectedOption = this.branchSelect.selectedOptions[0] this.branchSelect.style.background = selectedOption.style.background if(this.restartCheckbox.checked && !noRestart){ diff --git a/public/src/js/game.js b/public/src/js/game.js index 19609a4..3417209 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -150,10 +150,13 @@ class Game{ } } if(view.branch !== currentMeasure){ - view.branchAnimate = { - ms: ms, - fromBranch: view.branch + if(!this.branchStatic){ + view.branchAnimate = { + ms: ms, + fromBranch: view.branch + } } + this.branchStatic = false view.branch = currentMeasure } } diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index 93bd9ec..112b675 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -29,6 +29,8 @@ this.otherFiles = {} this.songs = [] this.stylesheet = [] + this.songTitle = {} + this.uraRegex = /\s*[\((]裏[\))]$/ this.courseTypes = { "easy": 0, "normal": 1, @@ -82,7 +84,7 @@ file: file, index: i }) - }else if(name === "genre.ini" || name === "box.def"){ + }else if(name === "genre.ini" || name === "box.def" || name === "songtitle.txt"){ var level = (file.webkitRelativePath.match(/\//g) || []).length metaFiles.push({ file: file, @@ -154,6 +156,20 @@ break } } + }else if(name === "songtitle.txt"){ + var lastTitle + for(var i = 0; i < data.length; i++){ + var line = data[i].trim() + if(line){ + var lang = line.slice(0, 2) + if(line.charAt(2) !== " " || !(lang in allStrings)){ + this.songTitle[line] = {} + lastTitle = line + }else if(lastTitle){ + this.songTitle[lastTitle][lang] = line.slice(3).trim() + } + } + } } if(category){ var metaPath = file.webkitRelativePath.toLowerCase().slice(0, file.name.length * -1) @@ -168,7 +184,11 @@ this.osuFiles.forEach(filesLoop) } }).catch(() => {}) - reader.readAsText(file, "sjis") + if(name === "songtitle.txt"){ + reader.readAsText(file) + }else{ + reader.readAsText(file, "sjis") + } return promise } @@ -212,8 +232,19 @@ songObj.song_skin = this.getSkin(dir, meta.taikowebskin) } for(var id in allStrings){ + var songTitle = songObj.title + var ura = "" + if(songTitle){ + var uraPos = songTitle.search(this.uraRegex) + if(uraPos !== -1){ + ura = songTitle.slice(uraPos) + songTitle = songTitle.slice(0, uraPos) + } + } if(meta["title" + id]){ titleLang[id] = meta["title" + id] + }else if(songTitle in this.songTitle && this.songTitle[songTitle][id]){ + titleLang[id] = this.songTitle[songTitle][id] + ura } if(meta["subtitle" + id]){ subtitleLang[id] = meta["subtitle" + id] diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index cb9a2ac..0a5b32a 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -59,8 +59,8 @@ class Keyboard{ } pageEvents.keyAdd(this, "all", "both", event => { - if(event.keyCode === 8){ - // Disable back navigation when pressing backspace + if(event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9){ + // Escape, Backspace, Tab event.preventDefault() } var key = this.kbdSearch[event.keyCode] diff --git a/public/src/js/loader.js b/public/src/js/loader.js index 47d1ecb..d7f45cd 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -38,7 +38,15 @@ class Loader{ document.head.appendChild(script) }) - this.addPromise(new Promise(resolve => { + this.addPromise(new Promise((resolve, reject) => { + if( + versionLink.href !== gameConfig._version.url && + gameConfig._version.commit && + versionLink.href.indexOf(gameConfig._version.commit) === -1 + ){ + // Version in the config does not match version on the page + reject() + } var cssCount = document.styleSheets.length + assets.css.length assets.css.forEach(name => { var stylesheet = document.createElement("link") @@ -195,6 +203,8 @@ class Loader{ p2.hash("") } + settings = new Settings() + Promise.all(this.promises).then(() => { this.canvasTest.drawAllImages().then(result => { perf.allImg = result @@ -212,7 +222,7 @@ class Loader{ } addPromise(promise){ this.promises.push(promise) - promise.then(this.assetLoaded.bind(this)) + promise.then(this.assetLoaded.bind(this), this.errorMsg.bind(this)) } loadSound(name, gain){ var id = this.getFilename(name) @@ -258,7 +268,9 @@ class Loader{ } clean(){ var fontDetectDiv = document.getElementById("fontdetectHelper") - fontDetectDiv.parentNode.removeChild(fontDetectDiv) + if(fontDetectDiv){ + fontDetectDiv.parentNode.removeChild(fontDetectDiv) + } delete this.loaderPercentage delete this.loaderProgress delete this.promises diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index 0c0b02f..d70b799 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -4,6 +4,15 @@ class LoadSong{ this.autoPlayEnabled = autoPlayEnabled this.multiplayer = multiplayer this.touchEnabled = touchEnabled + var resolution = settings.getItem("resolution") + this.imgScale = 1 + if(resolution === "medium"){ + this.imgScale = 0.75 + }else if(resolution === "low"){ + this.imgScale = 0.5 + }else if(resolution === "lowest"){ + this.imgScale = 0.25 + } loader.changePage("loadsong", true) var loadingText = document.getElementById("loading-text") @@ -57,9 +66,10 @@ class LoadSong{ } if(type === "don"){ song.donBg = null - } - if(type === "song"){ + }else if(type === "song"){ song.songBg = null + }else if(type === "stage"){ + song.songStage = null } } } @@ -71,19 +81,14 @@ class LoadSong{ continue } let img = document.createElement("img") - if(!songObj.music && this.touchEnabled && imgLoad[i].type === "song"){ + let force = imgLoad[i].type === "song" && this.touchEnabled + if(!songObj.music && (this.imgScale !== 1 || force)){ img.crossOrigin = "Anonymous" } let promise = pageEvents.load(img) - if(imgLoad[i].type === "song"){ - promises.push(promise.then(() => { - return this.scaleImg(img, filename, prefix) - })) - }else{ - promises.push(promise.then(() => { - assets.image[prefix + filename] = img - })) - } + promises.push(promise.then(() => { + return this.scaleImg(img, filename, prefix, force) + })) if(songObj.music){ img.src = URL.createObjectURL(song.songSkin[filename + ".png"]) }else{ @@ -126,6 +131,16 @@ class LoadSong{ this.songData = data.replace(/\0/g, "").split("\n") })) } + if(this.touchEnabled && !assets.image["touch_drum"]){ + let img = document.createElement("img") + if(this.imgScale !== 1){ + img.crossOrigin = "Anonymous" + } + promises.push(pageEvents.load(img).then(() => { + return this.scaleImg(img, "touch_drum", "") + })) + img.src = gameConfig.assets_baseurl + "img/touch_drum.png" + } Promise.all(promises).then(() => { this.setupMultiplayer() }, error => { @@ -151,23 +166,23 @@ class LoadSong{ filenames.push("bg_don2_" + this.selectedSong.donBg) } } + if(this.selectedSong.songStage !== null){ + filenames.push("bg_stage_" + this.selectedSong.songStage) + } for(var i = 0; i < filenames.length; i++){ - for(var letter = 0; letter < 2; letter++){ - let filenameAb = filenames[i] + (letter === 0 ? "a" : "b") + var filename = filenames[i] + var stage = filename.startsWith("bg_stage_") + for(var letter = 0; letter < (stage ? 1 : 2); letter++){ + let filenameAb = filenames[i] + (stage ? "" : (letter === 0 ? "a" : "b")) if(!(filenameAb in assets.image)){ let img = document.createElement("img") - if(filenameAb.startsWith("bg_song_")){ - if(this.touchEnabled){ - img.crossOrigin = "Anonymous" - } - promises.push(pageEvents.load(img).then(() => { - return this.scaleImg(img, filenameAb, "") - })) - }else{ - promises.push(pageEvents.load(img).then(() => { - assets.image[filenameAb] = img - })) + let force = filenameAb.startsWith("bg_song_") && this.touchEnabled + if(this.imgScale !== 1 || force){ + img.crossOrigin = "Anonymous" } + promises.push(pageEvents.load(img).then(() => { + return this.scaleImg(img, filenameAb, "", force) + })) img.src = gameConfig.assets_baseurl + "img/" + filenameAb + ".png" } } @@ -175,12 +190,16 @@ class LoadSong{ Promise.all(promises).then(resolve, reject) }) } - scaleImg(img, filename, prefix){ + scaleImg(img, filename, prefix, force){ return new Promise((resolve, reject) => { - if(this.touchEnabled){ + var scale = this.imgScale + if(force && scale > 0.5){ + scale = 0.5 + } + if(scale !== 1){ var canvas = document.createElement("canvas") - var w = Math.floor(img.width / 2) - var h = Math.floor(img.height / 2) + var w = Math.floor(img.width * scale) + var h = Math.floor(img.height * scale) canvas.width = w canvas.height = h var ctx = canvas.getContext("2d") diff --git a/public/src/js/main.js b/public/src/js/main.js index 1e632ca..e9067f1 100644 --- a/public/src/js/main.js +++ b/public/src/js/main.js @@ -82,6 +82,7 @@ var perf = { } var strings var vectors +var settings pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => { if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){ @@ -90,6 +91,7 @@ pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => { }) var versionDiv = document.getElementById("version") var versionLink = document.getElementById("version-link") +versionLink.tabIndex = -1 pageEvents.add(versionDiv, ["click", "touchend"], event => { if(event.target === versionDiv){ versionLink.click() diff --git a/public/src/js/settings.js b/public/src/js/settings.js new file mode 100644 index 0000000..c09bebc --- /dev/null +++ b/public/src/js/settings.js @@ -0,0 +1,210 @@ +class Settings{ + constructor(){ + var ios = /iPhone|iPad/.test(navigator.userAgent) + var phone = /Android|iPhone|iPad/.test(navigator.userAgent) + + this.items = { + resolution: { + type: "select", + options: ["high", "medium", "low", "lowest"], + default: phone ? "medium" : "high" + }, + touchAnimation: { + type: "toggle", + default: !ios, + touch: true + } + } + + this.storage = {} + try{ + var storage = JSON.parse(localStorage.getItem("settings") || "{}") + for(var i in this.items){ + var current = this.items[i] + if(i in storage){ + if(current.type === "select" && current.options.indexOf(storage[i]) === -1){ + this.storage[i] = null + }else{ + this.storage[i] = storage[i] + } + }else{ + this.storage[i] = null + } + } + }catch(e){ + for(var i in this.items){ + this.storage[i] = null + } + } + } + getItem(name){ + var value = this.storage[name] + return value === null ? this.items[name].default : value + } + setItem(name, value){ + this.storage[name] = value + try{ + localStorage.setItem("settings", JSON.stringify(this.storage)) + }catch(e){} + } +} + +class SettingsView{ + constructor(touchEnabled){ + this.touchEnabled = touchEnabled + loader.changePage("settings", true) + this.endButton = document.getElementById("tutorial-end-button") + if(touchEnabled){ + document.getElementById("tutorial-outer").classList.add("touch-enabled") + } + + var tutorialTitle = document.getElementById("tutorial-title") + tutorialTitle.innerText = strings.gameSettings + tutorialTitle.setAttribute("alt", strings.gameSettings) + this.endButton.innerText = strings.settings.ok + this.endButton.setAttribute("alt", strings.settings.ok) + this.resolution = settings.getItem("resolution") + + var content = document.getElementById("tutorial-content") + this.items = [] + this.selected = 0 + for(let i in settings.items){ + if(!touchEnabled && settings.items[i].touch){ + continue + } + var settingBox = document.createElement("div") + settingBox.classList.add("setting-box") + var nameDiv = document.createElement("div") + nameDiv.classList.add("setting-name", "stroke-sub") + var name = strings.settings[i].name + nameDiv.innerText = name + nameDiv.setAttribute("alt", name) + settingBox.appendChild(nameDiv) + var valueDiv = document.createElement("div") + valueDiv.classList.add("setting-value") + valueDiv.innerText = this.getValue(i) + settingBox.appendChild(valueDiv) + content.appendChild(settingBox) + if(this.items.length === this.selected){ + settingBox.classList.add("selected") + } + pageEvents.add(settingBox, ["mousedown", "touchstart"], event => { + event.preventDefault() + this.setValue(i) + }) + this.items.push({ + id: i, + settingBox: settingBox, + valueDiv: valueDiv + }) + } + this.items.push({ + id: "back", + settingBox: this.endButton + }) + + this.kbd = { + "confirm": [13, 32, 70, 74], // Enter, Space, F, J + "previous": [37, 38, 68], // Left, Up, D + "next": [39, 40, 75], // Right, Down, K + "back": [8, 27] // Backspace, Esc + } + pageEvents.once(this.endButton, ["mousedown", "touchstart"]).then(this.onEnd.bind(this)) + pageEvents.keyAdd(this, "all", "down", this.keyEvent.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("settings") + } + getValue(name){ + var current = settings.items[name] + var value = settings.getItem(name) + if(current.type === "select"){ + value = strings.settings[name][value] + }else if(current.type === "toggle"){ + value = value ? strings.settings.on : strings.settings.off + } + return value + } + setValue(name){ + var current = settings.items[name] + var value = settings.getItem(name) + if(current.type === "select"){ + value = current.options[this.mod(current.options.length, current.options.indexOf(value) + 1)] + }else if(current.type === "toggle"){ + value = !value + } + settings.setItem(name, value) + this.selected = this.items.findIndex(item => item.id === name) + this.items[this.selected].valueDiv.innerText = this.getValue(name) + } + keyEvent(event){ + if(event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9){ + // Escape, Backspace, Tab + event.preventDefault() + } + if(!event.repeat){ + for(var i in this.kbd){ + if(this.kbd[i].indexOf(event.keyCode) !== -1){ + this.keyPressed(true, i) + break + } + } + } + } + keyPressed(pressed, name){ + if(!pressed){ + return + } + var selected = this.items[this.selected] + if(name === "confirm"){ + if(selected.id === "back"){ + this.onEnd() + }else{ + this.setValue(selected.id) + } + }else if(name === "previous" || name === "next"){ + selected.settingBox.classList.remove("selected") + this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1)) + this.items[this.selected].settingBox.classList.add("selected") + }else if(name === "back"){ + this.onEnd() + } + } + onEnd(event){ + var touched = false + if(event && event.type === "touchstart"){ + event.preventDefault() + touched = true + } + this.clean() + assets.sounds["se_don"].play() + setTimeout(() => { + new SongSelect("settings", false, touched) + }, 500) + } + mod(length, index){ + return ((index % length) + length) % length + } + clean(){ + this.gamepad.clean() + pageEvents.keyRemove(this, "all") + for(var i in this.items){ + pageEvents.remove(this.items[i].settingBox, ["mousedown", "touchstart"]) + } + delete this.endButton + delete this.items + if(this.resolution !== settings.getItem("resolution")){ + for(var i in assets.image){ + if(i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_")){ + URL.revokeObjectURL(assets.image[i].src) + delete assets.image[i] + } + } + } + } +} diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index ed8d08b..530447e 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -25,21 +25,27 @@ class SongSelect{ }, "tutorial": { sort: 7, - background: "#9afbe1", - border: ["#d6ffff", "#6bae9c"], - outline: "#31ae94" + background: "#29e8aa", + border: ["#86ffbd", "#009a8c"], + outline: "#08a28c" }, "about": { sort: 7, - background: "#91cfff", - border: ["#dff0ff", "#6890b2"], - outline: "#217abb" + background: "#a2d0e7", + border: ["#c6dfff", "#4485d9"], + outline: "#2390d9" + }, + "settings": { + sort: 7, + background: "#ce93fa", + border: ["#dec4fd", "#a543ef"], + outline: "#a741ef" }, "browse": { sort: 7, - background: "#9791ff", - border: ["#e2dfff", "#6d68b2"], - outline: "#5350ba" + background: "#fab5d3", + border: ["#ffe7ef", "#d36aa2"], + outline: "#d36aa2" }, "J-POP": { sort: 0, @@ -149,6 +155,12 @@ class SongSelect{ action: "about", category: strings.random }) + this.songs.push({ + title: strings.gameSettings, + skin: this.songSkin.settings, + action: "settings", + category: strings.random + }) if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){ this.browse = document.getElementById("browse") pageEvents.add(this.browse, "change", this.browseChange.bind(this)) @@ -352,7 +364,8 @@ class SongSelect{ down: code == 40 // Down } - if(event && (code == 27 || code == 8)){ + if(event && (code == 27 || code == 8 || code == 9)){ + // Escape, Backspace, Tab event.preventDefault() } if(this.state.screen === "song"){ @@ -523,7 +536,7 @@ 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 + 3 || currentSong.stars[moveBy - this.diffOptions.length]){ + if(this.state.ura && moveBy === this.diffOptions.length + 3 || currentSong.stars[moveBy - this.diffOptions.length]){ return moveBy } } @@ -624,6 +637,8 @@ class SongSelect{ this.toTutorial() }else if(currentSong.action === "about"){ this.toAbout() + }else if(currentSong.action === "settings"){ + this.toSettings() }else if(currentSong.action === "browse"){ this.toBrowse() } @@ -722,6 +737,13 @@ class SongSelect{ new About(this.touchEnabled) }, 500) } + toSettings(){ + assets.sounds["se_don"].play() + this.clean() + setTimeout(() => { + new SettingsView(this.touchEnabled) + }, 500) + } toSession(){ if(p2.socket.readyState !== 1 || assets.customSongs){ return @@ -790,6 +812,14 @@ class SongSelect{ winW = winH / 9 * 32 } this.pixelRatio = window.devicePixelRatio || 1 + var resolution = settings.getItem("resolution") + if(resolution === "medium"){ + this.pixelRatio *= 0.75 + }else if(resolution === "low"){ + this.pixelRatio *= 0.5 + }else if(resolution === "lowest"){ + this.pixelRatio *= 0.25 + } winW *= this.pixelRatio winH *= this.pixelRatio var ratioX = winW / 1280 diff --git a/public/src/js/soundbuffer.js b/public/src/js/soundbuffer.js index cd179f5..3e6f45e 100644 --- a/public/src/js/soundbuffer.js +++ b/public/src/js/soundbuffer.js @@ -101,7 +101,7 @@ class SoundGain{ this.volume = amount } setVolumeMul(amount){ - this.setVolume(Math.sqrt(amount * amount * this.defaultVol)) + this.setVolume(amount * this.defaultVol) } setCrossfade(amount){ this.setVolume(Math.sqrt(Math.sin(Math.PI / 2 * amount))) diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 16ddaa8..8176d1e 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -24,6 +24,7 @@ this.randomSong = "ランダムに曲をえらぶ" this.howToPlay = "あそびかた説明" this.aboutSimulator = "このシミュレータについて" + this.gameSettings = "ゲーム設定" this.browse = "参照する…" this.defaultSongList = "デフォルト曲リスト" this.songOptions = "演奏オプション" @@ -98,6 +99,21 @@ linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.", cancel: "キャンセル" } + this.settings = { + resolution: { + name: "ゲームの解像度", + high: "高", + medium: "中", + low: "低", + lowest: "最低" + }, + touchAnimation: { + name: "タッチアニメーション" + }, + on: "オン", + off: "オフ", + ok: "OK" + } this.browserSupport = { browserWarning: "サポートされていないブラウザを実行しています (%s)", details: "詳しく", @@ -131,6 +147,7 @@ function StringsEn(){ this.randomSong = "Random Song" this.howToPlay = "How to Play" this.aboutSimulator = "About Simulator" + this.gameSettings = "Game Settings" this.browse = "Browse…" this.defaultSongList = "Default Song List" this.songOptions = "Song Options" @@ -205,6 +222,21 @@ function StringsEn(){ linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.", cancel: "Cancel" } + this.settings = { + resolution: { + name: "Game Resolution", + high: "High", + medium: "Medium", + low: "Low", + lowest: "Lowest" + }, + touchAnimation: { + name: "Touch Animation" + }, + on: "On", + off: "Off", + ok: "OK" + } this.browserSupport = { browserWarning: "You are running an unsupported browser (%s)", details: "Details...", @@ -238,6 +270,7 @@ function StringsCn(){ this.randomSong = "随机选曲" this.howToPlay = "操作说明" this.aboutSimulator = "关于模拟器" + this.gameSettings = "游戏设定" this.browse = "浏览…" this.defaultSongList = "默认歌曲列表" this.songOptions = "选项" @@ -312,6 +345,21 @@ function StringsCn(){ linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.", cancel: "取消" } + this.settings = { + resolution: { + name: "游戏分辨率", + high: "高", + medium: "中", + low: "低", + lowest: "最低" + }, + touchAnimation: { + name: "触摸动画" + }, + on: "开", + off: "关", + ok: "确定" + } this.browserSupport = { browserWarning: "You are running an unsupported browser (%s)", details: "Details...", @@ -345,6 +393,7 @@ function StringsTw(){ this.randomSong = "隨機選曲" this.howToPlay = "操作說明" this.aboutSimulator = "關於模擬器" + this.gameSettings = "遊戲設定" this.browse = "開啟檔案…" this.defaultSongList = "默認歌曲列表" this.songOptions = "選項" @@ -419,6 +468,21 @@ function StringsTw(){ linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.", cancel: "取消" } + this.settings = { + resolution: { + name: "遊戲分辨率", + high: "高", + medium: "中", + low: "低", + lowest: "最低" + }, + touchAnimation: { + name: "觸摸動畫" + }, + on: "開", + off: "關", + ok: "確定" + } this.browserSupport = { browserWarning: "You are running an unsupported browser (%s)", details: "Details...", @@ -452,6 +516,7 @@ function StringsKo(){ this.randomSong = "랜덤" this.howToPlay = "지도 시간" this.aboutSimulator = "게임 정보" + this.gameSettings = "게임 설정" this.browse = "찾아보기…" this.defaultSongList = "기본 노래 목록" this.songOptions = "옵션" @@ -526,6 +591,21 @@ function StringsKo(){ linkTutorial: "Share this link with your friend to start playing together! Do not leave this screen while they join.", cancel: "취소" } + this.settings = { + resolution: { + name: "게임 해상도", + high: "높은", + medium: "중간", + low: "저", + lowest: "최저" + }, + touchAnimation: { + name: "터치 애니메이션" + }, + on: "온", + off: "오프", + ok: "확인" + } 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 b81cd29..f045b09 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -118,6 +118,7 @@ this.touchEnabled = this.controller.touchEnabled this.touch = -Infinity + this.touchAnimation = settings.getItem("touchAnimation") if(this.multiplayer !== 2){ @@ -125,6 +126,8 @@ this.touchDrumDiv = document.getElementById("touch-drum") this.touchDrumImg = document.getElementById("touch-drum-img") + this.setBgImage(this.touchDrumImg, assets.image["touch_drum"].src) + if(this.controller.autoPlayEnabled){ this.touchDrumDiv.style.display = "none" } @@ -180,6 +183,14 @@ var touchMultiplayer = this.touchEnabled && this.multiplayer && !this.portrait this.pixelRatio = window.devicePixelRatio || 1 + var resolution = settings.getItem("resolution") + if(resolution === "medium"){ + this.pixelRatio *= 0.75 + }else if(resolution === "low"){ + this.pixelRatio *= 0.5 + }else if(resolution === "lowest"){ + this.pixelRatio *= 0.25 + } winW *= this.pixelRatio winH *= this.pixelRatio if(this.portrait){ @@ -1123,6 +1134,7 @@ if(!selectedSong.songSkin.stage){ this.songStage.classList.add("song-stage-" + selectedSong.songStage) + this.setBgImage(this.songStage, assets.image["bg_stage_" + selectedSong.songStage].src) }else if(selectedSong.songSkin.stage !== "none"){ var prefix = selectedSong.songSkin.prefix || "" this.setBgImage(this.songStage, assets.image[prefix + "bg_stage_" + songSkinName].src) @@ -1180,9 +1192,10 @@ } setDonBgHeight(){ this.donBg.style.setProperty("--h", getComputedStyle(this.donBg).height) - this.gameDiv.classList.add("fix-animations") + var gameDiv = this.gameDiv + gameDiv.classList.add("fix-animations") setTimeout(()=>{ - this.gameDiv.classList.remove("fix-animations") + gameDiv.classList.remove("fix-animations") }, 50) } setLayers(elements, file, ab){ @@ -1700,14 +1713,16 @@ this.touchDrumDiv.style.width = drumWidth + "px" this.touchDrumDiv.style.height = drumHeight + "px" } - if(this.touch > ms - 100){ - if(!this.drumPadding){ - this.drumPadding = true - this.touchDrumImg.style.backgroundPositionY = "7px" + if(this.touchAnimation){ + if(this.touch > ms - 100){ + if(!this.drumPadding){ + this.drumPadding = true + this.touchDrumImg.style.backgroundPositionY = "7px" + } + }else if(this.drumPadding){ + this.drumPadding = false + this.touchDrumImg.style.backgroundPositionY = "" } - }else if(this.drumPadding){ - this.drumPadding = false - this.touchDrumImg.style.backgroundPositionY = "" } } } diff --git a/public/src/views/settings.html b/public/src/views/settings.html new file mode 100644 index 0000000..983c90e --- /dev/null +++ b/public/src/views/settings.html @@ -0,0 +1,7 @@ +
+
+
+
+
+
+