From adc45cb6520633b92af165f1637b64273f6d5db2 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Thu, 14 Feb 2019 12:32:45 +0300 Subject: [PATCH 01/31] Custom scripting, #song=, translations - A song can be linked directly by adding "#song=" to the url, replace `` with the id in the database, after loading it jumps immediately jumps to the difficulty selection - Added tutorial translations - Fixed song preview not playing - Use text fallback for the logo when there are no vectors - Increased combo cache by 1 pixel - A custom javascript file can be loaded from config.json by defining "custom_js" value - Added lots of events to help writing custom js files: `version-link, title-screen, language-change, song-select, song-select-move, song-select-difficulty, song-select-back, about, about-link, tutorial, import-songs, import-songs-default, session, session-start, session-end, debug, load-song, load-song-player2, load-song-unfocused, load-song-cancel, load-song-error, game-start, key-events, p2-game-end, p2-disconnected, p2-abandoned, pause, unpause, pause-restart, pause-song-select, game-lag, scoresheet, scoresheet-player2` - Event syntax example: ```js addEventListener("game-start", event => { console.log("game-start", event.detail) }) ``` --- public/src/js/about.js | 5 +- public/src/js/controller.js | 6 +++ public/src/js/debug.js | 1 + public/src/js/game.js | 3 ++ public/src/js/gamepad.js | 2 + public/src/js/importsongs.js | 2 + public/src/js/keyboard.js | 24 +++++++++- public/src/js/loader.js | 17 ++++++- public/src/js/loadsong.js | 9 ++++ public/src/js/logo.js | 2 +- public/src/js/main.js | 5 +- public/src/js/p2.js | 7 +++ public/src/js/pageevents.js | 3 ++ public/src/js/scoresheet.js | 4 ++ public/src/js/session.js | 3 ++ public/src/js/songselect.js | 56 +++++++++++++++-------- public/src/js/strings.js | 68 ++++++++++++++-------------- public/src/js/titlescreen.js | 88 ++++++++++++++++++++++-------------- public/src/js/tutorial.js | 6 ++- public/src/js/view.js | 17 ++++--- 20 files changed, 224 insertions(+), 104 deletions(-) diff --git a/public/src/js/about.js b/public/src/js/about.js index 235a8de..069b007 100644 --- a/public/src/js/about.js +++ b/public/src/js/about.js @@ -41,7 +41,7 @@ "confirm": ["start", "b", "ls", "rs"] }, this.onEnd.bind(this)) - this.addDiag() + pageEvents.send("about", this.addDiag()) } onEnd(event){ var touched = false @@ -145,12 +145,15 @@ var issueBody = strings.issueTemplate + "\n\n\n\n" + diag this.getLink(this.linkEmail).href += "?body=" + encodeURIComponent(issueBody.replace(/\n/g, "
\r\n")) + + return diag } getLink(target){ return target.getElementsByTagName("a")[0] } linkButton(event){ this.getLink(event.currentTarget).click() + pageEvents.send("about-link", event) } clean(){ cancelTouch = true diff --git a/public/src/js/controller.js b/public/src/js/controller.js index ef7dca3..d161d83 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -58,6 +58,12 @@ class Controller{ this.viewLoop() if(this.multiplayer !== 2){ this.gameInterval = setInterval(this.gameLoop.bind(this), 1000 / 60) + pageEvents.send("game-start", { + selectedSong: this.selectedSong, + autoPlayEnabled: this.autoPlayEnabled, + multiplayer: this.multiplayer, + touchEnabled: this.touchEnabled + }) } } stopMainLoop(){ diff --git a/public/src/js/debug.js b/public/src/js/debug.js index ca2cf51..6b03534 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -37,6 +37,7 @@ class Debug{ this.moveTo(100, 100) this.restore() this.updateStatus() + pageEvents.send("debug") } startMove(event){ if(event.which === 1){ diff --git a/public/src/js/game.js b/public/src/js/game.js index 2df56d0..76f7006 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -362,6 +362,7 @@ class Game{ this.view.gameDiv.classList.add("game-paused") this.view.lastMousemove = this.view.getMS() this.view.cursorHidden = false + pageEvents.send("pause") }else{ assets.sounds["se_cancel"].play() this.paused = false @@ -370,6 +371,7 @@ class Game{ this.sndTime = currentDate - snd.buffer.getTime() * 1000 this.view.gameDiv.classList.remove("game-paused") this.view.pointer() + pageEvents.send("unpause", currentDate - this.latestDate) } } isPaused(){ @@ -391,6 +393,7 @@ class Game{ if(Math.abs(lag) >= 50){ this.startDate += lag this.sndTime = sndTime + pageEvents.send("game-lag", lag) } } this.elapsedTime = currentDate - this.startDate diff --git a/public/src/js/gamepad.js b/public/src/js/gamepad.js index b6255c0..2956c1a 100644 --- a/public/src/js/gamepad.js +++ b/public/src/js/gamepad.js @@ -25,6 +25,7 @@ class Gamepad{ "lsl": "lsl" } this.btn = {} + this.gamepadEvents = 0 if(callback){ this.interval = setInterval(() => { this.play(callback) @@ -123,6 +124,7 @@ class Gamepad{ if(pressed){ callback(true, keyCode) + this.gamepadEvents++ }else if(!button){ if(released){ this.toRelease[keyCode + "released"] = true diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index bf669ea..400f443 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -386,6 +386,7 @@ document.head.appendChild(style) } if(this.songs.length){ + var length = this.songs.length assets.songs = this.songs assets.customSongs = true assets.customSelected = 0 @@ -395,6 +396,7 @@ loader.screen.removeChild(this.loaderDiv) this.clean() new SongSelect("browse", false, this.songSelect.touchEnabled) + pageEvents.send("import-songs", length) }, 500) }else{ loader.screen.removeChild(this.loaderDiv) diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index 6d938ac..399fb98 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -28,6 +28,7 @@ class Keyboard{ "don": -Infinity, "ka": -Infinity } + this.keyboardEvents = 0 var gameBtn = {} gameBtn[this.kbd["don_l"]] = ["u", "d", "l", "r", "ls"] @@ -66,8 +67,19 @@ class Keyboard{ if(key && !event.repeat && this.buttonEnabled(key)){ var ms = this.game.getAccurateTime() this.setKey(key, event.type === "keydown", ms) + if(event.type === "keydown"){ + this.keyboardEvents++ + } } }) + + if(controller.multiplayer === 1){ + pageEvents.add(window, "beforeunload", event => { + if(p2.otherConnected){ + pageEvents.send("p2-abandoned", event) + } + }) + } } getBindings(){ return this.kbd @@ -165,8 +177,9 @@ class Keyboard{ } if(this.controller.multiplayer !== 2){ this.checkKey(this.kbd["back"], "menu", () => { - if(this.controller.multiplayer === 1){ + if(this.controller.multiplayer === 1 && p2.otherConnected){ p2.send("gameend") + pageEvents.send("p2-abandoned") } this.controller.togglePause() this.controller.songSelection() @@ -243,5 +256,14 @@ class Keyboard{ clean(){ pageEvents.keyRemove(this, "all") clearInterval(this.gamepadInterval) + if(this.controller.multiplayer === 1){ + pageEvents.remove(window, "beforeunload") + } + if(this.controller.multiplayer !== 2){ + pageEvents.send("key-events", { + keyboard: this.keyboardEvents, + gamepad: this.gamepad.gamepadEvents + }) + } } } diff --git a/public/src/js/loader.js b/public/src/js/loader.js index a2ce20d..f1c9bf9 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -25,6 +25,12 @@ class Loader{ var queryString = gameConfig._version.commit_short ? "?" + gameConfig._version.commit_short : "" + if(gameConfig.custom_js){ + var script = document.createElement("script") + this.addPromise(pageEvents.load(script)) + script.src = gameConfig.custom_js + queryString + document.head.appendChild(script) + } assets.js.forEach(name => { var script = document.createElement("script") this.addPromise(pageEvents.load(script)) @@ -147,8 +153,15 @@ class Loader{ } })) + var songId + var hashLower = location.hash.toLowerCase() p2 = new P2Connection() - if(location.hash.length === 6){ + if(hashLower.startsWith("#song=")){ + var number = parseInt(location.hash.slice(6)) + if(number > 0){ + songId = number + } + }else if(location.hash.length === 6){ p2.hashLock = true this.addPromise(new Promise(resolve => { p2.open() @@ -182,7 +195,7 @@ class Loader{ perf.load = Date.now() - this.startTime this.canvasTest.clean() this.clean() - this.callback() + this.callback(songId) }) }, this.errorMsg.bind(this)) diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index 8bf1da4..45b69e2 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -15,6 +15,11 @@ class LoadSong{ cancel.setAttribute("alt", strings.cancel) } this.run() + pageEvents.send("load-song", { + selectedSong: selectedSong, + autoPlayEnabled: autoPlayEnabled, + multiplayer: multiplayer + }) } run(){ var song = this.selectedSong @@ -117,6 +122,7 @@ class LoadSong{ if(Array.isArray(error) && error[1] instanceof HTMLElement){ error = error[0] + ": " + error[1].outerHTML } + pageEvents.send("load-song-error", error) errorMessage(new Error(error).stack) alert("An error occurred, please refresh") }) @@ -243,6 +249,7 @@ class LoadSong{ var taikoGame1 = new Controller(song, this.songData, false, 1, this.touchEnabled) var taikoGame2 = new Controller(this.selectedSong2, this.song2Data, true, 2, this.touchEnabled) taikoGame1.run(taikoGame2) + pageEvents.send("load-song-player2", this.selectedSong2) }else if(event.type === "left" || event.type === "gameend"){ this.clean() new SongSelect(false, false, this.touchEnabled) @@ -264,6 +271,7 @@ class LoadSong{ }else{ if(!repeat){ assets.sounds["v_sanka"].play() + pageEvents.send("load-song-unfocused") } setTimeout(() => { this.startMultiplayer(true) @@ -281,6 +289,7 @@ class LoadSong{ p2.send("leave") assets.sounds["se_don"].play() this.cancelButton.style.pointerEvents = "none" + pageEvents.send("load-song-cancel") } clean(){ pageEvents.remove(p2, "message") diff --git a/public/src/js/logo.js b/public/src/js/logo.js index 44f98d7..e77f992 100644 --- a/public/src/js/logo.js +++ b/public/src/js/logo.js @@ -2,7 +2,7 @@ constructor(){ this.canvas = document.getElementById("logo") this.ctx = this.canvas.getContext("2d") - this.pathSvg = failedTests.indexOf("Path2D SVG") === -1 + this.pathSvg = failedTests.indexOf("Path2D SVG") === -1 && vectors.logo1 this.symbolFont = "TnT, Meiryo, sans-serif" this.symbols = [{ x: 315, y: 18, xAlt: 15, scale: true, text: "ブ", diff --git a/public/src/js/main.js b/public/src/js/main.js index 1ba7a91..85b9625 100644 --- a/public/src/js/main.js +++ b/public/src/js/main.js @@ -92,6 +92,7 @@ var versionDiv = document.getElementById("version") var versionLink = document.getElementById("version-link") pageEvents.add(versionDiv, ["click", "touchend"], () => { versionLink.click() + pageEvents.send("version-link") }) resizeRoot() setInterval(resizeRoot, 100) @@ -112,7 +113,7 @@ pageEvents.keyAdd(debugObj, "all", "down", event => { } }) -var loader = new Loader(() => { - new Titlescreen() +var loader = new Loader(songId => { + new Titlescreen(songId) }) diff --git a/public/src/js/p2.js b/public/src/js/p2.js index b635cb1..aa5e834 100644 --- a/public/src/js/p2.js +++ b/public/src/js/p2.js @@ -58,6 +58,7 @@ class P2Connection{ this.open() } }, 500) + pageEvents.send("p2-disconnected") } var addedType = this.allEvents.get("close") if(addedType){ @@ -111,6 +112,11 @@ class P2Connection{ break case "gameend": this.otherConnected = false + if(this.session){ + pageEvents.send("session-end") + }else if(!this.results){ + pageEvents.send("p2-game-end") + } this.session = false if(this.hashLock){ this.hash("") @@ -122,6 +128,7 @@ class P2Connection{ for(var i in response.value){ this.results[i] = response.value[i].toString() } + pageEvents.send("scoresheet-player2", this.results) break case "note": this.notes.push(response.value) diff --git a/public/src/js/pageevents.js b/public/src/js/pageevents.js index 789275e..7ba8d68 100644 --- a/public/src/js/pageevents.js +++ b/public/src/js/pageevents.js @@ -143,4 +143,7 @@ class PageEvents{ getMouse(){ return this.lastMouse } + send(name, detail){ + dispatchEvent(new CustomEvent(name, {detail: detail})) + } } diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 35f93c1..3e1326a 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -60,6 +60,10 @@ class Scoresheet{ } }) } + pageEvents.send("scoresheet", { + results: this.results, + multiplayer: multiplayer + }) } keyDown(event, code){ if(!code){ diff --git a/public/src/js/session.js b/public/src/js/session.js index fb967c9..9a7c2cf 100644 --- a/public/src/js/session.js +++ b/public/src/js/session.js @@ -30,9 +30,11 @@ class Session{ }else if(response.type === "songsel"){ p2.clearMessage("users") this.onEnd(false, true) + pageEvents.send("session-start") } }) p2.send("invite") + pageEvents.send("session") } mouseDown(event){ if(event.target === this.sessionInvite){ @@ -50,6 +52,7 @@ class Session{ p2.send("leave") p2.hash("") p2.hashLock = false + pageEvents.send("session-cancel") }else if(!fromP2){ return p2.send("songsel") } diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index f8462be..f029739 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -1,5 +1,5 @@ class SongSelect{ - constructor(fromTutorial, fadeIn, touchEnabled){ + constructor(fromTutorial, fadeIn, touchEnabled, songId){ this.touchEnabled = touchEnabled loader.changePage("songselect", false) @@ -213,23 +213,32 @@ class SongSelect{ this.selectedDiff = 0 assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506) - if(!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage)){ + if(!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage) && !songId){ fromTutorial = touchEnabled ? "about" : "tutorial" } if(p2.session){ fromTutorial = false } + var songIdIndex = -1 if(fromTutorial){ this.selectedSong = this.songs.findIndex(song => song.action === fromTutorial) this.playBgm(true) }else{ - if(assets.customSongs){ + if(songId){ + songIdIndex = this.songs.findIndex(song => song.id === songId) + if(songIdIndex === -1){ + this.clearHash() + } + } + if(songIdIndex !== -1){ + this.selectedSong = songIdIndex + }else if(assets.customSongs){ this.selectedSong = assets.customSelected }else if((!p2.session || fadeIn) && "selectedSong" in localStorage){ this.selectedSong = Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length - 1) } - assets.sounds["v_songsel"].play() + assets.sounds[songIdIndex !== -1 ? "v_diffsel" : "v_songsel"].play() snd.musicGain.fadeOut() this.playBgm(false) } @@ -247,7 +256,7 @@ class SongSelect{ var skipStart = fromTutorial || p2.session this.state = { - screen: fadeIn ? "titleFadeIn" : (skipStart ? "song" : "title"), + screen: songIdIndex !== -1 ? "difficulty" : (fadeIn ? "titleFadeIn" : (skipStart ? "song" : "title")), screenMS: this.getMS(), move: 0, moveMS: 0, @@ -302,6 +311,8 @@ class SongSelect{ this.redrawRunning = true this.redrawBind = this.redraw.bind(this) this.redraw() + pageEvents.send("song-select") + pageEvents.send("song-select-move", this.songs[this.selectedSong]) } keyDown(event, code){ @@ -590,6 +601,7 @@ class SongSelect{ assets.sounds["se_don"].play() assets.sounds["v_songsel"].stop() assets.sounds["v_diffsel"].play(0.3) + pageEvents.send("song-select-difficulty", currentSong) }else if(currentSong.action === "back"){ this.clean() this.toTitleScreen() @@ -630,6 +642,8 @@ class SongSelect{ assets.sounds["v_diffsel"].stop() assets.sounds["se_cancel"].play() } + this.clearHash() + pageEvents.send("song-select-back") } toLoadSong(difficulty, shift, ctrl, touch){ this.clean() @@ -728,6 +742,7 @@ class SongSelect{ setTimeout(() => { new SongSelect("browse", false, this.touchEnabled) }, 500) + pageEvents.send("import-songs-default") }else{ this.browse.click() } @@ -952,7 +967,11 @@ class SongSelect{ var elapsed = ms - this.state.moveMS if(this.state.move && ms > this.state.moveMS + resize2 - scrollDelay){ assets.sounds["se_ka"].play() + var previousSelectedSong = this.selectedSong this.selectedSong = this.mod(this.songs.length, this.selectedSong + this.state.move) + if(previousSelectedSong !== this.selectedSong){ + pageEvents.send("song-select-move", this.songs[this.selectedSong]) + } this.state.move = 0 this.state.locked = 2 @@ -1687,16 +1706,11 @@ class SongSelect{ if("id" in currentSong){ var startLoad = this.getMS() if(loadOnly){ - var resolveLoading - this.previewLoading = currentSong.id + var currentId = null }else{ + var currentId = this.previewId this.previewing = this.selectedSong - if(this.previewLoading === currentSong.id){ - this.previewLoading = null - return - } } - var currentId = this.previewId var songObj = this.previewList.find(song => song && song.id === id) if(songObj){ @@ -1728,14 +1742,9 @@ class SongSelect{ }).then(sound => { if(currentId === this.previewId){ songObj.preview_sound = sound - if(!loadOnly || !this.previewLoading){ - this.previewing = this.selectedSong - this.preview = sound - this.previewLoaded(startLoad, songObj.preview_time) - } - if(loadOnly){ - this.previewLoading = null - } + this.preview = sound + this.previewLoaded(startLoad, songObj.preview_time) + var oldPreview = this.previewList.shift() if(oldPreview){ oldPreview.preview_sound.clean() @@ -1880,11 +1889,18 @@ class SongSelect{ return title } + clearHash(){ + if(location.hash.toLowerCase().startsWith("#song=")){ + p2.hash("") + } + } + getMS(){ return Date.now() } clean(){ + this.clearHash() this.draw.clean() this.songTitleCache.clean() this.selectTextCache.clean() diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 510a36c..967d0a1 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -65,16 +65,16 @@ this.tutorial = { basics: [ - "Hit the drum when the notes reach the taiko!", - "For red notes, hit the face of the drum (%s or %s)...", - "...and for blue notes, hit the rim! (%s or %s)", - "USB controllers are also supported!" + "流れてくる音符がワクに重なったらバチで太鼓をたたこう!", + "赤い音符は面をたたこう(%sまたは%s)", + "青い音符はフチをたたこう(%sまたは%s)", + "USBコントローラがサポートされています!" ], - otherControls: "Other controls", + otherControls: "他のコントロール", otherTutorial: [ - "%s \u2014 pause game", - "%s while selecting difficulty \u2014 enable autoplay mode", - "%s while selecting difficulty \u2014 enable 2P mode" + "%sはゲームを一時停止します", + "むずかしさをえらぶしながら%sキーを押しながらオートモードを有効", + "むずかしさをえらぶしながら%sキーを押しながらネットプレイモードを有効" ], ok: "OK" } @@ -166,8 +166,8 @@ function StringsEn(){ this.tutorial = { basics: [ - "Hit the drum when the notes reach the taiko!", - "For red notes, hit the face of the drum (%s or %s)...", + "When a note overlaps the frame, that is your cue to hit the drum!", + "For red notes, hit the surface of the drum (%s or %s)...", "...and for blue notes, hit the rim! (%s or %s)", "USB controllers are also supported!" ], @@ -267,16 +267,16 @@ function StringsCn(){ this.tutorial = { basics: [ - "Hit the drum when the notes reach the taiko!", - "For red notes, hit the face of the drum (%s or %s)...", - "...and for blue notes, hit the rim! (%s or %s)", - "USB controllers are also supported!" + "当流动的音符将与框框重叠时就用鼓棒敲打太鼓吧", + "遇到红色音符要敲打鼓面(%s或%s)", + "遇到蓝色音符则敲打鼓边(%s或%s)", + "USB控制器也支持!" ], - otherControls: "Other controls", + otherControls: "其他控制", otherTutorial: [ - "%s \u2014 pause game", - "%s while selecting difficulty \u2014 enable autoplay mode", - "%s while selecting difficulty \u2014 enable 2P mode" + "%s暂停游戏", + "选择难度时按住%s以启用自动模式", + "选择难度时按住%s以启用网络对战模式" ], ok: "确定" } @@ -368,16 +368,16 @@ function StringsTw(){ this.tutorial = { basics: [ - "Hit the drum when the notes reach the taiko!", - "For red notes, hit the face of the drum (%s or %s)...", - "...and for blue notes, hit the rim! (%s or %s)", - "USB controllers are also supported!" + "當流動的音符將與框框重疊時就用鼓棒敲打太鼓吧", + "遇到紅色音符要敲打鼓面(%s或%s)", + "遇到藍色音符則敲打鼓邊(%s或%s)", + "USB控制器也支持!" ], - otherControls: "Other controls", + otherControls: "其他控制", otherTutorial: [ - "%s \u2014 pause game", - "%s while selecting difficulty \u2014 enable autoplay mode", - "%s while selecting difficulty \u2014 enable 2P mode" + "%s暫停遊戲", + "選擇難度時按住%s以啟用自動模式", + "選擇難度時按住%s以啟用網上對打模式" ], ok: "確定" } @@ -469,16 +469,16 @@ function StringsKo(){ this.tutorial = { basics: [ - "Hit the drum when the notes reach the taiko!", - "For red notes, hit the face of the drum (%s or %s)...", - "...and for blue notes, hit the rim! (%s or %s)", - "USB controllers are also supported!" + "이동하는 음표가 테두리와 겹쳐졌을 때 북채로 태고를 두드리자!", + "빨간 음표는 면을 두드리자 (%s 또는 %s)", + "파란 음표는 테를 두드리자 (%s 또는 %s)", + "USB 컨트롤러도 지원됩니다!" ], - otherControls: "Other controls", + otherControls: "기타 컨트롤", otherTutorial: [ - "%s \u2014 pause game", - "%s while selecting difficulty \u2014 enable autoplay mode", - "%s while selecting difficulty \u2014 enable 2P mode" + "%s \u2014 게임을 일시 중지합니다", + "난이도 선택 동안 %s 홀드 \u2014 오토 모드 활성화", + "난이도 선택 동안 %s 홀드 \u2014 넷 플레이 모드 활성화" ], ok: "확인" } diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index 9f7c4b5..beac5f3 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -1,38 +1,47 @@ class Titlescreen{ - constructor(){ - loader.changePage("titlescreen", false) + constructor(songId){ + this.songId = songId - this.titleScreen = document.getElementById("title-screen") - this.proceed = document.getElementById("title-proceed") - this.langDropdown = document.getElementById("lang-dropdown") - this.langId = document.getElementById("lang-id") - this.disclaimerText = document.getElementById("title-disclaimer-text") - this.disclaimerCopyright = document.getElementById("title-disclaimer-copyright") - document.getElementById("globe-path").setAttribute("d", vectors.globe) - - this.logo = new Logo() + if(!songId){ + loader.changePage("titlescreen", false) + + this.titleScreen = document.getElementById("title-screen") + this.proceed = document.getElementById("title-proceed") + this.langDropdown = document.getElementById("lang-dropdown") + this.langId = document.getElementById("lang-id") + this.disclaimerText = document.getElementById("title-disclaimer-text") + this.disclaimerCopyright = document.getElementById("title-disclaimer-copyright") + document.getElementById("globe-path").setAttribute("d", vectors.globe) + this.logo = new Logo() + } this.lang = this.getLang() this.setLang(allStrings[this.lang]) - this.addLangs() - pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) - pageEvents.add(this.titleScreen, ["mousedown", "touchstart"], this.onPressed.bind(this)) - pageEvents.add(this.langDropdown, "change", this.langChange.bind(this)) - - assets.sounds["v_title"].play() - this.gamepad = new Gamepad({ - "13": ["a", "b", "x", "y", "start", "ls", "rs"] - }, pressed => { - if(pressed){ - this.onPressed() - } - }) - if(p2.session){ - pageEvents.add(p2, "message", response => { - if(response.type === "songsel"){ - this.goNext(true) + if(songId){ + this.goNext() + }else{ + this.addLangs() + + pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) + pageEvents.add(this.titleScreen, ["mousedown", "touchstart"], this.onPressed.bind(this)) + pageEvents.add(this.langDropdown, "change", this.langChange.bind(this)) + + assets.sounds["v_title"].play() + this.gamepad = new Gamepad({ + "13": ["a", "b", "x", "y", "start", "ls", "rs"] + }, pressed => { + if(pressed){ + this.onPressed() } }) + if(p2.session){ + pageEvents.add(p2, "message", response => { + if(response.type === "songsel"){ + this.goNext(true) + } + }) + } + pageEvents.send("title-screen") } } @@ -66,13 +75,16 @@ class Titlescreen{ if(p2.session && !fromP2){ p2.send("songsel") }else if(fromP2 || this.touched || localStorage.getItem("tutorial") === "true"){ + if(this.touched){ + localStorage.setItem("tutorial", "true") + } pageEvents.remove(p2, "message") setTimeout(() => { - new SongSelect(false, false, this.touched) + new SongSelect(false, false, this.touched, this.songId) }, 500) }else{ setTimeout(() => { - new Tutorial() + new Tutorial(false, this.songId) }, 500) } } @@ -96,6 +108,16 @@ class Titlescreen{ } setLang(lang){ strings = lang + + loader.screen.style.fontFamily = strings.font + loader.screen.style.fontWeight = strings.font === "Microsoft YaHei, sans-serif" ? "bold" : "" + + if(failedTests.length !== 0){ + showUnsupported(strings) + } + if(this.songId){ + return + } this.proceed.innerText = strings.titleProceed this.proceed.setAttribute("alt", strings.titleProceed) this.langId.innerText = strings.id.toUpperCase() @@ -106,12 +128,8 @@ class Titlescreen{ this.disclaimerCopyright.innerText = strings.titleCopyright this.disclaimerCopyright.setAttribute("alt", strings.titleCopyright) - loader.screen.style.fontFamily = strings.font - loader.screen.style.fontWeight = strings.font === "Microsoft YaHei, sans-serif" ? "bold" : "" - if(failedTests.length !== 0){ - showUnsupported(strings) - } this.logo.updateSubtitle() + pageEvents.send("language-change", lang.id) } addLangs(){ for(var i in allStrings){ diff --git a/public/src/js/tutorial.js b/public/src/js/tutorial.js index 7de7e31..d2999cd 100644 --- a/public/src/js/tutorial.js +++ b/public/src/js/tutorial.js @@ -1,6 +1,7 @@ class Tutorial{ - constructor(fromSongSel){ + constructor(fromSongSel, songId){ this.fromSongSel = fromSongSel + this.songId = songId loader.changePage("tutorial", true) assets.sounds["bgm_setsume"].playLoop(0.1, false, 0, 1.054, 16.054) this.endButton = document.getElementById("tutorial-end-button") @@ -47,6 +48,7 @@ class Tutorial{ this.gamepad = new Gamepad({ "confirm": ["start", "b", "ls", "rs"] }, this.onEnd.bind(this)) + pageEvents.send("tutorial") } insertText(text, parent){ parent.appendChild(document.createTextNode(text)) @@ -66,7 +68,7 @@ class Tutorial{ assets.sounds["se_don"].play() localStorage.setItem("tutorial", "true") setTimeout(() => { - new SongSelect(this.fromSongSel ? "tutorial" : false, false, touched) + new SongSelect(this.fromSongSel ? "tutorial" : false, false, touched, this.songId) }, 500) } clean(){ diff --git a/public/src/js/view.js b/public/src/js/view.js index 61513d0..f612782 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -750,7 +750,7 @@ comboScale = this.draw.fade(scoreMS / 100) } var glyphW = 51 - var glyphH = 64 + var glyphH = 65 var letterSpacing = (comboText.length >= 4 ? 38 : 42) * mul var orange = comboCount >= 100 ? "1" : "0" @@ -1374,8 +1374,8 @@ fillComboCache(){ var fontSize = 58 var letterSpacing = fontSize * 0.67 - var glyphW = 50 - var glyphH = 64 + var glyphW = 51 + var glyphH = 65 var textX = 5 var textY = 5 var letterBorder = fontSize * 0.15 @@ -1631,12 +1631,17 @@ switch(pos){ case 1: assets.sounds["se_don"].play() - return this.controller.restartSong() + this.controller.restartSong() + pageEvents.send("pause-restart") + break case 2: assets.sounds["se_don"].play() - return this.controller.songSelection() + this.controller.songSelection() + pageEvents.send("pause-song-select") + break default: - return this.controller.togglePause() + this.controller.togglePause() + break } } onmousedown(event){ From e677f2f03205e48edc20e14a48e4faa0632096a8 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Fri, 15 Feb 2019 01:10:34 +0300 Subject: [PATCH 02/31] Change some events, fix p2 sending gameend twice - Fixed gameend being sent twice in p2 - Add `ready`, `loader-error`, and `song-select-random` events - Fixed `session-start` not firing when invited, added event detail - Fixed `language-change` being fired at startup - Fixed `version-link` and `about-link` firing twice - "#song=" will fire `song-select-difficulty` - Removed `key-events` and `scoresheet-player2`, merged with `scoresheet` and made it more detailed --- public/src/js/about.js | 6 ++++-- public/src/js/keyboard.js | 6 ------ public/src/js/loader.js | 7 +++++++ public/src/js/loadsong.js | 3 ++- public/src/js/main.js | 8 +++++--- public/src/js/p2.js | 1 - public/src/js/scoresheet.js | 9 ++++++++- public/src/js/session.js | 2 +- public/src/js/songselect.js | 4 ++++ public/src/js/titlescreen.js | 8 +++++--- public/src/js/view.js | 2 ++ server.py | 3 +++ 12 files changed, 41 insertions(+), 18 deletions(-) diff --git a/public/src/js/about.js b/public/src/js/about.js index 069b007..1c3586d 100644 --- a/public/src/js/about.js +++ b/public/src/js/about.js @@ -152,8 +152,10 @@ return target.getElementsByTagName("a")[0] } linkButton(event){ - this.getLink(event.currentTarget).click() - pageEvents.send("about-link", event) + if(event.target === event.currentTarget){ + this.getLink(event.currentTarget).click() + pageEvents.send("about-link", event) + } } clean(){ cancelTouch = true diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index 399fb98..186c651 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -259,11 +259,5 @@ class Keyboard{ if(this.controller.multiplayer === 1){ pageEvents.remove(window, "beforeunload") } - if(this.controller.multiplayer !== 2){ - pageEvents.send("key-events", { - keyboard: this.keyboardEvents, - gamepad: this.gamepad.gamepadEvents - }) - } } } diff --git a/public/src/js/loader.js b/public/src/js/loader.js index f1c9bf9..a171642 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -153,6 +153,7 @@ class Loader{ } })) + var readyEvent = "normal" var songId var hashLower = location.hash.toLowerCase() p2 = new P2Connection() @@ -160,6 +161,7 @@ class Loader{ var number = parseInt(location.hash.slice(6)) if(number > 0){ songId = number + readyEvent = "song-id" } }else if(location.hash.length === 6){ p2.hashLock = true @@ -167,10 +169,13 @@ class Loader{ 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() } }) @@ -196,6 +201,7 @@ class Loader{ this.canvasTest.clean() this.clean() this.callback(songId) + pageEvents.send("ready", readyEvent) }) }, this.errorMsg.bind(this)) @@ -218,6 +224,7 @@ class Loader{ } errorMsg(error){ console.error(error) + pageEvents.send("loader-error", error) this.error = true this.loaderPercentage.appendChild(document.createElement("br")) this.loaderPercentage.appendChild(document.createTextNode("An error occurred, please refresh")) diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index 45b69e2..6629afc 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -18,7 +18,8 @@ class LoadSong{ pageEvents.send("load-song", { selectedSong: selectedSong, autoPlayEnabled: autoPlayEnabled, - multiplayer: multiplayer + multiplayer: multiplayer, + touchEnabled: touchEnabled }) } run(){ diff --git a/public/src/js/main.js b/public/src/js/main.js index 85b9625..a63691e 100644 --- a/public/src/js/main.js +++ b/public/src/js/main.js @@ -90,9 +90,11 @@ pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => { }) var versionDiv = document.getElementById("version") var versionLink = document.getElementById("version-link") -pageEvents.add(versionDiv, ["click", "touchend"], () => { - versionLink.click() - pageEvents.send("version-link") +pageEvents.add(versionDiv, ["click", "touchend"], event => { + if(event.target === versionDiv){ + versionLink.click() + pageEvents.send("version-link") + } }) resizeRoot() setInterval(resizeRoot, 100) diff --git a/public/src/js/p2.js b/public/src/js/p2.js index aa5e834..1f6f97e 100644 --- a/public/src/js/p2.js +++ b/public/src/js/p2.js @@ -128,7 +128,6 @@ class P2Connection{ for(var i in response.value){ this.results[i] = response.value[i].toString() } - pageEvents.send("scoresheet-player2", this.results) break case "note": this.notes.push(response.value) diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 3e1326a..1402b86 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -61,8 +61,15 @@ class Scoresheet{ }) } pageEvents.send("scoresheet", { + selectedSong: controller.selectedSong, + autoPlayEnabled: controller.autoPlayEnabled, + multiplayer: multiplayer, + touchEnabled: touchEnabled, results: this.results, - multiplayer: multiplayer + p2results: multiplayer ? p2.results : null, + keyboardEvents: controller.keyboard.keyboardEvents, + gamepadEvents: controller.keyboard.gamepad.gamepadEvents, + touchEvents: controller.view.touchEvents }) } keyDown(event, code){ diff --git a/public/src/js/session.js b/public/src/js/session.js index 9a7c2cf..8dfe8e6 100644 --- a/public/src/js/session.js +++ b/public/src/js/session.js @@ -30,7 +30,7 @@ class Session{ }else if(response.type === "songsel"){ p2.clearMessage("users") this.onEnd(false, true) - pageEvents.send("session-start") + pageEvents.send("session-start", "host") } }) p2.send("invite") diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index f029739..6dbe579 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -313,6 +313,9 @@ class SongSelect{ this.redraw() pageEvents.send("song-select") pageEvents.send("song-select-move", this.songs[this.selectedSong]) + if(songIdIndex !== -1){ + pageEvents.send("song-select-difficulty", this.songs[this.selectedSong]) + } } keyDown(event, code){ @@ -615,6 +618,7 @@ class SongSelect{ setTimeout(() => { this.moveToSong(moveBy) }, 200) + pageEvents.send("song-select-random") }else if(currentSong.action === "tutorial"){ this.toTutorial() }else if(currentSong.action === "about"){ diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index beac5f3..642aab9 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -15,7 +15,7 @@ class Titlescreen{ this.logo = new Logo() } this.lang = this.getLang() - this.setLang(allStrings[this.lang]) + this.setLang(allStrings[this.lang], true) if(songId){ this.goNext() @@ -106,7 +106,7 @@ class Titlescreen{ } return "ja" } - setLang(lang){ + setLang(lang, initial){ strings = lang loader.screen.style.fontFamily = strings.font @@ -129,7 +129,9 @@ class Titlescreen{ this.disclaimerCopyright.setAttribute("alt", strings.titleCopyright) this.logo.updateSubtitle() - pageEvents.send("language-change", lang.id) + if(!initial){ + pageEvents.send("language-change", lang.id) + } } addLangs(){ for(var i in allStrings){ diff --git a/public/src/js/view.js b/public/src/js/view.js index f612782..fa0c534 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -74,6 +74,7 @@ this.nextBeat = 0 this.gogoTime = 0 this.drumroll = [] + this.touchEvents = 0 this.beatInterval = this.controller.parsedSongData.beatInfo.beatInterval this.font = strings.font @@ -1601,6 +1602,7 @@ this.touchNote("ka_r") } } + this.touchEvents++ } } } diff --git a/server.py b/server.py index 19ddd03..b4b65ca 100644 --- a/server.py +++ b/server.py @@ -210,6 +210,7 @@ async def connection(ws, path): user["other_user"]["ws"].send(sent_msg1), user["other_user"]["ws"].send(sent_msg2) ]) + del user["other_user"]["other_user"] del user["other_user"] else: # Other user disconnected @@ -304,6 +305,7 @@ async def connection(ws, path): user["other_user"]["ws"].send(sent_msg1), user["other_user"]["ws"].send(sent_msg2) ]) + del user["other_user"]["other_user"] del user["other_user"] else: # Other user disconnected @@ -324,6 +326,7 @@ async def connection(ws, path): user["other_user"]["ws"].send(msgobj("gameend")), user["other_user"]["ws"].send(status_event()) ]) + del user["other_user"]["other_user"] if user["action"] == "waiting": del server_status["waiting"][user["gameid"]] await notify_status() From 03b9326733c3a9e0dfaf99e6c555ccc7c62e5c11 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sun, 17 Feb 2019 19:26:46 +0300 Subject: [PATCH 03/31] PraseTja: Add branches - Needs the following changes to the database: change `easy`, `normal`, `hard`, and `oni` to `TEXT` type - When adding songs to the database and if, for example, a song's 7-star difficulty has a branch, instead of `7` input `7 B`, this is to display song's branch support on the song selection - Branch can be forced in debug --- public/src/css/debug.css | 20 +++- public/src/js/canvasdraw.js | 1 + public/src/js/circle.js | 42 +------- public/src/js/debug.js | 64 ++++++++++-- public/src/js/game.js | 124 +++++++++++++++++----- public/src/js/importsongs.js | 2 +- public/src/js/keyboard.js | 4 +- public/src/js/mekadon.js | 16 +-- public/src/js/p2.js | 11 +- public/src/js/parsetja.js | 195 ++++++++++++++++++++++++----------- public/src/js/songselect.js | 64 ++++++++---- public/src/js/soundbuffer.js | 2 +- public/src/js/strings.js | 5 + public/src/js/titlescreen.js | 6 +- public/src/js/view.js | 70 +++++++++---- public/src/views/debug.html | 11 ++ server.py | 1 + 17 files changed, 445 insertions(+), 193 deletions(-) diff --git a/public/src/css/debug.css b/public/src/css/debug.css index 7c838f0..f9e1478 100644 --- a/public/src/css/debug.css +++ b/public/src/css/debug.css @@ -44,7 +44,8 @@ box-sizing: border-box; } -#debug .input-slider{ +#debug .input-slider, +#debug .select{ display: flex; width: 100%; height: 30px; @@ -59,7 +60,8 @@ padding: 2px 4px; text-align: center; } -#debug .input-slider>span{ +#debug .input-slider>span, +#debug .select>span{ display: block; width: 10%; height: 100%; @@ -70,10 +72,19 @@ line-height: 2em; cursor: pointer; } -#debug .input-slider>span:hover{ +#debug .input-slider>span:hover, +#debug .select>span:hover{ opacity: 1; background: #333; } +#debug .select select{ + width: 90%; + height: 100%; + box-sizing: border-box; + font-size: 18px; + font-family: sans-serif; + padding: 2px 4px; +} #debug label{ display: block; @@ -111,6 +122,7 @@ margin-left: 3px; } -#debug .autoplay-label{ +#debug .autoplay-label, +#debug .branch-hide{ display: none; } diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index 472730c..8a072a4 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -1167,6 +1167,7 @@ var firstTop = config.multiplayer ? 0 : 30 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 gaugeW = 14 * 50 diff --git a/public/src/js/circle.js b/public/src/js/circle.js index 6d3c650..261629e 100644 --- a/public/src/js/circle.js +++ b/public/src/js/circle.js @@ -1,6 +1,5 @@ class Circle{ constructor(config){ - // id, ms, type, text, speed, endTime, requiredHits this.id = config.id this.ms = config.start this.originalMS = this.ms @@ -23,38 +22,13 @@ class Circle{ this.gogoChecked = false this.beatMS = config.beatMS this.fixedPos = config.fixedPos - } - getMS(){ - return this.ms - } - getEndTime(){ - return this.endTime - } - getType(){ - return this.type - } - getLastFrame(){ - return this.lastFrame + this.branch = config.branch + this.section = config.section } animate(ms){ this.animating = true this.animT = ms } - isAnimated(){ - return this.animating - } - getAnimT(){ - return this.animT - } - getPlayed(){ - return this.isPlayed - } - isAnimationFinished(){ - return this.animationEnded - } - endAnimation(){ - this.animationEnded = true - } played(score, big){ this.score = score this.isPlayed = score <= 0 ? score - 1 : (big ? 2 : 1) @@ -65,16 +39,4 @@ class Circle{ this.timesKa++ } } - getScore(){ - return this.score - } - getID(){ - return this.id - } - getText(){ - return this.text - } - getSpeed(){ - return this.speed - } } \ No newline at end of file diff --git a/public/src/js/debug.js b/public/src/js/debug.js index 6b03534..607002d 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -8,15 +8,19 @@ class Debug{ this.debugDiv.innerHTML = assets.pages["debug"] document.body.appendChild(this.debugDiv) - this.titleDiv = this.debugDiv.getElementsByClassName("title")[0] - this.minimiseDiv = this.debugDiv.getElementsByClassName("minimise")[0] - this.offsetDiv = this.debugDiv.getElementsByClassName("offset")[0] - this.measureNumDiv = this.debugDiv.getElementsByClassName("measure-num")[0] - this.restartCheckbox = this.debugDiv.getElementsByClassName("change-restart")[0] - this.autoplayLabel = this.debugDiv.getElementsByClassName("autoplay-label")[0] - this.autoplayCheckbox = this.debugDiv.getElementsByClassName("autoplay")[0] - this.restartBtn = this.debugDiv.getElementsByClassName("restart-btn")[0] - this.exitBtn = this.debugDiv.getElementsByClassName("exit-btn")[0] + this.titleDiv = this.byClass("title") + this.minimiseDiv = this.byClass("minimise") + this.offsetDiv = this.byClass("offset") + this.measureNumDiv = this.byClass("measure-num") + this.branchHideDiv = this.byClass("branch-hide") + this.branchSelectDiv = this.byClass("branch-select") + this.branchSelect = this.branchSelectDiv.getElementsByTagName("select")[0] + this.branchResetBtn = this.branchSelectDiv.getElementsByClassName("reset")[0] + this.restartCheckbox = this.byClass("change-restart") + this.autoplayLabel = this.byClass("autoplay-label") + this.autoplayCheckbox = this.byClass("autoplay") + this.restartBtn = this.byClass("restart-btn") + this.exitBtn = this.byClass("exit-btn") this.moving = false pageEvents.add(window, ["mousedown", "mouseup", "blur"], this.stopMove.bind(this)) @@ -26,6 +30,8 @@ class Debug{ pageEvents.add(this.restartBtn, "click", this.restartSong.bind(this)) pageEvents.add(this.exitBtn, "click", this.clean.bind(this)) pageEvents.add(this.autoplayCheckbox, "change", this.toggleAutoplay.bind(this)) + pageEvents.add(this.branchSelect, "change", this.branchChange.bind(this)) + pageEvents.add(this.branchResetBtn, "click", this.branchReset.bind(this)) this.offsetSlider = new InputSlider(this.offsetDiv, -60, 60, 3) this.offsetSlider.onchange(this.offsetChange.bind(this)) @@ -39,6 +45,9 @@ class Debug{ this.updateStatus() pageEvents.send("debug") } + byClass(name){ + return this.debugDiv.getElementsByClassName(name)[0] + } startMove(event){ if(event.which === 1){ event.stopPropagation() @@ -88,17 +97,23 @@ class Debug{ } updateStatus(){ if(debugObj.controller && !this.controller){ + this.controller = debugObj.controller + this.restartBtn.style.display = "block" this.autoplayLabel.style.display = "block" + if(this.controller.parsedSongData.branches){ + this.branchHideDiv.style.display = "block" + } - this.controller = debugObj.controller var selectedSong = this.controller.selectedSong this.defaultOffset = selectedSong.offset || 0 if(this.songFolder === selectedSong.folder){ this.offsetChange(this.offsetSlider.get(), true) + this.branchChange(null, true) }else{ this.songFolder = selectedSong.folder this.offsetSlider.set(this.defaultOffset) + this.branchReset(null, true) } var measures = this.controller.parsedSongData.measures @@ -128,6 +143,7 @@ class Debug{ if(this.controller && !debugObj.controller){ this.restartBtn.style.display = "" this.autoplayLabel.style.display = "" + this.branchHideDiv.style.display = "" this.controller = null } } @@ -142,6 +158,11 @@ class Debug{ songData.measures.forEach(measure => { measure.ms = measure.originalMS + offset }) + if(songData.branches){ + songData.branches.forEach(branch => { + branch.ms = branch.originalMS + offset + }) + } if(this.restartCheckbox.checked && !noRestart){ this.restartSong() } @@ -171,21 +192,44 @@ class Debug{ } } } + branchChange(event, noRestart){ + if(this.controller){ + var game = this.controller.game + var name = this.branchSelect.value + game.branch = name === "auto" ? false : name + game.branchSet = false + if(this.restartCheckbox.checked && !noRestart){ + this.restartSong() + } + } + } + branchReset(event, noRestart){ + this.branchSelect.value = "auto" + this.branchChange(null, noRestart) + } clean(){ this.offsetSlider.clean() + this.measureNumSlider.clean() pageEvents.remove(window, ["mousedown", "mouseup", "blur"]) pageEvents.mouseRemove(this) + pageEvents.remove(this.titleDiv, "mousedown") pageEvents.remove(this.title, "mousedown") pageEvents.remove(this.minimiseDiv, "click") pageEvents.remove(this.restartBtn, "click") pageEvents.remove(this.exitBtn, "click") pageEvents.remove(this.autoplayCheckbox, "change") + pageEvents.remove(this.branchSelect, "change") + pageEvents.remove(this.branchResetBtn, "click") delete this.titleDiv delete this.minimiseDiv delete this.offsetDiv delete this.measureNumDiv + delete this.branchHideDiv + delete this.branchSelectDiv + delete this.branchSelect + delete this.branchResetBtn delete this.restartCheckbox delete this.autoplayLabel delete this.autoplayCheckbox diff --git a/public/src/js/game.js b/public/src/js/game.js index 76f7006..865b905 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -19,8 +19,8 @@ class Game{ difficulty: this.rules.difficulty } this.HPGain = 100 / this.songData.circles.filter(circle => { - var type = circle.getType() - return type === "don" || type === "ka" || type === "daiDon" || type === "daiKa" + var type = circle.type + return (type === "don" || type === "ka" || type === "daiDon" || type === "daiKa") && (!circle.branch || circle.branch.active) }).length this.paused = false this.started = false @@ -28,6 +28,8 @@ class Game{ this.musicFadeOut = 0 this.fadeOutStarted = false this.currentTimingPoint = 0 + this.sectionNotes = [] + this.sectionDrumroll = 0 assets.songs.forEach(song => { if(song.id == selectedSong.folder){ @@ -69,17 +71,18 @@ class Game{ } updateCirclesStatus(){ var nextSet = false + var ms = this.elapsedTime var circles = this.songData.circles var startIndex = this.currentCircle === 0 ? 0 : this.currentCircle - 1 - for(var i = startIndex; i < circles.length && i < this.currentCircle + 2; i++){ + var index = 0 + for(var i = startIndex; i < circles.length; i++){ var circle = circles[i] - if(!circle.getPlayed()){ - var ms = this.elapsedTime - var type = circle.getType() + if((!circle.branch || circle.branch.active) && !circle.isPlayed){ + var type = circle.type var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll" - var endTime = circle.getEndTime() + (drumrollNotes ? 0 : this.rules.bad) + var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad) - if(ms >= circle.getMS()){ + if(ms >= circle.ms){ if(drumrollNotes && !circle.rendaPlayed && ms < endTime){ circle.rendaPlayed = true if(this.rules.difficulty === "easy"){ @@ -101,7 +104,7 @@ class Game{ this.updateCurrentCircle() if(this.controller.multiplayer === 1){ var value = { - pace: (ms - circle.getMS()) / circle.timesHit + pace: (ms - circle.ms) / circle.timesHit } if(type === "drumroll" || type === "daiDrumroll"){ value.kaAmount = circle.timesKa / circle.timesHit @@ -111,6 +114,7 @@ class Game{ }else{ var currentScore = 0 circle.played(-1, type === "daiDon" || type === "daiKa") + this.sectionNotes.push(0) this.controller.displayScore(currentScore, true) this.updateCurrentCircle() this.updateCombo(currentScore) @@ -126,6 +130,56 @@ class Game{ nextSet = true this.currentCircle = i } + if(index++ > 1){ + break + } + } + } + var branches = this.songData.branches + if(branches){ + if(this.controller.multiplayer === 2 || this.branch){ + var parent = this.controller.multiplayer === 2 ? p2 : this + var view = this.controller.view + if(view.branch !== parent.branch){ + view.branchAnimate = { + ms: ms, + fromBranch: view.branch + } + view.branch = parent.branch + } + if(!parent.branchSet){ + parent.branchSet = true + for(var i = 0; i < branches.length; i++){ + this.setBranch(branches[i], parent.branch) + } + } + }else{ + var measures = this.songData.measures + for(var i = 0; i < measures.length; i++){ + var measure = measures[i] + if(measure.ms > ms){ + break + }else if(measure.nextBranch && !measure.gameChecked){ + measure.gameChecked = true + var branch = measure.nextBranch + if(branch.type){ + if(branch.type === "drumroll"){ + var accuracy = this.sectionDrumroll + }else{ + var accuracy = this.sectionNotes.reduce((a, b) => a + b) / this.sectionNotes.length * 100 + } + if(accuracy >= branch.requirement[1]){ + this.setBranch(branch, "master") + }else if(accuracy >= branch.requirement[0]){ + this.setBranch(branch, "advanced") + }else{ + this.setBranch(branch, "normal") + } + }else if(this.controller.multiplayer === 1){ + p2.send("branch", "normal") + } + } + } } } } @@ -160,7 +214,7 @@ class Game{ } } checkKey(keyCodes, circle, check){ - if(circle && !circle.getPlayed()){ + if(circle && !circle.isPlayed){ if(!this.checkScore(circle, check)){ return } @@ -171,7 +225,7 @@ class Game{ } checkScore(circle, check){ var ms = this.elapsedTime - var type = circle.getType() + var type = circle.type var keysDon = check === "don" || check === "daiDon" var keysKa = check === "ka" || check === "daiKa" @@ -182,7 +236,7 @@ class Game{ var keyTime = this.controller.getKeyTime() var currentTime = keysDon ? keyTime["don"] : keyTime["ka"] - var relative = currentTime - circle.getMS() + var relative = currentTime - circle.ms if(typeDon || typeKa){ if(-this.rules.bad >= relative || relative >= this.rules.bad){ @@ -219,10 +273,11 @@ class Game{ this.updateCombo(score) this.updateGlobalScore(score, typeDai && keyDai ? 2 : 1, circle.gogoTime) this.updateCurrentCircle() - if(this.controller.multiplayer == 1){ + this.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0)) + if(this.controller.multiplayer === 1){ var value = { score: score, - ms: circle.getMS() - currentTime, + ms: circle.ms - currentTime, dai: typeDai ? keyDai ? 2 : 1 : 0 } if((!keysDon || !typeDon) && (!keysKa || !typeKa)){ @@ -231,12 +286,12 @@ class Game{ p2.send("note", value) } }else{ - if(circle.getMS() > currentTime || currentTime > circle.getEndTime()){ + if(circle.ms > currentTime || currentTime > circle.endTime){ return true } if(keysDon && type === "balloon"){ this.checkBalloon(circle) - if(check === "daiDon" && !circle.getPlayed()){ + if(check === "daiDon" && !circle.isPlayed){ this.checkBalloon(circle) } }else if((keysDon || keysKa) && (type === "drumroll" || type === "daiDrumroll")){ @@ -256,24 +311,25 @@ class Game{ circle.played(score) if(this.controller.multiplayer == 1){ p2.send("drumroll", { - pace: (this.elapsedTime - circle.getMS()) / circle.timesHit + pace: (this.elapsedTime - circle.ms) / circle.timesHit }) } }else{ var score = 300 circle.hit() } - this.globalScore.drumroll ++ + this.globalScore.drumroll++ + this.sectionDrumroll++ this.globalScore.points += score this.view.setDarkBg(false) } checkDrumroll(circle, keysKa){ var ms = this.elapsedTime - var dai = circle.getType() === "daiDrumroll" + var dai = circle.type === "daiDrumroll" var score = 100 circle.hit(keysKa) var keyTime = this.controller.getKeyTime() - if(circle.getType() === "drumroll"){ + if(circle.type === "drumroll"){ var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka" }else{ var sound = keyTime["don"] > keyTime["ka"] ? "daiDon" : "daiKa" @@ -291,6 +347,9 @@ class Game{ circleAnim.animate(ms) this.view.drumroll.push(circleAnim) this.globalScore.drumroll++ + if(this.controller.multiplayer !== 2){ + this.sectionDrumroll++ + } this.globalScore.points += score * (dai ? 2 : 1) this.view.setDarkBg(false) } @@ -298,11 +357,11 @@ class Game{ var ms = this.elapsedTime if(!this.lastCircle){ var circles = this.songData.circles - this.lastCircle = circles[circles.length - 1].getEndTime() + this.lastCircle = circles[circles.length - 1].endTime if(this.controller.multiplayer){ var syncWith = this.controller.syncWith var syncCircles = syncWith.game.songData.circles - var syncLastCircle = syncCircles[syncCircles.length - 1].getEndTime() + var syncLastCircle = syncCircles[syncCircles.length - 1].endTime if(syncLastCircle > this.lastCircle){ this.lastCircle = syncLastCircle } @@ -410,7 +469,14 @@ class Game{ return this.songData.circles } updateCurrentCircle(){ - this.currentCircle++ + var circles = this.songData.circles + do{ + var circle = circles[++this.currentCircle] + }while(circle && circle.branch && !circle.branch.active) + if(circle && circle.section){ + this.sectionNotes = [] + this.sectionDrumroll = 0 + } } getCurrentCircle(){ return this.currentCircle @@ -464,4 +530,16 @@ class Game{ } this.globalScore.points += Math.floor(score * multiplier / 10) * 10 } + setBranch(branch, name){ + var names = ["normal", "advanced", "master"] + for(var i in names){ + if(names[i] in branch){ + branch[names[i]].active = names[i] === name + } + } + branch.active = name + if(this.controller.multiplayer === 1){ + p2.send("branch", name) + } + } } diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index 400f443..cb907f5 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -199,7 +199,7 @@ songObj.subtitle = songObj.subtitle_en = subtitle songObj.preview = meta.demostart ? Math.floor(meta.demostart * 1000) : 0 if(meta.level){ - songObj.stars[this.courseTypes[diff]] = meta.level + songObj.stars[this.courseTypes[diff]] = meta.level + (meta.branch ? " B" : "") } if(meta.wave){ songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index 186c651..5925658 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -199,8 +199,8 @@ class Keyboard{ if( sound === "don" && circle - && !circle.getPlayed() - && circle.getType() === "balloon" + && !circle.isPlayed + && circle.type === "balloon" && circle.requiredHits - circle.timesHit <= 1 ){ this.controller.playSound("se_balloon") diff --git a/public/src/js/mekadon.js b/public/src/js/mekadon.js index cca80e2..22f7d73 100644 --- a/public/src/js/mekadon.js +++ b/public/src/js/mekadon.js @@ -6,12 +6,12 @@ class Mekadon{ this.lastHit = -Infinity } play(circle){ - var type = circle.getType() - if((type === "balloon" || type === "drumroll" || type === "daiDrumroll") && this.getMS() > circle.getEndTime()){ + var type = circle.type + if((type === "balloon" || type === "drumroll" || type === "daiDrumroll") && this.getMS() > circle.endTime){ circle.played(-1, false) this.game.updateCurrentCircle() } - type = circle.getType() + type = circle.type if(type === "balloon"){ this.playDrumrollAt(circle, 0, 30) }else if(type === "drumroll" || type === "daiDrumroll"){ @@ -21,7 +21,7 @@ class Mekadon{ } } playAt(circle, ms, score, dai, reverse){ - var currentMs = circle.getMS() - this.getMS() + var currentMs = circle.ms - this.getMS() if(ms > currentMs - 10){ return this.playNow(circle, score, dai, reverse) } @@ -36,18 +36,19 @@ class Mekadon{ } } miss(circle){ - var currentMs = circle.getMS() - this.getMS() + var currentMs = circle.ms - this.getMS() if(0 >= currentMs - 10){ this.controller.displayScore(0, true) this.game.updateCurrentCircle() this.game.updateCombo(0) this.game.updateGlobalScore(0, 1, circle.gogoTime) + this.game.sectionNotes.push(0) return true } } playNow(circle, score, dai, reverse){ var kbd = this.controller.getBindings() - var type = circle.getType() + var type = circle.type var keyDai = false var playDai = !dai || dai === 2 var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll" @@ -55,7 +56,7 @@ class Mekadon{ if(drumrollNotes){ var ms = this.getMS() }else{ - var ms = circle.getMS() + var ms = circle.ms } if(reverse){ @@ -95,6 +96,7 @@ class Mekadon{ this.game.updateGlobalScore(score, keyDai ? 2 : 1, circle.gogoTime) this.game.updateCurrentCircle() circle.played(score, keyDai) + this.game.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0)) } this.lastHit = ms return true diff --git a/public/src/js/p2.js b/public/src/js/p2.js index 1f6f97e..a0c266d 100644 --- a/public/src/js/p2.js +++ b/public/src/js/p2.js @@ -109,6 +109,7 @@ class P2Connection{ this.dai = 2 this.kaAmount = 0 this.results = false + this.branch = "normal" break case "gameend": this.otherConnected = false @@ -141,6 +142,10 @@ class P2Connection{ this.kaAmount = response.value.kaAmount } break + case "branch": + this.branch = response.value + this.branchSet = false + break case "session": this.clearMessage("users") this.otherConnected = true @@ -161,10 +166,10 @@ class P2Connection{ } play(circle, mekadon){ if(this.otherConnected || this.notes.length > 0){ - var type = circle.getType() + var type = circle.type var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll" - if(drumrollNotes && mekadon.getMS() > circle.getEndTime()){ + if(drumrollNotes && mekadon.getMS() > circle.endTime){ circle.played(-1, false) mekadon.game.updateCurrentCircle() } @@ -177,7 +182,7 @@ class P2Connection{ var note = this.notes[0] if(note.score >= 0){ var dai = 1 - if(circle.getType() === "daiDon" || circle.getType() === "daiKa"){ + if(circle.type === "daiDon" || circle.type === "daiKa"){ dai = this.dai } if(mekadon.playAt(circle, note.ms, note.score, dai, note.reverse)){ diff --git a/public/src/js/parsetja.js b/public/src/js/parsetja.js index 0055b99..262c0b9 100644 --- a/public/src/js/parsetja.js +++ b/public/src/js/parsetja.js @@ -59,7 +59,9 @@ if(!(courseName in courses)){ courses[courseName] = {} } - courses[courseName][name] = currentCourse[name] + if(name !== "branch"){ + courses[courseName][name] = currentCourse[name] + } } courses[courseName].start = lineNum + 1 courses[courseName].end = this.data.length @@ -70,6 +72,8 @@ hasSong = true courses[courseName].end = lineNum } + }else if(name.startsWith("branchstart") && inSong){ + courses[courseName].branch = true } }else if(!inSong){ @@ -128,9 +132,13 @@ var balloons = meta.balloon || [] var lastDrumroll = false + var branch = false - var branchType - var branchPreference = "m" + var branchObj = {} + var currentBranch = false + var branchSettings = {} + var branchPushed = false + var sectionBegin = true var currentMeasure = [] var firstNote = true @@ -138,19 +146,19 @@ var circleID = 0 var pushMeasure = () => { - if(barLine){ - var note = currentMeasure[0] - if(note){ - var speed = note.bpm * note.scroll / 60 - }else{ - var speed = bpm * scroll / 60 - } - this.measures.push({ - ms: ms, - originalMS: ms, - speed: speed - }) + var note = currentMeasure[0] + if(note){ + var speed = note.bpm * note.scroll / 60 + }else{ + var speed = bpm * scroll / 60 } + this.measures.push({ + ms: ms, + originalMS: ms, + speed: speed, + visible: barLine, + branch: currentBranch + }) if(currentMeasure.length){ for(var i = 0; i < currentMeasure.length; i++){ var note = currentMeasure[i] @@ -182,7 +190,9 @@ gogoTime: note.gogo, endTime: note.endTime, requiredHits: note.requiredHits, - beatMS: 60000 / note.bpm + beatMS: 60000 / note.bpm, + branch: currentBranch, + section: note.section }) if(lastDrumroll === note){ lastDrumroll = circleObj @@ -204,59 +214,112 @@ var line = line.slice(1).toLowerCase() var [name, value] = this.split(line, " ") - if(!branch || branch && branchType === branchPreference){ - switch(name){ - case "gogostart": - gogo = true - break - case "gogoend": - gogo = false - break - case "bpmchange": - bpm = parseFloat(value) - break - case "scroll": - scroll = parseFloat(value) - break - case "measure": - var [numerator, denominator] = value.split("/") - measure = numerator / denominator * 4 - break - case "delay": - ms += (parseFloat(value) || 0) * 1000 - break - case "barlineon": - barLine = true - break - case "barlineoff": - barLine = false - break - } - } switch(name){ + case "gogostart": + gogo = true + break + case "gogoend": + gogo = false + break + case "bpmchange": + bpm = parseFloat(value) + break + case "scroll": + scroll = parseFloat(value) + break + case "measure": + var [numerator, denominator] = value.split("/") + measure = numerator / denominator * 4 + break + case "delay": + ms += (parseFloat(value) || 0) * 1000 + break + case "barlineon": + barLine = true + break + case "barlineoff": + barLine = false + break case "branchstart": branch = true - branchType = "" + currentBranch = false + branchPushed = false + branchSettings = { + ms: ms, + gogo: gogo, + bpm: bpm, + scroll: scroll, + sectionBegin: sectionBegin + } value = value.split(",") - var forkType = value[0].toLowerCase() - if(forkType === "r" || parseFloat(value[2]) <= 100){ - branchPreference = "m" - }else if(parseFloat(value[1]) <= 100){ - branchPreference = "e" - }else{ - branchPreference = "n" + if(!this.branches){ + this.branches = [] + } + branchObj = { + ms: ms, + originalMS: ms, + type: value[0].toLowerCase() === "r" ? "drumroll" : "perfect", + requirement: [ + parseFloat(value[1]), + parseFloat(value[2]) + ] } break case "branchend": - case "section": branch = false + currentBranch = false + if(this.measures.length !== 0){ + this.measures[this.measures.length - 1].nextBranch = { + ms: ms, + originalMS: ms, + active: "normal" + } + } + break + case "section": + sectionBegin = true + if(branch && !currentBranch){ + branchSettings.sectionBegin = true + } break case "n": case "e": case "m": - branchType = name + if(!branchPushed){ + branchPushed = true + this.branches.push(branchObj) + if(this.measures.length === 1 && branchObj.type === "drumroll"){ + for(var i = circles.length; i--;){ + var circle = circles[i] + if(circle.endTime && circle.type === "drumroll" || circle.type === "daiDrumroll" || circle.type === "balloon"){ + this.measures.push({ + ms: circle.endTime, + originalMS: circle.endTime, + speed: circle.bpm * circle.scroll / 60, + visible: false, + branch: circle.branch + }) + break + } + } + } + if(this.measures.length !== 0){ + this.measures[this.measures.length - 1].nextBranch = branchObj + } + } + ms = branchSettings.ms + gogo = branchSettings.gogo + bpm = branchSettings.bpm + scroll = branchSettings.scroll + sectionBegin = branchSettings.sectionBegin + var branchName = name === "m" ? "master" : (name === "e" ? "advanced" : "normal") + currentBranch = { + name: branchName, + active: branchName === "normal" + } + branchObj[branchName] = currentBranch break } - }else if(!branch || branch && branchType === branchPreference){ + }else{ var string = line.split("") @@ -278,8 +341,10 @@ txt: type.txt, gogo: gogo, bpm: bpm, - scroll: scroll + scroll: scroll, + section: sectionBegin } + sectionBegin = false if(lastDrumroll){ circleObj.endDrumroll = lastDrumroll lastDrumroll = false @@ -293,15 +358,19 @@ txt: type.txt, gogo: gogo, bpm: bpm, - scroll: scroll + scroll: scroll, + section: sectionBegin } + sectionBegin = false if(lastDrumroll){ if(symbol === "9"){ currentMeasure.push({ endDrumroll: lastDrumroll, bpm: bpm, - scroll: scroll + scroll: scroll, + section: sectionBegin }) + sectionBegin = false lastDrumroll = false }else{ currentMeasure.push({ @@ -327,8 +396,10 @@ currentMeasure.push({ endDrumroll: lastDrumroll, bpm: bpm, - scroll: scroll + scroll: scroll, + section: sectionBegin }) + sectionBegin = false lastDrumroll = false }else{ currentMeasure.push({ @@ -359,6 +430,10 @@ lastDrumroll.originalEndTime = ms } + if(this.branches){ + circles.sort((a, b) => a.ms > b.ms ? 1 : -1) + circles.forEach((circle, i) => circle.id = i + 1) + } return circles } } diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 6dbe579..114a276 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -351,7 +351,7 @@ class SongSelect{ down: code == 40 // Down } - if(key.cancel && event){ + if(event && (code == 27 || code == 8)){ event.preventDefault() } if(this.state.screen === "song"){ @@ -1322,27 +1322,47 @@ class SongSelect{ outlineSize: currentUra ? this.songAsset.letterBorder : 0 }) }) - var songStars = currentUra ? currentSong.stars[4] : currentSong.stars[i] - for(var j = 0; j < 10; j++){ - if(songSel){ - var yPos = _y + 113 + j * 17 - }else{ - var yPos = _y + 178 + j * 19.5 - } - if(10 - j > songStars){ - ctx.fillStyle = currentUra ? "#187085" : (songSel ? "#e97526" : "#e7e7e7") - ctx.beginPath() - ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2) - ctx.fill() - }else{ - this.draw.diffStar({ - ctx: ctx, - songSel: songSel, - ura: currentUra, - x: _x, - y: yPos, - ratio: ratio - }) + var songStarsArray = (currentUra ? currentSong.stars[4] : currentSong.stars[i]).toString().split(" ") + var songStars = songStarsArray[0] + var songBranch = songStarsArray[1] === "B" + var elapsedMS = this.state.screenMS > this.state.moveMS ? this.state.screenMS : this.state.moveMS + var fade = ((ms - elapsedMS) % 2000) / 2000 + if(songBranch && fade > 0.25 && fade < 0.75){ + this.draw.verticalText({ + ctx: ctx, + text: strings.songBranch, + x: _x, + y: _y + (songSel ? 110 : 185), + width: songSel ? 44 : 56, + height: songSel ? 160 : 170, + fill: songSel && !currentUra ? "#c85200" : "#fff", + fontSize: songSel ? 25 : 27, + fontFamily: songSel ? "Meiryo, Microsoft YaHei, sans-serif" : this.font, + outline: songSel ? false : "#f22666", + outlineSize: songSel ? 0 : this.songAsset.letterBorder + }) + }else{ + for(var j = 0; j < 10; j++){ + if(songSel){ + var yPos = _y + 113 + j * 17 + }else{ + var yPos = _y + 178 + j * 19.5 + } + if(10 - j > songStars){ + ctx.fillStyle = currentUra ? "#187085" : (songSel ? "#e97526" : "#e7e7e7") + ctx.beginPath() + ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2) + ctx.fill() + }else{ + this.draw.diffStar({ + ctx: ctx, + songSel: songSel, + ura: currentUra, + x: _x, + y: yPos, + ratio: ratio + }) + } } } var currentDiff = this.selectedDiff - this.diffOptions.length diff --git a/public/src/js/soundbuffer.js b/public/src/js/soundbuffer.js index 0262a6b..f220863 100644 --- a/public/src/js/soundbuffer.js +++ b/public/src/js/soundbuffer.js @@ -86,7 +86,7 @@ class SoundGain{ this.volume = amount } setCrossfade(amount){ - this.setVolume(Math.pow(Math.sin(Math.PI / 2 * amount), 1 / 4)) + this.setVolume(Math.sqrt(Math.sin(Math.PI / 2 * amount))) } fadeIn(duration, time, absolute){ this.fadeVolume(0, this.volume * this.volume, duration, time, absolute) diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 967d0a1..7c6b029 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -34,6 +34,7 @@ this.normal = "ふつう" this.hard = "むずかしい" this.oni = "おに" + this.songBranch = "譜面分岐あり" this.sessionStart = "オンラインセッションを開始する!" this.sessionEnd = "オンラインセッションを終了する" this.loading = "ロード中..." @@ -135,6 +136,7 @@ function StringsEn(){ this.normal = "Normal" this.hard = "Hard" this.oni = "Extreme" + this.songBranch = "Forked Paths" this.sessionStart = "Begin an Online Session!" this.sessionEnd = "End Online Session" this.loading = "Loading..." @@ -236,6 +238,7 @@ function StringsCn(){ this.normal = "普通" this.hard = "困难" this.oni = "魔王" + this.songBranch = "有分数分支" this.sessionStart = "开始在线会话!" this.sessionEnd = "结束在线会话" this.loading = "加载中..." @@ -337,6 +340,7 @@ function StringsTw(){ this.normal = "普通" this.hard = "困難" this.oni = "魔王" + this.songBranch = "有分數分支" this.sessionStart = "開始多人模式!" this.sessionEnd = "結束多人模式" this.loading = "讀取中..." @@ -438,6 +442,7 @@ function StringsKo(){ this.normal = "보통" this.hard = "어려움" this.oni = "귀신" + this.songBranch = "악보 분기 있습니다" this.sessionStart = "온라인 세션 시작!" this.sessionEnd = "온라인 세션 끝내기" this.loading = "로딩 중..." diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index 642aab9..9f95cdc 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -18,7 +18,11 @@ class Titlescreen{ this.setLang(allStrings[this.lang], true) if(songId){ - this.goNext() + if(localStorage.getItem("tutorial") === "true"){ + new SongSelect(false, false, this.touched, this.songId) + }else{ + new Tutorial(false, this.songId) + } }else{ this.addLangs() diff --git a/public/src/js/view.js b/public/src/js/view.js index fa0c534..f3f8383 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -75,6 +75,18 @@ this.gogoTime = 0 this.drumroll = [] this.touchEvents = 0 + if(this.controller.parsedSongData.branches){ + this.branch = "normal" + this.branchAnimate = { + ms: -Infinity, + fromBranch: "normal" + } + this.branchMap = { + "normal": "rgba(0, 0, 0, 0)", + "advanced": "rgba(29, 129, 189, 0.4)", + "master": "rgba(230, 29, 189, 0.4)" + } + } this.beatInterval = this.controller.parsedSongData.beatInfo.beatInterval this.font = strings.font @@ -693,6 +705,18 @@ } ctx.fillRect(padding, barY, winW - padding, barH) } + if(this.branchAnimate && ms <= this.branchAnimate.ms + 300){ + var alpha = Math.max(0, (ms - this.branchAnimate.ms) / 300) + ctx.globalAlpha = 1 - alpha + ctx.fillStyle = this.branchMap[this.branchAnimate.fromBranch] + ctx.fillRect(padding, barY, winW - padding, barH) + ctx.globalAlpha = alpha + } + if(this.branch){ + ctx.fillStyle = this.branchMap[this.branch] + ctx.fillRect(padding, barY, winW - padding, barH) + ctx.globalAlpha = 1 + } if(keyTime[sound] > ms - 130){ var gradients = { "don": "255, 0, 0", @@ -1102,7 +1126,7 @@ var timeForDistance = this.posToMs(distanceForCircle, measure.speed) var startingTime = measure.ms - timeForDistance var finishTime = measure.ms + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + 3, measure.speed) - if(ms >= startingTime && ms <= finishTime){ + if(measure.visible && (!measure.branch || measure.branch.active) && ms >= startingTime && ms <= finishTime){ var measureX = this.slotPos.x + this.msToPos(measure.ms - ms, measure.speed) this.ctx.strokeStyle = "#bdbdbd" this.ctx.lineWidth = 3 @@ -1111,6 +1135,14 @@ this.ctx.lineTo(measureX, measureY + measureH) this.ctx.stroke() } + if(this.multiplayer !== 2 && ms >= measure.ms && measure.nextBranch && !measure.viewChecked && measure.gameChecked){ + measure.viewChecked = true + this.branchAnimate = { + ms: ms, + fromBranch: this.branch + } + this.branch = measure.nextBranch.active + } }) } updateNoteFaces(){ @@ -1137,17 +1169,17 @@ for(var i = circles.length; i--;){ var circle = circles[i] - var speed = circle.getSpeed() + var speed = circle.speed var timeForDistance = this.posToMs(distanceForCircle + this.slotPos.size / 2, speed) - var startingTime = circle.getMS() - timeForDistance - var finishTime = circle.getEndTime() + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed) + var startingTime = circle.ms - timeForDistance + var finishTime = circle.endTime + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed) - if(circle.getPlayed() <= 0 || circle.getScore() === 0){ - if(ms >= startingTime && ms <= finishTime && circle.getPlayed() !== -1){ + if(circle.isPlayed <= 0 || circle.score === 0){ + if((!circle.branch || circle.branch.active) && ms >= startingTime && ms <= finishTime && circle.isPlayed !== -1){ this.drawCircle(circle) } - }else if(!circle.isAnimated()){ + }else if(!circle.animating){ // Start animation to gauge circle.animate(ms) } @@ -1165,9 +1197,9 @@ for(var i = 0; i < circles.length; i++){ var circle = circles[i] - if(circle.isAnimated()){ + if(circle.animating){ - var animT = circle.getAnimT() + var animT = circle.animT if(ms < animT + 490){ if(circle.fixedPos){ @@ -1183,7 +1215,7 @@ var pos = this.animateBezier[3] this.drawCircle(circle, pos, (ms - animT - 490) / 160) }else{ - circle.endAnimation() + circle.animationEnded = true } } } @@ -1211,13 +1243,13 @@ var lyricsSize = 20 * mul var fill, size, faceID - var type = circle.getType() + var type = circle.type var ms = this.getMS() - var circleMs = circle.getMS() - var endTime = circle.getEndTime() - var animated = circle.isAnimated() - var speed = circle.getSpeed() - var played = circle.getPlayed() + var circleMs = circle.ms + var endTime = circle.endTime + var animated = circle.animating + var speed = circle.speed + var played = circle.isPlayed var drumroll = 0 var endX = 0 @@ -1323,9 +1355,9 @@ ctx.fill() ctx.globalAlpha = 1 } - if(!circle.isAnimated()){ + if(!circle.animating){ // Text - var text = circle.getText() + var text = circle.text var textX = circlePos.x var textY = circlePos.y + 83 * mul ctx.font = lyricsSize + "px Kozuka, Microsoft YaHei, sans-serif" @@ -1466,7 +1498,7 @@ if(this.gogoTime){ var circles = this.controller.parsedSongData.circles var lastCircle = circles[circles.length - 1] - var endTime = lastCircle.getEndTime() + 3000 + var endTime = lastCircle.endTime + 3000 if(ms >= endTime){ this.toggleGogoTime({ gogoTime: 0, diff --git a/public/src/views/debug.html b/public/src/views/debug.html index eba64ac..b681717 100644 --- a/public/src/views/debug.html +++ b/public/src/views/debug.html @@ -9,6 +9,17 @@
x-+
+
+
Branch:
+
+ x +
+
diff --git a/server.py b/server.py index b4b65ca..aafcad4 100644 --- a/server.py +++ b/server.py @@ -185,6 +185,7 @@ async def connection(ws, path): if "other_user" in user and "ws" in user["other_user"]: if type == "note"\ or type == "drumroll"\ + or type == "branch"\ or type == "gameresults": await user["other_user"]["ws"].send(msgobj(type, value)) elif type == "songsel" and user["session"]: From 4e9bf237a1a66ef54aa5b69cb49f8c408004b9b5 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sun, 17 Feb 2019 20:17:07 +0300 Subject: [PATCH 04/31] Fix go go time --- public/src/js/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/js/view.js b/public/src/js/view.js index f3f8383..43975cb 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -1183,7 +1183,7 @@ // Start animation to gauge circle.animate(ms) } - if(ms >= circle.ms && !circle.gogoChecked){ + if(ms >= circle.ms && !circle.gogoChecked && (!circle.branch || circle.branch.active)){ if(this.gogoTime != circle.gogoTime){ this.toggleGogoTime(circle) } From 76e56736f72f2f87125ff3d3fe8287b9bc9e538d Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Mon, 18 Feb 2019 01:29:18 +0300 Subject: [PATCH 05/31] Do not restart animation with d-pad --- public/src/js/songselect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 114a276..c6271f4 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -1325,7 +1325,7 @@ class SongSelect{ var songStarsArray = (currentUra ? currentSong.stars[4] : currentSong.stars[i]).toString().split(" ") var songStars = songStarsArray[0] var songBranch = songStarsArray[1] === "B" - var elapsedMS = this.state.screenMS > this.state.moveMS ? this.state.screenMS : this.state.moveMS + 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){ this.draw.verticalText({ From c72b5a742c4f8bafd5b5fcbd400724bb3ea2120a Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Wed, 20 Feb 2019 23:48:21 +0300 Subject: [PATCH 06/31] Add animations and fix behaviour --- public/src/js/canvascache.js | 1 + public/src/js/canvasdraw.js | 9 +- public/src/js/debug.js | 8 +- public/src/js/game.js | 164 ++++++++++++++++++++++++----------- public/src/js/main.js | 4 +- public/src/js/mekadon.js | 6 ++ public/src/js/parsetja.js | 84 +++++++++--------- public/src/js/strings.js | 31 ++++++- public/src/js/view.js | 157 +++++++++++++++++++++++++++------ public/src/views/debug.html | 8 +- 10 files changed, 344 insertions(+), 128 deletions(-) diff --git a/public/src/js/canvascache.js b/public/src/js/canvascache.js index 56a1f7d..1b4c121 100644 --- a/public/src/js/canvascache.js +++ b/public/src/js/canvascache.js @@ -11,6 +11,7 @@ class CanvasCache{ this.map = new Map() this.canvas = document.createElement("canvas") this.ctx = this.canvas.getContext("2d") + document.body.appendChild(this.canvas) } this.scale = scale this.x = 0 diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index 8a072a4..4431774 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -268,6 +268,9 @@ easeOut(pos){ return Math.sin(Math.PI / 2 * pos) } + easeOutBack(pos){ + return Math.sin(Math.PI / 1.74 * pos) * 1.03 + } easeInOut(pos){ return (Math.cos(Math.PI * pos) - 1) / -2 } @@ -572,7 +575,7 @@ } if(symbol.ura){ ctx.font = (30 * mul) + "px Meiryo, sans-serif" - ctx.textBaseline = "center" + ctx.textBaseline = "middle" ctx.beginPath() ctx.arc(currentX, currentY + (17 * mul), (18 * mul), 0, Math.PI * 2) if(action === "stroke"){ @@ -581,7 +584,7 @@ }else if(action === "fill"){ ctx.strokeStyle = config.fill ctx.lineWidth = 2.5 * mul - ctx.fillText(symbol.text, currentX, currentY) + ctx.fillText(symbol.text, currentX, currentY + (17 * mul)) } ctx.stroke() }else{ @@ -788,7 +791,7 @@ } if(symbol.ura){ ctx.font = (30 * mul) + "px Meiryo, sans-serif" - ctx.textBaseline = "center" + ctx.textBaseline = "middle" ctx.beginPath() ctx.arc(currentX, currentY + (17 * mul), (18 * mul), 0, Math.PI * 2) if(action === "strokeText"){ diff --git a/public/src/js/debug.js b/public/src/js/debug.js index 607002d..9f2836c 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -116,7 +116,9 @@ class Debug{ this.branchReset(null, true) } - var measures = this.controller.parsedSongData.measures + var measures = this.controller.parsedSongData.measures.filter((measure, i, array) => { + return i === 0 || Math.abs(measure.ms - array[i - 1].ms) > 0.01 + }) this.measureNumSlider.setMinMax(0, measures.length - 1) if(this.measureNum && measures.length > this.measureNum){ var measureMS = measures[this.measureNum].ms @@ -197,7 +199,9 @@ class Debug{ var game = this.controller.game var name = this.branchSelect.value game.branch = name === "auto" ? false : name - game.branchSet = false + game.branchSet = name === "auto" + var selectedOption = this.branchSelect.selectedOptions[0] + this.branchSelect.style.background = selectedOption.style.background if(this.restartCheckbox.checked && !noRestart){ this.restartSong() } diff --git a/public/src/js/game.js b/public/src/js/game.js index 865b905..3f0845b 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -28,8 +28,8 @@ class Game{ this.musicFadeOut = 0 this.fadeOutStarted = false this.currentTimingPoint = 0 - this.sectionNotes = [] - this.sectionDrumroll = 0 + this.branchNames = ["normal", "advanced", "master"] + this.resetSection() assets.songs.forEach(song => { if(song.id == selectedSong.folder){ @@ -77,7 +77,7 @@ class Game{ var index = 0 for(var i = startIndex; i < circles.length; i++){ var circle = circles[i] - if((!circle.branch || circle.branch.active) && !circle.isPlayed){ + if(circle && (!circle.branch || circle.branch.active) && !circle.isPlayed){ var type = circle.type var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll" var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad) @@ -100,6 +100,9 @@ class Game{ if(ms > endTime){ if(!this.controller.autoPlayEnabled){ if(drumrollNotes){ + if(circle.section && circle.timesHit === 0){ + this.resetSection() + } circle.played(-1, false) this.updateCurrentCircle() if(this.controller.multiplayer === 1){ @@ -112,6 +115,9 @@ class Game{ p2.send("drumroll", value) } }else{ + if(circle.section){ + this.resetSection() + } var currentScore = 0 circle.played(-1, type === "daiDon" || type === "daiKa") this.sectionNotes.push(0) @@ -137,47 +143,62 @@ class Game{ } var branches = this.songData.branches if(branches){ - if(this.controller.multiplayer === 2 || this.branch){ - var parent = this.controller.multiplayer === 2 ? p2 : this - var view = this.controller.view - if(view.branch !== parent.branch){ - view.branchAnimate = { - ms: ms, - fromBranch: view.branch + var force = this.controller.multiplayer === 2 ? p2 : this + var measures = this.songData.measures + if(this.controller.multiplayer === 2 || force.branch){ + if(!force.branchSet){ + force.branchSet = true + if(branches.length){ + this.setBranch(branches[0], force.branch) } - view.branch = parent.branch - } - if(!parent.branchSet){ - parent.branchSet = true - for(var i = 0; i < branches.length; i++){ - this.setBranch(branches[i], parent.branch) + var view = this.controller.view + var currentMeasure = view.branch + for(var i = measures.length; i--;){ + var measure = measures[i] + if(measure.nextBranch && measure.ms <= ms){ + currentMeasure = measure.nextBranch.active + } + } + if(view.branch !== currentMeasure){ + view.branchAnimate = { + ms: ms, + fromBranch: view.branch + } + view.branch = currentMeasure } } - }else{ - var measures = this.songData.measures - for(var i = 0; i < measures.length; i++){ - var measure = measures[i] - if(measure.ms > ms){ - break - }else if(measure.nextBranch && !measure.gameChecked){ - measure.gameChecked = true - var branch = measure.nextBranch - if(branch.type){ - if(branch.type === "drumroll"){ + } + for(var i = 0; i < measures.length; i++){ + var measure = measures[i] + if(measure.ms > ms){ + break + }else if(measure.nextBranch && !measure.gameChecked){ + measure.gameChecked = true + var branch = measure.nextBranch + if(branch.type){ + var accuracy = 0 + if(branch.type === "drumroll"){ + if(force.branch){ + var accuracy = Math.max(0, branch.requirement[force.branch]) + }else{ var accuracy = this.sectionDrumroll + } + }else if(this.sectionNotes.length !== 0){ + if(force.branch){ + var accuracy = Math.max(0, Math.min(100, branch.requirement[force.branch])) }else{ var accuracy = this.sectionNotes.reduce((a, b) => a + b) / this.sectionNotes.length * 100 } - if(accuracy >= branch.requirement[1]){ - this.setBranch(branch, "master") - }else if(accuracy >= branch.requirement[0]){ - this.setBranch(branch, "advanced") - }else{ - this.setBranch(branch, "normal") - } - }else if(this.controller.multiplayer === 1){ - p2.send("branch", "normal") } + if(accuracy >= branch.requirement.master){ + this.setBranch(branch, "master") + }else if(accuracy >= branch.requirement.advanced){ + this.setBranch(branch, "advanced") + }else{ + this.setBranch(branch, "normal") + } + }else if(this.controller.multiplayer === 1){ + p2.send("branch", "normal") } } } @@ -273,6 +294,9 @@ class Game{ this.updateCombo(score) this.updateGlobalScore(score, typeDai && keyDai ? 2 : 1, circle.gogoTime) this.updateCurrentCircle() + if(circle.section){ + this.resetSection() + } this.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0)) if(this.controller.multiplayer === 1){ var value = { @@ -327,6 +351,9 @@ class Game{ var ms = this.elapsedTime var dai = circle.type === "daiDrumroll" var score = 100 + if(circle.section && circle.timesHit === 0){ + this.resetSection() + } circle.hit(keysKa) var keyTime = this.controller.getKeyTime() if(circle.type === "drumroll"){ @@ -347,9 +374,7 @@ class Game{ circleAnim.animate(ms) this.view.drumroll.push(circleAnim) this.globalScore.drumroll++ - if(this.controller.multiplayer !== 2){ - this.sectionDrumroll++ - } + this.sectionDrumroll++ this.globalScore.points += score * (dai ? 2 : 1) this.view.setDarkBg(false) } @@ -473,10 +498,6 @@ class Game{ do{ var circle = circles[++this.currentCircle] }while(circle && circle.branch && !circle.branch.active) - if(circle && circle.section){ - this.sectionNotes = [] - this.sectionDrumroll = 0 - } } getCurrentCircle(){ return this.currentCircle @@ -530,16 +551,59 @@ class Game{ } this.globalScore.points += Math.floor(score * multiplier / 10) * 10 } - setBranch(branch, name){ - var names = ["normal", "advanced", "master"] - for(var i in names){ - if(names[i] in branch){ - branch[names[i]].active = names[i] === name + setBranch(currentBranch, activeName){ + var pastActive = currentBranch.active + var ms = currentBranch.ms + for(var i = 0; i < this.songData.branches.length; i++){ + var branch = this.songData.branches[i] + if(branch.ms >= ms){ + var relevantName = activeName + var req = branch.requirement + var noNormal = req.advanced <= 0 + var noAdvanced = req.master <= 0 || req.advanced >= req.master || branch.type === "accuracy" && req.advanced > 100 + var noMaster = branch.type === "accuracy" && req.master > 100 + if(relevantName === "normal" && noNormal){ + relevantName = noAdvanced ? "master" : "advanced" + } + if(relevantName === "advanced" && noAdvanced){ + relevantName = noMaster ? "normal" : "master" + } + if(relevantName === "master" && noMaster){ + relevantName = noAdvanced ? "normal" : "advanced" + } + for(var j in this.branchNames){ + var name = this.branchNames[j] + if(name in branch){ + branch[name].active = name === relevantName + } + } + if(branch === currentBranch){ + activeName = relevantName + } + branch.active = relevantName } } - branch.active = name + var circles = this.songData.circles + var circle = circles[this.currentCircle] + if(!circle || circle.branch === currentBranch[pastActive]){ + var ms = this.elapsedTime + var closestCircle = circles.findIndex(circle => { + return (!circle.branch || circle.branch.active) && circle.endTime >= ms + }) + if(closestCircle !== -1){ + 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", name) + p2.send("branch", activeName) } } + resetSection(){ + this.sectionNotes = [] + this.sectionDrumroll = 0 + } } diff --git a/public/src/js/main.js b/public/src/js/main.js index a63691e..1e632ca 100644 --- a/public/src/js/main.js +++ b/public/src/js/main.js @@ -106,7 +106,9 @@ pageEvents.keyAdd(debugObj, "all", "down", event => { }else if(debugObj.state === "minimised"){ debugObj.debug.restore() }else{ - debugObj.debug = new Debug() + try{ + debugObj.debug = new Debug() + }catch(e){} } } if(event.keyCode === 82 && debugObj.debug && debugObj.controller){ diff --git a/public/src/js/mekadon.js b/public/src/js/mekadon.js index 22f7d73..962b9ff 100644 --- a/public/src/js/mekadon.js +++ b/public/src/js/mekadon.js @@ -8,6 +8,9 @@ class Mekadon{ play(circle){ var type = circle.type if((type === "balloon" || type === "drumroll" || type === "daiDrumroll") && this.getMS() > circle.endTime){ + if(circle.section && circle.timesHit === 0){ + this.game.resetSection() + } circle.played(-1, false) this.game.updateCurrentCircle() } @@ -96,6 +99,9 @@ class Mekadon{ this.game.updateGlobalScore(score, keyDai ? 2 : 1, circle.gogoTime) this.game.updateCurrentCircle() circle.played(score, keyDai) + if(circle.section){ + this.game.resetSection() + } this.game.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0)) } this.lastHit = ms diff --git a/public/src/js/parsetja.js b/public/src/js/parsetja.js index 262c0b9..22e2e9f 100644 --- a/public/src/js/parsetja.js +++ b/public/src/js/parsetja.js @@ -137,7 +137,7 @@ var branchObj = {} var currentBranch = false var branchSettings = {} - var branchPushed = false + var branchFirstMeasure = false var sectionBegin = true var currentMeasure = [] @@ -157,8 +157,10 @@ originalMS: ms, speed: speed, visible: barLine, - branch: currentBranch + branch: currentBranch, + branchFirst: branchFirstMeasure }) + branchFirstMeasure = false if(currentMeasure.length){ for(var i = 0; i < currentMeasure.length; i++){ var note = currentMeasure[i] @@ -222,14 +224,14 @@ gogo = false break case "bpmchange": - bpm = parseFloat(value) + bpm = parseFloat(value) || bpm break case "scroll": - scroll = parseFloat(value) + scroll = parseFloat(value) || scroll break case "measure": var [numerator, denominator] = value.split("/") - measure = numerator / denominator * 4 + measure = numerator / denominator * 4 || measure break case "delay": ms += (parseFloat(value) || 0) * 1000 @@ -243,7 +245,7 @@ case "branchstart": branch = true currentBranch = false - branchPushed = false + branchFirstMeasure = true branchSettings = { ms: ms, gogo: gogo, @@ -255,26 +257,45 @@ if(!this.branches){ this.branches = [] } + var req = { + advanced: parseFloat(value[1]) || 0, + master: parseFloat(value[2]) || 0 + } + if(req.advanced > 0){ + var active = req.master > 0 ? "normal" : "master" + }else{ + var active = req.master > 0 ? "advanced" : "master" + } branchObj = { ms: ms, originalMS: ms, - type: value[0].toLowerCase() === "r" ? "drumroll" : "perfect", - requirement: [ - parseFloat(value[1]), - parseFloat(value[2]) - ] + active: active, + type: value[0].trim().toLowerCase() === "r" ? "drumroll" : "accuracy", + requirement: req + } + this.branches.push(branchObj) + if(this.measures.length === 1 && branchObj.type === "drumroll"){ + for(var i = circles.length; i--;){ + var circle = circles[i] + if(circle.endTime && circle.type === "drumroll" || circle.type === "daiDrumroll" || circle.type === "balloon"){ + this.measures.push({ + ms: circle.endTime, + originalMS: circle.endTime, + speed: circle.bpm * circle.scroll / 60, + visible: false, + branch: circle.branch + }) + break + } + } + } + if(this.measures.length !== 0){ + this.measures[this.measures.length - 1].nextBranch = branchObj } break case "branchend": branch = false currentBranch = false - if(this.measures.length !== 0){ - this.measures[this.measures.length - 1].nextBranch = { - ms: ms, - originalMS: ms, - active: "normal" - } - } break case "section": sectionBegin = true @@ -283,37 +304,19 @@ } break case "n": case "e": case "m": - if(!branchPushed){ - branchPushed = true - this.branches.push(branchObj) - if(this.measures.length === 1 && branchObj.type === "drumroll"){ - for(var i = circles.length; i--;){ - var circle = circles[i] - if(circle.endTime && circle.type === "drumroll" || circle.type === "daiDrumroll" || circle.type === "balloon"){ - this.measures.push({ - ms: circle.endTime, - originalMS: circle.endTime, - speed: circle.bpm * circle.scroll / 60, - visible: false, - branch: circle.branch - }) - break - } - } - } - if(this.measures.length !== 0){ - this.measures[this.measures.length - 1].nextBranch = branchObj - } + if(!branch){ + break } ms = branchSettings.ms gogo = branchSettings.gogo bpm = branchSettings.bpm scroll = branchSettings.scroll sectionBegin = branchSettings.sectionBegin + branchFirstMeasure = true var branchName = name === "m" ? "master" : (name === "e" ? "advanced" : "normal") currentBranch = { name: branchName, - active: branchName === "normal" + active: branchName === branchObj.active } branchObj[branchName] = currentBranch break @@ -432,6 +435,7 @@ if(this.branches){ circles.sort((a, b) => a.ms > b.ms ? 1 : -1) + this.measures.sort((a, b) => a.ms > b.ms ? 1 : -1) circles.forEach((circle, i) => circle.id = i + 1) } return circles diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 7c6b029..7d13389 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -54,6 +54,11 @@ this.good = "良" this.ok = "可" this.bad = "不可" + this.branch = { + "normal": "普通譜面", + "advanced": "玄人譜面", + "master": "達人譜面" + } this.pauseOptions = [ "演奏をつづける", "はじめからやりなおす", @@ -136,7 +141,7 @@ function StringsEn(){ this.normal = "Normal" this.hard = "Hard" this.oni = "Extreme" - this.songBranch = "Forked Paths" + this.songBranch = "Diverge Notes" this.sessionStart = "Begin an Online Session!" this.sessionEnd = "End Online Session" this.loading = "Loading..." @@ -156,6 +161,11 @@ function StringsEn(){ this.good = "GOOD" this.ok = "OK" this.bad = "BAD" + this.branch = { + "normal": "Normal", + "advanced": "Professional", + "master": "Master" + } this.pauseOptions = [ "Continue", "Retry", @@ -238,7 +248,7 @@ function StringsCn(){ this.normal = "普通" this.hard = "困难" this.oni = "魔王" - this.songBranch = "有分数分支" + this.songBranch = "有谱面分歧" this.sessionStart = "开始在线会话!" this.sessionEnd = "结束在线会话" this.loading = "加载中..." @@ -258,6 +268,11 @@ function StringsCn(){ this.good = "良" this.ok = "可" this.bad = "不可" + this.branch = { + "normal": "一般谱面", + "advanced": "进阶谱面", + "master": "达人谱面" + } this.pauseOptions = [ "继续演奏", "从头开始", @@ -340,7 +355,7 @@ function StringsTw(){ this.normal = "普通" this.hard = "困難" this.oni = "魔王" - this.songBranch = "有分數分支" + this.songBranch = "有譜面分歧" this.sessionStart = "開始多人模式!" this.sessionEnd = "結束多人模式" this.loading = "讀取中..." @@ -360,6 +375,11 @@ function StringsTw(){ this.good = "良" this.ok = "可" this.bad = "不可" + this.branch = { + "normal": "一般譜面", + "advanced": "進階譜面", + "master": "達人譜面" + } this.pauseOptions = [ "繼續演奏", "從頭開始", @@ -462,6 +482,11 @@ function StringsKo(){ this.good = "얼쑤" this.ok = "좋다" this.bad = "에구" + this.branch = { + "normal": "보통 악보", + "advanced": "현인 악보", + "master": "달인 악보" + } this.pauseOptions = [ "연주 계속하기", "처음부터 다시", diff --git a/public/src/js/view.js b/public/src/js/view.js index 43975cb..a50bf13 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -82,9 +82,24 @@ fromBranch: "normal" } this.branchMap = { - "normal": "rgba(0, 0, 0, 0)", - "advanced": "rgba(29, 129, 189, 0.4)", - "master": "rgba(230, 29, 189, 0.4)" + "normal": { + "bg": "rgba(0, 0, 0, 0)", + "text": "#d3d3d3", + "stroke": "#393939", + "shadow": "#000" + }, + "advanced": { + "bg": "rgba(29, 129, 189, 0.4)", + "text": "#94d7e7", + "stroke": "#315973", + "shadow": "#082031" + }, + "master": { + "bg": "rgba(230, 29, 189, 0.4)", + "text": "#f796ef", + "stroke": "#7e2e6e", + "shadow": "#3e0836" + } } } @@ -97,6 +112,7 @@ this.titleCache = new CanvasCache() this.comboCache = new CanvasCache() this.pauseCache = new CanvasCache() + this.branchCache = new CanvasCache() this.multiplayer = this.controller.multiplayer @@ -144,7 +160,8 @@ } this.setDonBg() - this.lastMousemove = this.controller.game.getAccurateTime() + this.startTime = this.controller.game.getAccurateTime() + this.lastMousemove = this.startTime pageEvents.mouseAdd(this, this.onmousemove.bind(this)) this.refresh() @@ -174,6 +191,7 @@ } var ratio = (ratioX < ratioY ? ratioX : ratioY) + var resized = false if(this.winW !== winW || this.winH !== winH){ this.winW = winW this.winH = winH @@ -193,6 +211,7 @@ } this.fillComboCache() this.setDonBgHeight() + resized = true }else if(this.controller.game.paused && !document.hasFocus()){ return }else if(this.multiplayer !== 2){ @@ -681,7 +700,7 @@ } ctx.restore() - // Bar pressed keys + // Branch background var keyTime = this.controller.getKeyTime() var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka" var padding = this.slotPos.paddingLeft @@ -689,12 +708,78 @@ var barY = this.slotPos.y - 65 * mul var barH = 130 * mul + if(this.branchAnimate && ms <= this.branchAnimate.ms + 300){ + var alpha = Math.max(0, (ms - this.branchAnimate.ms) / 300) + ctx.globalAlpha = 1 - alpha + ctx.fillStyle = this.branchMap[this.branchAnimate.fromBranch].bg + ctx.fillRect(padding, barY, winW - padding, barH) + ctx.globalAlpha = alpha + } + if(this.branch){ + ctx.fillStyle = this.branchMap[this.branch].bg + ctx.fillRect(padding, barY, winW - padding, barH) + ctx.globalAlpha = 1 + } + + // Current branch text + if(this.branch){ + if(resized){ + this.fillBranchCache() + } + var textW = Math.floor(260 * mul) + var textH = Math.floor(barH) + var textX = winW - textW + var oldOffset = 0 + var newOffset = 0 + var elapsed = ms - this.startTime + if(elapsed < 250){ + textX = winW + }else if(elapsed < 500){ + textX += (1 - this.draw.easeOutBack((elapsed - 250) / 250)) * textW + } + if(this.branchAnimate && ms - this.branchAnimate.ms < 310 && ms >= this.branchAnimate.ms){ + var fromBranch = this.branchAnimate.fromBranch + var elapsed = ms - this.branchAnimate.ms + var reverse = fromBranch === "master" || fromBranch === "advanced" && this.branch === "normal" ? -1 : 1 + if(elapsed < 65){ + oldOffset = elapsed / 65 * 12 * mul * reverse + ctx.globalAlpha = 1 + var newAlpha = 0 + }else if(elapsed < 215){ + var animPoint = (elapsed - 65) / 150 + oldOffset = (12 - animPoint * 48) * mul * reverse + newOffset = (36 - animPoint * 48) * mul * reverse + ctx.globalAlpha = this.draw.easeIn(1 - animPoint) + var newAlpha = this.draw.easeIn(animPoint) + }else{ + newOffset = (1 - (elapsed - 215) / 95) * -12 * mul * reverse + ctx.globalAlpha = 0 + var newAlpha = 1 + } + this.branchCache.get({ + ctx: ctx, + x: textX, y: barY + oldOffset, + w: textW, h: textH, + id: fromBranch + }) + ctx.globalAlpha = newAlpha + } + this.branchCache.get({ + ctx: ctx, + x: textX, y: barY + newOffset, + w: textW, h: textH, + id: this.branch + }) + ctx.globalAlpha = 1 + } + + // Go go time background if(this.gogoTime || ms <= this.gogoTimeStarted + 100){ var grd = ctx.createLinearGradient(padding, 0, winW, 0) - grd.addColorStop(0, "#512a2c") - grd.addColorStop(0.46, "#6f2a2d") - grd.addColorStop(0.76, "#8a4763") - grd.addColorStop(1, "#2c2a2c") + grd.addColorStop(0, "rgba(255, 0, 0, 0.16)") + grd.addColorStop(0.45, "rgba(255, 0, 0, 0.28)") + grd.addColorStop(0.77, "rgba(255, 83, 157, 0.4)") + grd.addColorStop(1, "rgba(255, 83, 157, 0)") ctx.fillStyle = grd if(!this.touchEnabled){ var alpha = Math.min(100, ms - this.gogoTimeStarted) / 100 @@ -705,18 +790,8 @@ } ctx.fillRect(padding, barY, winW - padding, barH) } - if(this.branchAnimate && ms <= this.branchAnimate.ms + 300){ - var alpha = Math.max(0, (ms - this.branchAnimate.ms) / 300) - ctx.globalAlpha = 1 - alpha - ctx.fillStyle = this.branchMap[this.branchAnimate.fromBranch] - ctx.fillRect(padding, barY, winW - padding, barH) - ctx.globalAlpha = alpha - } - if(this.branch){ - ctx.fillStyle = this.branchMap[this.branch] - ctx.fillRect(padding, barY, winW - padding, barH) - ctx.globalAlpha = 1 - } + + // Bar pressed keys if(keyTime[sound] > ms - 130){ var gradients = { "don": "255, 0, 0", @@ -814,6 +889,7 @@ ctx.lineWidth = 7 * mul ctx.textAlign = "center" ctx.miterLimit = 1 + ctx.strokeStyle = "#000" ctx.strokeText(strings.combo, comboX, comboTextY) ctx.miterLimit = 10 ctx.fillText(strings.combo, comboX, comboTextY) @@ -1128,7 +1204,7 @@ var finishTime = measure.ms + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + 3, measure.speed) if(measure.visible && (!measure.branch || measure.branch.active) && ms >= startingTime && ms <= finishTime){ var measureX = this.slotPos.x + this.msToPos(measure.ms - ms, measure.speed) - this.ctx.strokeStyle = "#bdbdbd" + this.ctx.strokeStyle = measure.branchFirst ? "#ff0" : "#bdbdbd" this.ctx.lineWidth = 3 this.ctx.beginPath() this.ctx.moveTo(measureX, measureY) @@ -1137,9 +1213,9 @@ } if(this.multiplayer !== 2 && ms >= measure.ms && measure.nextBranch && !measure.viewChecked && measure.gameChecked){ measure.viewChecked = true - this.branchAnimate = { - ms: ms, - fromBranch: this.branch + if(measure.nextBranch.active !== this.branch){ + this.branchAnimate.ms = ms + this.branchAnimate.fromBranch = this.branch } this.branch = measure.nextBranch.active } @@ -1468,6 +1544,37 @@ }) this.globalAlpha = 1 } + fillBranchCache(){ + var mul = this.slotPos.size / 106 + var textW = Math.floor(260 * mul) + var barH = Math.floor(130 * mul) + var branchNames = this.controller.game.branchNames + var textX = textW - 33 * mul + var textY = 63 * mul + var fontSize = (strings.id === "en" ? 33 : (strings.id === "ko" ? 38 : 43)) * mul + this.branchCache.resize((textW + 1), (barH + 1) * 3, this.ratio) + for(var i in branchNames){ + this.branchCache.set({ + w: textW, + h: barH, + id: branchNames[i] + }, ctx => { + var currentMap = this.branchMap[branchNames[i]] + ctx.font = this.draw.bold(this.font) + fontSize + "px " + this.font + ctx.lineJoin = "round" + ctx.miterLimit = 1 + ctx.textAlign = "right" + ctx.textBaseline = "middle" + ctx.lineWidth = 8 * mul + ctx.strokeStyle = currentMap.shadow + ctx.strokeText(strings.branch[branchNames[i]], textX, textY + 4 * mul) + ctx.strokeStyle = currentMap.stroke + ctx.strokeText(strings.branch[branchNames[i]], textX, textY) + ctx.fillStyle = currentMap.text + ctx.fillText(strings.branch[branchNames[i]], textX, textY) + }) + } + } toggleGogoTime(circle){ this.gogoTime = circle.gogoTime this.gogoTimeStarted = circle.ms diff --git a/public/src/views/debug.html b/public/src/views/debug.html index b681717..dd37b44 100644 --- a/public/src/views/debug.html +++ b/public/src/views/debug.html @@ -13,10 +13,10 @@
Branch:
x
From a09709843d35851a6fc4a1b45d89d9b2ea13701f Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Wed, 20 Feb 2019 23:51:45 +0300 Subject: [PATCH 07/31] Fix ura symbol --- public/src/js/canvasdraw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index 4431774..7733c3d 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -800,7 +800,7 @@ }else if(action === "fillText"){ ctx.strokeStyle = layer.fill ctx.lineWidth = 2.5 * mul - ctx.fillText(symbol.text, currentX, currentY) + ctx.fillText(symbol.text, currentX, currentY + (17 * mul)) } ctx.stroke() }else{ From 92510c302101798019e61c0e7ff4243024c49938 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Thu, 21 Feb 2019 00:06:16 +0300 Subject: [PATCH 08/31] Do not add canvascache to body --- public/src/js/canvascache.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/src/js/canvascache.js b/public/src/js/canvascache.js index 1b4c121..56a1f7d 100644 --- a/public/src/js/canvascache.js +++ b/public/src/js/canvascache.js @@ -11,7 +11,6 @@ class CanvasCache{ this.map = new Map() this.canvas = document.createElement("canvas") this.ctx = this.canvas.getContext("2d") - document.body.appendChild(this.canvas) } this.scale = scale this.x = 0 From 3a28d967b9e800fd99d8944e7214ef45ceabba62 Mon Sep 17 00:00:00 2001 From: Bui Date: Wed, 20 Feb 2019 22:02:44 +0000 Subject: [PATCH 09/31] move song previews to the database --- app.py | 82 ++++------------------------------------------------------ 1 file changed, 5 insertions(+), 77 deletions(-) diff --git a/app.py b/app.py index 9d6ae49..6b0abb9 100644 --- a/app.py +++ b/app.py @@ -40,10 +40,10 @@ def get_config(): try: config = json.load(open('config.json', 'r')) except ValueError: - print 'WARNING: Invalid config.json, using default values' + print('WARNING: Invalid config.json, using default values') config = {} else: - print 'WARNING: No config.json found, using default values' + print('WARNING: No config.json found, using default values') config = {} if not config.get('songs_baseurl'): @@ -55,77 +55,6 @@ def get_config(): return config -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 - - def get_version(): version = {'commit': None, 'commit_short': '', 'version': None, 'url': DEFAULT_URL} if os.path.isfile('version.json'): @@ -168,7 +97,7 @@ def route_api_preview(): abort(400) song_type = song_row[0][12] - prev_path = make_preview(song_id, song_type) + prev_path = make_preview(song_id, song_type, song_row[0][15]) if not prev_path: return redirect(get_config()['songs_baseurl'] + '%s/main.mp3' % song_id) @@ -195,7 +124,7 @@ def route_api_songs(): for song in songs: song_id = song[0] song_type = song[12] - preview = get_preview(song_id, song_type) + preview = song[15] category_out = categories[song[11]] if song[11] in categories else def_category song_skin_out = song_skins[song[14]] if song[14] in song_skins else None @@ -226,12 +155,11 @@ def route_api_config(): return jsonify(config) -def make_preview(song_id, song_type): +def make_preview(song_id, song_type, preview): song_path = 'public/songs/%s/main.mp3' % song_id prev_path = 'public/songs/%s/preview.mp3' % song_id if os.path.isfile(song_path) and not os.path.isfile(prev_path): - preview = get_preview(song_id, song_type) / 1000 if not preview or preview <= 0.1: print('Skipping #%s due to no preview' % song_id) return False From 1aecc7d52f61f3ead2d5e0b3ce1e16b01ba7c6ad Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Thu, 21 Feb 2019 01:42:18 +0300 Subject: [PATCH 10/31] SongSelect: Use seconds for previews --- app.py | 2 +- public/src/js/importsongs.js | 4 ++-- public/src/js/songselect.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app.py b/app.py index 6b0abb9..9714b69 100644 --- a/app.py +++ b/app.py @@ -160,7 +160,7 @@ def make_preview(song_id, song_type, preview): prev_path = 'public/songs/%s/preview.mp3' % song_id if os.path.isfile(song_path) and not os.path.isfile(prev_path): - if not preview or preview <= 0.1: + if not preview or preview <= 0: print('Skipping #%s due to no preview' % song_id) return False diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index cb907f5..374908f 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -197,7 +197,7 @@ subtitle = subtitle.slice(2) } songObj.subtitle = songObj.subtitle_en = subtitle - songObj.preview = meta.demostart ? Math.floor(meta.demostart * 1000) : 0 + songObj.preview = meta.demostart || 0 if(meta.level){ songObj.stars[this.courseTypes[diff]] = meta.level + (meta.branch ? " B" : "") } @@ -238,7 +238,7 @@ chart: data, subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist, subtitle_en: osu.metadata.Artist || osu.metadata.ArtistUnicode, - preview: osu.generalInfo.PreviewTime, + preview: osu.generalInfo.PreviewTime / 1000, stars: [null, null, null, parseInt(osu.difficulty.overallDifficulty) || 1], music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] } diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index c6271f4..1f9bedf 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -1746,7 +1746,7 @@ class SongSelect{ }else{ songObj = {id: id} - var previewFilename = prvTime > 0.1 ? "/preview.mp3" : "/main.mp3" + var previewFilename = prvTime > 0 ? "/preview.mp3" : "/main.mp3" var loadPreview = previewFilename => { return snd.previewGain.load(gameConfig.songs_baseurl + id + previewFilename) @@ -1786,7 +1786,7 @@ class SongSelect{ var difference = endLoad - startLoad var minDelay = 300 var delay = minDelay - Math.min(minDelay, difference) - this.preview.playLoop(delay / 1000, false, prvTime / 1000) + this.preview.playLoop(delay / 1000, false, prvTime) } endPreview(){ this.previewId++ From 29a4d154b363e153ff836d625e380f807059d762 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sun, 24 Feb 2019 15:04:14 +0300 Subject: [PATCH 11/31] Game: Fix notelocking - Will not skip the note if `ka` was pressed right before `don` note or `don` was pressed right before `ka` note - Will still skip the note if `don` and `ka` is pressed at the same time (within 25ms) - Fixed `TAIKOWEBSKIN:` in imported songs crashing the game with some values --- public/src/js/controller.js | 2 +- public/src/js/game.js | 107 ++++++++++++++++++++++++++---------- public/src/js/mekadon.js | 8 +-- public/src/js/parseosu.js | 3 +- public/src/js/view.js | 11 ++-- 5 files changed, 93 insertions(+), 38 deletions(-) diff --git a/public/src/js/controller.js b/public/src/js/controller.js index d161d83..1c33b39 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -220,7 +220,7 @@ class Controller{ if(this.multiplayer){ p2.play(circle, this.mekadon) }else{ - this.mekadon.play(circle) + return this.mekadon.play(circle) } } clean(){ diff --git a/public/src/js/game.js b/public/src/js/game.js index 3f0845b..f612ed2 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -75,6 +75,23 @@ class Game{ var circles = this.songData.circles var startIndex = this.currentCircle === 0 ? 0 : this.currentCircle - 1 var index = 0 + + var skipNote = circle => { + if(circle.section){ + this.resetSection() + } + circle.played(-1, circle.type === "daiDon" || circle.type === "daiKa") + this.sectionNotes.push(0) + this.controller.displayScore(0, true) + this.updateCombo(0) + this.updateGlobalScore(0, 1) + if(this.controller.multiplayer === 1){ + p2.send("note", { + score: -1 + }) + } + } + for(var i = startIndex; i < circles.length; i++){ var circle = circles[i] if(circle && (!circle.branch || circle.branch.active) && !circle.isPlayed){ @@ -115,21 +132,8 @@ class Game{ p2.send("drumroll", value) } }else{ - if(circle.section){ - this.resetSection() - } - var currentScore = 0 - circle.played(-1, type === "daiDon" || type === "daiKa") - this.sectionNotes.push(0) - this.controller.displayScore(currentScore, true) + skipNote(circle) this.updateCurrentCircle() - this.updateCombo(currentScore) - this.updateGlobalScore(currentScore, 1) - if(this.controller.multiplayer === 1){ - p2.send("note", { - score: -1 - }) - } } } }else if(!this.controller.autoPlayEnabled && !nextSet){ @@ -141,6 +145,33 @@ class Game{ } } } + + var circleIsNote = circle => { + var type = circle.type + return type === "don" || type === "ka" || type === "daiDon" || type === "daiKa" + } + var currentCircle = circles[this.currentCircle] + if(!this.controller.autoPlayEnabled && currentCircle && ms - currentCircle.ms >= this.rules.ok && circleIsNote(currentCircle)){ + for(var i = this.currentCircle + 1; i < circles.length; i++){ + var circle = circles[i] + var relative = ms - circle.ms + if(!circle.branch || circle.branch.active){ + if(!circleIsNote(circle) || relative < -this.rules.bad){ + break + }else if(Math.abs(relative) < this.rules.ok){ + for(var j = this.currentCircle; j < i; j++){ + var circle = circles[j] + if(circle && (!circle.branch || circle.branch.active)){ + skipNote(circles[j]) + } + } + this.currentCircle = i + break + } + } + } + } + var branches = this.songData.branches if(branches){ var force = this.controller.multiplayer === 2 ? p2 : this @@ -208,8 +239,11 @@ class Game{ var circles = this.songData.circles var circle = circles[this.currentCircle] - if(circle && this.controller.autoPlayEnabled){ - return this.controller.autoPlay(circle) + if(this.controller.autoPlayEnabled){ + while(circle && this.controller.autoPlay(circle)){ + circle = circles[this.currentCircle] + } + return } var keys = this.controller.getKeys() var kbd = this.controller.getBindings() @@ -219,19 +253,31 @@ class Game{ var ka_l = keys[kbd["ka_l"]] && !this.controller.isWaiting(kbd["ka_l"], "score") var ka_r = keys[kbd["ka_r"]] && !this.controller.isWaiting(kbd["ka_r"], "score") - if(don_l && don_r){ - this.checkKey([kbd["don_l"], kbd["don_r"]], circle, "daiDon") - }else if(don_l){ - this.checkKey([kbd["don_l"]], circle, "don") - }else if(don_r){ - this.checkKey([kbd["don_r"]], circle, "don") + var checkDon = () => { + if(don_l && don_r){ + this.checkKey([kbd["don_l"], kbd["don_r"]], circle, "daiDon") + }else if(don_l){ + this.checkKey([kbd["don_l"]], circle, "don") + }else if(don_r){ + this.checkKey([kbd["don_r"]], circle, "don") + } } - if(ka_l && ka_r){ - this.checkKey([kbd["ka_l"], kbd["ka_r"]], circle, "daiKa") - }else if(ka_l){ - this.checkKey([kbd["ka_l"]], circle, "ka") - }else if(ka_r){ - this.checkKey([kbd["ka_r"]], circle, "ka") + var checkKa = () => { + if(ka_l && ka_r){ + this.checkKey([kbd["ka_l"], kbd["ka_r"]], circle, "daiKa") + }else if(ka_l){ + this.checkKey([kbd["ka_l"]], circle, "ka") + }else if(ka_r){ + this.checkKey([kbd["ka_r"]], circle, "ka") + } + } + var keyTime = this.controller.getKeyTime() + if(keyTime["don"] >= keyTime["ka"]){ + checkDon() + checkKa() + }else{ + checkKa() + checkDon() } } checkKey(keyCodes, circle, check){ @@ -288,6 +334,11 @@ class Game{ circle.played(score, score === 0 ? typeDai : keyDai) this.controller.displayScore(score, false, typeDai && keyDai) }else{ + var keyTime = this.controller.getKeyTime() + var keyTimeRelative = Math.abs(keyTime.don - keyTime.ka) + if(Math.abs(relative) >= (keyTimeRelative <= 25 ? this.rules.bad : this.rules.good)){ + return true + } circle.played(-1, typeDai) this.controller.displayScore(score, true, false) } diff --git a/public/src/js/mekadon.js b/public/src/js/mekadon.js index 962b9ff..fe63d71 100644 --- a/public/src/js/mekadon.js +++ b/public/src/js/mekadon.js @@ -16,11 +16,11 @@ class Mekadon{ } type = circle.type if(type === "balloon"){ - this.playDrumrollAt(circle, 0, 30) + return this.playDrumrollAt(circle, 0, 30) }else if(type === "drumroll" || type === "daiDrumroll"){ - this.playDrumrollAt(circle, 0, 60) + return this.playDrumrollAt(circle, 0, 60) }else{ - this.playAt(circle, 0, 450) + return this.playAt(circle, 0, 450) } } playAt(circle, ms, score, dai, reverse){ @@ -35,7 +35,7 @@ class Mekadon{ if(kaAmount > 0){ score = Math.random() > kaAmount ? 1 : 2 } - this.playAt(circle, ms, score) + return this.playAt(circle, ms, score) } } miss(circle){ diff --git a/public/src/js/parseosu.js b/public/src/js/parseosu.js index 336bc70..b8d94e2 100644 --- a/public/src/js/parseosu.js +++ b/public/src/js/parseosu.js @@ -181,7 +181,8 @@ class ParseOsu{ measures.push({ ms: ms, originalMS: ms, - speed: speed + speed: speed, + visible: true }) } } diff --git a/public/src/js/view.js b/public/src/js/view.js index a50bf13..211b0bd 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -1100,7 +1100,7 @@ var songSkinName = selectedSong.songSkin.name var supportsBlend = "mixBlendMode" in this.songBg.style var songLayers = [document.getElementById("layer1"), document.getElementById("layer2")] - var prefix = selectedSong.songSkin.prefix || "" + var prefix = "" if(selectedSong.category in this.categories){ var catId = this.categories[selectedSong.category].sort @@ -1111,8 +1111,9 @@ if(!selectedSong.songSkin.song){ var id = selectedSong.songBg this.songBg.classList.add("songbg-" + id) - this.setLayers(songLayers, prefix + "bg_song_" + id + (supportsBlend ? "" : "a"), supportsBlend) + this.setLayers(songLayers, "bg_song_" + id + (supportsBlend ? "" : "a"), supportsBlend) }else if(selectedSong.songSkin.song !== "none"){ + var prefix = selectedSong.songSkin.prefix || "" var notStatic = selectedSong.songSkin.song !== "static" if(notStatic){ this.songBg.classList.add("songbg-" + selectedSong.songSkin.song) @@ -1123,6 +1124,7 @@ if(!selectedSong.songSkin.stage){ this.songStage.classList.add("song-stage-" + selectedSong.songStage) }else if(selectedSong.songSkin.stage !== "none"){ + var prefix = selectedSong.songSkin.prefix || "" this.setBgImage(this.songStage, assets.image[prefix + "bg_stage_" + songSkinName].src) } } @@ -1131,7 +1133,7 @@ var songSkinName = selectedSong.songSkin.name var donLayers = [] var filename = !selectedSong.songSkin.don && this.multiplayer === 2 ? "bg_don2_" : "bg_don_" - var prefix = selectedSong.songSkin.prefix || "" + var prefix = "" this.donBg = document.createElement("div") this.donBg.classList.add("donbg") @@ -1150,10 +1152,11 @@ var asset1, asset2 if(!selectedSong.songSkin.don){ this.donBg.classList.add("donbg-" + selectedSong.donBg) - this.setLayers(donLayers, prefix + filename + selectedSong.donBg, true) + this.setLayers(donLayers, filename + selectedSong.donBg, true) asset1 = filename + selectedSong.donBg + "a" asset2 = filename + selectedSong.donBg + "b" }else if(selectedSong.songSkin.don !== "none"){ + var prefix = selectedSong.songSkin.prefix || "" var notStatic = selectedSong.songSkin.don !== "static" if(notStatic){ this.donBg.classList.add("donbg-" + selectedSong.songSkin.don) From fbbed1a71413980e2effc7e857050bacb1e7bdb7 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sun, 24 Feb 2019 18:13:41 +0300 Subject: [PATCH 12/31] Fix note streams on key press --- public/src/js/game.js | 97 ++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 43 deletions(-) diff --git a/public/src/js/game.js b/public/src/js/game.js index f612ed2..558b095 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -76,22 +76,6 @@ class Game{ var startIndex = this.currentCircle === 0 ? 0 : this.currentCircle - 1 var index = 0 - var skipNote = circle => { - if(circle.section){ - this.resetSection() - } - circle.played(-1, circle.type === "daiDon" || circle.type === "daiKa") - this.sectionNotes.push(0) - this.controller.displayScore(0, true) - this.updateCombo(0) - this.updateGlobalScore(0, 1) - if(this.controller.multiplayer === 1){ - p2.send("note", { - score: -1 - }) - } - } - for(var i = startIndex; i < circles.length; i++){ var circle = circles[i] if(circle && (!circle.branch || circle.branch.active) && !circle.isPlayed){ @@ -132,7 +116,7 @@ class Game{ p2.send("drumroll", value) } }else{ - skipNote(circle) + this.skipNote(circle) this.updateCurrentCircle() } } @@ -146,32 +130,6 @@ class Game{ } } - var circleIsNote = circle => { - var type = circle.type - return type === "don" || type === "ka" || type === "daiDon" || type === "daiKa" - } - var currentCircle = circles[this.currentCircle] - if(!this.controller.autoPlayEnabled && currentCircle && ms - currentCircle.ms >= this.rules.ok && circleIsNote(currentCircle)){ - for(var i = this.currentCircle + 1; i < circles.length; i++){ - var circle = circles[i] - var relative = ms - circle.ms - if(!circle.branch || circle.branch.active){ - if(!circleIsNote(circle) || relative < -this.rules.bad){ - break - }else if(Math.abs(relative) < this.rules.ok){ - for(var j = this.currentCircle; j < i; j++){ - var circle = circles[j] - if(circle && (!circle.branch || circle.branch.active)){ - skipNote(circles[j]) - } - } - this.currentCircle = i - break - } - } - } - } - var branches = this.songData.branches if(branches){ var force = this.controller.multiplayer === 2 ? p2 : this @@ -235,6 +193,52 @@ class Game{ } } } + fixNoteStream(keysDon){ + var circleIsNote = circle => { + var type = circle.type + return type === "don" || type === "ka" || type === "daiDon" || type === "daiKa" + } + var correctNote = circle => { + var type = circle.type + return keysDon ? (type === "don" || type === "daiDon") : (type === "ka" || type === "daiKa") + } + var ms = this.elapsedTime + var circles = this.songData.circles + + for(var i = this.currentCircle + 1; i < circles.length; i++){ + var circle = circles[i] + var relative = ms - circle.ms + if(!circle.branch || circle.branch.active){ + if((!circleIsNote(circle) || relative < -this.rules.bad)){ + break + }else if(Math.abs(relative) < this.rules.ok && correctNote(circle)){ + for(var j = this.currentCircle; j < i; j++){ + var circle = circles[j] + if(circle && !circle.branch || circle.branch.active){ + this.skipNote(circles[j]) + } + } + this.currentCircle = i + return circles[i] + } + } + } + } + skipNote(circle){ + if(circle.section){ + this.resetSection() + } + circle.played(-1, circle.type === "daiDon" || circle.type === "daiKa") + this.sectionNotes.push(0) + this.controller.displayScore(0, true) + this.updateCombo(0) + this.updateGlobalScore(0, 1) + if(this.controller.multiplayer === 1){ + p2.send("note", { + score: -1 + }) + } + } checkPlays(){ var circles = this.songData.circles var circle = circles[this.currentCircle] @@ -305,6 +309,13 @@ class Game{ var currentTime = keysDon ? keyTime["don"] : keyTime["ka"] var relative = currentTime - circle.ms + if(relative >= this.rules.ok){ + var fixedNote = this.fixNoteStream(keysDon) + if(fixedNote){ + return this.checkScore(fixedNote, check) + } + } + if(typeDon || typeKa){ if(-this.rules.bad >= relative || relative >= this.rules.bad){ return true From 7ee8773e61b8dd2d6f50e4283d048a9506b003f5 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Wed, 6 Mar 2019 00:48:30 +0300 Subject: [PATCH 13/31] View: Fix donbg scrolling - All metadata fields in imported TJA files are now optional - Added new metadata fields: `TITLEEN` and `SUBTITLEEN` (`EN` can be any language id from strings.js) --- public/src/css/game.css | 3 ++ public/src/js/canvasdraw.js | 8 +++--- public/src/js/controller.js | 4 ++- public/src/js/game.js | 9 ++++-- public/src/js/importsongs.js | 53 +++++++++++++++++++++++++----------- public/src/js/loadsong.js | 14 ++++++---- public/src/js/parsetja.js | 15 ++++------ public/src/js/songselect.js | 8 +++--- public/src/js/view.js | 4 +++ 9 files changed, 75 insertions(+), 43 deletions(-) diff --git a/public/src/css/game.css b/public/src/css/game.css index 2944e82..f8b7214 100644 --- a/public/src/css/game.css +++ b/public/src/css/game.css @@ -94,3 +94,6 @@ z-index: 2; transition: 1s background-color linear; } +.fix-animations *{ + animation: none !important; +} diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index 7733c3d..97d3afd 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -48,9 +48,9 @@ this.regex = { comma: /[,.]/, ideographicComma: /[、。]/, - apostrophe: /['']/, + apostrophe: /[''’]/, degree: /[゚°]/, - brackets: /[\((\))\[\]「」『』【】]/, + brackets: /[\((\))\[\]「」『』【】::;;]/, tilde: /[\--~~〜_]/, tall: /[bbddffgghhj-lj-ltt♪]/, i: /[ii]/, @@ -625,8 +625,6 @@ drawn.push({text: symbol, x: -2, y: 0, w: 20, scale: [0.6, 0.5]}) }else if(symbol === " "){ drawn.push({text: symbol, x: 0, y: 0, w: 10}) - }else if(symbol === "'"){ - drawn.push({text: ",", x: 0, y: -15, w: 7, scale: [1, 0.7]}) }else if(symbol === '"'){ drawn.push({text: symbol, x: 2, y: 0, w: 10}) }else if(symbol === "∀"){ @@ -637,6 +635,8 @@ } }else if(symbol === "."){ drawn.push({text: symbol, x: -9, y: 0, w: 37}) + }else if(r.apostrophe.test(symbol)){ + drawn.push({text: ",", x: 0, y: -15, w: 7, scale: [1, 0.7]}) }else if(r.comma.test(symbol)){ // Comma, full stop if(bold){ diff --git a/public/src/js/controller.js b/public/src/js/controller.js index 1c33b39..d0bc63b 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -68,7 +68,9 @@ class Controller{ } stopMainLoop(){ this.mainLoopRunning = false - this.mainAsset.stop() + if(this.mainAsset){ + this.mainAsset.stop() + } if(this.multiplayer !== 2){ clearInterval(this.gameInterval) } diff --git a/public/src/js/game.js b/public/src/js/game.js index 558b095..ceabf3f 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -465,7 +465,8 @@ class Game{ var started = this.fadeOutStarted if(started){ var ms = this.elapsedTime - var musicDuration = this.controller.mainAsset.duration * 1000 - this.controller.offset + var duration = this.mainAsset ? this.mainAsset.duration : 0 + var musicDuration = duration * 1000 - this.controller.offset if(this.musicFadeOut === 0){ if(this.controller.multiplayer === 1){ p2.send("gameresults", this.getGlobalScore()) @@ -491,7 +492,7 @@ class Game{ playMainMusic(){ var ms = this.elapsedTime + this.controller.offset if(!this.mainMusicPlaying && (!this.fadeOutStarted || ms < this.fadeOutStarted + 1600)){ - if(this.controller.multiplayer !== 2){ + if(this.controller.multiplayer !== 2 && this.mainAsset){ this.mainAsset.play((ms < 0 ? -ms : 0) / 1000, false, Math.max(0, ms / 1000)) } this.mainMusicPlaying = true @@ -502,7 +503,9 @@ class Game{ assets.sounds["se_pause"].play() this.paused = true this.latestDate = Date.now() - this.mainAsset.stop() + if(this.mainAsset){ + this.mainAsset.stop() + } this.mainMusicPlaying = false this.view.pauseMove(0, true) this.view.gameDiv.classList.add("game-paused") diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index 374908f..5040c92 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -184,25 +184,26 @@ id: index + 1, type: "tja", chart: data, - stars: [] + stars: [], + music: "muted" } + var titleLang = {} + var subtitleLang = {} var dir = file.webkitRelativePath.toLowerCase() dir = dir.slice(0, dir.lastIndexOf("/") + 1) var hasCategory = false for(var diff in tja.metadata){ var meta = tja.metadata[diff] - songObj.title = songObj.title_en = meta.title || file.name.slice(0, file.name.lastIndexOf(".")) + songObj.title = meta.title || file.name.slice(0, file.name.lastIndexOf(".")) var subtitle = meta.subtitle || "" - if(subtitle.startsWith("--")){ - subtitle = subtitle.slice(2) + if(subtitle.startsWith("--") || subtitle.startsWith("++")){ + subtitle = subtitle.slice(2).trim() } - songObj.subtitle = songObj.subtitle_en = subtitle + songObj.subtitle = subtitle songObj.preview = meta.demostart || 0 - if(meta.level){ - songObj.stars[this.courseTypes[diff]] = meta.level + (meta.branch ? " B" : "") - } + songObj.stars[this.courseTypes[diff]] = (meta.level || "0") + (meta.branch ? " B" : "") if(meta.wave){ - songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] + songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] || songObj.music } if(meta.genre){ songObj.category = this.categories[meta.genre.toLowerCase()] || meta.genre @@ -210,11 +211,33 @@ if(meta.taikowebskin){ songObj.song_skin = this.getSkin(dir, meta.taikowebskin) } + for(var id in allStrings){ + if(meta["title" + id]){ + titleLang[id] = meta["title" + id] + } + if(meta["subtitle" + id]){ + subtitleLang[id] = meta["subtitle" + id] + } + } + } + var titleLangArray = [] + for(var id in titleLang){ + titleLangArray.push(id + " " + titleLang[id]) + } + 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(!songObj.category){ songObj.category = category || this.getCategory(file) } - if(songObj.music && songObj.stars.filter(star => star).length !== 0){ + if(songObj.stars.length !== 0){ this.songs[index] = songObj } }).catch(() => {}) @@ -237,10 +260,10 @@ type: "osu", chart: data, subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist, - subtitle_en: osu.metadata.Artist || osu.metadata.ArtistUnicode, + 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()] + 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 @@ -251,13 +274,11 @@ suffix = " " + matches[0] } songObj.title = title + suffix - songObj.title_en = (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix + songObj.title_lang = (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix }else{ songObj.title = filename } - if(songObj.music){ - this.songs[index] = songObj - } + this.songs[index] = songObj songObj.category = category || this.getCategory(file) }).catch(() => {}) reader.readAsText(file) diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index 6629afc..94012fb 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -97,16 +97,18 @@ class LoadSong{ if(songObj.sound){ songObj.sound.gain = snd.musicGain resolve() - }else if(songObj.music){ + }else if(!songObj.music){ + snd.musicGain.load(gameConfig.songs_baseurl + id + "/main.mp3").then(sound => { + songObj.sound = sound + resolve() + }, reject) + }else if(songObj.music !== "muted"){ snd.musicGain.load(songObj.music, true).then(sound => { songObj.sound = sound resolve() }, reject) }else{ - snd.musicGain.load(gameConfig.songs_baseurl + id + "/main.mp3").then(sound => { - songObj.sound = sound - resolve() - }, reject) + resolve() } })) if(songObj.chart){ @@ -119,10 +121,10 @@ class LoadSong{ Promise.all(promises).then(() => { this.setupMultiplayer() }, error => { - console.error(error) if(Array.isArray(error) && error[1] instanceof HTMLElement){ error = error[0] + ": " + error[1].outerHTML } + console.error(error) pageEvents.send("load-song-error", error) errorMessage(new Error(error).stack) alert("An error occurred, please refresh") diff --git a/public/src/js/parsetja.js b/public/src/js/parsetja.js index 22e2e9f..427bb43 100644 --- a/public/src/js/parsetja.js +++ b/public/src/js/parsetja.js @@ -44,7 +44,7 @@ var hasSong = false var courses = {} var currentCourse = {} - var courseName = this.difficulty + var courseName = "oni" for(var lineNum = 0; lineNum < this.data.length; lineNum++){ var line = this.data[lineNum] @@ -55,10 +55,10 @@ inSong = true if(!hasSong){ + if(!(courseName in courses)){ + courses[courseName] = {} + } for(var name in currentCourse){ - if(!(courseName in courses)){ - courses[courseName] = {} - } if(name !== "branch"){ courses[courseName][name] = currentCourse[name] } @@ -118,10 +118,7 @@ parseCircles(){ var meta = this.metadata[this.difficulty] var ms = (meta.offset || 0) * -1000 + this.offset - var bpm = meta.bpm || 0 - if(bpm <= 0){ - bpm = 1 - } + var bpm = Math.abs(meta.bpm) || 120 var scroll = 1 var measure = 4 this.beatInfo.beatInterval = 60000 / bpm @@ -227,7 +224,7 @@ bpm = parseFloat(value) || bpm break case "scroll": - scroll = parseFloat(value) || scroll + scroll = Math.abs(parseFloat(value)) || scroll break case "measure": var [numerator, denominator] = value.split("/") diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 1f9bedf..09857f8 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -1753,15 +1753,15 @@ class SongSelect{ } new Promise((resolve, reject) => { - if(currentSong.music){ - songObj.preview_time = prvTime - snd.previewGain.load(currentSong.music, true).then(resolve, reject) - }else{ + if(!currentSong.music){ songObj.preview_time = 0 loadPreview(previewFilename).catch(() => { songObj.preview_time = prvTime return loadPreview("/main.mp3") }).then(resolve, reject) + }else if(currentSong.music !== "muted"){ + songObj.preview_time = prvTime + snd.previewGain.load(currentSong.music, true).then(resolve, reject) } }).then(sound => { if(currentId === this.previewId){ diff --git a/public/src/js/view.js b/public/src/js/view.js index 211b0bd..b1733c8 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -1180,6 +1180,10 @@ } setDonBgHeight(){ this.donBg.style.setProperty("--h", getComputedStyle(this.donBg).height) + this.gameDiv.classList.add("fix-animations") + setTimeout(()=>{ + this.gameDiv.classList.remove("fix-animations") + }, 50) } setLayers(elements, file, ab){ if(ab){ From 61a5d6d496e3a54fd51572d9719da296eb9879bb Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 16 Mar 2019 00:34:48 +0300 Subject: [PATCH 14/31] SoundBuffer: Set song volume - Requires a new column in the database after preview: `volume` REAL - The value is a volume multiplier, if the value is set to null or 1 there will be no change - The volume can be set in debugger - Imported TJA files are now read from disk every time the song is played, freeing some memory and making it easier to create charts - Correctly parse TJA files with alphabet notes, added "A" and "B" notes, which appear as DON (Big) and KA (Big) respectively --- app.py | 3 ++- public/src/js/about.js | 2 +- public/src/js/controller.js | 27 ++++++++++++++++++++++-- public/src/js/debug.js | 16 +++++++++++++++ public/src/js/game.js | 5 +++-- public/src/js/importsongs.js | 4 ++-- public/src/js/keyboard.js | 24 ++++++++++++---------- public/src/js/loader.js | 1 + public/src/js/loadsong.js | 10 ++++++++- public/src/js/mekadon.js | 10 ++++----- public/src/js/parsetja.js | 40 ++++++++++++++++++++++-------------- public/src/js/scoresheet.js | 2 +- public/src/js/songselect.js | 12 ++++++----- public/src/js/soundbuffer.js | 20 +++++++++++++++++- public/src/js/strings.js | 2 +- public/src/js/view.js | 2 +- public/src/views/debug.html | 4 ++++ 17 files changed, 135 insertions(+), 49 deletions(-) diff --git a/app.py b/app.py index 9714b69..91f45b0 100644 --- a/app.py +++ b/app.py @@ -142,7 +142,8 @@ def route_api_songs(): 'category': category_out, 'type': song_type, 'offset': song[13], - 'song_skin': song_skin_out + 'song_skin': song_skin_out, + 'volume': song[16] }) return jsonify(songs_out) diff --git a/public/src/js/about.js b/public/src/js/about.js index 1c3586d..d01c66d 100644 --- a/public/src/js/about.js +++ b/public/src/js/about.js @@ -143,7 +143,7 @@ } } - var issueBody = strings.issueTemplate + "\n\n\n\n" + diag + var issueBody = strings.about.issueTemplate + "\n\n\n\n" + diag this.getLink(this.linkEmail).href += "?body=" + encodeURIComponent(issueBody.replace(/\n/g, "
\r\n")) return diag diff --git a/public/src/js/controller.js b/public/src/js/controller.js index d0bc63b..6e2baae 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -21,6 +21,7 @@ class Controller{ assets.songs.forEach(song => { if(song.id == this.selectedSong.folder){ this.mainAsset = song.sound + this.volume = song.volume || 1 } }) @@ -35,6 +36,9 @@ class Controller{ if(syncWith){ this.syncWith = syncWith } + if(this.multiplayer !== 2){ + snd.musicGain.setVolumeMul(this.volume) + } this.game.run() this.view.run() if(this.multiplayer === 1){ @@ -161,8 +165,26 @@ class Controller{ if(this.multiplayer){ new LoadSong(this.selectedSong, false, true, this.touchEnabled) }else{ - var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled) - taikoGame.run() + new Promise(resolve => { + var songObj = assets.songs.find(song => song.id === this.selectedSong.folder) + if(songObj.chart){ + var reader = new FileReader() + var promise = pageEvents.load(reader).then(event => { + this.songData = event.target.result.replace(/\0/g, "").split("\n") + resolve() + }) + if(this.selectedSong.type === "tja"){ + reader.readAsText(songObj.chart, "sjis") + }else{ + reader.readAsText(songObj.chart) + } + }else{ + resolve() + } + }).then(() => { + var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled) + taikoGame.run() + }) } } playSound(id, time){ @@ -232,6 +254,7 @@ class Controller{ this.stopMainLoop() this.keyboard.clean() this.view.clean() + snd.buffer.loadSettings() if(!this.multiplayer){ debugObj.controller = null diff --git a/public/src/js/debug.js b/public/src/js/debug.js index 9f2836c..beeede3 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -16,6 +16,7 @@ class Debug{ this.branchSelectDiv = this.byClass("branch-select") this.branchSelect = this.branchSelectDiv.getElementsByTagName("select")[0] this.branchResetBtn = this.branchSelectDiv.getElementsByClassName("reset")[0] + this.volumeDiv = this.byClass("music-volume") this.restartCheckbox = this.byClass("change-restart") this.autoplayLabel = this.byClass("autoplay-label") this.autoplayCheckbox = this.byClass("autoplay") @@ -40,6 +41,10 @@ class Debug{ this.measureNumSlider.onchange(this.measureNumChange.bind(this)) this.measureNumSlider.set(0) + this.volumeSlider = new InputSlider(this.volumeDiv, 0, 3, 2) + this.volumeSlider.onchange(this.volumeChange.bind(this)) + this.volumeSlider.set(1) + this.moveTo(100, 100) this.restore() this.updateStatus() @@ -110,10 +115,12 @@ class Debug{ if(this.songFolder === selectedSong.folder){ this.offsetChange(this.offsetSlider.get(), true) this.branchChange(null, true) + this.volumeChange(this.volumeSlider.get(), true) }else{ this.songFolder = selectedSong.folder this.offsetSlider.set(this.defaultOffset) this.branchReset(null, true) + this.volumeSlider.set(this.controller.volume) } var measures = this.controller.parsedSongData.measures.filter((measure, i, array) => { @@ -176,6 +183,14 @@ class Debug{ this.restartSong() } } + volumeChange(value, noRestart){ + if(this.controller){ + snd.musicGain.setVolumeMul(value) + } + if(this.restartCheckbox.checked && !noRestart){ + this.restartSong() + } + } restartSong(){ if(this.controller){ this.controller.restartSong() @@ -234,6 +249,7 @@ class Debug{ delete this.branchSelectDiv delete this.branchSelect delete this.branchResetBtn + delete this.volumeDiv delete this.restartCheckbox delete this.autoplayLabel delete this.autoplayCheckbox diff --git a/public/src/js/game.js b/public/src/js/game.js index ceabf3f..19609a4 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -30,6 +30,7 @@ class Game{ this.currentTimingPoint = 0 this.branchNames = ["normal", "advanced", "master"] this.resetSection() + this.gameLagSync = !this.controller.touchEnabled && !(/Firefox/.test(navigator.userAgent)) assets.songs.forEach(song => { if(song.id == selectedSong.folder){ @@ -364,7 +365,7 @@ class Game{ var value = { score: score, ms: circle.ms - currentTime, - dai: typeDai ? keyDai ? 2 : 1 : 0 + dai: typeDai ? (keyDai ? 2 : 1) : 0 } if((!keysDon || !typeDon) && (!keysKa || !typeKa)){ value.reverse = true @@ -536,7 +537,7 @@ class Game{ this.sndTime = this.startDate - snd.buffer.getTime() * 1000 }else if(ms < 0 || ms >= 0 && this.started){ var currentDate = Date.now() - if(!this.controller.touchEnabled){ + if(this.gameLagSync){ var sndTime = currentDate - snd.buffer.getTime() * 1000 var lag = sndTime - this.sndTime if(Math.abs(lag) >= 50){ diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index 5040c92..93bd9ec 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -183,7 +183,7 @@ var songObj = { id: index + 1, type: "tja", - chart: data, + chart: file, stars: [], music: "muted" } @@ -258,7 +258,7 @@ var songObj = { id: index + 1, type: "osu", - chart: data, + chart: file, subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist, subtitle_lang: osu.metadata.Artist || osu.metadata.ArtistUnicode, preview: osu.generalInfo.PreviewTime / 1000, diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index 5925658..cb9a2ac 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -196,18 +196,17 @@ class Keyboard{ this.checkKey(keyCode, "sound", () => { var circles = this.controller.getCircles() var circle = circles[this.controller.getCurrentCircle()] - if( - sound === "don" - && circle - && !circle.isPlayed - && circle.type === "balloon" - && circle.requiredHits - circle.timesHit <= 1 - ){ - this.controller.playSound("se_balloon") - }else{ - this.controller.playSound("neiro_1_" + sound) + var currentTime = this.keyTime[keyCode] + this.keyTime[sound] = currentTime + if(circle && !circle.isPlayed){ + if(circle.type === "balloon"){ + if(sound === "don" && circle.requiredHits - circle.timesHit <= 1){ + this.controller.playSound("se_balloon") + return + } + } } - this.keyTime[sound] = this.keyTime[keyCode] + this.controller.playSound("neiro_1_" + sound) }) } getKeys(){ @@ -242,6 +241,9 @@ class Keyboard{ } } waitForKeyup(keyCode, type){ + if(!this.keys[keyCode]){ + return + } if(type === "score"){ this.waitKeyupScore[keyCode] = true }else if(type === "sound"){ diff --git a/public/src/js/loader.js b/public/src/js/loader.js index a171642..47d1ecb 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -118,6 +118,7 @@ class Loader{ 0.5 ) snd.sfxLoudGain.setVolume(1.2) + snd.buffer.saveSettings() this.afterJSCount = 0 diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index 94012fb..0c0b02f 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -112,7 +112,15 @@ class LoadSong{ } })) if(songObj.chart){ - this.songData = songObj.chart + var reader = new FileReader() + promises.push(pageEvents.load(reader).then(event => { + this.songData = event.target.result.replace(/\0/g, "").split("\n") + })) + if(song.type === "tja"){ + reader.readAsText(songObj.chart, "sjis") + }else{ + reader.readAsText(songObj.chart) + } }else{ promises.push(loader.ajax(this.getSongPath(song)).then(data => { this.songData = data.replace(/\0/g, "").split("\n") diff --git a/public/src/js/mekadon.js b/public/src/js/mekadon.js index fe63d71..eff1537 100644 --- a/public/src/js/mekadon.js +++ b/public/src/js/mekadon.js @@ -69,25 +69,25 @@ class Mekadon{ type = "don" } } - if(type == "daiDon" && playDai){ + if(type === "daiDon" && playDai){ this.setKey(kbd["don_l"], ms) this.setKey(kbd["don_r"], ms) this.lr = false keyDai = true - }else if(type == "don" || type == "daiDon" || drumrollNotes && score !== 2){ + }else if(type === "don" || type === "daiDon" || drumrollNotes && score !== 2){ this.setKey(this.lr ? kbd["don_l"] : kbd["don_r"], ms) this.lr = !this.lr - }else if(type == "daiKa" && playDai){ + }else if(type === "daiKa" && playDai){ this.setKey(kbd["ka_l"], ms) this.setKey(kbd["ka_r"], ms) this.lr = false keyDai = true - }else if(type == "ka" || type == "daiKa" || drumrollNotes){ + }else if(type === "ka" || type === "daiKa" || drumrollNotes){ this.setKey(this.lr ? kbd["ka_l"] : kbd["ka_r"], ms) this.lr = !this.lr } if(type === "balloon"){ - if(circle.requiredHits == 1){ + if(circle.requiredHits === 1){ assets.sounds["se_balloon"].play() } this.game.checkBalloon(circle) diff --git a/public/src/js/parsetja.js b/public/src/js/parsetja.js index 427bb43..2b10f9b 100644 --- a/public/src/js/parsetja.js +++ b/public/src/js/parsetja.js @@ -10,18 +10,20 @@ this.difficulty = difficulty this.offset = (offset || 0) * -1000 this.soundOffset = 0 - this.noteTypes = [ - {name: false, txt: false}, - {name: "don", txt: strings.note.don}, - {name: "ka", txt: strings.note.ka}, - {name: "daiDon", txt: strings.note.daiDon}, - {name: "daiKa", txt: strings.note.daiKa}, - {name: "drumroll", txt: strings.note.drumroll}, - {name: "daiDrumroll", txt: strings.note.daiDrumroll}, - {name: "balloon", txt: strings.note.balloon}, - {name: false, txt: false}, - {name: "balloon", txt: strings.note.balloon} - ] + this.noteTypes = { + "0": {name: false, txt: false}, + "1": {name: "don", txt: strings.note.don}, + "2": {name: "ka", txt: strings.note.ka}, + "3": {name: "daiDon", txt: strings.note.daiDon}, + "4": {name: "daiKa", txt: strings.note.daiKa}, + "5": {name: "drumroll", txt: strings.note.drumroll}, + "6": {name: "daiDrumroll", txt: strings.note.daiDrumroll}, + "7": {name: "balloon", txt: strings.note.balloon}, + "8": {name: false, txt: false}, + "9": {name: "balloon", txt: strings.note.balloon}, + "A": {name: "daiDon", txt: strings.note.daiDon}, + "B": {name: "daiKa", txt: strings.note.daiKa} + } this.courseTypes = { "0": "easy", "1": "normal", @@ -141,6 +143,7 @@ var firstNote = true var circles = [] var circleID = 0 + var regexAZ = /[A-Z]/ var pushMeasure = () => { var note = currentMeasure[0] @@ -321,7 +324,7 @@ }else{ - var string = line.split("") + var string = line.toUpperCase().split("") for(let symbol of string){ @@ -334,7 +337,7 @@ scroll: scroll }) break - case "1": case "2": case "3": case "4": + case "1": case "2": case "3": case "4": case "A": case "B": var type = this.noteTypes[symbol] var circleObj = { type: type.name, @@ -413,7 +416,14 @@ currentMeasure = [] break default: - error = true + if(regexAZ.test(symbol)){ + currentMeasure.push({ + bpm: bpm, + scroll: scroll + }) + }else{ + error = true + } break } diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 1402b86..037e7e4 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -856,7 +856,7 @@ class Scoresheet{ this.draw.clean() this.canvasCache.clean() assets.sounds["bgm_result"].stop() - snd.musicGain.fadeIn() + snd.buffer.loadSettings() this.redrawRunning = false pageEvents.keyRemove(this, "all") pageEvents.remove(this.canvas, ["mousedown", "touchstart"]) diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 09857f8..3af61af 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -107,7 +107,8 @@ class SongSelect{ type: song.type, offset: song.offset, songSkin: song.song_skin || {}, - music: song.music + music: song.music, + volume: song.volume }) } this.songs.sort((a, b) => { @@ -1741,7 +1742,7 @@ class SongSelect{ if(!loadOnly){ this.preview = songObj.preview_sound this.preview.gain = snd.previewGain - this.previewLoaded(startLoad, songObj.preview_time) + this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume) } }else{ songObj = {id: id} @@ -1767,7 +1768,7 @@ class SongSelect{ if(currentId === this.previewId){ songObj.preview_sound = sound this.preview = sound - this.previewLoaded(startLoad, songObj.preview_time) + this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume) var oldPreview = this.previewList.shift() if(oldPreview){ @@ -1781,11 +1782,12 @@ class SongSelect{ } } } - previewLoaded(startLoad, prvTime){ + previewLoaded(startLoad, prvTime, volume){ var endLoad = this.getMS() var difference = endLoad - startLoad var minDelay = 300 var delay = minDelay - Math.min(minDelay, difference) + snd.previewGain.setVolumeMul(volume || 1) this.preview.playLoop(delay / 1000, false, prvTime) } endPreview(){ @@ -1935,7 +1937,7 @@ class SongSelect{ if(!this.bgmEnabled){ snd.musicGain.fadeIn() setTimeout(() => { - snd.musicGain.fadeIn() + snd.buffer.loadSettings() }, 500) } this.redrawRunning = false diff --git a/public/src/js/soundbuffer.js b/public/src/js/soundbuffer.js index f220863..cd179f5 100644 --- a/public/src/js/soundbuffer.js +++ b/public/src/js/soundbuffer.js @@ -3,6 +3,7 @@ var AudioContext = window.AudioContext || window.webkitAudioContext this.context = new AudioContext() pageEvents.add(window, ["click", "touchend"], this.pageClicked.bind(this)) + this.gainList = [] } load(url, local, gain){ if(local){ @@ -27,7 +28,9 @@ }) } createGain(channel){ - return new SoundGain(this, channel) + var gain = new SoundGain(this, channel) + this.gainList.push(gain) + return gain } setCrossfade(gain1, gain2, median){ if(!Array.isArray(gain1)){ @@ -60,6 +63,18 @@ this.context.resume() } } + saveSettings(){ + for(var i = 0; i < this.gainList.length; i++){ + var gain = this.gainList[i] + gain.defaultVol = gain.volume + } + } + loadSettings(){ + for(var i = 0; i < this.gainList.length; i++){ + var gain = this.gainList[i] + gain.setVolume(gain.defaultVol) + } + } } class SoundGain{ constructor(soundBuffer, channel){ @@ -85,6 +100,9 @@ class SoundGain{ this.gainNode.gain.value = amount * amount this.volume = amount } + setVolumeMul(amount){ + this.setVolume(Math.sqrt(amount * 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 7d13389..16ddaa8 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -1,7 +1,7 @@ function StringsJa(){ this.id = "ja" this.name = "日本語" - this.regex = /^ja$/ + this.regex = /^ja$|^ja-/ this.font = "TnT, Meiryo, sans-serif" this.taikoWeb = "たいこウェブ" diff --git a/public/src/js/view.js b/public/src/js/view.js index b1733c8..f8fee1f 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -1438,7 +1438,7 @@ ctx.fill() ctx.globalAlpha = 1 } - if(!circle.animating){ + if(!circle.animating && circle.text){ // Text var text = circle.text var textX = circlePos.x diff --git a/public/src/views/debug.html b/public/src/views/debug.html index dd37b44..e57addc 100644 --- a/public/src/views/debug.html +++ b/public/src/views/debug.html @@ -20,6 +20,10 @@ +
Music volume:
+
+ x-+ +
From 911da048bb02b2bb6eb30d892e01e03042cdc276 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Wed, 20 Mar 2019 17:00:30 +0300 Subject: [PATCH 15/31] Fix code - Fixes code --- public/src/css/loader.css | 6 ++--- public/src/js/canvascache.js | 44 ++++++++++++++++++++++++++++-------- public/src/js/songselect.js | 9 +++++--- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/public/src/css/loader.css b/public/src/css/loader.css index 2899ce0..9e97ec1 100644 --- a/public/src/css/loader.css +++ b/public/src/css/loader.css @@ -15,7 +15,7 @@ body{ margin: 0; padding: 0; background-color: #000; - background-position: top center; + background-position: center; background-size: 30vh; } #screen.pattern-bg{ @@ -29,8 +29,8 @@ body{ width:90%; height:10%; border:1px solid black; - position: fixed; - top:50%; + position: absolute; + top:45%; left:5%; background: rgba(0,0,0,0.65); } diff --git a/public/src/js/canvascache.js b/public/src/js/canvascache.js index 56a1f7d..b8070a8 100644 --- a/public/src/js/canvascache.js +++ b/public/src/js/canvascache.js @@ -29,33 +29,58 @@ class CanvasCache{ return } var saved = false + var time = Date.now() if(!img){ var w = config.w var h = config.h - this.x += this.lastW + 1 + this.x += this.lastW + (this.lastW ? 1 : 0) if(this.x + w > this.w){ this.x = 0 this.y += this.lastH + 1 } - this.lastW = w - this.lastH = Math.max(this.lastH, h) - img = { - x: this.x, - y: this.y, - w: w, - h: h + if(this.y + h > this.h){ + var clear = true + var oldest = {time: time} + this.map.forEach((oldImg, id) => { + if(oldImg.time < oldest.time){ + oldest.id = id + oldest.time = oldImg.time + } + }) + var oldImg = this.map.get(oldest.id) + this.map.delete(oldest.id) + img = { + x: oldImg.x, + y: oldImg.y, + w: w, + h: h + } + }else{ + var clear = false + this.lastW = w + this.lastH = Math.max(this.lastH, h) + img = { + x: this.x, + y: this.y, + w: w, + h: h + } } - this.map.set(config.id, img) saved = true this.ctx.save() this.ctx.translate(img.x |0, img.y |0) + if(clear){ + this.ctx.clearRect(0, 0, (img.w |0) + 1, (img.h |0) + 1) + } this.ctx.beginPath() this.ctx.rect(0, 0, img.w |0, img.h |0) this.ctx.clip() + this.map.set(config.id, img) callback(this.ctx) } + img.time = time if(setOnly){ this.ctx.restore() return @@ -81,6 +106,7 @@ class CanvasCache{ this.ctx.clearRect(0, 0, this.w, this.h) } clean(){ + this.resize(1, 1, 1) delete this.map delete this.ctx delete this.canvas diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 3af61af..ed8d08b 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -803,9 +803,11 @@ class SongSelect{ this.canvas.style.height = (winH / this.pixelRatio) + "px" var borders = (this.songAsset.border + this.songAsset.innerBorder) * 2 + var songsLength = Math.ceil(winW / ratio / (this.songAsset.width + this.songAsset.marginLeft)) + 1 + this.songTitleCache.resize( - (this.songAsset.width - borders + 1) * Math.ceil(this.songs.length / 3), - (this.songAsset.height - borders + 1) * 3, + (this.songAsset.width - borders + 1) * songsLength, + this.songAsset.height - borders + 1, ratio + 0.2 ) @@ -827,7 +829,7 @@ class SongSelect{ categories++ } }) - this.categoryCache.resize(280, (this.songAsset.marginTop + 1) * categories , ratio + 0.5) + this.categoryCache.resize(280, this.songAsset.marginTop + 1 , ratio + 0.5) this.difficultyCache.resize((44 + 56 + 2) * 5, 135 + 10, ratio + 0.5) @@ -1933,6 +1935,7 @@ class SongSelect{ this.categoryCache.clean() this.difficultyCache.clean() this.sessionCache.clean() + this.currentSongCache.clean() assets.sounds["bgm_songsel"].stop() if(!this.bgmEnabled){ snd.musicGain.fadeIn() From 267461dd8405f2df04111e9b755f0c1242df8af6 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Fri, 22 Mar 2019 13:47:10 +0300 Subject: [PATCH 16/31] Clean more caches --- public/src/js/view.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/src/js/view.js b/public/src/js/view.js index f8fee1f..b81cd29 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -1874,6 +1874,8 @@ this.assets.clean() this.titleCache.clean() this.comboCache.clean() + this.pauseCache.clean() + this.branchCache.clean() if(this.multiplayer !== 2){ if(this.touchEnabled){ From cb64777012a916c22598793b6ad35af30d037493 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Thu, 4 Apr 2019 23:40:11 +0300 Subject: [PATCH 17/31] 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 @@ +
+
+
+
+
+
+
From 78528b424c86c75cf1d20dbf6973b09344f8124c Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Fri, 5 Apr 2019 00:21:08 +0300 Subject: [PATCH 18/31] Fix results screen, clear drum asset --- public/src/js/scoresheet.js | 8 ++++++++ public/src/js/settings.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 037e7e4..9ea0240 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -193,6 +193,14 @@ class Scoresheet{ var winW = innerWidth var winH = lastHeight 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/settings.js b/public/src/js/settings.js index c09bebc..b4e477e 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -200,7 +200,7 @@ class SettingsView{ 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_")){ + if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_")){ URL.revokeObjectURL(assets.image[i].src) delete assets.image[i] } From 3f533a02555a4763adcdd03b5e4eb019d4851594 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Fri, 5 Apr 2019 12:36:14 +0300 Subject: [PATCH 19/31] Add background music --- public/assets/audio/bgm_settings.mp3 | Bin 0 -> 120017 bytes public/src/js/assets.js | 3 ++- public/src/js/settings.js | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 public/assets/audio/bgm_settings.mp3 diff --git a/public/assets/audio/bgm_settings.mp3 b/public/assets/audio/bgm_settings.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e09d4922e742e09bd6aeb9cd2f219a6acd4b4dea GIT binary patch literal 120017 zcmeI$e`J+m0EXdjSxn3(k%(4C5wlq(5|tupDw>K&nq?vqNtB70#FB_4QlcUjQ4vv; zjFgdNDG^DGL`>G%A5UZJY$cB5nsN5a-tFCUF1L*gpYAB8c-h{ye{bna>WZTJSWz|b zyqGqK7cv**rVxOnOE)oV9y_T9RD=idDX4<9{#+W+juz{^*!-@JSO;p3;z zLtnmr8~$E8Z}Z00ZR=W_TAG`y@8&05+R#}2q^+oWJNk2+YijAsC;r>Vk>UD|qHd@t zx=O!dO;OY~6h&3(^-Ag8(wj@VOMjK07u8^jNR_L70 z3Z2tap>wWR=$!rvoio@w`L4%?RpzbV+?#X8-Jg5=rK96+Y2v%?&ApAT!Vc`f4$AiE zL^#KN#Pi4V$MeVYCoJ%Q2Rz^b4<`LTU#_R@BS$&PQH}-(9`Jw%Jm7(@$_9AAgKR+` z=mQ?`fCu^@8{h#CvITvh4|u=>9_WK?fCoIt7W9EW-~kVKpbxSE9`GPr&3OAY0G}`hW*K;DJ8K26(`OY(XFB10L{z2l^lz-~kV^1%03oc)$Z5=!0y4 z2Rz6Y^npI$0S|bf53&Is@E}{z2l{{qJm7&o$Od@8gKR+`=mQ?`fCu^@8{h#CvITvh z4|u=>9_WK?fCoIt7W9EW-~kVKpbxSE9`GPr&3OAY0G}`hW*K z;DJ8K26(`OY(XFB10L{z2l^lz-~kV^1%03oc)$Z5=!0y42Rz6Y^npI$0S|bf53&Is z@E}{z2l{{qJm7&o$Od@8gKR+`=mQ?`fCu^@8{h#CvITvh4|u=>9_WK?fCoIt7W9EW z-~kVKpbxSE9`GPr&3OAY0G}`hW*K;DJ8K26(`OY(XFB10L{z z2l^lz-~kV^1%03oc)$Z5=!0y42Rz6Y^npI$0S|bf53&Is@E}{z2l{{qJm7&o$Od@8 zgKR+`=mQ?`fCu^@8{h#CvITvh4|u=>9_WK?fCoIt7W9EW-~kVKpbxSE9`GPr& item.id === name) this.items[this.selected].valueDiv.innerText = this.getValue(name) + assets.sounds["se_ka"].play() } keyEvent(event){ if(event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9){ @@ -171,6 +174,7 @@ class SettingsView{ 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") + assets.sounds["se_ka"].play() }else if(name === "back"){ this.onEnd() } @@ -192,6 +196,7 @@ class SettingsView{ } clean(){ this.gamepad.clean() + assets.sounds["bgm_settings"].stop() pageEvents.keyRemove(this, "all") for(var i in this.items){ pageEvents.remove(this.items[i].settingBox, ["mousedown", "touchstart"]) From 879186c313dd313b5f8f7577c5cd8c4355f00b24 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Fri, 5 Apr 2019 22:53:51 +0300 Subject: [PATCH 20/31] Add keyboard and gamepad settings --- public/assets/img/img.css | 3 + public/src/css/main.css | 29 ++++- public/src/js/about.js | 74 +++++++++++-- public/src/js/keyboard.js | 50 ++++++--- public/src/js/loader.js | 1 + public/src/js/pageevents.js | 12 +- public/src/js/scoresheet.js | 27 +++-- public/src/js/session.js | 3 + public/src/js/settings.js | 194 +++++++++++++++++++++++++++------ public/src/js/songselect.js | 83 +++++++------- public/src/js/strings.js | 65 +++++++++++ public/src/js/titlescreen.js | 26 +++-- public/src/js/tutorial.js | 30 +++-- public/src/views/about.html | 2 +- public/src/views/settings.html | 2 +- public/src/views/tutorial.html | 2 +- 16 files changed, 468 insertions(+), 135 deletions(-) diff --git a/public/assets/img/img.css b/public/assets/img/img.css index 9d4073a..7d339c6 100644 --- a/public/assets/img/img.css +++ b/public/assets/img/img.css @@ -16,3 +16,6 @@ #touch-pause-btn{ background-image: url("touch_pause.png"); } +.settings-outer{ + background-image: url("bg_settings.png"); +} diff --git a/public/src/css/main.css b/public/src/css/main.css index c31abb0..9b44bb3 100644 --- a/public/src/css/main.css +++ b/public/src/css/main.css @@ -92,6 +92,7 @@ kbd{ color: #000; } .taibtn:hover, +.taibtn.selected, #tutorial-end-button:hover, #tutorial-end-button.selected{ position: relative; @@ -148,6 +149,19 @@ kbd{ cursor: text; overflow: hidden; } +@keyframes border-pulse{ + 0%{border-color: #ff0} + 50%{border-color: rgba(255, 255, 0, 0)} + 100%{border-color: #ff0} +} +@keyframes border-pulse2{ + 0%{border-color: #e29e06} + 50%{border-color: rgba(226, 158, 6, 0)} + 100%{border-color: #e29e06} +} +.settings-outer{ + background-size: 50vh; +} .setting-box{ display: flex; height: 2em; @@ -165,7 +179,7 @@ kbd{ #tutorial-content:not(:hover) .setting-box.selected, .setting-box:hover{ background: #ffb547; - border-color: #21211a; + animation: 2s linear border-pulse infinite; } .setting-name{ position: relative; @@ -183,12 +197,25 @@ kbd{ padding-left: 0.3em; } .setting-value{ + display: flex; background: #fff; width: 50%; border-radius: 0.2em; padding: 0.5em; box-sizing: border-box; } +.setting-value.selected{ + width: calc(50% + 0.2em); + margin: -0.1em; + border: 0.2em solid #e29e06; + padding: 0.4em; + animation: 2s linear border-pulse2 infinite; +} +.setting-value>div{ + padding: 0 0.4em; + overflow: hidden; + text-overflow: ellipsis; +} @keyframes bgscroll{ from{ background-position: 0 top; diff --git a/public/src/js/about.js b/public/src/js/about.js index d01c66d..95575b7 100644 --- a/public/src/js/about.js +++ b/public/src/js/about.js @@ -32,22 +32,77 @@ var versionUrl = gameConfig._version.url this.getLink(this.linkIssues).href = versionUrl + "issues" + var kbdSettings = settings.getItem("keyboardSettings") + this.kbd = { + confirm: ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]], + previous: ["arrowleft", "arrowup", kbdSettings.ka_l[0]], + next: ["arrowright", "arrowdown", kbdSettings.ka_r[0]], + back: ["backspace", "escape"] + } pageEvents.add(this.linkIssues, ["click", "touchend"], this.linkButton.bind(this)) pageEvents.add(this.linkEmail, ["click", "touchend"], this.linkButton.bind(this)) - pageEvents.once(this.endButton, ["mousedown", "touchstart"]).then(this.onEnd.bind(this)) - pageEvents.keyOnce(this, 13, "down").then(this.onEnd.bind(this)) + pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) + pageEvents.keyAdd(this, "all", "down", this.keyEvent.bind(this)) + this.items = [this.linkIssues, this.linkEmail, this.endButton] + this.selected = 2 this.gamepad = new Gamepad({ - "confirm": ["start", "b", "ls", "rs"] - }, this.onEnd.bind(this)) + "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("about", this.addDiag()) } + 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.key.toLowerCase()) !== -1){ + this.keyPressed(true, i) + break + } + } + } + } + keyPressed(pressed, name){ + if(!pressed){ + return + } + var selected = this.items[this.selected] + if(name === "confirm"){ + if(selected === this.endButton){ + this.onEnd() + }else{ + this.getLink(selected).click() + pageEvents.send("about-link", selected) + assets.sounds["se_don"].play() + } + }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 + } onEnd(event){ var touched = false - if(event && event.type === "touchstart"){ - event.preventDefault() - touched = true + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + touched = true + }else if(event.which !== 1){ + return + } } this.clean() assets.sounds["se_don"].play() @@ -154,7 +209,8 @@ linkButton(event){ if(event.target === event.currentTarget){ this.getLink(event.currentTarget).click() - pageEvents.send("about-link", event) + pageEvents.send("about-link", event.currentTarget) + assets.sounds["se_don"].play() } } clean(){ @@ -166,7 +222,7 @@ if(this.textarea){ pageEvents.remove(this.textarea, ["focus", "blur"]) } - pageEvents.keyRemove(this, 13) + pageEvents.keyRemove(this, "all") delete this.endButton delete this.diagTxt delete this.version diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index 0a5b32a..2960a97 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -3,22 +3,23 @@ class Keyboard{ this.controller = controller this.game = this.controller.game + var kbdSettings = settings.getItem("keyboardSettings") this.kbd = { - "don_l": 70, // F - "don_r": 74, // J - "ka_l": 68, // D - "ka_r": 75, // K - "pause": 81, // Q - "back": 8, // Backspace - "previous": 37, // Left - "next": 39, // Right - "confirm": 13 // Enter + "ka_l": kbdSettings.ka_l[0], + "don_l": kbdSettings.don_l[0], + "don_r": kbdSettings.don_r[0], + "ka_r": kbdSettings.ka_r[0], + "pause": "q", + "back": "backspace", + "previous": "arrowleft", + "next": "arrowright", + "confirm": "enter" } this.kbdAlias = { - "pause": [27], // Esc - "previous": [38], // Up - "next": [40], // Down - "confirm": [32] // Space + "pause": ["escape"], + "previous": ["arrowup"], + "next": ["arrowdown"], + "confirm": [" "] } this.keys = {} this.waitKeyupScore = {} @@ -30,11 +31,24 @@ class Keyboard{ } this.keyboardEvents = 0 + var layout = settings.getItem("gamepadLayout") var gameBtn = {} - gameBtn[this.kbd["don_l"]] = ["u", "d", "l", "r", "ls"] - gameBtn[this.kbd["don_r"]] = ["a", "b", "x", "y", "rs"] - gameBtn[this.kbd["ka_l"]] = ["lb", "lt"] - gameBtn[this.kbd["ka_r"]] = ["rb", "rt"] + if(layout === "b"){ + gameBtn[this.kbd["don_l"]] = ["d", "r", "ls"] + gameBtn[this.kbd["don_r"]] = ["a", "x", "rs"] + gameBtn[this.kbd["ka_l"]] = ["u", "l", "lb", "lt"] + gameBtn[this.kbd["ka_r"]] = ["b", "y", "rb", "rt"] + }else if(layout === "c"){ + gameBtn[this.kbd["don_l"]] = ["d", "l", "ls"] + gameBtn[this.kbd["don_r"]] = ["a", "b", "rs"] + gameBtn[this.kbd["ka_l"]] = ["u", "r", "lb", "lt"] + gameBtn[this.kbd["ka_r"]] = ["x", "y", "rb", "rt"] + }else{ + gameBtn[this.kbd["don_l"]] = ["u", "d", "l", "r", "ls"] + gameBtn[this.kbd["don_r"]] = ["a", "b", "x", "y", "rs"] + gameBtn[this.kbd["ka_l"]] = ["lb", "lt"] + gameBtn[this.kbd["ka_r"]] = ["rb", "rt"] + } this.gamepad = new Gamepad(gameBtn) this.gamepadInterval = setInterval(this.gamepadKeys.bind(this), 1000 / 60 / 2) @@ -63,7 +77,7 @@ class Keyboard{ // Escape, Backspace, Tab event.preventDefault() } - var key = this.kbdSearch[event.keyCode] + var key = this.kbdSearch[event.key.toLowerCase()] if(key && !event.repeat && this.buttonEnabled(key)){ var ms = this.game.getAccurateTime() this.setKey(key, event.type === "keydown", ms) diff --git a/public/src/js/loader.js b/public/src/js/loader.js index d7f45cd..a3ba450 100644 --- a/public/src/js/loader.js +++ b/public/src/js/loader.js @@ -204,6 +204,7 @@ class Loader{ } settings = new Settings() + pageEvents.setKbd() Promise.all(this.promises).then(() => { this.canvasTest.drawAllImages().then(result => { diff --git a/public/src/js/pageevents.js b/public/src/js/pageevents.js index 7ba8d68..a1bba1d 100644 --- a/public/src/js/pageevents.js +++ b/public/src/js/pageevents.js @@ -7,6 +7,7 @@ class PageEvents{ this.add(window, "keydown", this.keyEvent.bind(this)) this.add(window, "keyup", this.keyEvent.bind(this)) this.add(window, "mousemove", this.mouseEvent.bind(this)) + this.kbd = [] } add(target, type, callback){ if(Array.isArray(type)){ @@ -81,7 +82,7 @@ class PageEvents{ }) } keyEvent(event){ - if ([68, 70, 74, 75].indexOf(event.keyCode) > -1) { // D, F, J, K + if(this.kbd.indexOf(event.key.toLowerCase()) !== -1){ this.lastKeyEvent = Date.now() } this.keyListeners.forEach(addedKeyCode => { @@ -146,4 +147,13 @@ class PageEvents{ send(name, detail){ dispatchEvent(new CustomEvent(name, {detail: detail})) } + setKbd(){ + var kbdSettings = settings.getItem("keyboardSettings") + this.kbd = [ + kbdSettings.ka_l[0], + kbdSettings.don_l[0], + kbdSettings.don_r[0], + kbdSettings.ka_r[0] + ] + } } diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 9ea0240..4d0360c 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -31,8 +31,12 @@ class Scoresheet{ this.draw = new CanvasDraw() this.canvasCache = new CanvasCache() + var kbdSettings = settings.getItem("keyboardSettings") + this.kbd = { + confirm: ["enter", " ", "escape", "backspace", kbdSettings.don_l[0], kbdSettings.don_r[0]] + } this.gamepad = new Gamepad({ - "13": ["a", "b", "start", "ls", "rs"] + confirm: ["a", "b", "start", "ls", "rs"] }) this.difficulty = { @@ -72,23 +76,22 @@ class Scoresheet{ touchEvents: controller.view.touchEvents }) } - keyDown(event, code){ - if(!code){ + keyDown(event, key){ + if(!key){ if(event.repeat){ return } - code = event.keyCode + for(var i in this.kbd){ + if(this.kbd[i].indexOf(event.key.toLowerCase()) !== -1){ + key = i + break + } + } } - var key = { - confirm: code == 13 || code == 32 || code == 70 || code == 74, - // Enter, Space, F, J - cancel: code == 27 || code == 8 - // Esc, Backspace - } - if(key.cancel && event){ + if(event && event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9){ event.preventDefault() } - if(key.confirm || key.cancel){ + if(key === "confirm"){ this.toNext() } } diff --git a/public/src/js/session.js b/public/src/js/session.js index 8dfe8e6..43efa9e 100644 --- a/public/src/js/session.js +++ b/public/src/js/session.js @@ -37,6 +37,9 @@ class Session{ pageEvents.send("session") } mouseDown(event){ + if(event.type === "mousedown" && event.which !== 1){ + return + } if(event.target === this.sessionInvite){ this.sessionInvite.focus() }else{ diff --git a/public/src/js/settings.js b/public/src/js/settings.js index 1f83c01..97b8a41 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -13,6 +13,22 @@ class Settings{ type: "toggle", default: !ios, touch: true + }, + keyboardSettings: { + type: "keyboard", + default: { + ka_l: ["d"], + don_l: ["f"], + don_r: ["j"], + ka_r: ["k"] + }, + touch: false + }, + gamepadLayout: { + type: "select", + options: ["a", "b", "c"], + default: "a", + gamepad: true } } @@ -24,6 +40,17 @@ class Settings{ if(i in storage){ if(current.type === "select" && current.options.indexOf(storage[i]) === -1){ this.storage[i] = null + }else if(current.type === "keyboard"){ + var obj = {} + for(var j in current.default){ + if(storage[i][j] && storage[i][j][0]){ + obj[j] = storage[i][j] + }else{ + obj = null + break + } + } + this.storage[i] = obj }else{ this.storage[i] = storage[i] } @@ -52,13 +79,24 @@ class Settings{ class SettingsView{ constructor(touchEnabled){ this.touchEnabled = touchEnabled - loader.changePage("settings", true) + loader.changePage("settings", false) assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) this.endButton = document.getElementById("tutorial-end-button") if(touchEnabled){ document.getElementById("tutorial-outer").classList.add("touch-enabled") } + var gamepadEnabled = false + if("getGamepads" in navigator){ + var gamepads = navigator.getGamepads() + for(var i = 0; i < gamepads.length; i++){ + if(gamepads[i]){ + gamepadEnabled = true + break + } + } + } + this.mode = "settings" var tutorialTitle = document.getElementById("tutorial-title") tutorialTitle.innerText = strings.gameSettings @@ -71,7 +109,12 @@ class SettingsView{ this.items = [] this.selected = 0 for(let i in settings.items){ - if(!touchEnabled && settings.items[i].touch){ + var current = settings.items[i] + if( + !touchEnabled && current.touch === true || + touchEnabled && current.touch === false || + !gamepadEnabled && current.gamepad === true + ){ continue } var settingBox = document.createElement("div") @@ -84,15 +127,17 @@ class SettingsView{ settingBox.appendChild(nameDiv) var valueDiv = document.createElement("div") valueDiv.classList.add("setting-value") - valueDiv.innerText = this.getValue(i) + this.getValue(i, valueDiv) 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) + if(event.which === 1){ + event.preventDefault() + this.setValue(i) + } }) this.items.push({ id: i, @@ -105,13 +150,8 @@ class SettingsView{ 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)) + this.setKbd() + pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) pageEvents.keyAdd(this, "all", "down", this.keyEvent.bind(this)) this.gamepad = new Gamepad({ "confirm": ["b", "ls", "rs"], @@ -122,27 +162,65 @@ class SettingsView{ pageEvents.send("settings") } - getValue(name){ + setKbd(){ + var kbdSettings = settings.getItem("keyboardSettings") + this.kbd = { + "confirm": ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]], + "previous": ["arrowleft", "arrowup", kbdSettings.ka_l[0]], + "next": ["arrowright", "arrowdown", kbdSettings.ka_r[0]], + "back": ["backspace", "escape"] + } + } + getValue(name, valueDiv){ 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 + }else if(current.type === "keyboard"){ + valueDiv.innerHTML = "" + for(var i in value){ + var key = document.createElement("div") + key.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10" + key.innerText = value[i][0].toUpperCase() + valueDiv.appendChild(key) + } + return } - return value + valueDiv.innerText = value } setValue(name){ var current = settings.items[name] var value = settings.getItem(name) + var selectedIndex = this.items.findIndex(item => item.id === name) + var selected = this.items[selectedIndex] + if(this.mode !== "settings"){ + if(this.selected === selectedIndex){ + this.keyboardBack(selected) + } + return + } + if(this.selected !== selectedIndex){ + this.items[this.selected].settingBox.classList.remove("selected") + this.selected = selectedIndex + selected.settingBox.classList.add("selected") + } if(current.type === "select"){ value = current.options[this.mod(current.options.length, current.options.indexOf(value) + 1)] }else if(current.type === "toggle"){ value = !value + }else if(current.type === "keyboard"){ + this.mode = "keyboard" + selected.settingBox.style.animation = "none" + selected.valueDiv.classList.add("selected") + this.keyboardKeys = {} + this.keyboardSet() + assets.sounds["se_don"].play() + return } settings.setItem(name, value) - this.selected = this.items.findIndex(item => item.id === name) - this.items[this.selected].valueDiv.innerText = this.getValue(name) + this.getValue(name, this.items[this.selected].valueDiv) assets.sounds["se_ka"].play() } keyEvent(event){ @@ -152,11 +230,23 @@ class SettingsView{ } if(!event.repeat){ for(var i in this.kbd){ - if(this.kbd[i].indexOf(event.keyCode) !== -1){ - this.keyPressed(true, i) - break + if(this.kbd[i].indexOf(event.key.toLowerCase()) !== -1){ + if(this.mode !== "keyboard" || i === "back"){ + this.keyPressed(true, i) + return + } } } + if(this.mode === "keyboard"){ + var currentKey = event.key.toLowerCase() + for(var i in this.keyboardKeys){ + if(this.keyboardKeys[i][0] === currentKey || !currentKey){ + return + } + } + this.keyboardKeys[this.keyboardCurrent] = [currentKey] + this.keyboardSet() + } } } keyPressed(pressed, name){ @@ -164,26 +254,64 @@ class SettingsView{ return } var selected = this.items[this.selected] - if(name === "confirm"){ - if(selected.id === "back"){ + if(this.mode === "settings"){ + 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") + assets.sounds["se_ka"].play() + }else if(name === "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") - assets.sounds["se_ka"].play() - }else if(name === "back"){ - this.onEnd() + }else if(this.mode === "keyboard"){ + if(name === "back"){ + this.keyboardBack(selected) + } } } + keyboardSet(){ + var selected = this.items[this.selected] + var current = settings.items[selected.id] + selected.valueDiv.innerHTML = "" + for(var i in current.default){ + var key = document.createElement("div") + key.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10" + if(this.keyboardKeys[i]){ + key.innerText = this.keyboardKeys[i][0].toUpperCase() + selected.valueDiv.appendChild(key) + }else{ + key.innerText = "[" + strings.settings[selected.id][i] + "]" + selected.valueDiv.appendChild(key) + this.keyboardCurrent = i + return + } + } + settings.setItem(selected.id, this.keyboardKeys) + this.keyboardBack(selected) + this.setKbd() + pageEvents.setKbd() + } + keyboardBack(selected){ + this.mode = "settings" + selected.settingBox.style.animation = "" + selected.valueDiv.classList.remove("selected") + this.getValue(selected.id, selected.valueDiv) + } onEnd(event){ var touched = false - if(event && event.type === "touchstart"){ - event.preventDefault() - touched = true + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + touched = true + }else if(event.which !== 1){ + return + } } this.clean() assets.sounds["se_don"].play() diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 530447e..ea7ba1b 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -289,16 +289,26 @@ class SongSelect{ this.startPreview(true) this.pressedKeys = {} + var kbdSettings = settings.getItem("keyboardSettings") + this.kbd = { + confirm: ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]], + back: ["escape"], + left: ["arrowleft", kbdSettings.ka_l[0]], + right: ["arrowright", kbdSettings.ka_r[0]], + up: ["arrowup"], + down: ["arrowdown"], + session: ["backspace"] + } this.gamepad = new Gamepad({ - "13": ["b", "start", "ls", "rs"], - "27": ["a"], - "37": ["l", "lb", "lt", "lsl"], - "39": ["r", "rb", "rt", "lsr"], - "38": ["u", "lsu"], - "40": ["d", "lsd"], - "8": ["back"], - "ctrl": ["y"], - "shift": ["x"] + confirm: ["b", "start", "ls", "rs"], + back: ["a"], + left: ["l", "lb", "lt", "lsl"], + right: ["r", "rb", "rt", "lsr"], + up: ["u", "lsu"], + down: ["d", "lsd"], + session: ["back"], + ctrl: ["y"], + shift: ["x"] }) if(!assets.customSongs){ @@ -331,57 +341,46 @@ class SongSelect{ } } - keyDown(event, code){ - if(code){ + keyDown(event, key){ + if(key){ var modifiers = { shift: this.pressedKeys["shift"], ctrl: this.pressedKeys["ctrl"] } }else{ - code = event.keyCode var modifiers = { shift: event.shiftKey, ctrl: event.ctrlKey } + for(var i in this.kbd){ + if(this.kbd[i].indexOf(event.key.toLowerCase()) !== -1){ + key = i + break + } + } } - if(code === "ctrl" || code === "shift" || !this.redrawRunning){ + if(key === "ctrl" || key === "shift" || !this.redrawRunning){ return } - - var key = { - confirm: code == 13 || code == 32 || code == 70 || code == 74, - // Enter, Space, F, J - cancel: code == 27, - // Esc - session: code == 8, - // Backspace - left: code == 37 || code == 68, - // Left, D - right: code == 39 || code == 75, - // Right, K - up: code == 38, - // Up - down: code == 40 - // Down - } - if(event && (code == 27 || code == 8 || code == 9)){ + + if(event && (event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9)){ // Escape, Backspace, Tab event.preventDefault() } if(this.state.screen === "song"){ - if(key.confirm){ + if(key === "confirm"){ this.toSelectDifficulty() - }else if(key.cancel){ + }else if(key === "back"){ this.toTitleScreen() - }else if(key.session){ + }else if(key === "session"){ this.toSession() - }else if(key.left){ + }else if(key === "left"){ this.moveToSong(-1) - }else if(key.right){ + }else if(key === "right"){ this.moveToSong(1) } }else if(this.state.screen === "difficulty"){ - if(key.confirm){ + if(key === "confirm"){ if(this.selectedDiff === 0){ this.toSongSelect() }else if(this.selectedDiff === 1){ @@ -389,14 +388,14 @@ class SongSelect{ }else{ this.toLoadSong(this.selectedDiff - this.diffOptions.length, modifiers.shift, modifiers.ctrl) } - }else if(key.cancel || key.session){ + }else if(key === "back" || key === "session"){ this.toSongSelect() - }else if(key.left){ + }else if(key === "left"){ this.moveToDiff(-1) - }else if(key.right){ + }else if(key === "right"){ this.moveToDiff(1) - }else if(this.selectedDiff === 1 && (key.up || key.down)){ - this.toOptions(key.up ? -1 : 1) + }else if(this.selectedDiff === 1 && (key === "up" || key === "down")){ + this.toOptions(key === "up" ? -1 : 1) } } } diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 8176d1e..39a3bde 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -110,6 +110,19 @@ touchAnimation: { name: "タッチアニメーション" }, + keyboardSettings: { + name: "キーボード設定", + ka_l: "ふち(左)", + don_l: "面(左)", + don_r: "面(右)", + ka_r: "ふち(右)" + }, + gamepadLayout: { + name: "そうさタイプ設定", + a: "タイプA", + b: "タイプB", + c: "タイプC" + }, on: "オン", off: "オフ", ok: "OK" @@ -233,6 +246,19 @@ function StringsEn(){ touchAnimation: { name: "Touch Animation" }, + keyboardSettings: { + name: "Keyboard Settings", + ka_l: "Left Rim", + don_l: "Left Surface", + don_r: "Right Surface", + ka_r: "Right Rim" + }, + gamepadLayout: { + name: "Gamepad Layout", + a: "Type A", + b: "Type B", + c: "Type C" + }, on: "On", off: "Off", ok: "OK" @@ -356,6 +382,19 @@ function StringsCn(){ touchAnimation: { name: "触摸动画" }, + keyboardSettings: { + name: "Keyboard Settings", + ka_l: "Left Rim", + don_l: "Left Surface", + don_r: "Right Surface", + ka_r: "Right Rim" + }, + gamepadLayout: { + name: "操作类型设定", + a: "类型A", + b: "类型B", + c: "类型C" + }, on: "开", off: "关", ok: "确定" @@ -479,6 +518,19 @@ function StringsTw(){ touchAnimation: { name: "觸摸動畫" }, + keyboardSettings: { + name: "Keyboard Settings", + ka_l: "Left Rim", + don_l: "Left Surface", + don_r: "Right Surface", + ka_r: "Right Rim" + }, + gamepadLayout: { + name: "操作類型設定", + a: "類型A", + b: "類型B", + c: "類型C" + }, on: "開", off: "關", ok: "確定" @@ -602,6 +654,19 @@ function StringsKo(){ touchAnimation: { name: "터치 애니메이션" }, + keyboardSettings: { + name: "Keyboard Settings", + ka_l: "Left Rim", + don_l: "Left Surface", + don_r: "Right Surface", + ka_r: "Right Rim" + }, + gamepadLayout: { + name: "조작 타입 설정", + a: "타입 A", + b: "타입 B", + c: "타입 C" + }, on: "온", off: "오프", ok: "확인" diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index 9f95cdc..709de14 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -31,8 +31,12 @@ class Titlescreen{ pageEvents.add(this.langDropdown, "change", this.langChange.bind(this)) assets.sounds["v_title"].play() + var kbdSettings = settings.getItem("keyboardSettings") + this.kbd = { + confirm: ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]] + } this.gamepad = new Gamepad({ - "13": ["a", "b", "x", "y", "start", "ls", "rs"] + confirm: ["a", "b", "x", "y", "start", "ls", "rs"] }, pressed => { if(pressed){ this.onPressed() @@ -49,15 +53,19 @@ class Titlescreen{ } } - keyDown(event, code){ - if(event && event.target === this.langDropdown){ - return + keyDown(event, key){ + if(!key){ + if(event.repeat || event.target === this.langDropdown){ + return + } + for(var i in this.kbd){ + if(this.kbd[i].indexOf(event.key.toLowerCase()) !== -1){ + key = i + break + } + } } - if(!code){ - code = event.keyCode - } - if(code == 13 || code == 32 || code == 70 || code == 74){ - // Enter, Space, F, J + if(key === "confirm"){ this.onPressed() } } diff --git a/public/src/js/tutorial.js b/public/src/js/tutorial.js index d2999cd..7a1f484 100644 --- a/public/src/js/tutorial.js +++ b/public/src/js/tutorial.js @@ -10,7 +10,14 @@ class Tutorial{ tutorialTitle.innerText = strings.howToPlay tutorialTitle.setAttribute("alt", strings.howToPlay) var tutorialContent = document.getElementById("tutorial-content") - var keys = ["F", "J", "D", "K", "Q", "SHIFT", "CTRL"] + var kbdSettings = settings.getItem("keyboardSettings") + var keys = [ + kbdSettings.don_l[0].toUpperCase(), + kbdSettings.don_r[0].toUpperCase(), + kbdSettings.ka_l[0].toUpperCase(), + kbdSettings.ka_r[0].toUpperCase(), + "Q", "SHIFT", "CTRL" + ] var keyIndex = 0 strings.tutorial.basics.forEach(string => { var par = document.createElement("p") @@ -42,8 +49,13 @@ class Tutorial{ this.endButton.innerText = strings.tutorial.ok this.endButton.setAttribute("alt", strings.tutorial.ok) - pageEvents.once(this.endButton, ["mousedown", "touchstart"]).then(this.onEnd.bind(this)) - pageEvents.keyOnce(this, 13, "down").then(this.onEnd.bind(this)) + pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) + pageEvents.keyAdd(this, "all", "down", event => { + if(event.keyCode === 13 || event.keyCode === 27 || event.keyCode === 8){ + // Enter, Esc, Backspace + this.onEnd.bind(this) + } + }) this.gamepad = new Gamepad({ "confirm": ["start", "b", "ls", "rs"] @@ -60,9 +72,13 @@ class Tutorial{ } onEnd(event){ var touched = false - if(event && event.type === "touchstart"){ - event.preventDefault() - touched = true + if(event){ + if(event.type === "touchstart"){ + event.preventDefault() + touched = true + }else if(event.which !== 1){ + return + } } this.clean() assets.sounds["se_don"].play() @@ -75,7 +91,7 @@ class Tutorial{ this.gamepad.clean() assets.sounds["bgm_setsume"].stop() pageEvents.remove(this.endButton, ["mousedown", "touchstart"]) - pageEvents.keyRemove(this, 13) + pageEvents.keyRemove(this, "all") delete this.endButton } } diff --git a/public/src/views/about.html b/public/src/views/about.html index 7ec8436..f6165c7 100644 --- a/public/src/views/about.html +++ b/public/src/views/about.html @@ -11,6 +11,6 @@ taiko@bui.pm
-
+
diff --git a/public/src/views/settings.html b/public/src/views/settings.html index 983c90e..bfaa4ee 100644 --- a/public/src/views/settings.html +++ b/public/src/views/settings.html @@ -1,4 +1,4 @@ -
+
diff --git a/public/src/views/tutorial.html b/public/src/views/tutorial.html index 239daf2..ffd124a 100644 --- a/public/src/views/tutorial.html +++ b/public/src/views/tutorial.html @@ -2,6 +2,6 @@
-
+
From 59fc718a08fa583ed294f2ddb4ce437eea93bac3 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Fri, 5 Apr 2019 23:21:22 +0300 Subject: [PATCH 21/31] Fix touching settings --- public/src/js/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/js/settings.js b/public/src/js/settings.js index 97b8a41..0e38926 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -134,7 +134,7 @@ class SettingsView{ settingBox.classList.add("selected") } pageEvents.add(settingBox, ["mousedown", "touchstart"], event => { - if(event.which === 1){ + if(event.type !== "mousedown" || event.which === 1){ event.preventDefault() this.setValue(i) } From 8565a3cbba2d28fa908647cc1e3dc95dae28932a Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 6 Apr 2019 00:10:15 +0300 Subject: [PATCH 22/31] Remove gamepad layout settings --- public/src/js/keyboard.js | 21 +++-------- public/src/js/settings.js | 65 ++++++++++++++++++++++------------ public/src/js/strings.js | 35 +++--------------- public/src/views/settings.html | 3 ++ 4 files changed, 54 insertions(+), 70 deletions(-) diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index 2960a97..f038a44 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -31,24 +31,11 @@ class Keyboard{ } this.keyboardEvents = 0 - var layout = settings.getItem("gamepadLayout") var gameBtn = {} - if(layout === "b"){ - gameBtn[this.kbd["don_l"]] = ["d", "r", "ls"] - gameBtn[this.kbd["don_r"]] = ["a", "x", "rs"] - gameBtn[this.kbd["ka_l"]] = ["u", "l", "lb", "lt"] - gameBtn[this.kbd["ka_r"]] = ["b", "y", "rb", "rt"] - }else if(layout === "c"){ - gameBtn[this.kbd["don_l"]] = ["d", "l", "ls"] - gameBtn[this.kbd["don_r"]] = ["a", "b", "rs"] - gameBtn[this.kbd["ka_l"]] = ["u", "r", "lb", "lt"] - gameBtn[this.kbd["ka_r"]] = ["x", "y", "rb", "rt"] - }else{ - gameBtn[this.kbd["don_l"]] = ["u", "d", "l", "r", "ls"] - gameBtn[this.kbd["don_r"]] = ["a", "b", "x", "y", "rs"] - gameBtn[this.kbd["ka_l"]] = ["lb", "lt"] - gameBtn[this.kbd["ka_r"]] = ["rb", "rt"] - } + gameBtn[this.kbd["don_l"]] = ["u", "d", "l", "r", "ls"] + gameBtn[this.kbd["don_r"]] = ["a", "b", "x", "y", "rs"] + gameBtn[this.kbd["ka_l"]] = ["lb", "lt"] + gameBtn[this.kbd["ka_r"]] = ["rb", "rt"] this.gamepad = new Gamepad(gameBtn) this.gamepadInterval = setInterval(this.gamepadKeys.bind(this), 1000 / 60 / 2) diff --git a/public/src/js/settings.js b/public/src/js/settings.js index 0e38926..b72c13b 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -23,12 +23,6 @@ class Settings{ ka_r: ["k"] }, touch: false - }, - gamepadLayout: { - type: "select", - options: ["a", "b", "c"], - default: "a", - gamepad: true } } @@ -82,25 +76,18 @@ class SettingsView{ loader.changePage("settings", false) assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) - this.endButton = document.getElementById("tutorial-end-button") if(touchEnabled){ document.getElementById("tutorial-outer").classList.add("touch-enabled") } - var gamepadEnabled = false - if("getGamepads" in navigator){ - var gamepads = navigator.getGamepads() - for(var i = 0; i < gamepads.length; i++){ - if(gamepads[i]){ - gamepadEnabled = true - break - } - } - } this.mode = "settings" var tutorialTitle = document.getElementById("tutorial-title") tutorialTitle.innerText = strings.gameSettings tutorialTitle.setAttribute("alt", strings.gameSettings) + this.defaultButton = document.getElementById("settings-default") + this.defaultButton.innerText = strings.settings.default + this.defaultButton.setAttribute("alt", strings.settings.default) + this.endButton = document.getElementById("tutorial-end-button") this.endButton.innerText = strings.settings.ok this.endButton.setAttribute("alt", strings.settings.ok) this.resolution = settings.getItem("resolution") @@ -112,8 +99,7 @@ class SettingsView{ var current = settings.items[i] if( !touchEnabled && current.touch === true || - touchEnabled && current.touch === false || - !gamepadEnabled && current.gamepad === true + touchEnabled && current.touch === false ){ continue } @@ -145,17 +131,23 @@ class SettingsView{ valueDiv: valueDiv }) } + this.items.push({ + id: "default", + settingBox: this.defaultButton + }) this.items.push({ id: "back", settingBox: this.endButton }) this.setKbd() + pageEvents.add(this.defaultButton, ["mousedown", "touchstart"], this.defaultSettings.bind(this)) pageEvents.add(this.endButton, ["mousedown", "touchstart"], 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"], + "up": ["u", "lsu"], + "previous": ["l", "lb", "lt", "lsl"], "next": ["d", "r", "rb", "rt", "lsd", "lsr"], "back": ["start", "a"] }, this.keyPressed.bind(this)) @@ -166,7 +158,8 @@ class SettingsView{ var kbdSettings = settings.getItem("keyboardSettings") this.kbd = { "confirm": ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]], - "previous": ["arrowleft", "arrowup", kbdSettings.ka_l[0]], + "up": ["arrowup"], + "previous": ["arrowleft", kbdSettings.ka_l[0]], "next": ["arrowright", "arrowdown", kbdSettings.ka_r[0]], "back": ["backspace", "escape"] } @@ -258,12 +251,16 @@ class SettingsView{ if(name === "confirm"){ if(selected.id === "back"){ this.onEnd() + }else if(selected.id === "default"){ + this.defaultSettings() }else{ this.setValue(selected.id) } - }else if(name === "previous" || name === "next"){ + }else if(name === "up" || name === "previous" || name === "next"){ selected.settingBox.classList.remove("selected") - this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1)) + do{ + this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1)) + }while(this.items[this.selected].id === "default" && name !== "previous") this.items[this.selected].settingBox.classList.add("selected") assets.sounds["se_ka"].play() }else if(name === "back"){ @@ -303,6 +300,27 @@ class SettingsView{ selected.valueDiv.classList.remove("selected") this.getValue(selected.id, selected.valueDiv) } + defaultSettings(event){ + if(event && event.type === "touchstart"){ + event.preventDefault() + } + var selectedIndex = this.items.findIndex(item => item.id === "default") + if(this.selected !== selectedIndex){ + this.items[this.selected].settingBox.classList.remove("selected") + this.selected = selectedIndex + this.items[this.selected].settingBox.classList.add("selected") + } + for(var i in settings.items){ + settings.setItem(i, null) + } + for(var i in this.items){ + var item = this.items[i] + if(item.valueDiv){ + this.getValue(item.id, item.valueDiv) + } + } + assets.sounds["se_don"].play() + } onEnd(event){ var touched = false if(event){ @@ -329,6 +347,7 @@ class SettingsView{ for(var i in this.items){ pageEvents.remove(this.items[i].settingBox, ["mousedown", "touchstart"]) } + delete this.defaultButton delete this.endButton delete this.items if(this.resolution !== settings.getItem("resolution")){ diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 39a3bde..9d692ba 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -117,14 +117,9 @@ don_r: "面(右)", ka_r: "ふち(右)" }, - gamepadLayout: { - name: "そうさタイプ設定", - a: "タイプA", - b: "タイプB", - c: "タイプC" - }, on: "オン", off: "オフ", + default: "既定値にリセット", ok: "OK" } this.browserSupport = { @@ -253,14 +248,9 @@ function StringsEn(){ don_r: "Right Surface", ka_r: "Right Rim" }, - gamepadLayout: { - name: "Gamepad Layout", - a: "Type A", - b: "Type B", - c: "Type C" - }, on: "On", off: "Off", + default: "Reset to Defaults", ok: "OK" } this.browserSupport = { @@ -389,14 +379,9 @@ function StringsCn(){ don_r: "Right Surface", ka_r: "Right Rim" }, - gamepadLayout: { - name: "操作类型设定", - a: "类型A", - b: "类型B", - c: "类型C" - }, on: "开", off: "关", + default: "重置为默认值", ok: "确定" } this.browserSupport = { @@ -525,14 +510,9 @@ function StringsTw(){ don_r: "Right Surface", ka_r: "Right Rim" }, - gamepadLayout: { - name: "操作類型設定", - a: "類型A", - b: "類型B", - c: "類型C" - }, on: "開", off: "關", + default: "重置為默認值", ok: "確定" } this.browserSupport = { @@ -661,14 +641,9 @@ function StringsKo(){ don_r: "Right Surface", ka_r: "Right Rim" }, - gamepadLayout: { - name: "조작 타입 설정", - a: "타입 A", - b: "타입 B", - c: "타입 C" - }, on: "온", off: "오프", + default: "기본값으로 재설정", ok: "확인" } this.browserSupport = { diff --git a/public/src/views/settings.html b/public/src/views/settings.html index bfaa4ee..2333ff5 100644 --- a/public/src/views/settings.html +++ b/public/src/views/settings.html @@ -2,6 +2,9 @@
+
From b3c0f8cd73591116a0e85cad9f125a7a1691a779 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 6 Apr 2019 13:19:10 +0300 Subject: [PATCH 23/31] Add language to settings --- public/src/css/main.css | 3 + public/src/js/pageevents.js | 1 + public/src/js/settings.js | 110 +++++++++++++++++++++++++++-------- public/src/js/strings.js | 21 ++++++- public/src/js/titlescreen.js | 35 ++--------- 5 files changed, 113 insertions(+), 57 deletions(-) diff --git a/public/src/css/main.css b/public/src/css/main.css index 9b44bb3..e312d72 100644 --- a/public/src/css/main.css +++ b/public/src/css/main.css @@ -181,6 +181,9 @@ kbd{ background: #ffb547; animation: 2s linear border-pulse infinite; } +.bold-fonts .setting-box{ + line-height: 1em; +} .setting-name{ position: relative; width: 50%; diff --git a/public/src/js/pageevents.js b/public/src/js/pageevents.js index a1bba1d..c180802 100644 --- a/public/src/js/pageevents.js +++ b/public/src/js/pageevents.js @@ -84,6 +84,7 @@ class PageEvents{ keyEvent(event){ if(this.kbd.indexOf(event.key.toLowerCase()) !== -1){ this.lastKeyEvent = Date.now() + event.preventDefault() } this.keyListeners.forEach(addedKeyCode => { this.checkListener(addedKeyCode.get("all"), event) diff --git a/public/src/js/settings.js b/public/src/js/settings.js index b72c13b..1056fd9 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -4,6 +4,11 @@ class Settings{ var phone = /Android|iPhone|iPad/.test(navigator.userAgent) this.items = { + language: { + type: "language", + options: ["ja", "en", "cn", "tw", "ko"], + default: this.getLang() + }, resolution: { type: "select", options: ["high", "medium", "low", "lowest"], @@ -31,13 +36,18 @@ class Settings{ 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 === "language"){ + this.storage[i] = localStorage.getItem("lang") + if(current.options.indexOf(this.storage[i]) === -1){ + this.storage[i] = null + } + }else if(i in storage){ if(current.type === "select" && current.options.indexOf(storage[i]) === -1){ this.storage[i] = null }else if(current.type === "keyboard"){ var obj = {} for(var j in current.default){ - if(storage[i][j] && storage[i][j][0]){ + if(storage[i] && storage[i][j] && storage[i][j][0]){ obj[j] = storage[i][j] }else{ obj = null @@ -65,9 +75,44 @@ class Settings{ setItem(name, value){ this.storage[name] = value try{ - localStorage.setItem("settings", JSON.stringify(this.storage)) + if(name === "language"){ + if(value){ + localStorage.setItem("lang", value) + }else{ + localStorage.removeItem("lang") + } + }else{ + var language = this.storage.language + delete this.storage.language + localStorage.setItem("settings", JSON.stringify(this.storage)) + this.storage.language = language + } }catch(e){} } + getLang(){ + if("languages" in navigator){ + var userLang = navigator.languages.slice() + userLang.unshift(navigator.language) + for(var i in userLang){ + for(var j in allStrings){ + if(allStrings[j].regex.test(userLang[i])){ + return j + } + } + } + } + return "ja" + } + setLang(lang, noEvent){ + strings = lang + var boldFonts = strings.font === "Microsoft YaHei, sans-serif" + loader.screen.style.fontFamily = strings.font + loader.screen.style.fontWeight = boldFonts ? "bold" : "" + loader.screen.classList[boldFonts ? "add" : "remove"]("bold-fonts") + if(!noEvent){ + pageEvents.send("language-change", lang.id) + } + } } class SettingsView{ @@ -81,15 +126,10 @@ class SettingsView{ } this.mode = "settings" - var tutorialTitle = document.getElementById("tutorial-title") - tutorialTitle.innerText = strings.gameSettings - tutorialTitle.setAttribute("alt", strings.gameSettings) + this.tutorialTitle = document.getElementById("tutorial-title") this.defaultButton = document.getElementById("settings-default") - this.defaultButton.innerText = strings.settings.default - this.defaultButton.setAttribute("alt", strings.settings.default) this.endButton = document.getElementById("tutorial-end-button") - this.endButton.innerText = strings.settings.ok - this.endButton.setAttribute("alt", strings.settings.ok) + this.setStrings() this.resolution = settings.getItem("resolution") var content = document.getElementById("tutorial-content") @@ -128,6 +168,7 @@ class SettingsView{ this.items.push({ id: i, settingBox: settingBox, + nameDiv: nameDiv, valueDiv: valueDiv }) } @@ -167,7 +208,9 @@ class SettingsView{ getValue(name, valueDiv){ var current = settings.items[name] var value = settings.getItem(name) - if(current.type === "select"){ + if(current.type === "language"){ + value = allStrings[value].name + " (" + value + ")" + }else if(current.type === "select"){ value = strings.settings[name][value] }else if(current.type === "toggle"){ value = value ? strings.settings.on : strings.settings.off @@ -199,7 +242,7 @@ class SettingsView{ this.selected = selectedIndex selected.settingBox.classList.add("selected") } - if(current.type === "select"){ + if(current.type === "language" || current.type === "select"){ value = current.options[this.mod(current.options.length, current.options.indexOf(value) + 1)] }else if(current.type === "toggle"){ value = !value @@ -215,6 +258,9 @@ class SettingsView{ settings.setItem(name, value) this.getValue(name, this.items[this.selected].valueDiv) assets.sounds["se_ka"].play() + if(current.type === "language"){ + this.setLang(allStrings[value]) + } } keyEvent(event){ if(event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9){ @@ -231,6 +277,7 @@ class SettingsView{ } } if(this.mode === "keyboard"){ + event.preventDefault() var currentKey = event.key.toLowerCase() for(var i in this.keyboardKeys){ if(this.keyboardKeys[i][0] === currentKey || !currentKey){ @@ -304,21 +351,13 @@ class SettingsView{ if(event && event.type === "touchstart"){ event.preventDefault() } - var selectedIndex = this.items.findIndex(item => item.id === "default") - if(this.selected !== selectedIndex){ - this.items[this.selected].settingBox.classList.remove("selected") - this.selected = selectedIndex - this.items[this.selected].settingBox.classList.add("selected") + if(this.mode === "keyboard"){ + this.keyboardBack(this.items[this.selected]) } for(var i in settings.items){ settings.setItem(i, null) } - for(var i in this.items){ - var item = this.items[i] - if(item.valueDiv){ - this.getValue(item.id, item.valueDiv) - } - } + this.setLang(allStrings[settings.getItem("language")]) assets.sounds["se_don"].play() } onEnd(event){ @@ -337,6 +376,30 @@ class SettingsView{ new SongSelect("settings", false, touched) }, 500) } + setLang(lang){ + settings.setLang(lang) + if(failedTests.length !== 0){ + showUnsupported(strings) + } + for(var i in this.items){ + var item = this.items[i] + if(item.valueDiv){ + var name = strings.settings[item.id].name + item.nameDiv.innerText = name + item.nameDiv.setAttribute("alt", name) + this.getValue(item.id, item.valueDiv) + } + } + this.setStrings() + } + setStrings(){ + this.tutorialTitle.innerText = strings.gameSettings + this.tutorialTitle.setAttribute("alt", strings.gameSettings) + this.defaultButton.innerText = strings.settings.default + this.defaultButton.setAttribute("alt", strings.settings.default) + this.endButton.innerText = strings.settings.ok + this.endButton.setAttribute("alt", strings.settings.ok) + } mod(length, index){ return ((index % length) + length) % length } @@ -347,6 +410,7 @@ class SettingsView{ for(var i in this.items){ pageEvents.remove(this.items[i].settingBox, ["mousedown", "touchstart"]) } + delete this.tutorialTitle delete this.defaultButton delete this.endButton delete this.items diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 9d692ba..57134ef 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -100,6 +100,9 @@ cancel: "キャンセル" } this.settings = { + language: { + name: "言語" + }, resolution: { name: "ゲームの解像度", high: "高", @@ -231,6 +234,9 @@ function StringsEn(){ cancel: "Cancel" } this.settings = { + language: { + name: "Language" + }, resolution: { name: "Game Resolution", high: "High", @@ -362,6 +368,9 @@ function StringsCn(){ cancel: "取消" } this.settings = { + language: { + name: "语言" + }, resolution: { name: "游戏分辨率", high: "高", @@ -373,7 +382,7 @@ function StringsCn(){ name: "触摸动画" }, keyboardSettings: { - name: "Keyboard Settings", + name: "键盘设置", ka_l: "Left Rim", don_l: "Left Surface", don_r: "Right Surface", @@ -493,6 +502,9 @@ function StringsTw(){ cancel: "取消" } this.settings = { + language: { + name: "語系" + }, resolution: { name: "遊戲分辨率", high: "高", @@ -504,7 +516,7 @@ function StringsTw(){ name: "觸摸動畫" }, keyboardSettings: { - name: "Keyboard Settings", + name: "鍵盤設置", ka_l: "Left Rim", don_l: "Left Surface", don_r: "Right Surface", @@ -624,6 +636,9 @@ function StringsKo(){ cancel: "취소" } this.settings = { + language: { + name: "언어" + }, resolution: { name: "게임 해상도", high: "높은", @@ -635,7 +650,7 @@ function StringsKo(){ name: "터치 애니메이션" }, keyboardSettings: { - name: "Keyboard Settings", + name: "키보드 설정", ka_l: "Left Rim", don_l: "Left Surface", don_r: "Right Surface", diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index 709de14..7e62525 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -14,7 +14,7 @@ class Titlescreen{ document.getElementById("globe-path").setAttribute("d", vectors.globe) this.logo = new Logo() } - this.lang = this.getLang() + this.lang = settings.getItem("language") this.setLang(allStrings[this.lang], true) if(songId){ @@ -101,32 +101,8 @@ class Titlescreen{ } } - getLang(){ - if(localStorage.lang && localStorage.lang in allStrings){ - return localStorage.lang - } - if("languages" in navigator){ - var userLang = navigator.languages.slice() - userLang.unshift(navigator.language) - for(var i in userLang){ - for(var j in allStrings){ - if(allStrings[j].regex.test(userLang[i])){ - return j - } - } - } - } - return "ja" - } - setLang(lang, initial){ - strings = lang - - loader.screen.style.fontFamily = strings.font - loader.screen.style.fontWeight = strings.font === "Microsoft YaHei, sans-serif" ? "bold" : "" - - if(failedTests.length !== 0){ - showUnsupported(strings) - } + setLang(lang, noEvent){ + settings.setLang(lang, noEvent || this.songId) if(this.songId){ return } @@ -141,9 +117,6 @@ class Titlescreen{ this.disclaimerCopyright.setAttribute("alt", strings.titleCopyright) this.logo.updateSubtitle() - if(!initial){ - pageEvents.send("language-change", lang.id) - } } addLangs(){ for(var i in allStrings){ @@ -158,7 +131,7 @@ class Titlescreen{ } langChange(){ this.lang = this.langDropdown.value - localStorage.lang = this.lang + settings.setItem("language", this.lang) this.setLang(allStrings[this.lang]) } From a33ff2a86808fcde4e6be9f725769dfafd42be85 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 6 Apr 2019 13:26:06 +0300 Subject: [PATCH 24/31] Fix keys not resetting --- public/src/js/settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/src/js/settings.js b/public/src/js/settings.js index 1056fd9..e57b2c1 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -358,6 +358,7 @@ class SettingsView{ settings.setItem(i, null) } this.setLang(allStrings[settings.getItem("language")]) + this.setKbd() assets.sounds["se_don"].play() } onEnd(event){ From a4d938e888d762cdba17b5e92d029c39656b3eca Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Sat, 6 Apr 2019 22:14:44 +0300 Subject: [PATCH 25/31] Move language settings from title screen --- public/assets/img/vectors.json | 3 - public/src/css/main.css | 4 +- public/src/css/titlescreen.css | 47 ---------- public/src/js/settings.js | 68 ++++++++++----- public/src/js/titlescreen.js | 37 +------- public/src/js/tutorial.js | 137 +++++++++++++++++------------- public/src/views/titlescreen.html | 5 -- 7 files changed, 131 insertions(+), 170 deletions(-) diff --git a/public/assets/img/vectors.json b/public/assets/img/vectors.json index 0e55a2a..536fb30 100644 --- a/public/assets/img/vectors.json +++ b/public/assets/img/vectors.json @@ -66,8 +66,5 @@ "", "logo5": -"", - -"globe": "" } diff --git a/public/src/css/main.css b/public/src/css/main.css index e312d72..11e4c64 100644 --- a/public/src/css/main.css +++ b/public/src/css/main.css @@ -176,7 +176,8 @@ kbd{ .setting-box:first-child{ margin-top: 0; } -#tutorial-content:not(:hover) .setting-box.selected, +.settings-outer #tutorial-content:not(:hover) .setting-box.selected, +#tutorial-outer:not(.settings-outer) .setting-box.selected, .setting-box:hover{ background: #ffb547; animation: 2s linear border-pulse infinite; @@ -192,6 +193,7 @@ kbd{ box-sizing: border-box; } #tutorial-content:not(:hover) .setting-box.selected .setting-name, +#tutorial-outer:not(.settings-outer) .setting-box.selected .setting-name, .setting-box:hover .setting-name{ color: #fff; z-index: 0; diff --git a/public/src/css/titlescreen.css b/public/src/css/titlescreen.css index 6a280a3..3ca80fc 100644 --- a/public/src/css/titlescreen.css +++ b/public/src/css/titlescreen.css @@ -38,53 +38,6 @@ -webkit-text-stroke: 0.25em #f00; filter: blur(0.3vmin); } -#lang{ - font-size: 3vmin; - position: absolute; - bottom: 0; - left: 0; - width: 7em; - height: 4em; - color: #000; - z-index: 5; -} -#lang:focus-within{ - outline: #4d90fe auto 5px; -} -#lang-dropdown{ - font-size: 1.7em; - font-family: Microsoft YaHei, sans-serif; - opacity: 0; - width: 100%; - height: 100%; - color: #000; - cursor: pointer; -} -#lang-id{ - position: absolute; - top: 0; - bottom: 0; - left: 2.6em; - font-family: TnT, Meiryo, sans-serif; - font-size: 1.5em; - font-weight: normal; - color: #fff; - line-height: 2.75em; - z-index: 0; -} -#lang-icon{ - position: absolute; - width: 2.8em; - height: 2.8em; - padding: 0.6em; - fill: currentColor; -} -#lang:hover #lang-icon{ - color: #f00; -} -#lang:hover #lang-id::before { - -webkit-text-stroke: 0.25em #f00; -} #title-disclaimer { text-align: center; position: absolute; diff --git a/public/src/js/settings.js b/public/src/js/settings.js index e57b2c1..d17c92b 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -116,30 +116,37 @@ class Settings{ } class SettingsView{ - constructor(touchEnabled){ + constructor(touchEnabled, tutorial){ this.touchEnabled = touchEnabled - loader.changePage("settings", false) - assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) - + this.tutorial = tutorial + if(!tutorial){ + loader.changePage("settings", tutorial) + assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) + this.defaultButton = document.getElementById("settings-default") + }else if(touchEnabled){ + document.getElementById("tutorial-outer").classList.add("settings-outer") + } if(touchEnabled){ document.getElementById("tutorial-outer").classList.add("touch-enabled") } this.mode = "settings" this.tutorialTitle = document.getElementById("tutorial-title") - this.defaultButton = document.getElementById("settings-default") this.endButton = document.getElementById("tutorial-end-button") - this.setStrings() + if(!tutorial){ + this.setStrings() + } this.resolution = settings.getItem("resolution") var content = document.getElementById("tutorial-content") this.items = [] - this.selected = 0 + this.selected = tutorial ? 1 : 0 for(let i in settings.items){ var current = settings.items[i] if( !touchEnabled && current.touch === true || - touchEnabled && current.touch === false + touchEnabled && current.touch === false || + tutorial && current.type !== "language" ){ continue } @@ -172,17 +179,19 @@ class SettingsView{ valueDiv: valueDiv }) } - this.items.push({ - id: "default", - settingBox: this.defaultButton - }) + if(!tutorial){ + this.items.push({ + id: "default", + settingBox: this.defaultButton + }) + pageEvents.add(this.defaultButton, ["mousedown", "touchstart"], this.defaultSettings.bind(this)) + } this.items.push({ id: "back", settingBox: this.endButton }) this.setKbd() - pageEvents.add(this.defaultButton, ["mousedown", "touchstart"], this.defaultSettings.bind(this)) pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) pageEvents.keyAdd(this, "all", "down", this.keyEvent.bind(this)) this.gamepad = new Gamepad({ @@ -192,8 +201,9 @@ class SettingsView{ "next": ["d", "r", "rb", "rt", "lsd", "lsr"], "back": ["start", "a"] }, this.keyPressed.bind(this)) - - pageEvents.send("settings") + if(!tutorial){ + pageEvents.send("settings") + } } setKbd(){ var kbdSettings = settings.getItem("keyboardSettings") @@ -308,7 +318,9 @@ class SettingsView{ do{ this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1)) }while(this.items[this.selected].id === "default" && name !== "previous") - this.items[this.selected].settingBox.classList.add("selected") + selected = this.items[this.selected] + selected.settingBox.classList.add("selected") + selected.settingBox.scrollIntoView() assets.sounds["se_ka"].play() }else if(name === "back"){ this.onEnd() @@ -362,6 +374,10 @@ class SettingsView{ assets.sounds["se_don"].play() } onEnd(event){ + if(this.tutorial){ + this.clean() + return this.tutorial.onEnd(event) + } var touched = false if(event){ if(event.type === "touchstart"){ @@ -394,12 +410,16 @@ class SettingsView{ this.setStrings() } setStrings(){ - this.tutorialTitle.innerText = strings.gameSettings - this.tutorialTitle.setAttribute("alt", strings.gameSettings) - this.defaultButton.innerText = strings.settings.default - this.defaultButton.setAttribute("alt", strings.settings.default) - this.endButton.innerText = strings.settings.ok - this.endButton.setAttribute("alt", strings.settings.ok) + if(this.tutorial){ + this.tutorial.setStrings() + }else{ + this.tutorialTitle.innerText = strings.gameSettings + this.tutorialTitle.setAttribute("alt", strings.gameSettings) + this.defaultButton.innerText = strings.settings.default + this.defaultButton.setAttribute("alt", strings.settings.default) + this.endButton.innerText = strings.settings.ok + this.endButton.setAttribute("alt", strings.settings.ok) + } } mod(length, index){ return ((index % length) + length) % length @@ -411,8 +431,10 @@ class SettingsView{ for(var i in this.items){ pageEvents.remove(this.items[i].settingBox, ["mousedown", "touchstart"]) } + if(this.defaultButton){ + delete this.defaultButton + } delete this.tutorialTitle - delete this.defaultButton delete this.endButton delete this.items if(this.resolution !== settings.getItem("resolution")){ diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index 7e62525..a5ba7b6 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -7,15 +7,11 @@ class Titlescreen{ this.titleScreen = document.getElementById("title-screen") this.proceed = document.getElementById("title-proceed") - this.langDropdown = document.getElementById("lang-dropdown") - this.langId = document.getElementById("lang-id") this.disclaimerText = document.getElementById("title-disclaimer-text") this.disclaimerCopyright = document.getElementById("title-disclaimer-copyright") - document.getElementById("globe-path").setAttribute("d", vectors.globe) this.logo = new Logo() } - this.lang = settings.getItem("language") - this.setLang(allStrings[this.lang], true) + this.setLang(allStrings[settings.getItem("language")]) if(songId){ if(localStorage.getItem("tutorial") === "true"){ @@ -24,11 +20,8 @@ class Titlescreen{ new Tutorial(false, this.songId) } }else{ - this.addLangs() - pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) pageEvents.add(this.titleScreen, ["mousedown", "touchstart"], this.onPressed.bind(this)) - pageEvents.add(this.langDropdown, "change", this.langChange.bind(this)) assets.sounds["v_title"].play() var kbdSettings = settings.getItem("keyboardSettings") @@ -86,7 +79,7 @@ class Titlescreen{ goNext(fromP2){ if(p2.session && !fromP2){ p2.send("songsel") - }else if(fromP2 || this.touched || localStorage.getItem("tutorial") === "true"){ + }else if(fromP2 || localStorage.getItem("tutorial") === "true"){ if(this.touched){ localStorage.setItem("tutorial", "true") } @@ -96,20 +89,17 @@ class Titlescreen{ }, 500) }else{ setTimeout(() => { - new Tutorial(false, this.songId) + new Tutorial(false, this.songId, this.touched) }, 500) } } - setLang(lang, noEvent){ - settings.setLang(lang, noEvent || this.songId) + settings.setLang(lang, true) if(this.songId){ return } this.proceed.innerText = strings.titleProceed this.proceed.setAttribute("alt", strings.titleProceed) - this.langId.innerText = strings.id.toUpperCase() - this.langId.setAttribute("alt", strings.id.toUpperCase()) this.disclaimerText.innerText = strings.titleDisclaimer this.disclaimerText.setAttribute("alt", strings.titleDisclaimer) @@ -118,34 +108,15 @@ class Titlescreen{ this.logo.updateSubtitle() } - addLangs(){ - for(var i in allStrings){ - var option = document.createElement("option") - option.value = i - if(i === this.lang){ - option.selected = true - } - option.appendChild(document.createTextNode(allStrings[i].name)) - this.langDropdown.appendChild(option) - } - } - langChange(){ - this.lang = this.langDropdown.value - settings.setItem("language", this.lang) - this.setLang(allStrings[this.lang]) - } - clean(){ this.gamepad.clean() this.logo.clean() assets.sounds["v_title"].stop() pageEvents.keyRemove(this, "all") pageEvents.remove(this.titleScreen, ["mousedown", "touchstart"]) - pageEvents.remove(this.langDropdown, "change") delete this.titleScreen delete this.proceed delete this.titleDisclaimer delete this.titleCopyright - delete this.langDropdown } } diff --git a/public/src/js/tutorial.js b/public/src/js/tutorial.js index 7a1f484..01a30a7 100644 --- a/public/src/js/tutorial.js +++ b/public/src/js/tutorial.js @@ -1,65 +1,32 @@ class Tutorial{ - constructor(fromSongSel, songId){ + constructor(fromSongSel, songId, touchEnabled){ this.fromSongSel = fromSongSel this.songId = songId - loader.changePage("tutorial", true) + this.touchEnabled = touchEnabled + loader.changePage("tutorial", fromSongSel || !touchEnabled) assets.sounds["bgm_setsume"].playLoop(0.1, false, 0, 1.054, 16.054) this.endButton = document.getElementById("tutorial-end-button") - var tutorialTitle = document.getElementById("tutorial-title") - tutorialTitle.innerText = strings.howToPlay - tutorialTitle.setAttribute("alt", strings.howToPlay) - var tutorialContent = document.getElementById("tutorial-content") - var kbdSettings = settings.getItem("keyboardSettings") - var keys = [ - kbdSettings.don_l[0].toUpperCase(), - kbdSettings.don_r[0].toUpperCase(), - kbdSettings.ka_l[0].toUpperCase(), - kbdSettings.ka_r[0].toUpperCase(), - "Q", "SHIFT", "CTRL" - ] - var keyIndex = 0 - strings.tutorial.basics.forEach(string => { - var par = document.createElement("p") - var stringKeys = string.split("%s") - stringKeys.forEach((stringKey, i) => { - if(i !== 0){ - this.insertKey(keys[keyIndex++], par) - } - this.insertText(stringKey, par) - }) - tutorialContent.appendChild(par) - }) - var par = document.createElement("p") - var span = document.createElement("span") - span.style.fontWeight = "bold" - span.innerText = strings.tutorial.otherControls - par.appendChild(span) - strings.tutorial.otherTutorial.forEach(string => { - par.appendChild(document.createElement("br")) - var stringKeys = string.split("%s") - stringKeys.forEach((stringKey, i) => { - if(i !== 0){ - this.insertKey(keys[keyIndex++], par) - } - this.insertText(stringKey, par) - }) - }) - tutorialContent.appendChild(par) - this.endButton.innerText = strings.tutorial.ok - this.endButton.setAttribute("alt", strings.tutorial.ok) + this.tutorialTitle = document.getElementById("tutorial-title") + this.tutorialDiv = document.createElement("div") + document.getElementById("tutorial-content").appendChild(this.tutorialDiv) + this.setStrings() - pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) - pageEvents.keyAdd(this, "all", "down", event => { - if(event.keyCode === 13 || event.keyCode === 27 || event.keyCode === 8){ - // Enter, Esc, Backspace - this.onEnd.bind(this) - } - }) - - this.gamepad = new Gamepad({ - "confirm": ["start", "b", "ls", "rs"] - }, this.onEnd.bind(this)) + if(fromSongSel){ + pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) + pageEvents.keyAdd(this, "all", "down", event => { + if(event.keyCode === 13 || event.keyCode === 27 || event.keyCode === 8){ + // Enter, Esc, Backspace + this.onEnd.bind(this) + } + }) + + this.gamepad = new Gamepad({ + "confirm": ["start", "b", "ls", "rs"] + }, this.onEnd.bind(this)) + }else{ + new SettingsView(touchEnabled, this) + } pageEvents.send("tutorial") } insertText(text, parent){ @@ -87,11 +54,65 @@ class Tutorial{ new SongSelect(this.fromSongSel ? "tutorial" : false, false, touched, this.songId) }, 500) } + setStrings(){ + if(!this.fromSongSel && this.touchEnabled){ + this.tutorialTitle.innerText = strings.gameSettings + this.tutorialTitle.setAttribute("alt", strings.gameSettings) + this.endButton.innerText = strings.settings.ok + this.endButton.setAttribute("alt", strings.settings.ok) + return + } + this.tutorialTitle.innerText = strings.howToPlay + this.tutorialTitle.setAttribute("alt", strings.howToPlay) + this.endButton.innerText = strings.tutorial.ok + this.endButton.setAttribute("alt", strings.tutorial.ok) + this.tutorialDiv.innerHTML = "" + var kbdSettings = settings.getItem("keyboardSettings") + var keys = [ + kbdSettings.don_l[0].toUpperCase(), + kbdSettings.don_r[0].toUpperCase(), + kbdSettings.ka_l[0].toUpperCase(), + kbdSettings.ka_r[0].toUpperCase(), + "Q", "SHIFT", "CTRL" + ] + var keyIndex = 0 + strings.tutorial.basics.forEach(string => { + var par = document.createElement("p") + var stringKeys = string.split("%s") + stringKeys.forEach((stringKey, i) => { + if(i !== 0){ + this.insertKey(keys[keyIndex++], par) + } + this.insertText(stringKey, par) + }) + this.tutorialDiv.appendChild(par) + }) + var par = document.createElement("p") + var span = document.createElement("span") + span.style.fontWeight = "bold" + span.innerText = strings.tutorial.otherControls + par.appendChild(span) + strings.tutorial.otherTutorial.forEach(string => { + par.appendChild(document.createElement("br")) + var stringKeys = string.split("%s") + stringKeys.forEach((stringKey, i) => { + if(i !== 0){ + this.insertKey(keys[keyIndex++], par) + } + this.insertText(stringKey, par) + }) + }) + this.tutorialDiv.appendChild(par) + } clean(){ - this.gamepad.clean() + if(this.fromSongSel){ + this.gamepad.clean() + pageEvents.remove(this.endButton, ["mousedown", "touchstart"]) + pageEvents.keyRemove(this, "all") + } assets.sounds["bgm_setsume"].stop() - pageEvents.remove(this.endButton, ["mousedown", "touchstart"]) - pageEvents.keyRemove(this, "all") + delete this.tutorialTitle delete this.endButton + delete this.tutorialDiv } } diff --git a/public/src/views/titlescreen.html b/public/src/views/titlescreen.html index e660734..449427f 100644 --- a/public/src/views/titlescreen.html +++ b/public/src/views/titlescreen.html @@ -6,8 +6,3 @@
-
- -
- -
From f05b2518f35e2b6ffd37b28c9a72b6a5ce8f90f8 Mon Sep 17 00:00:00 2001 From: LoveEevee Date: Tue, 16 Apr 2019 21:06:41 +0300 Subject: [PATCH 26/31] Split language and tutorial, add gamepad settings --- public/assets/img/img.css | 4 + public/assets/img/settings_gamepad.png | Bin 0 -> 490 bytes public/src/css/main.css | 183 -------------- public/src/css/view.css | 235 ++++++++++++++++++ public/src/js/about.js | 38 +-- public/src/js/assets.js | 7 +- public/src/js/controller.js | 9 +- public/src/js/debug.js | 9 +- public/src/js/game.js | 21 +- public/src/js/gameinput.js | 247 +++++++++++++++++++ public/src/js/gamepad.js | 11 +- public/src/js/keyboard.js | 327 +++++++------------------ public/src/js/mekadon.js | 18 +- public/src/js/pageevents.js | 24 +- public/src/js/scoresheet.js | 37 +-- public/src/js/session.js | 32 ++- public/src/js/settings.js | 314 +++++++++++++++--------- public/src/js/songselect.js | 89 +++---- public/src/js/strings.js | 54 +++- public/src/js/titlescreen.js | 64 ++--- public/src/js/tutorial.js | 86 +++---- public/src/js/view.js | 8 +- public/src/views/about.html | 12 +- public/src/views/session.html | 10 +- public/src/views/settings.html | 22 +- public/src/views/tutorial.html | 10 +- 26 files changed, 1036 insertions(+), 835 deletions(-) create mode 100644 public/assets/img/settings_gamepad.png create mode 100644 public/src/css/view.css create mode 100644 public/src/js/gameinput.js diff --git a/public/assets/img/img.css b/public/assets/img/img.css index 7d339c6..2a79238 100644 --- a/public/assets/img/img.css +++ b/public/assets/img/img.css @@ -19,3 +19,7 @@ .settings-outer{ background-image: url("bg_settings.png"); } +#gamepad-bg, +#gamepad-buttons{ + background-image: url("settings_gamepad.png"); +} diff --git a/public/assets/img/settings_gamepad.png b/public/assets/img/settings_gamepad.png new file mode 100644 index 0000000000000000000000000000000000000000..c287f06cc505d9858d2bc4bfb04d759bd9a92330 GIT binary patch literal 490 zcmeAS@N?(olHy`uVBq!ia0y~yU{V9(lN`)Ik?l+p%0P-az$e5NNH4Fly#=HgOM?7@ z862M7NCUE`db&7{F_6 M)78&qol`;+0G}!ymjD0& literal 0 HcmV?d00001 diff --git a/public/src/css/main.css b/public/src/css/main.css index 11e4c64..6927ff1 100644 --- a/public/src/css/main.css +++ b/public/src/css/main.css @@ -23,117 +23,6 @@ left: 0; z-index: -1; } -#tutorial-outer{ - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - position: absolute; - width: 100%; - height: 100%; -} -#tutorial{ - background: rgb(246, 234, 212); - color: black; - border: 0.25em black solid; - border-radius: 0.5em; - width: 800px; - padding: 1em; - margin: 1em; - font-size: 21px; - position: relative; -} -.touch-enabled #tutorial{ - font-size: 3vmin; -} -#tutorial-title{ - z-index: 1; - position: absolute; - color: white; - top: -0.7em; - font-size: 1.65em; -} -#tutorial-content{ - margin: 0.7em 0; - overflow-y: auto; - max-height: calc(100vh - 14em); -} -kbd{ - font-family: inherit; - padding: 0.1em 0.6em; - border: 1px solid #ccc; - font-size: 0.6em; - background-color: #f7f7f7; - color: #333; - box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset; - border-radius: 3px; - display: inline-block; - text-shadow: 0 1px 0 #fff; - line-height: 1.4; - white-space: nowrap; -} -.taibtn{ - display: inline-block; - background: #f6ead4; - padding: 0.4em 0.4em; - border-radius: 0.5em; - border: 0.1em rgba(218, 205, 178, 1) solid; - cursor: pointer; - font-size: 1.4em; - box-sizing: border-box; - color: #555; - text-align: center; -} -#tutorial-end-button{ - float: right; - padding: 0.4em 1.5em; - font-weight: bold; - border-color: #000; - color: #000; -} -.taibtn:hover, -.taibtn.selected, -#tutorial-end-button:hover, -#tutorial-end-button.selected{ - position: relative; - z-index: 1; - color: #fff; - background: #ffb547; - border-color: #fff; -} -.taibtn::before{ - padding-left: inherit; -} -#about-link-btns{ - float: left; - display: flex; -} -#about-link-btns .taibtn{ - margin-right: 0.4em; -} -#diag-txt textarea, -#diag-txt iframe{ - width: 100%; - height: 5em; - font-size: inherit; - resize: none; - word-break: break-all; - margin-bottom: 1em; - background: #fff; - border: 1px solid #a9a9a9; - user-select: all; -} -.text-warn{ - color: #d00; -} -.link-btn a{ - color: inherit; - text-decoration: none; - pointer-events: none; -} -.nowrap{ - white-space: nowrap; -} #session-invite{ width: 100%; height: 1.9em; @@ -149,78 +38,6 @@ kbd{ cursor: text; overflow: hidden; } -@keyframes border-pulse{ - 0%{border-color: #ff0} - 50%{border-color: rgba(255, 255, 0, 0)} - 100%{border-color: #ff0} -} -@keyframes border-pulse2{ - 0%{border-color: #e29e06} - 50%{border-color: rgba(226, 158, 6, 0)} - 100%{border-color: #e29e06} -} -.settings-outer{ - background-size: 50vh; -} -.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; -} -.settings-outer #tutorial-content:not(:hover) .setting-box.selected, -#tutorial-outer:not(.settings-outer) .setting-box.selected, -.setting-box:hover{ - background: #ffb547; - animation: 2s linear border-pulse infinite; -} -.bold-fonts .setting-box{ - line-height: 1em; -} -.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, -#tutorial-outer:not(.settings-outer) .setting-box.selected .setting-name, -.setting-box:hover .setting-name{ - color: #fff; - z-index: 0; -} -.setting-name::before{ - padding-left: 0.3em; -} -.setting-value{ - display: flex; - background: #fff; - width: 50%; - border-radius: 0.2em; - padding: 0.5em; - box-sizing: border-box; -} -.setting-value.selected{ - width: calc(50% + 0.2em); - margin: -0.1em; - border: 0.2em solid #e29e06; - padding: 0.4em; - animation: 2s linear border-pulse2 infinite; -} -.setting-value>div{ - padding: 0 0.4em; - overflow: hidden; - text-overflow: ellipsis; -} @keyframes bgscroll{ from{ background-position: 0 top; diff --git a/public/src/css/view.css b/public/src/css/view.css new file mode 100644 index 0000000..3b80242 --- /dev/null +++ b/public/src/css/view.css @@ -0,0 +1,235 @@ +.view-outer{ + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + position: absolute; + width: 100%; + height: 100%; +} +.view{ + background: rgb(246, 234, 212); + color: black; + border: 0.25em black solid; + border-radius: 0.5em; + width: 800px; + padding: 1em; + margin: 1em; + font-size: 21px; + position: relative; +} +.touch-enabled .view{ + font-size: 3vmin; +} +.view-title{ + z-index: 1; + position: absolute; + color: white; + top: -0.7em; + font-size: 1.65em; +} +.view-content{ + margin: 0.7em 0; + overflow-y: auto; + max-height: calc(100vh - 14em); +} +kbd{ + font-family: inherit; + padding: 0.1em 0.6em; + border: 1px solid #ccc; + font-size: 0.6em; + background-color: #f7f7f7; + color: #333; + box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset; + border-radius: 3px; + display: inline-block; + text-shadow: 0 1px 0 #fff; + line-height: 1.4; + white-space: nowrap; +} +.taibtn{ + display: inline-block; + background: #f6ead4; + padding: 0.4em 0.4em; + border-radius: 0.5em; + border: 0.1em rgba(218, 205, 178, 1) solid; + cursor: pointer; + font-size: 1.4em; + box-sizing: border-box; + color: #555; + text-align: center; +} +.view-end-button{ + float: right; + padding: 0.4em 1.5em; + font-weight: bold; + border-color: #000; + color: #000; + z-index: 1; +} +.taibtn:hover, +.taibtn.selected, +.view-end-button:hover, +.view-end-button.selected{ + position: relative; + color: #fff; + background: #ffb547; + border-color: #fff; +} +.taibtn::before, +.view-end-button::before{ + display: none; +} +.taibtn:hover::before, +.taibtn.selected::before, +.view-end-button:hover::before, +.view-end-button.selected::before{ + display: block +} +.taibtn::before{ + padding-left: inherit; +} +.left-buttons{ + float: left; + display: flex; +} +.left-buttons .taibtn{ + margin-right: 0.4em; +} +#diag-txt textarea, +#diag-txt iframe{ + width: 100%; + height: 5em; + font-size: inherit; + resize: none; + word-break: break-all; + margin-bottom: 1em; + background: #fff; + border: 1px solid #a9a9a9; + user-select: all; +} +.text-warn{ + color: #d00; +} +.link-btn a{ + color: inherit; + text-decoration: none; + pointer-events: none; +} +.nowrap{ + white-space: nowrap; +} +@keyframes border-pulse{ + 0%{border-color: #ff0} + 50%{border-color: rgba(255, 255, 0, 0)} + 100%{border-color: #ff0} +} +@keyframes border-pulse2{ + 0%{border-color: #e29e06} + 50%{border-color: rgba(226, 158, 6, 0)} + 100%{border-color: #e29e06} +} +.settings-outer{ + background-size: 50vh; +} +.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; +} +.settings-outer .view-content:not(:hover) .setting-box.selected, +.view-outer:not(.settings-outer) .setting-box.selected, +.setting-box:hover{ + background: #ffb547; + animation: 2s linear border-pulse infinite; +} +.bold-fonts .setting-box{ + line-height: 1em; +} +.setting-name{ + position: relative; + width: 50%; + padding: 0.3em; + font-size: 1.3em; + box-sizing: border-box; +} +.view-content:not(:hover) .setting-box.selected .setting-name, +.view-outer:not(.settings-outer) .setting-box.selected .setting-name, +.setting-box:hover .setting-name{ + color: #fff; + z-index: 0; +} +.setting-name::before{ + padding-left: 0.3em; +} +.setting-value{ + display: flex; + background: #fff; + width: 50%; + border-radius: 0.2em; + padding: 0.5em; + box-sizing: border-box; +} +.setting-value.selected{ + width: calc(50% + 0.2em); + margin: -0.1em; + border: 0.2em solid #e29e06; + padding: 0.4em; + animation: 2s linear border-pulse2 infinite; +} +.setting-value>div{ + padding: 0 0.4em; + overflow: hidden; + text-overflow: ellipsis; +} +.shadow-outer{ + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 1; +} +#settings-gamepad{ + display: none; +} +#settings-gamepad .view{ + position: absolute; + margin: auto; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 550px; + height: 417px; +} +#gamepad-bg{ + position: relative; + width: 550px; + height: 317px; + max-height: none; + background-repeat: none; + text-align: center; + font-size: 1.4em; + cursor: pointer; +} +#gamepad-buttons{ + position: absolute; + left: 141px; + top: 120px; + width: 282px; + height: 131px; + background-position: 0 -318px; + background-repeat: none; + pointer-events: none; +} diff --git a/public/src/js/about.js b/public/src/js/about.js index 95575b7..104d8a1 100644 --- a/public/src/js/about.js +++ b/public/src/js/about.js @@ -4,20 +4,20 @@ loader.changePage("about", true) cancelTouch = false - this.endButton = document.getElementById("tutorial-end-button") + this.endButton = this.getElement("view-end-button") this.diagTxt = document.getElementById("diag-txt") this.version = document.getElementById("version-link").href - this.tutorialOuter = document.getElementById("tutorial-outer") + this.tutorialOuter = this.getElement("view-outer") if(touchEnabled){ this.tutorialOuter.classList.add("touch-enabled") } this.linkIssues = document.getElementById("link-issues") this.linkEmail = document.getElementById("link-email") - var tutorialTitle = document.getElementById("tutorial-title") + var tutorialTitle = this.getElement("view-title") tutorialTitle.innerText = strings.aboutSimulator tutorialTitle.setAttribute("alt", strings.aboutSimulator) - var tutorialContent = document.getElementById("tutorial-content") + var tutorialContent = this.getElement("view-content") strings.about.bugReporting.forEach(string => { tutorialContent.appendChild(document.createTextNode(string)) tutorialContent.appendChild(document.createElement("br")) @@ -32,20 +32,18 @@ var versionUrl = gameConfig._version.url this.getLink(this.linkIssues).href = versionUrl + "issues" - var kbdSettings = settings.getItem("keyboardSettings") - this.kbd = { - confirm: ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]], - previous: ["arrowleft", "arrowup", kbdSettings.ka_l[0]], - next: ["arrowright", "arrowdown", kbdSettings.ka_r[0]], - back: ["backspace", "escape"] - } pageEvents.add(this.linkIssues, ["click", "touchend"], this.linkButton.bind(this)) pageEvents.add(this.linkEmail, ["click", "touchend"], this.linkButton.bind(this)) pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) - pageEvents.keyAdd(this, "all", "down", this.keyEvent.bind(this)) this.items = [this.linkIssues, this.linkEmail, this.endButton] this.selected = 2 + 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"], @@ -55,19 +53,8 @@ pageEvents.send("about", this.addDiag()) } - 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.key.toLowerCase()) !== -1){ - this.keyPressed(true, i) - break - } - } - } + getElement(name){ + return loader.screen.getElementsByClassName(name)[0] } keyPressed(pressed, name){ if(!pressed){ @@ -215,6 +202,7 @@ } clean(){ cancelTouch = true + this.keyboard.clean() this.gamepad.clean() pageEvents.remove(this.linkIssues, ["click", "touchend"]) pageEvents.remove(this.linkEmail, ["click", "touchend"]) diff --git a/public/src/js/assets.js b/public/src/js/assets.js index 80b46f3..e363842 100644 --- a/public/src/js/assets.js +++ b/public/src/js/assets.js @@ -7,6 +7,7 @@ var assets = { "scoresheet.js", "songselect.js", "keyboard.js", + "gameinput.js", "game.js", "controller.js", "circle.js", @@ -36,7 +37,8 @@ var assets = { "loadsong.css", "game.css", "debug.css", - "songbg.css" + "songbg.css", + "view.css" ], "assetsCss": [ "fonts/fonts.css", @@ -79,7 +81,8 @@ var assets = { "results_flowers.png", "results_mikoshi.png", "results_tetsuohana.png", - "results_tetsuohana2.png" + "results_tetsuohana2.png", + "settings_gamepad.png" ], "audioSfx": [ "se_cancel.wav", diff --git a/public/src/js/controller.js b/public/src/js/controller.js index 6e2baae..cf253b4 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -28,7 +28,7 @@ class Controller{ this.game = new Game(this, this.selectedSong, this.parsedSongData) this.view = new View(this) this.mekadon = new Mekadon(this, this.game) - this.keyboard = new Keyboard(this) + this.keyboard = new GameInput(this) this.playedSounds = {} } @@ -210,11 +210,8 @@ class Controller{ getKeys(){ return this.keyboard.getKeys() } - setKey(keyCode, down, ms){ - return this.keyboard.setKey(keyCode, down, ms) - } - getBindings(){ - return this.keyboard.getBindings() + setKey(pressed, name, ms){ + return this.keyboard.setKey(pressed, name, ms) } getElapsedTime(){ return this.game.elapsedTime diff --git a/public/src/js/debug.js b/public/src/js/debug.js index 844e8d1..a6fa9a7 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -202,11 +202,10 @@ class Debug{ this.controller.autoPlayEnabled = this.autoplayCheckbox.checked if(!this.controller.autoPlayEnabled){ var keyboard = debugObj.controller.keyboard - var kbd = keyboard.getBindings() - keyboard.setKey(kbd.don_l, false) - keyboard.setKey(kbd.don_r, false) - keyboard.setKey(kbd.ka_l, false) - keyboard.setKey(kbd.ka_r, false) + keyboard.setKey(false, "don_l") + keyboard.setKey(false, "don_r") + keyboard.setKey(false, "ka_l") + keyboard.setKey(false, "ka_r") } } } diff --git a/public/src/js/game.js b/public/src/js/game.js index 3417209..63a92b7 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -254,29 +254,28 @@ class Game{ return } var keys = this.controller.getKeys() - var kbd = this.controller.getBindings() - var don_l = keys[kbd["don_l"]] && !this.controller.isWaiting(kbd["don_l"], "score") - var don_r = keys[kbd["don_r"]] && !this.controller.isWaiting(kbd["don_r"], "score") - var ka_l = keys[kbd["ka_l"]] && !this.controller.isWaiting(kbd["ka_l"], "score") - var ka_r = keys[kbd["ka_r"]] && !this.controller.isWaiting(kbd["ka_r"], "score") + var don_l = keys["don_l"] && !this.controller.isWaiting("don_l", "score") + var don_r = keys["don_r"] && !this.controller.isWaiting("don_r", "score") + var ka_l = keys["ka_l"] && !this.controller.isWaiting("ka_l", "score") + var ka_r = keys["ka_r"] && !this.controller.isWaiting("ka_r", "score") var checkDon = () => { if(don_l && don_r){ - this.checkKey([kbd["don_l"], kbd["don_r"]], circle, "daiDon") + this.checkKey(["don_l", "don_r"], circle, "daiDon") }else if(don_l){ - this.checkKey([kbd["don_l"]], circle, "don") + this.checkKey(["don_l"], circle, "don") }else if(don_r){ - this.checkKey([kbd["don_r"]], circle, "don") + this.checkKey(["don_r"], circle, "don") } } var checkKa = () => { if(ka_l && ka_r){ - this.checkKey([kbd["ka_l"], kbd["ka_r"]], circle, "daiKa") + this.checkKey(["ka_l", "ka_r"], circle, "daiKa") }else if(ka_l){ - this.checkKey([kbd["ka_l"]], circle, "ka") + this.checkKey(["ka_l"], circle, "ka") }else if(ka_r){ - this.checkKey([kbd["ka_r"]], circle, "ka") + this.checkKey(["ka_r"], circle, "ka") } } var keyTime = this.controller.getKeyTime() diff --git a/public/src/js/gameinput.js b/public/src/js/gameinput.js new file mode 100644 index 0000000..4707652 --- /dev/null +++ b/public/src/js/gameinput.js @@ -0,0 +1,247 @@ +class GameInput{ + constructor(controller){ + this.controller = controller + this.game = this.controller.game + + this.keyboard = new Keyboard({ + ka_l: ["ka_l"], + don_l: ["don_l"], + don_r: ["don_r"], + ka_r: ["ka_r"], + pause: ["q", "esc"], + back: ["backspace"], + previous: ["left", "up"], + next: ["right", "down"], + confirm: ["enter", "space"] + }, this.keyPress.bind(this)) + this.keys = {} + this.waitKeyupScore = {} + this.waitKeyupSound = {} + this.waitKeyupMenu = {} + this.keyTime = { + "don": -Infinity, + "ka": -Infinity + } + this.keyboardEvents = 0 + + var layout = settings.getItem("gamepadLayout") + if(layout === "b"){ + var gameBtn = { + don_l: ["d", "r", "ls"], + don_r: ["a", "x", "rs"], + ka_l: ["u", "l", "lb", "lt"], + ka_r: ["b", "y", "rb", "rt"] + } + }else if(layout === "c"){ + var gameBtn = { + don_l: ["d", "l", "ls"], + don_r: ["a", "b", "rs"], + ka_l: ["u", "r", "lb", "lt"], + ka_r: ["x", "y", "rb", "rt"] + } + }else{ + var gameBtn = { + don_l: ["u", "d", "l", "r", "ls"], + don_r: ["a", "b", "x", "y", "rs"], + ka_l: ["lb", "lt"], + ka_r: ["rb", "rt"] + } + } + this.gamepad = new Gamepad(gameBtn) + this.gamepadInterval = setInterval(this.gamepadKeys.bind(this), 1000 / 60 / 2) + + this.gamepadMenu = new Gamepad({ + cancel: ["a"], + confirm: ["b", "ls", "rs"], + previous: ["u", "l", "lb", "lt", "lsu", "lsl"], + next: ["d", "r", "rb", "rt", "lsd", "lsr"], + pause: ["start"] + }) + + if(controller.multiplayer === 1){ + pageEvents.add(window, "beforeunload", event => { + if(p2.otherConnected){ + pageEvents.send("p2-abandoned", event) + } + }) + } + } + keyPress(pressed, name){ + if(!this.controller.autoPlayEnabled || this.game.isPaused() || name !== "don_l" && name !== "don_r" && name !== "ka_l" && name !== "ka_r"){ + this.setKey(pressed, name, this.game.getAccurateTime()) + } + } + checkGameKeys(){ + if(this.controller.autoPlayEnabled){ + this.checkKeySound("don_l", "don") + this.checkKeySound("don_r", "don") + this.checkKeySound("ka_l", "ka") + this.checkKeySound("ka_r", "ka") + } + } + gamepadKeys(){ + if(!this.game.isPaused() && !this.controller.autoPlayEnabled){ + this.gamepad.play((pressed, name) => { + if(pressed){ + if(this.keys[name]){ + this.setKey(false, name) + } + this.setKey(true, name, this.game.getAccurateTime()) + }else{ + this.setKey(false, name) + } + }) + } + } + checkMenuKeys(){ + if(!this.controller.multiplayer && !this.locked){ + var moveMenu = 0 + var ms = this.game.getAccurateTime() + this.gamepadMenu.play((pressed, name) => { + if(pressed){ + if(this.game.isPaused()){ + if(name === "cancel"){ + this.locked = true + return setTimeout(() => { + this.controller.togglePause() + this.locked = false + }, 200) + } + } + if(this.keys[name]){ + this.setKey(false, name) + } + this.setKey(true, name, ms) + }else{ + this.setKey(false, name) + } + }) + this.checkKey("pause", "menu", () => { + this.controller.togglePause() + for(var key in this.keyTime){ + this.keys[key] = null + this.keyTime[key] = -Infinity + } + }) + var moveMenuMinus = () => { + moveMenu = -1 + } + var moveMenuPlus = () => { + moveMenu = 1 + } + var moveMenuConfirm = () => { + if(this.game.isPaused()){ + this.locked = true + setTimeout(() => { + this.controller.view.pauseConfirm() + this.locked = false + }, 200) + } + } + this.checkKey("previous", "menu", moveMenuMinus) + this.checkKey("ka_l", "menu", moveMenuMinus) + this.checkKey("next", "menu", moveMenuPlus) + this.checkKey("ka_r", "menu", moveMenuPlus) + this.checkKey("confirm", "menu", moveMenuConfirm) + this.checkKey("don_l", "menu", moveMenuConfirm) + this.checkKey("don_r", "menu", moveMenuConfirm) + if(moveMenu && this.game.isPaused()){ + assets.sounds["se_ka"].play() + this.controller.view.pauseMove(moveMenu) + } + } + if(this.controller.multiplayer !== 2){ + this.checkKey("back", "menu", () => { + if(this.controller.multiplayer === 1 && p2.otherConnected){ + p2.send("gameend") + pageEvents.send("p2-abandoned") + } + this.controller.togglePause() + this.controller.songSelection() + }) + } + } + checkKey(name, type, callback){ + if(this.keys[name] && !this.isWaiting(name, type)){ + this.waitForKeyup(name, type) + callback() + } + } + checkKeySound(name, sound){ + this.checkKey(name, "sound", () => { + var circles = this.controller.getCircles() + var circle = circles[this.controller.getCurrentCircle()] + var currentTime = this.keyTime[name] + this.keyTime[sound] = currentTime + if(circle && !circle.isPlayed){ + if(circle.type === "balloon"){ + if(sound === "don" && circle.requiredHits - circle.timesHit <= 1){ + this.controller.playSound("se_balloon") + return + } + } + } + this.controller.playSound("neiro_1_" + sound) + }) + } + getKeys(){ + return this.keys + } + setKey(pressed, name, ms){ + if(pressed){ + this.keys[name] = true + this.waitKeyupScore[name] = false + this.waitKeyupSound[name] = false + this.waitKeyupMenu[name] = false + if(this.game.isPaused()){ + return + } + this.keyTime[name] = ms + if(name == "don_l" || name == "don_r"){ + this.checkKeySound(name, "don") + this.keyboardEvents++ + }else if(name == "ka_l" || name == "ka_r"){ + this.checkKeySound(name, "ka") + this.keyboardEvents++ + } + }else{ + this.keys[name] = false + this.waitKeyupScore[name] = false + this.waitKeyupSound[name] = false + this.waitKeyupMenu[name] = false + } + } + isWaiting(name, type){ + if(type === "score"){ + return this.waitKeyupScore[name] + }else if(type === "sound"){ + return this.waitKeyupSound[name] + }else if(type === "menu"){ + return this.waitKeyupMenu[name] + } + } + waitForKeyup(name, type){ + if(!this.keys[name]){ + return + } + if(type === "score"){ + this.waitKeyupScore[name] = true + }else if(type === "sound"){ + this.waitKeyupSound[name] = true + }else if(type === "menu"){ + this.waitKeyupMenu[name] = true + } + } + getKeyTime(){ + return this.keyTime + } + clean(){ + this.keyboard.clean() + this.gamepad.clean() + this.gamepadMenu.clean() + clearInterval(this.gamepadInterval) + if(this.controller.multiplayer === 1){ + pageEvents.remove(window, "beforeunload") + } + } +} diff --git a/public/src/js/gamepad.js b/public/src/js/gamepad.js index 2956c1a..a4b3327 100644 --- a/public/src/js/gamepad.js +++ b/public/src/js/gamepad.js @@ -1,6 +1,7 @@ class Gamepad{ constructor(bindings, callback){ this.bindings = bindings + this.callback = !!callback this.b = { "a": 0, "b": 1, @@ -87,6 +88,9 @@ class Gamepad{ for(var name in bindings[bind]){ var bindName = bindings[bind][name] this.checkButton(gamepads, this.b[bindName], bind, callback, force[bindName]) + if(!this.b){ + return + } } } break @@ -136,6 +140,11 @@ class Gamepad{ } } clean(){ - clearInterval(this.interval) + if(this.callback){ + clearInterval(this.interval) + } + delete this.bindings + delete this.b + delete this.btn } } diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index f038a44..949673b 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -1,266 +1,105 @@ class Keyboard{ - constructor(controller){ - this.controller = controller - this.game = this.controller.game - + constructor(bindings, callback){ + this.bindings = bindings + this.callback = callback + this.wildcard = false + this.substitute = { + "up": "arrowup", + "right": "arrowright", + "down": "arrowdown", + "left": "arrowleft", + "space": " ", + "esc": "escape", + "ctrl": "control", + "altgr": "altgraph" + } + this.btn = {} + this.update() + pageEvents.keyAdd(this, "all", "both", this.keyEvent.bind(this)) + pageEvents.blurAdd(this, this.blurEvent.bind(this)) + } + update(){ var kbdSettings = settings.getItem("keyboardSettings") - this.kbd = { - "ka_l": kbdSettings.ka_l[0], - "don_l": kbdSettings.don_l[0], - "don_r": kbdSettings.don_r[0], - "ka_r": kbdSettings.ka_r[0], - "pause": "q", - "back": "backspace", - "previous": "arrowleft", - "next": "arrowright", - "confirm": "enter" - } - this.kbdAlias = { - "pause": ["escape"], - "previous": ["arrowup"], - "next": ["arrowdown"], - "confirm": [" "] - } - this.keys = {} - this.waitKeyupScore = {} - this.waitKeyupSound = {} - this.waitKeyupMenu = {} - this.keyTime = { - "don": -Infinity, - "ka": -Infinity - } - this.keyboardEvents = 0 - - var gameBtn = {} - gameBtn[this.kbd["don_l"]] = ["u", "d", "l", "r", "ls"] - gameBtn[this.kbd["don_r"]] = ["a", "b", "x", "y", "rs"] - gameBtn[this.kbd["ka_l"]] = ["lb", "lt"] - gameBtn[this.kbd["ka_r"]] = ["rb", "rt"] - this.gamepad = new Gamepad(gameBtn) - this.gamepadInterval = setInterval(this.gamepadKeys.bind(this), 1000 / 60 / 2) - - var menuBtn = { - "cancel": ["a"], - } - menuBtn[this.kbd["confirm"]] = ["b", "ls", "rs"] - menuBtn[this.kbd["previous"]] = ["u", "l", "lb", "lt", "lsu", "lsl"], - menuBtn[this.kbd["next"]] = ["d", "r", "rb", "rt", "lsd", "lsr"] - menuBtn[this.kbd["pause"]] = ["start"] - this.gamepadMenu = new Gamepad(menuBtn) - - this.kbdSearch = {} - for(var name in this.kbdAlias){ - var list = this.kbdAlias[name] - for(var i in list){ - this.kbdSearch[list[i]] = this.kbd[name] + var drumKeys = {} + for(var name in kbdSettings){ + var keys = kbdSettings[name] + for(var i in keys){ + drumKeys[keys[i]] = name } } - for(var name in this.kbd){ - this.kbdSearch[this.kbd[name]] = this.kbd[name] - } - - pageEvents.keyAdd(this, "all", "both", event => { - if(event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9){ - // Escape, Backspace, Tab - event.preventDefault() - } - var key = this.kbdSearch[event.key.toLowerCase()] - if(key && !event.repeat && this.buttonEnabled(key)){ - var ms = this.game.getAccurateTime() - this.setKey(key, event.type === "keydown", ms) - if(event.type === "keydown"){ - this.keyboardEvents++ + this.kbd = {} + for(var name in this.bindings){ + var keys = this.bindings[name] + for(var i in keys){ + var key = keys[i] + if(key in drumKeys){ + continue } - } - }) - - if(controller.multiplayer === 1){ - pageEvents.add(window, "beforeunload", event => { - if(p2.otherConnected){ - pageEvents.send("p2-abandoned", event) - } - }) - } - } - getBindings(){ - return this.kbd - } - buttonEnabled(keyCode){ - if(this.controller.autoPlayEnabled){ - switch(keyCode){ - case this.kbd["don_l"]: - case this.kbd["don_r"]: - case this.kbd["ka_l"]: - case this.kbd["ka_r"]: - return false - } - } - return true - } - checkGameKeys(){ - if(this.controller.autoPlayEnabled){ - this.checkKeySound(this.kbd["don_l"], "don") - this.checkKeySound(this.kbd["don_r"], "don") - this.checkKeySound(this.kbd["ka_l"], "ka") - this.checkKeySound(this.kbd["ka_r"], "ka") - } - } - gamepadKeys(){ - if(!this.game.isPaused() && !this.controller.autoPlayEnabled){ - this.gamepad.play((pressed, keyCode) => { - if(pressed){ - if(this.keys[keyCode]){ - this.setKey(keyCode, false) - } - this.setKey(keyCode, true, this.game.getAccurateTime()) - }else{ - this.setKey(keyCode, false) - } - }) - } - } - checkMenuKeys(){ - if(!this.controller.multiplayer && !this.locked){ - var moveMenu = 0 - var ms = this.game.getAccurateTime() - this.gamepadMenu.play((pressed, keyCode) => { - if(pressed){ - if(this.game.isPaused()){ - if(keyCode === "cancel"){ - this.locked = true - return setTimeout(() => { - this.controller.togglePause() - this.locked = false - }, 200) + if(key in kbdSettings){ + var keyArray = kbdSettings[key] + for(var j in keyArray){ + key = keyArray[j] + if(!(key in this.kbd)){ + this.kbd[key] = name } } - if(this.keys[keyCode]){ - this.setKey(keyCode, false) - } - this.setKey(keyCode, true, ms) }else{ - this.setKey(keyCode, false) - } - }) - this.checkKey(this.kbd["pause"], "menu", () => { - this.controller.togglePause() - for(var key in this.keyTime){ - this.keys[key] = null - this.keyTime[key] = -Infinity - } - }) - var moveMenuMinus = () => { - moveMenu = -1 - } - var moveMenuPlus = () => { - moveMenu = 1 - } - var moveMenuConfirm = () => { - if(this.game.isPaused()){ - this.locked = true - setTimeout(() => { - this.controller.view.pauseConfirm() - this.locked = false - }, 200) - } - } - this.checkKey(this.kbd["previous"], "menu", moveMenuMinus) - this.checkKey(this.kbd["ka_l"], "menu", moveMenuMinus) - this.checkKey(this.kbd["next"], "menu", moveMenuPlus) - this.checkKey(this.kbd["ka_r"], "menu", moveMenuPlus) - this.checkKey(this.kbd["confirm"], "menu", moveMenuConfirm) - this.checkKey(this.kbd["don_l"], "menu", moveMenuConfirm) - this.checkKey(this.kbd["don_r"], "menu", moveMenuConfirm) - if(moveMenu && this.game.isPaused()){ - assets.sounds["se_ka"].play() - this.controller.view.pauseMove(moveMenu) - } - } - if(this.controller.multiplayer !== 2){ - this.checkKey(this.kbd["back"], "menu", () => { - if(this.controller.multiplayer === 1 && p2.otherConnected){ - p2.send("gameend") - pageEvents.send("p2-abandoned") - } - this.controller.togglePause() - this.controller.songSelection() - }) - } - } - checkKey(keyCode, type, callback){ - if(this.keys[keyCode] && !this.isWaiting(keyCode, type)){ - this.waitForKeyup(keyCode, type) - callback() - } - } - checkKeySound(keyCode, sound){ - this.checkKey(keyCode, "sound", () => { - var circles = this.controller.getCircles() - var circle = circles[this.controller.getCurrentCircle()] - var currentTime = this.keyTime[keyCode] - this.keyTime[sound] = currentTime - if(circle && !circle.isPlayed){ - if(circle.type === "balloon"){ - if(sound === "don" && circle.requiredHits - circle.timesHit <= 1){ - this.controller.playSound("se_balloon") - return + if(key in this.substitute){ + key = this.substitute[key] + } + if(!(key in this.kbd)){ + if(key === "wildcard"){ + this.wildcard = true + } + this.kbd[key] = name } } } - this.controller.playSound("neiro_1_" + sound) - }) + } } - getKeys(){ - return this.keys - } - setKey(keyCode, down, ms){ - if(down){ - this.keys[keyCode] = true - if(this.game.isPaused()){ - return + keyEvent(event){ + var key = event.key.toLowerCase() + if(key === "escape" || key === "backspace" || key === "tab"){ + event.preventDefault() + } + if(!event.repeat){ + var pressed = event.type === "keydown" + if(pressed){ + this.btn[key] = true + }else{ + delete this.btn[key] + if(key in this.kbd){ + for(var i in this.btn){ + if(this.kbd[i] === this.kbd[key]){ + return + } + } + } } - this.keyTime[keyCode] = ms - if(keyCode == this.kbd.don_l || keyCode == this.kbd.don_r){ - this.checkKeySound(keyCode, "don") - }else if(keyCode == this.kbd.ka_l || keyCode == this.kbd.ka_r){ - this.checkKeySound(keyCode, "ka") + if(key in this.kbd){ + this.callback(pressed, this.kbd[key], event) + }else if(this.wildcard){ + this.callback(pressed, this.kbd["wildcard"], event) } - }else{ - this.keys[keyCode] = false - this.waitKeyupScore[keyCode] = false - this.waitKeyupSound[keyCode] = false - this.waitKeyupMenu[keyCode] = false } } - isWaiting(keyCode, type){ - if(type === "score"){ - return this.waitKeyupScore[keyCode] - }else if(type === "sound"){ - return this.waitKeyupSound[keyCode] - }else if(type === "menu"){ - return this.waitKeyupMenu[keyCode] + blurEvent(){ + for(var key in this.btn){ + if(this.btn[key]){ + delete this.btn[key] + var name = this.kbd[key] || (this.wildcard ? "wildcard" : false) + if(name){ + this.callback(false, name) + } + } } } - waitForKeyup(keyCode, type){ - if(!this.keys[keyCode]){ - return - } - if(type === "score"){ - this.waitKeyupScore[keyCode] = true - }else if(type === "sound"){ - this.waitKeyupSound[keyCode] = true - }else if(type === "menu"){ - this.waitKeyupMenu[keyCode] = true - } - } - getKeyTime(){ - return this.keyTime - } clean(){ pageEvents.keyRemove(this, "all") - clearInterval(this.gamepadInterval) - if(this.controller.multiplayer === 1){ - pageEvents.remove(window, "beforeunload") - } + pageEvents.blurRemove(this) + delete this.bindings + delete this.callback + delete this.kbd + delete this.btn } } diff --git a/public/src/js/mekadon.js b/public/src/js/mekadon.js index eff1537..a58247e 100644 --- a/public/src/js/mekadon.js +++ b/public/src/js/mekadon.js @@ -50,7 +50,6 @@ class Mekadon{ } } playNow(circle, score, dai, reverse){ - var kbd = this.controller.getBindings() var type = circle.type var keyDai = false var playDai = !dai || dai === 2 @@ -70,20 +69,20 @@ class Mekadon{ } } if(type === "daiDon" && playDai){ - this.setKey(kbd["don_l"], ms) - this.setKey(kbd["don_r"], ms) + this.setKey("don_l", ms) + this.setKey("don_r", ms) this.lr = false keyDai = true }else if(type === "don" || type === "daiDon" || drumrollNotes && score !== 2){ - this.setKey(this.lr ? kbd["don_l"] : kbd["don_r"], ms) + this.setKey(this.lr ? "don_l" : "don_r", ms) this.lr = !this.lr }else if(type === "daiKa" && playDai){ - this.setKey(kbd["ka_l"], ms) - this.setKey(kbd["ka_r"], ms) + this.setKey("ka_l", ms) + this.setKey("ka_r", ms) this.lr = false keyDai = true }else if(type === "ka" || type === "daiKa" || drumrollNotes){ - this.setKey(this.lr ? kbd["ka_l"] : kbd["ka_r"], ms) + this.setKey(this.lr ? "ka_l" : "ka_r", ms) this.lr = !this.lr } if(type === "balloon"){ @@ -110,8 +109,7 @@ class Mekadon{ getMS(){ return this.controller.getElapsedTime() } - setKey(keyCode, ms){ - this.controller.setKey(keyCode, false) - this.controller.setKey(keyCode, true, ms) + setKey(name, ms){ + this.controller.setKey(true, name, ms) } } diff --git a/public/src/js/pageevents.js b/public/src/js/pageevents.js index c180802..c8c0af7 100644 --- a/public/src/js/pageevents.js +++ b/public/src/js/pageevents.js @@ -3,10 +3,12 @@ class PageEvents{ this.allEvents = new Map() this.keyListeners = new Map() this.mouseListeners = new Map() + this.blurListeners = new Map() this.lastKeyEvent = -Infinity this.add(window, "keydown", this.keyEvent.bind(this)) this.add(window, "keyup", this.keyEvent.bind(this)) this.add(window, "mousemove", this.mouseEvent.bind(this)) + this.add(window, "blur", this.blurEvent.bind(this)) this.kbd = [] } add(target, type, callback){ @@ -142,6 +144,15 @@ class PageEvents{ mouseRemove(target){ this.mouseListeners.delete(target) } + blurEvent(event){ + this.blurListeners.forEach(callback => callback(event)) + } + blurAdd(target, callback){ + this.blurListeners.set(target, callback) + } + blurRemove(target){ + this.blurListeners.delete(target) + } getMouse(){ return this.lastMouse } @@ -149,12 +160,13 @@ class PageEvents{ dispatchEvent(new CustomEvent(name, {detail: detail})) } setKbd(){ + this.kbd = [] var kbdSettings = settings.getItem("keyboardSettings") - this.kbd = [ - kbdSettings.ka_l[0], - kbdSettings.don_l[0], - kbdSettings.don_r[0], - kbdSettings.ka_r[0] - ] + for(var name in kbdSettings){ + var keys = kbdSettings[name] + for(var i in keys){ + this.kbd.push(keys[i]) + } + } } } diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 4d0360c..24fde5d 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -31,13 +31,12 @@ class Scoresheet{ this.draw = new CanvasDraw() this.canvasCache = new CanvasCache() - var kbdSettings = settings.getItem("keyboardSettings") - this.kbd = { - confirm: ["enter", " ", "escape", "backspace", kbdSettings.don_l[0], kbdSettings.don_r[0]] - } + this.keyboard = new Keyboard({ + confirm: ["enter", "space", "esc", "don_l", "don_r"] + }, this.keyDown.bind(this)) this.gamepad = new Gamepad({ confirm: ["a", "b", "start", "ls", "rs"] - }) + }, this.keyDown.bind(this)) this.difficulty = { "easy": 0, @@ -76,22 +75,8 @@ class Scoresheet{ touchEvents: controller.view.touchEvents }) } - keyDown(event, key){ - if(!key){ - if(event.repeat){ - return - } - for(var i in this.kbd){ - if(this.kbd[i].indexOf(event.key.toLowerCase()) !== -1){ - key = i - break - } - } - } - if(event && event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9){ - event.preventDefault() - } - if(key === "confirm"){ + keyDown(pressed){ + if(pressed && this.redrawing){ this.toNext() } } @@ -140,7 +125,6 @@ class Scoresheet{ this.winW = null this.winH = null - pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.mouseDown.bind(this)) if(!this.multiplayer){ @@ -180,12 +164,6 @@ class Scoresheet{ } var ms = this.getMS() - this.gamepad.play((pressed, keyCode) => { - if(pressed){ - this.keyDown(false, keyCode) - } - }) - if(!this.redrawRunning){ return } @@ -864,12 +842,13 @@ class Scoresheet{ } clean(){ + this.keyboard.clean() + this.gamepad.clean() this.draw.clean() this.canvasCache.clean() assets.sounds["bgm_result"].stop() snd.buffer.loadSettings() this.redrawRunning = false - pageEvents.keyRemove(this, "all") pageEvents.remove(this.canvas, ["mousedown", "touchstart"]) if(this.multiplayer !== 2 && this.touchEnabled){ pageEvents.remove(document.getElementById("touch-full-btn"), "touchend") diff --git a/public/src/js/session.js b/public/src/js/session.js index 43efa9e..0f08c67 100644 --- a/public/src/js/session.js +++ b/public/src/js/session.js @@ -2,13 +2,13 @@ class Session{ constructor(touchEnabled){ this.touchEnabled = touchEnabled loader.changePage("session", true) - this.endButton = document.getElementById("tutorial-end-button") + this.endButton = this.getElement("view-end-button") if(touchEnabled){ - document.getElementById("tutorial-outer").classList.add("touch-enabled") + this.getElement("view-outer").classList.add("touch-enabled") } this.sessionInvite = document.getElementById("session-invite") - var tutorialTitle = document.getElementById("tutorial-title") + var tutorialTitle = this.getElement("view-title") tutorialTitle.innerText = strings.session.multiplayerSession tutorialTitle.setAttribute("alt", strings.session.multiplayerSession) this.sessionInvite.parentNode.insertBefore(document.createTextNode(strings.session.linkTutorial), this.sessionInvite) @@ -16,11 +16,12 @@ class Session{ this.endButton.setAttribute("alt", strings.session.cancel) pageEvents.add(window, ["mousedown", "touchstart"], this.mouseDown.bind(this)) - pageEvents.keyOnce(this, 27, "down").then(this.onEnd.bind(this)) - + this.keyboard = new Keyboard({ + confirm: ["esc"] + }, this.keyPress.bind(this)) this.gamepad = new Gamepad({ - "confirm": ["start", "b", "ls", "rs"] - }, this.onEnd.bind(this)) + confirm: ["start", "b", "ls", "rs"] + }, this.keyPress.bind(this)) p2.hashLock = true pageEvents.add(p2, "message", response => { @@ -29,13 +30,16 @@ class Session{ p2.hash(response.value) }else if(response.type === "songsel"){ p2.clearMessage("users") - this.onEnd(false, true) + this.onEnd(true) pageEvents.send("session-start", "host") } }) p2.send("invite") pageEvents.send("session") } + getElement(name){ + return loader.screen.getElementsByClassName(name)[0] + } mouseDown(event){ if(event.type === "mousedown" && event.which !== 1){ return @@ -50,7 +54,12 @@ class Session{ this.onEnd() } } - onEnd(event, fromP2){ + keyPress(pressed){ + if(pressed){ + this.onEnd() + } + } + onEnd(fromP2){ if(!p2.session){ p2.send("leave") p2.hash("") @@ -59,9 +68,6 @@ class Session{ }else if(!fromP2){ return p2.send("songsel") } - if(event && event.type === "keydown"){ - event.preventDefault() - } this.clean() assets.sounds["se_don"].play() setTimeout(() => { @@ -69,9 +75,9 @@ class Session{ }, 500) } clean(){ + this.keyboard.clean() this.gamepad.clean() pageEvents.remove(window, ["mousedown", "touchstart"]) - pageEvents.keyRemove(this, 27) pageEvents.remove(p2, "message") delete this.endButton delete this.sessionInvite diff --git a/public/src/js/settings.js b/public/src/js/settings.js index d17c92b..572d1ae 100644 --- a/public/src/js/settings.js +++ b/public/src/js/settings.js @@ -28,6 +28,12 @@ class Settings{ ka_r: ["k"] }, touch: false + }, + gamepadLayout: { + type: "gamepad", + options: ["a", "b", "c"], + default: "a", + gamepad: true } } @@ -42,7 +48,7 @@ class Settings{ this.storage[i] = null } }else if(i in storage){ - if(current.type === "select" && current.options.indexOf(storage[i]) === -1){ + if((current.type === "select" || current.type === "gamepad") && current.options.indexOf(storage[i]) === -1){ this.storage[i] = null }else if(current.type === "keyboard"){ var obj = {} @@ -116,29 +122,50 @@ class Settings{ } class SettingsView{ - constructor(touchEnabled, tutorial){ + constructor(touchEnabled, tutorial, songId){ this.touchEnabled = touchEnabled this.tutorial = tutorial - if(!tutorial){ - loader.changePage("settings", tutorial) - assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) - this.defaultButton = document.getElementById("settings-default") - }else if(touchEnabled){ - document.getElementById("tutorial-outer").classList.add("settings-outer") - } + this.songId = songId + + loader.changePage("settings", tutorial) + assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) + this.defaultButton = document.getElementById("settings-default") if(touchEnabled){ - document.getElementById("tutorial-outer").classList.add("touch-enabled") + this.getElement("view-outer").classList.add("touch-enabled") + } + var gamepadEnabled = false + if("getGamepads" in navigator){ + var gamepads = navigator.getGamepads() + for(var i = 0; i < gamepads.length; i++){ + if(gamepads[i]){ + gamepadEnabled = true + break + } + } } this.mode = "settings" - this.tutorialTitle = document.getElementById("tutorial-title") - this.endButton = document.getElementById("tutorial-end-button") - if(!tutorial){ - this.setStrings() - } + this.keyboard = new Keyboard({ + "confirm": ["enter", "space", "don_l", "don_r"], + "up": ["up"], + "previous": ["left", "ka_l"], + "next": ["right", "down", "ka_r"], + "back": ["esc"], + "other": ["wildcard"] + }, this.keyPressed.bind(this)) + this.gamepad = new Gamepad({ + "confirm": ["b", "ls", "rs"], + "up": ["u", "lsu"], + "previous": ["l", "lb", "lt", "lsl"], + "next": ["d", "r", "rb", "rt", "lsd", "lsr"], + "back": ["start", "a"] + }, this.keyPressed.bind(this)) + + this.viewTitle = this.getElement("view-title") + this.endButton = this.getElement("view-end-button") this.resolution = settings.getItem("resolution") - var content = document.getElementById("tutorial-content") + var content = this.getElement("view-content") this.items = [] this.selected = tutorial ? 1 : 0 for(let i in settings.items){ @@ -146,6 +173,7 @@ class SettingsView{ if( !touchEnabled && current.touch === true || touchEnabled && current.touch === false || + !gamepadEnabled && current.gamepad === true || tutorial && current.type !== "language" ){ continue @@ -166,12 +194,7 @@ class SettingsView{ if(this.items.length === this.selected){ settingBox.classList.add("selected") } - pageEvents.add(settingBox, ["mousedown", "touchstart"], event => { - if(event.type !== "mousedown" || event.which === 1){ - event.preventDefault() - this.setValue(i) - } - }) + this.addTouch(settingBox, event => this.setValue(i)) this.items.push({ id: i, settingBox: settingBox, @@ -179,58 +202,82 @@ class SettingsView{ valueDiv: valueDiv }) } - if(!tutorial){ + if(tutorial){ + this.defaultButton.style.display = "none" + this.endButton.classList.add("selected") + }else{ this.items.push({ id: "default", settingBox: this.defaultButton }) - pageEvents.add(this.defaultButton, ["mousedown", "touchstart"], this.defaultSettings.bind(this)) + this.addTouch(this.defaultButton, this.defaultSettings.bind(this)) } this.items.push({ id: "back", settingBox: this.endButton }) + this.addTouch(this.endButton, this.onEnd.bind(this)) - this.setKbd() - pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) - pageEvents.keyAdd(this, "all", "down", this.keyEvent.bind(this)) - this.gamepad = new Gamepad({ - "confirm": ["b", "ls", "rs"], - "up": ["u", "lsu"], - "previous": ["l", "lb", "lt", "lsl"], - "next": ["d", "r", "rb", "rt", "lsd", "lsr"], - "back": ["start", "a"] - }, this.keyPressed.bind(this)) - if(!tutorial){ - pageEvents.send("settings") - } + this.gamepadSettings = document.getElementById("settings-gamepad") + this.addTouch(this.gamepadSettings, event => { + if(event.target === event.currentTarget){ + this.gamepadBack() + } + }) + this.gamepadTitle = this.gamepadSettings.getElementsByClassName("view-title")[0] + this.gamepadEndButton = this.gamepadSettings.getElementsByClassName("view-end-button")[0] + this.addTouch(this.gamepadEndButton, event => this.gamepadBack(true)) + this.gamepadBg = document.getElementById("gamepad-bg") + this.addTouch(this.gamepadBg, event => this.gamepadSet(1)) + this.gamepadButtons = document.getElementById("gamepad-buttons") + this.gamepadValue = document.getElementById("gamepad-value") + + this.setStrings() + + pageEvents.send("settings") } - setKbd(){ - var kbdSettings = settings.getItem("keyboardSettings") - this.kbd = { - "confirm": ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]], - "up": ["arrowup"], - "previous": ["arrowleft", kbdSettings.ka_l[0]], - "next": ["arrowright", "arrowdown", kbdSettings.ka_r[0]], - "back": ["backspace", "escape"] - } + getElement(name){ + return loader.screen.getElementsByClassName(name)[0] + } + addTouch(element, callback){ + pageEvents.add(element, ["mousedown", "touchstart"], event => { + if(event.type === "touchstart"){ + event.preventDefault() + this.touched = true + }else if(event.which !== 1){ + return + }else{ + this.touched = false + } + callback(event) + }) + } + removeTouch(element){ + pageEvents.remove(element, ["mousedown", "touchstart"]) } getValue(name, valueDiv){ var current = settings.items[name] var value = settings.getItem(name) if(current.type === "language"){ value = allStrings[value].name + " (" + value + ")" - }else if(current.type === "select"){ + }else if(current.type === "select" || current.type === "gamepad"){ value = strings.settings[name][value] }else if(current.type === "toggle"){ value = value ? strings.settings.on : strings.settings.off }else if(current.type === "keyboard"){ valueDiv.innerHTML = "" for(var i in value){ - var key = document.createElement("div") - key.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10" - key.innerText = value[i][0].toUpperCase() - valueDiv.appendChild(key) + var keyDiv = document.createElement("div") + keyDiv.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10" + var key = value[i][0] + for(var j in this.keyboard.substitute){ + if(this.keyboard.substitute[j] === key){ + key = j + break + } + } + keyDiv.innerText = key.toUpperCase() + valueDiv.appendChild(keyDiv) } return } @@ -264,6 +311,12 @@ class SettingsView{ this.keyboardSet() assets.sounds["se_don"].play() return + }else if(current.type === "gamepad"){ + this.mode = "gamepad" + this.gamepadSelected = current.options.indexOf(value) + this.gamepadSet() + assets.sounds["se_don"].play() + return } settings.setItem(name, value) this.getValue(name, this.items[this.selected].valueDiv) @@ -272,37 +325,11 @@ class SettingsView{ this.setLang(allStrings[value]) } } - 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.key.toLowerCase()) !== -1){ - if(this.mode !== "keyboard" || i === "back"){ - this.keyPressed(true, i) - return - } - } - } - if(this.mode === "keyboard"){ - event.preventDefault() - var currentKey = event.key.toLowerCase() - for(var i in this.keyboardKeys){ - if(this.keyboardKeys[i][0] === currentKey || !currentKey){ - return - } - } - this.keyboardKeys[this.keyboardCurrent] = [currentKey] - this.keyboardSet() - } - } - } - keyPressed(pressed, name){ + keyPressed(pressed, name, event){ if(!pressed){ return } + this.touched = false var selected = this.items[this.selected] if(this.mode === "settings"){ if(name === "confirm"){ @@ -325,9 +352,30 @@ class SettingsView{ }else if(name === "back"){ this.onEnd() } + }else if(this.mode === "gamepad"){ + if(name === "confirm"){ + this.gamepadBack(true) + }else if(name === "up" || name === "previous" || name === "next"){ + this.gamepadSet(name === "next" ? 1 : -1) + }else if(name === "back"){ + this.gamepadBack() + } }else if(this.mode === "keyboard"){ if(name === "back"){ this.keyboardBack(selected) + assets.sounds["se_cancel"].play() + }else{ + event.preventDefault() + var currentKey = event.key.toLowerCase() + for(var i in this.keyboardKeys){ + if(this.keyboardKeys[i][0] === currentKey || !currentKey){ + return + } + } + var current = this.keyboardCurrent + assets.sounds[current === "ka_l" || current === "ka_r" ? "se_ka" : "se_don"].play() + this.keyboardKeys[current] = [currentKey] + this.keyboardSet() } } } @@ -336,21 +384,28 @@ class SettingsView{ var current = settings.items[selected.id] selected.valueDiv.innerHTML = "" for(var i in current.default){ - var key = document.createElement("div") - key.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10" + var keyDiv = document.createElement("div") + keyDiv.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10" if(this.keyboardKeys[i]){ - key.innerText = this.keyboardKeys[i][0].toUpperCase() - selected.valueDiv.appendChild(key) + var key = this.keyboardKeys[i][0] + for(var j in this.keyboard.substitute){ + if(this.keyboard.substitute[j] === key){ + key = j + break + } + } + keyDiv.innerText = key.toUpperCase() + selected.valueDiv.appendChild(keyDiv) }else{ - key.innerText = "[" + strings.settings[selected.id][i] + "]" - selected.valueDiv.appendChild(key) + keyDiv.innerText = "[" + strings.settings[selected.id][i] + "]" + selected.valueDiv.appendChild(keyDiv) this.keyboardCurrent = i return } } settings.setItem(selected.id, this.keyboardKeys) this.keyboardBack(selected) - this.setKbd() + this.keyboard.update() pageEvents.setKbd() } keyboardBack(selected){ @@ -359,10 +414,38 @@ class SettingsView{ selected.valueDiv.classList.remove("selected") this.getValue(selected.id, selected.valueDiv) } - defaultSettings(event){ - if(event && event.type === "touchstart"){ - event.preventDefault() + gamepadSet(diff){ + if(this.mode !== "gamepad"){ + return } + var selected = this.items[this.selected] + var current = settings.items[selected.id] + if(diff){ + this.gamepadSelected = this.mod(current.options.length, this.gamepadSelected + diff) + assets.sounds["se_ka"].play() + } + var opt = current.options[this.gamepadSelected] + this.gamepadValue.innerText = strings.settings[selected.id][opt] + this.gamepadButtons.style.backgroundPosition = "0 " + (-318 - 132 * this.gamepadSelected) + "px" + this.gamepadSettings.style.display = "block" + } + gamepadBack(save){ + if(this.mode !== "gamepad"){ + return + } + if(save){ + var selected = this.items[this.selected] + var current = settings.items[selected.id] + settings.setItem(selected.id, current.options[this.gamepadSelected]) + this.getValue(selected.id, selected.valueDiv) + assets.sounds["se_don"].play() + }else{ + assets.sounds["se_cancel"].play() + } + this.gamepadSettings.style.display = "" + this.mode = "settings" + } + defaultSettings(){ if(this.mode === "keyboard"){ this.keyboardBack(this.items[this.selected]) } @@ -370,27 +453,19 @@ class SettingsView{ settings.setItem(i, null) } this.setLang(allStrings[settings.getItem("language")]) - this.setKbd() + this.keyboard.update() + pageEvents.setKbd() assets.sounds["se_don"].play() } - onEnd(event){ - if(this.tutorial){ - this.clean() - return this.tutorial.onEnd(event) - } - var touched = false - if(event){ - if(event.type === "touchstart"){ - event.preventDefault() - touched = true - }else if(event.which !== 1){ - return - } - } + onEnd(){ this.clean() assets.sounds["se_don"].play() setTimeout(() => { - new SongSelect("settings", false, touched) + if(this.tutorial && !this.touched){ + new Tutorial(false, this.songId) + }else{ + new SongSelect(this.tutorial ? false : "settings", false, this.touched, this.songId) + } }, 500) } setLang(lang){ @@ -410,33 +485,44 @@ class SettingsView{ this.setStrings() } setStrings(){ - if(this.tutorial){ - this.tutorial.setStrings() - }else{ - this.tutorialTitle.innerText = strings.gameSettings - this.tutorialTitle.setAttribute("alt", strings.gameSettings) + this.viewTitle.innerText = strings.gameSettings + this.viewTitle.setAttribute("alt", strings.gameSettings) + this.endButton.innerText = strings.settings.ok + this.endButton.setAttribute("alt", strings.settings.ok) + this.gamepadTitle.innerText = strings.settings.gamepadLayout.name + this.gamepadTitle.setAttribute("alt", strings.settings.gamepadLayout.name) + this.gamepadEndButton.innerText = strings.settings.ok + this.gamepadEndButton.setAttribute("alt", strings.settings.ok) + if(!this.tutorial){ this.defaultButton.innerText = strings.settings.default this.defaultButton.setAttribute("alt", strings.settings.default) - this.endButton.innerText = strings.settings.ok - this.endButton.setAttribute("alt", strings.settings.ok) } } mod(length, index){ return ((index % length) + length) % length } clean(){ + this.keyboard.clean() this.gamepad.clean() assets.sounds["bgm_settings"].stop() - pageEvents.keyRemove(this, "all") for(var i in this.items){ - pageEvents.remove(this.items[i].settingBox, ["mousedown", "touchstart"]) + this.removeTouch(this.items[i].settingBox) } if(this.defaultButton){ delete this.defaultButton } + this.removeTouch(this.gamepadSettings) + this.removeTouch(this.gamepadEndButton) + this.removeTouch(this.gamepadBg) delete this.tutorialTitle delete this.endButton delete this.items + delete this.gamepadSettings + delete this.gamepadTitle + delete this.gamepadEndButton + delete this.gamepadBg + delete this.gamepadButtons + delete this.gamepadValue if(this.resolution !== settings.getItem("resolution")){ for(var i in assets.image){ if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_")){ diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index ea7ba1b..f50dbc1 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -289,16 +289,17 @@ class SongSelect{ this.startPreview(true) this.pressedKeys = {} - var kbdSettings = settings.getItem("keyboardSettings") - this.kbd = { - confirm: ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]], + this.keyboard = new Keyboard({ + confirm: ["enter", "space", "don_l", "don_r"], back: ["escape"], - left: ["arrowleft", kbdSettings.ka_l[0]], - right: ["arrowright", kbdSettings.ka_r[0]], - up: ["arrowup"], - down: ["arrowdown"], - session: ["backspace"] - } + left: ["left", "ka_l"], + right: ["right", "ka_r"], + up: ["up"], + down: ["down"], + session: ["backspace"], + ctrl: ["ctrl"], + shift: ["shift"] + }, this.keyPress.bind(this)) this.gamepad = new Gamepad({ confirm: ["b", "start", "ls", "rs"], back: ["a"], @@ -309,13 +310,12 @@ class SongSelect{ session: ["back"], ctrl: ["y"], shift: ["x"] - }) + }, this.keyPress.bind(this)) if(!assets.customSongs){ this.startP2() } - pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) pageEvents.add(loader.screen, "mousemove", this.mouseMove.bind(this)) pageEvents.add(loader.screen, "mouseleave", () => { this.state.moveHover = null @@ -341,61 +341,47 @@ class SongSelect{ } } - keyDown(event, key){ - if(key){ - var modifiers = { - shift: this.pressedKeys["shift"], - ctrl: this.pressedKeys["ctrl"] + keyPress(pressed, name, event){ + if(pressed){ + if(!this.pressedKeys[name]){ + this.pressedKeys[name] = this.getMS() + 300 } }else{ - var modifiers = { - shift: event.shiftKey, - ctrl: event.ctrlKey - } - for(var i in this.kbd){ - if(this.kbd[i].indexOf(event.key.toLowerCase()) !== -1){ - key = i - break - } - } - } - if(key === "ctrl" || key === "shift" || !this.redrawRunning){ + this.pressedKeys[name] = 0 return } - - if(event && (event.keyCode === 27 || event.keyCode === 8 || event.keyCode === 9)){ - // Escape, Backspace, Tab - event.preventDefault() + if(name === "ctrl" || name === "shift" || !this.redrawRunning){ + return } if(this.state.screen === "song"){ - if(key === "confirm"){ + if(name === "confirm"){ this.toSelectDifficulty() - }else if(key === "back"){ + }else if(name === "back"){ this.toTitleScreen() - }else if(key === "session"){ + }else if(name === "session"){ this.toSession() - }else if(key === "left"){ + }else if(name === "left"){ this.moveToSong(-1) - }else if(key === "right"){ + }else if(name === "right"){ this.moveToSong(1) } }else if(this.state.screen === "difficulty"){ - if(key === "confirm"){ + if(name === "confirm"){ if(this.selectedDiff === 0){ this.toSongSelect() }else if(this.selectedDiff === 1){ this.toOptions(1) }else{ - this.toLoadSong(this.selectedDiff - this.diffOptions.length, modifiers.shift, modifiers.ctrl) + this.toLoadSong(this.selectedDiff - this.diffOptions.length, this.pressedKeys["shift"], this.pressedKeys["ctrl"]) } - }else if(key === "back" || key === "session"){ + }else if(name === "back" || name === "session"){ this.toSongSelect() - }else if(key === "left"){ + }else if(name === "left"){ this.moveToDiff(-1) - }else if(key === "right"){ + }else if(name === "right"){ this.moveToDiff(1) - }else if(this.selectedDiff === 1 && (key === "up" || key === "down")){ - this.toOptions(key === "up" ? -1 : 1) + }else if(this.selectedDiff === 1 && (name === "up" || name === "down")){ + this.toOptions(name === "up" ? -1 : 1) } } } @@ -781,20 +767,10 @@ class SongSelect{ requestAnimationFrame(this.redrawBind) var ms = this.getMS() - this.gamepad.play((pressed, keyCode) => { - if(pressed){ - if(!this.pressedKeys[keyCode]){ - this.pressedKeys[keyCode] = ms + 300 - this.keyDown(false, keyCode) - } - }else{ - this.pressedKeys[keyCode] = 0 - } - }) for(var key in this.pressedKeys){ if(this.pressedKeys[key]){ if(ms >= this.pressedKeys[key] + 50){ - this.keyDown(false, key) + this.keyPress(true, key) this.pressedKeys[key] = ms } } @@ -1957,6 +1933,8 @@ class SongSelect{ } clean(){ + this.keyboard.clean() + this.gamepad.clean() this.clearHash() this.draw.clean() this.songTitleCache.clean() @@ -1979,7 +1957,6 @@ class SongSelect{ song.preview_sound.clean() } }) - pageEvents.keyRemove(this, "all") pageEvents.remove(loader.screen, ["mousemove", "mouseleave", "mousedown", "touchstart"]) pageEvents.remove(this.canvas, "touchend") pageEvents.remove(p2, "message") diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 57134ef..eb77015 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -120,6 +120,12 @@ don_r: "面(右)", ka_r: "ふち(右)" }, + gamepadLayout: { + name: "そうさタイプ設定", + a: "タイプA", + b: "タイプB", + c: "タイプC" + }, on: "オン", off: "オフ", default: "既定値にリセット", @@ -254,6 +260,12 @@ function StringsEn(){ don_r: "Right Surface", ka_r: "Right Rim" }, + gamepadLayout: { + name: "Gamepad Layout", + a: "Type A", + b: "Type B", + c: "Type C" + }, on: "On", off: "Off", default: "Reset to Defaults", @@ -383,10 +395,16 @@ function StringsCn(){ }, keyboardSettings: { name: "键盘设置", - ka_l: "Left Rim", - don_l: "Left Surface", - don_r: "Right Surface", - ka_r: "Right Rim" + ka_l: "边缘(左)", + don_l: "表面(左)", + don_r: "表面(右)", + ka_r: "边缘(右)" + }, + gamepadLayout: { + name: "操作类型设定", + a: "类型A", + b: "类型B", + c: "类型C" }, on: "开", off: "关", @@ -517,10 +535,16 @@ function StringsTw(){ }, keyboardSettings: { name: "鍵盤設置", - ka_l: "Left Rim", - don_l: "Left Surface", - don_r: "Right Surface", - ka_r: "Right Rim" + ka_l: "邊緣(左)", + don_l: "表面(左)", + don_r: "表面(右)", + ka_r: "邊緣(右)" + }, + gamepadLayout: { + name: "操作類型設定", + a: "類型A", + b: "類型B", + c: "類型C" }, on: "開", off: "關", @@ -651,10 +675,16 @@ function StringsKo(){ }, keyboardSettings: { name: "키보드 설정", - ka_l: "Left Rim", - don_l: "Left Surface", - don_r: "Right Surface", - ka_r: "Right Rim" + ka_l: "가장자리 (왼쪽)", + don_l: "표면 (왼쪽)", + don_r: "표면 (오른쪽)", + ka_r: "가장자리 (오른쪽)" + }, + gamepadLayout: { + name: "조작 타입 설정", + a: "타입 A", + b: "타입 B", + c: "타입 C" }, on: "온", off: "오프", diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index a5ba7b6..6068a93 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -17,24 +17,26 @@ class Titlescreen{ if(localStorage.getItem("tutorial") === "true"){ new SongSelect(false, false, this.touched, this.songId) }else{ - new Tutorial(false, this.songId) + new SettingsView(false, true, this.songId) } }else{ - pageEvents.keyAdd(this, "all", "down", this.keyDown.bind(this)) - pageEvents.add(this.titleScreen, ["mousedown", "touchstart"], this.onPressed.bind(this)) + pageEvents.add(this.titleScreen, ["mousedown", "touchstart"], event => { + if(event.type === "touchstart"){ + event.preventDefault() + this.touched = true + }else if(event.type === "mousedown" && event.which !== 1){ + return + } + this.onPressed(true) + }) assets.sounds["v_title"].play() - var kbdSettings = settings.getItem("keyboardSettings") - this.kbd = { - confirm: ["enter", " ", kbdSettings.don_l[0], kbdSettings.don_r[0]] - } + this.keyboard = new Keyboard({ + confirm: ["enter", "space", "don_l", "don_r"] + }, this.onPressed.bind(this)) this.gamepad = new Gamepad({ confirm: ["a", "b", "x", "y", "start", "ls", "rs"] - }, pressed => { - if(pressed){ - this.onPressed() - } - }) + }, this.onPressed.bind(this)) if(p2.session){ pageEvents.add(p2, "message", response => { if(response.type === "songsel"){ @@ -46,35 +48,13 @@ class Titlescreen{ } } - keyDown(event, key){ - if(!key){ - if(event.repeat || event.target === this.langDropdown){ - return - } - for(var i in this.kbd){ - if(this.kbd[i].indexOf(event.key.toLowerCase()) !== -1){ - key = i - break - } - } + onPressed(pressed, name){ + if(pressed){ + this.titleScreen.style.cursor = "auto" + this.clean() + assets.sounds["se_don"].play() + this.goNext() } - if(key === "confirm"){ - this.onPressed() - } - } - onPressed(event){ - if(event){ - if(event.type === "touchstart"){ - event.preventDefault() - this.touched = true - }else if(event.type === "mousedown" && event.which !== 1){ - return - } - } - this.titleScreen.style.cursor = "auto" - this.clean() - assets.sounds["se_don"].play() - this.goNext() } goNext(fromP2){ if(p2.session && !fromP2){ @@ -89,7 +69,7 @@ class Titlescreen{ }, 500) }else{ setTimeout(() => { - new Tutorial(false, this.songId, this.touched) + new SettingsView(this.touched, true, this.songId) }, 500) } } @@ -109,10 +89,10 @@ class Titlescreen{ this.logo.updateSubtitle() } clean(){ + this.keyboard.clean() this.gamepad.clean() this.logo.clean() assets.sounds["v_title"].stop() - pageEvents.keyRemove(this, "all") pageEvents.remove(this.titleScreen, ["mousedown", "touchstart"]) delete this.titleScreen delete this.proceed diff --git a/public/src/js/tutorial.js b/public/src/js/tutorial.js index 01a30a7..711265c 100644 --- a/public/src/js/tutorial.js +++ b/public/src/js/tutorial.js @@ -1,34 +1,37 @@ class Tutorial{ - constructor(fromSongSel, songId, touchEnabled){ + constructor(fromSongSel, songId){ this.fromSongSel = fromSongSel this.songId = songId - this.touchEnabled = touchEnabled - loader.changePage("tutorial", fromSongSel || !touchEnabled) + loader.changePage("tutorial", true) assets.sounds["bgm_setsume"].playLoop(0.1, false, 0, 1.054, 16.054) - this.endButton = document.getElementById("tutorial-end-button") + this.endButton = this.getElement("view-end-button") - this.tutorialTitle = document.getElementById("tutorial-title") + this.tutorialTitle = this.getElement("view-title") this.tutorialDiv = document.createElement("div") - document.getElementById("tutorial-content").appendChild(this.tutorialDiv) + this.getElement("view-content").appendChild(this.tutorialDiv) this.setStrings() - if(fromSongSel){ - pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) - pageEvents.keyAdd(this, "all", "down", event => { - if(event.keyCode === 13 || event.keyCode === 27 || event.keyCode === 8){ - // Enter, Esc, Backspace - this.onEnd.bind(this) - } - }) - - this.gamepad = new Gamepad({ - "confirm": ["start", "b", "ls", "rs"] - }, this.onEnd.bind(this)) - }else{ - new SettingsView(touchEnabled, this) - } + pageEvents.add(this.endButton, ["mousedown", "touchstart"], event => { + if(event.type === "touchstart"){ + event.preventDefault() + this.touched = true + }else if(event.type === "mousedown" && event.which !== 1){ + return + } + this.onEnd(true) + }) + this.keyboard = new Keyboard({ + confirm: ["enter", "space", "esc", "don_l", "don_r"] + }, this.onEnd.bind(this)) + this.gamepad = new Gamepad({ + confirm: ["start", "b", "ls", "rs"] + }, this.onEnd.bind(this)) + pageEvents.send("tutorial") } + getElement(name){ + return loader.screen.getElementsByClassName(name)[0] + } insertText(text, parent){ parent.appendChild(document.createTextNode(text)) } @@ -37,43 +40,30 @@ class Tutorial{ kbd.innerText = key parent.appendChild(kbd) } - onEnd(event){ - var touched = false - if(event){ - if(event.type === "touchstart"){ - event.preventDefault() - touched = true - }else if(event.which !== 1){ - return - } + onEnd(pressed, name){ + if(pressed){ + this.clean() + assets.sounds["se_don"].play() + localStorage.setItem("tutorial", "true") + setTimeout(() => { + new SongSelect(this.fromSongSel ? "tutorial" : false, false, this.touched, this.songId) + }, 500) } - this.clean() - assets.sounds["se_don"].play() - localStorage.setItem("tutorial", "true") - setTimeout(() => { - new SongSelect(this.fromSongSel ? "tutorial" : false, false, touched, this.songId) - }, 500) } setStrings(){ - if(!this.fromSongSel && this.touchEnabled){ - this.tutorialTitle.innerText = strings.gameSettings - this.tutorialTitle.setAttribute("alt", strings.gameSettings) - this.endButton.innerText = strings.settings.ok - this.endButton.setAttribute("alt", strings.settings.ok) - return - } this.tutorialTitle.innerText = strings.howToPlay this.tutorialTitle.setAttribute("alt", strings.howToPlay) this.endButton.innerText = strings.tutorial.ok this.endButton.setAttribute("alt", strings.tutorial.ok) this.tutorialDiv.innerHTML = "" var kbdSettings = settings.getItem("keyboardSettings") + var pauseKey = pageEvents.kbd.indexOf("q") === -1 ? "Q" : "ESC" var keys = [ kbdSettings.don_l[0].toUpperCase(), kbdSettings.don_r[0].toUpperCase(), kbdSettings.ka_l[0].toUpperCase(), kbdSettings.ka_r[0].toUpperCase(), - "Q", "SHIFT", "CTRL" + pauseKey, "SHIFT", "CTRL" ] var keyIndex = 0 strings.tutorial.basics.forEach(string => { @@ -105,11 +95,9 @@ class Tutorial{ this.tutorialDiv.appendChild(par) } clean(){ - if(this.fromSongSel){ - this.gamepad.clean() - pageEvents.remove(this.endButton, ["mousedown", "touchstart"]) - pageEvents.keyRemove(this, "all") - } + this.keyboard.clean() + this.gamepad.clean() + pageEvents.remove(this.endButton, ["mousedown", "touchstart"]) assets.sounds["bgm_setsume"].stop() delete this.tutorialTitle delete this.endButton diff --git a/public/src/js/view.js b/public/src/js/view.js index f045b09..06c30c7 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -830,11 +830,10 @@ ) // Taiko pressed keys - var kbd = this.controller.getBindings() var keys = ["ka_l", "ka_r", "don_l", "don_r"] for(var i = 0; i < keys.length; i++){ - var keyMS = ms - keyTime[kbd[keys[i]]] + var keyMS = ms - keyTime[keys[i]] if(keyMS < 130){ if(keyMS > 70 && !this.touchEnabled){ ctx.globalAlpha = this.draw.easeOut(1 - (keyMS - 70) / 60) @@ -1769,11 +1768,10 @@ } touchNote(note){ var keyboard = this.controller.keyboard - var kbd = keyboard.getBindings() var ms = this.controller.game.getAccurateTime() this.touch = ms - keyboard.setKey(kbd[note], false) - keyboard.setKey(kbd[note], true, ms) + keyboard.setKey(false, note) + keyboard.setKey(true, note, ms) } mod(length, index){ return ((index % length) + length) % length diff --git a/public/src/views/about.html b/public/src/views/about.html index f6165c7..3506eb3 100644 --- a/public/src/views/about.html +++ b/public/src/views/about.html @@ -1,9 +1,9 @@ -
-
-
-
+
+
+
+
- -
+
diff --git a/public/src/views/session.html b/public/src/views/session.html index 5e57005..f47ce54 100644 --- a/public/src/views/session.html +++ b/public/src/views/session.html @@ -1,9 +1,9 @@ -
-
-
-
+
+
+
+
-
+
diff --git a/public/src/views/settings.html b/public/src/views/settings.html index 2333ff5..6a35a91 100644 --- a/public/src/views/settings.html +++ b/public/src/views/settings.html @@ -1,10 +1,20 @@ -
-
-
-
-