From 0655b7929387967f9e2a1e58a02a7f981439f016 Mon Sep 17 00:00:00 2001 From: KatieFrogs <23621460+KatieFrogs@users.noreply.github.com> Date: Thu, 17 Feb 2022 23:50:07 +0300 Subject: [PATCH] Bug fixes - Change song select mouse wheel song scrolling to be instant - Clicking on don chan in account settings toggles the animation - If the music is too long for the chart, the results screen is shown earlier - Fix weird BPM values freezing the browser (zero, negative, and very large) - Add a warning to the page when JavaScript is disabled in the browser - Fix Chrome auto dark mode by forcing light mode on the page - Add a meta keywords tag to the page - Fix plugin names getting cut off in the menu - Delay the function editing of the EditFunction class in plugins to the start() function instead of load() - When stopping one of the plugins, all the plugins have to be stopped in reverse order and started again so that patched code of a stopped plugin does not linger around - Fix importing plugins that have a SyntaxError - Fix plugins getting the same internal name when added without one, causing them to not appear in the plugin settings - Support editing args in EditFunction for plugins - Prevent multiple websockets from being opened - Fix page freezing after selecting Random song with no songs - Fix the back button being repeated twice when there are no songs - Fix /admin/users not accepting case insensitive usernames - Pressing enter on the Delete Account field does the expected action instead of refreshing the page - Better error message when custom folder access is denied - Fix being able to start netplay in custom songs after refreshing the page (#383) - Fix an error when importing songs from previous session and clicking on the white spot where you normally start multiplayer session - Fix canvas elements becoming smaller than 1x1 resolution and crashing the game (#390) - Fix song frame shadow cache on song select not being cleared when resizing the browser window, causing it to become blurry - Fix a pause-restart error when you hit both confirm keys on the restart button --- app.py | 11 ++-- public/src/css/view.css | 15 ++++++ public/src/js/abstractfile.js | 2 +- public/src/js/account.js | 20 +++++++- public/src/js/canvascache.js | 4 +- public/src/js/canvasdraw.js | 6 +-- public/src/js/canvastest.js | 4 +- public/src/js/controller.js | 7 +++ public/src/js/game.js | 4 +- public/src/js/importsongs.js | 5 +- public/src/js/loadsong.js | 4 +- public/src/js/logo.js | 4 +- public/src/js/p2.js | 38 +++++++++----- public/src/js/plugins.js | 97 ++++++++++++++++++++++++----------- public/src/js/scoresheet.js | 4 +- public/src/js/songselect.js | 55 +++++++++++--------- public/src/js/soundbuffer.js | 10 +++- public/src/js/strings.js | 4 ++ public/src/js/tutorial.js | 82 ++++++++++++++++++++++++----- public/src/js/view.js | 8 ++- templates/index.html | 14 +++-- 21 files changed, 286 insertions(+), 112 deletions(-) diff --git a/app.py b/app.py index 8fd1e55..5fb216c 100644 --- a/app.py +++ b/app.py @@ -371,12 +371,15 @@ def route_admin_users_post(): max_level = admin['user_level'] - 1 username = request.form.get('username') - level = int(request.form.get('level')) or 0 + try: + level = int(request.form.get('level')) or 0 + except ValueError: + level = 0 - user = db.users.find_one({'username': username}) + user = db.users.find_one({'username_lower': username.lower()}) if not user: flash('Error: User was not found.') - elif admin_name == username: + elif admin['username'] == user['username']: flash('Error: You cannot modify your own level.') else: user_level = user['user_level'] @@ -386,7 +389,7 @@ def route_admin_users_post(): flash('Error: This user has higher level than you.') else: output = {'user_level': level} - db.users.update_one({'username': username}, {'$set': output}) + db.users.update_one({'username': user['username']}, {'$set': output}) flash('User updated.') return render_template('admin_users.html', config=get_config(), max_level=max_level, username=username, level=level) diff --git a/public/src/css/view.css b/public/src/css/view.css index 0324d93..640931c 100644 --- a/public/src/css/view.css +++ b/public/src/css/view.css @@ -201,6 +201,20 @@ kbd{ .setting-name::before{ padding-left: 0.3em; } +.setting-name::after{ + content: ""; + display: block; + position: absolute; + top: 0; + right: 0; + width: 40px; + height: 100%; + background-image: linear-gradient(90deg, transparent, #f6ead4 90%); +} +.view-content:not(:hover) .setting-box.selected .setting-name::after, +.setting-box:hover .setting-name::after{ + background-image: linear-gradient(90deg, transparent, #ffb547 90%); +} .setting-value{ display: flex; background: #fff; @@ -403,6 +417,7 @@ kbd{ } .customdon-canvas{ width: 13em; + cursor: pointer; } .customdon-div label{ display: block; diff --git a/public/src/js/abstractfile.js b/public/src/js/abstractfile.js index 84b0526..9855687 100644 --- a/public/src/js/abstractfile.js +++ b/public/src/js/abstractfile.js @@ -13,7 +13,7 @@ function filePermission(file){ if(response === "granted"){ return file }else{ - return Promise.reject(file) + return Promise.reject(strings.accessNotGrantedError) } }) } diff --git a/public/src/js/account.js b/public/src/js/account.js index d4b7754..b2584ef 100644 --- a/public/src/js/account.js +++ b/public/src/js/account.js @@ -46,6 +46,8 @@ class Account{ this.inputForms.push(this.displayname) this.redrawRunning = true + this.redrawPaused = matchMedia("(prefers-reduced-motion: reduce)").matches + this.redrawForce = true this.customdonRedrawBind = this.customdonRedraw.bind(this) this.start = new Date().getTime() this.frames = [ @@ -57,6 +59,7 @@ class Account{ this.customdonCache = new CanvasCache() this.customdonCache.resize(723 * 2, 1858, 1) this.customdonCanvas = this.getElement("customdon-canvas") + pageEvents.add(this.customdonCanvas, "click", this.customdonPause.bind(this)) this.customdonCtx = this.customdonCanvas.getContext("2d") this.customdonBodyFill = this.getElement("customdon-bodyfill") this.customdonBodyFill.value = account.don.body_fill @@ -120,6 +123,11 @@ class Account{ pageEvents.add(this.inputForms[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this)) } } + customdonPause(){ + this.redrawPaused = !this.redrawPaused + this.redrawForce = true + this.start = new Date().getTime() + } customdonChange(){ var ctx = this.customdonCtx this.customdonCache.clear() @@ -148,6 +156,7 @@ class Account{ id: "bodyFill" }) }) + this.redrawForce = true } customdonReset(event){ if(event.type === "touchstart"){ @@ -162,12 +171,16 @@ class Account{ return } requestAnimationFrame(this.customdonRedrawBind) - if(!document.hasFocus()){ + if(!document.hasFocus() || this.redrawPaused && !this.redrawForce){ return } var ms = new Date().getTime() var ctx = this.customdonCtx - var frame = this.frames[Math.floor((ms - this.start) / 30) % this.frames.length] + if(this.redrawPaused){ + var frame = 0 + }else{ + var frame = this.frames[Math.floor((ms - this.start) / 30) % this.frames.length] + } var w = 360 var h = 184 var sx = Math.floor(frame / 10) * (w + 2) @@ -183,6 +196,7 @@ class Account{ sx, sy, w, h, -26, 0, w, h ) + this.redrawForce = false } showDiv(event, div){ if(event){ @@ -318,6 +332,7 @@ class Account{ onFormPress(event){ event.stopPropagation() if(event.type === "keypress" && event.keyCode === 13){ + event.preventDefault() if(this.mode === "account"){ this.onSave() }else{ @@ -611,6 +626,7 @@ class Account{ } this.redrawRunning = false this.customdonCache.clean() + pageEvents.remove(this.customdonCanvas, "click") pageEvents.remove(this.customdonBodyFill, ["change", "input"]) pageEvents.remove(this.customdonFaceFill, ["change", "input"]) pageEvents.remove(this.customdonResetBtn, ["click", "touchstart"]) diff --git a/public/src/js/canvascache.js b/public/src/js/canvascache.js index bb67c38..afa2cfb 100644 --- a/public/src/js/canvascache.js +++ b/public/src/js/canvascache.js @@ -27,8 +27,8 @@ class CanvasCache{ this.h = h this.lastW = 0 this.lastH = 0 - this.canvas.width = this.w * this.scale - this.canvas.height = this.h * this.scale + this.canvas.width = Math.max(1, this.w * this.scale) + this.canvas.height = Math.max(1, this.h * this.scale) this.ctx.scale(this.scale, this.scale) } get(config, callback, setOnly){ diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index d341623..e80529e 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -157,7 +157,7 @@ ctx.fillRect(0, 0, w, h) } if(config.cached){ - if(this.songFrameCache.w !== config.frameCache.w){ + if(this.songFrameCache.w !== config.frameCache.w || this.songFrameCache.scale !== config.frameCache.ratio){ this.songFrameCache.resize(config.frameCache.w, config.frameCache.h, config.frameCache.ratio) } this.songFrameCache.get({ @@ -1680,8 +1680,8 @@ if(amount >= 1){ return callback(ctx) }else if(amount >= 0){ - this.tmpCanvas.width = winW || ctx.canvas.width - this.tmpCanvas.height = winH || ctx.canvas.height + this.tmpCanvas.width = Math.max(1, winW || ctx.canvas.width) + this.tmpCanvas.height = Math.max(1, winH || ctx.canvas.height) callback(this.tmpCtx) ctx.save() ctx.globalAlpha = amount diff --git a/public/src/js/canvastest.js b/public/src/js/canvastest.js index da20dd6..882c1b5 100644 --- a/public/src/js/canvastest.js +++ b/public/src/js/canvastest.js @@ -7,8 +7,8 @@ class CanvasTest{ var pixelRatio = window.devicePixelRatio || 1 var width = innerWidth * pixelRatio var height = innerHeight * pixelRatio - this.canvas.width = width - this.canvas.height = height + this.canvas.width = Math.max(1, width) + this.canvas.height = Math.max(1, height) this.ctx = this.canvas.getContext("2d") this.ctx.scale(pixelRatio, pixelRatio) this.ratio = pixelRatio diff --git a/public/src/js/controller.js b/public/src/js/controller.js index cb01da9..e1b7e81 100644 --- a/public/src/js/controller.js +++ b/public/src/js/controller.js @@ -231,6 +231,9 @@ class Controller{ this.view.displayScore(score, notPlayed, bigNote) } songSelection(fadeIn, showWarning){ + if(this.cleaned){ + return + } if(!fadeIn){ this.clean() } @@ -241,6 +244,9 @@ class Controller{ } } restartSong(){ + if(this.cleaned){ + return + } this.clean() if(this.multiplayer){ new LoadSong(this.selectedSong, false, true, this.touchEnabled) @@ -363,6 +369,7 @@ class Controller{ return true } clean(){ + this.cleaned = true if(this.multiplayer === 1){ this.syncWith.clean() } diff --git a/public/src/js/game.js b/public/src/js/game.js index c649936..27eefb2 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -506,10 +506,10 @@ class Game{ p2.send("gameend") } this.musicFadeOut++ - }else if(this.musicFadeOut === 2 && (ms >= started + 8600 && ms >= musicDuration + 250)){ + }else if(this.musicFadeOut === 2 && (ms >= Math.max(started + 8600, Math.min(started + 8600 + 5000, musicDuration + 250)))){ this.controller.displayResults() this.musicFadeOut++ - }else if(this.musicFadeOut === 3 && (ms >= started + 9600 && ms >= musicDuration + 1250)){ + }else if(this.musicFadeOut === 3 && (ms >= Math.max(started + 9600, Math.min(started + 9600 + 5000, musicDuration + 1250)))){ this.controller.clean() if(this.controller.scoresheet){ this.controller.scoresheet.startRedraw() diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index f6a053a..ba199ae 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -111,10 +111,7 @@ var plugin = plugins.add(obj.data, obj.name) if(plugin){ pluginAmount++ - plugins.imported.push({ - name: plugin.name, - plugin: plugin - }) + plugin.imported = true startPromises.push(plugin.start()) } }) diff --git a/public/src/js/loadsong.js b/public/src/js/loadsong.js index dc1fdf0..685c30e 100644 --- a/public/src/js/loadsong.js +++ b/public/src/js/loadsong.js @@ -239,8 +239,8 @@ class LoadSong{ var canvas = document.createElement("canvas") var w = Math.floor(img.width * scale) var h = Math.floor(img.height * scale) - canvas.width = w - canvas.height = h + canvas.width = Math.max(1, w) + canvas.height = Math.max(1, h) var ctx = canvas.getContext("2d") ctx.drawImage(img, 0, 0, w, h) var saveScaled = url => { diff --git a/public/src/js/logo.js b/public/src/js/logo.js index 7a7e515..2979625 100644 --- a/public/src/js/logo.js +++ b/public/src/js/logo.js @@ -64,8 +64,8 @@ var pixelRatio = window.devicePixelRatio || 1 var winW = this.canvas.offsetWidth * pixelRatio var winH = this.canvas.offsetHeight * pixelRatio - this.canvas.width = winW - this.canvas.height = winH + this.canvas.width = Math.max(1, winW) + this.canvas.height = Math.max(1, winH) ctx.scale(winW / this.width, winH / this.height) ctx.lineJoin = "round" diff --git a/public/src/js/p2.js b/public/src/js/p2.js index 9f7d9aa..f8bcc69 100644 --- a/public/src/js/p2.js +++ b/public/src/js/p2.js @@ -28,16 +28,18 @@ class P2Connection{ } } open(){ - this.closed = false - var wsProtocol = location.protocol == "https:" ? "wss:" : "ws:" - this.socket = new WebSocket(wsProtocol + "//" + location.host + "/p2") - pageEvents.race(this.socket, "open", "close").then(response => { - if(response.type === "open"){ - return this.openEvent() - } - return this.closeEvent() - }) - pageEvents.add(this.socket, "message", this.messageEvent.bind(this)) + if(this.closed && !this.disabled){ + this.closed = false + var wsProtocol = location.protocol == "https:" ? "wss:" : "ws:" + this.socket = new WebSocket(wsProtocol + "//" + location.host + "/p2") + pageEvents.race(this.socket, "open", "close").then(response => { + if(response.type === "open"){ + return this.openEvent() + } + return this.closeEvent() + }) + pageEvents.add(this.socket, "message", this.messageEvent.bind(this)) + } } openEvent(){ var addedType = this.allEvents.get("open") @@ -46,8 +48,12 @@ class P2Connection{ } } close(){ - this.closed = true - this.socket.close() + if(!this.closed){ + this.closed = true + if(this.socket){ + this.socket.close() + } + } } closeEvent(){ this.removeEventListener(onmessage) @@ -250,4 +256,12 @@ class P2Connection{ this.notes.shift() } } + enable(){ + this.disabled = false + this.open() + } + disable(){ + this.disabled = true + this.close() + } } diff --git a/public/src/js/plugins.js b/public/src/js/plugins.js index 9d5e63a..288a6af 100644 --- a/public/src/js/plugins.js +++ b/public/src/js/plugins.js @@ -3,10 +3,10 @@ class Plugins{ this.init(...args) } init(){ - this.imported = [] this.allPlugins = [] this.pluginMap = {} this.hashes = [] + this.startOrder = [] } add(script, name){ var hash = md5.base64(script.toString()) @@ -16,7 +16,7 @@ class Plugins{ } name = name || "plugin" var baseName = name - for(var i = 2; name in this.allPlugins; i++){ + for(var i = 2; name in this.pluginMap; i++){ name = baseName + i.toString() } var plugin = new PluginLoader(script, name, hash) @@ -37,10 +37,6 @@ class Plugins{ } } this.unload(name) - var index = this.imported.findIndex(obj => obj.name === name) - if(index !== -1){ - this.imported.splice(index, 1) - } var index = this.allPlugins.findIndex(obj => obj.name === name) if(index !== -1){ this.allPlugins.splice(index, 1) @@ -67,21 +63,33 @@ class Plugins{ this.pluginMap[name].stop() } stopAll(){ - for(var i = this.allPlugins.length; i--;){ - this.allPlugins[i].plugin.stop() + for(var i = this.startOrder.length; i--;){ + this.pluginMap[this.startOrder[i]].stop() } } unload(name){ this.pluginMap[name].unload() } unloadAll(){ + for(var i = this.startOrder.length; i--;){ + this.pluginMap[this.startOrder[i]].unload() + } for(var i = this.allPlugins.length; i--;){ this.allPlugins[i].plugin.unload() } } unloadImported(){ - for(var i = this.imported.length; i--;){ - this.imported[i].plugin.unload() + for(var i = this.startOrder.length; i--;){ + var plugin = this.pluginMap[this.startOrder[i]] + if(plugin.imported){ + plugin.unload() + } + } + for(var i = this.allPlugins.length; i--;){ + var obj = this.allPlugins[i] + if(obj.plugin.imported){ + obj.plugin.unload() + } } } @@ -130,9 +138,9 @@ class Plugins{ getItem: () => plugin.started, setItem: value => { if(plugin.started && !value){ - plugin.stop() + this.stop(plugin.name) }else if(!plugin.started && value){ - plugin.start() + this.start(plugin.name) } } } @@ -188,10 +196,17 @@ class PluginLoader{ console.error(e) this.error() } + }, e => { + console.error(e) + this.error() + return Promise.resolve() }) } } - start(){ + start(orderChange){ + if(!orderChange){ + plugins.startOrder.push(this.name) + } return this.load().then(() => { if(!this.started && this.module){ this.started = true @@ -209,8 +224,18 @@ class PluginLoader{ } }) } - stop(error){ + stop(orderChange, error){ if(this.loaded && this.started){ + if(!orderChange){ + var stopIndex = plugins.startOrder.indexOf(this.name) + if(stopIndex !== -1){ + plugins.startOrder.splice(stopIndex, 1) + for(var i = plugins.startOrder.length; i-- > stopIndex;){ + plugins.pluginMap[plugins.startOrder[i]].stop(true) + } + } + } + this.started = false try{ if(this.module.beforeStop){ @@ -225,12 +250,18 @@ class PluginLoader{ this.error() } } + + if(!orderChange && stopIndex !== -1){ + for(var i = stopIndex; i < plugins.startOrder.length; i++){ + plugins.pluginMap[plugins.startOrder[i]].start(true) + } + } } } unload(error){ if(this.loaded){ if(this.started){ - this.stop(error) + this.stop(false, error) } this.loaded = false plugins.remove(this.name) @@ -267,7 +298,6 @@ class EditValue{ } init(parent, name){ if(name){ - this.original = parent[name] this.name = [parent, name] this.delete = !(name in parent) }else{ @@ -275,18 +305,21 @@ class EditValue{ } } load(callback){ - var output = callback(this.original) - if(typeof output === "undefined"){ - throw new Error("A value is expected to be returned") - } - this.edited = output + this.loadCallback = callback return this } start(){ if(this.name){ - this.name[0][this.name[1]] = this.edited + this.original = this.name[0][this.name[1]] } - return this.edited + var output = this.loadCallback(this.original) + if(typeof output === "undefined"){ + throw new Error("A value is expected to be returned") + } + if(this.name){ + this.name[0][this.name[1]] = output + } + return output } stop(){ if(this.name){ @@ -300,20 +333,26 @@ class EditValue{ } unload(){ delete this.name - delete this.edited delete this.original + delete this.loadCallback } } class EditFunction extends EditValue{ - load(callback){ - var output = callback(plugins.strFromFunc(this.original)) + start(){ + if(this.name){ + this.original = this.name[0][this.name[1]] + } + var args = plugins.argsFromFunc(this.original) + var output = this.loadCallback(plugins.strFromFunc(this.original), args) if(typeof output === "undefined"){ throw new Error("A value is expected to be returned") } - var args = plugins.argsFromFunc(this.original) - this.edited = Function(...args, output) - return this + var output = Function(...args, output) + if(this.name){ + this.name[0][this.name[1]] = output + } + return output } } diff --git a/public/src/js/scoresheet.js b/public/src/js/scoresheet.js index 04396c4..a220be9 100644 --- a/public/src/js/scoresheet.js +++ b/public/src/js/scoresheet.js @@ -215,8 +215,8 @@ class Scoresheet{ if(this.redrawing){ if(this.winW !== winW || this.winH !== winH){ - this.canvas.width = winW - this.canvas.height = winH + this.canvas.width = Math.max(1, winW) + this.canvas.height = Math.max(1, winH) ctx.scale(ratio, ratio) this.canvas.style.width = (winW / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px" diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 4e7f161..13270b1 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -104,18 +104,20 @@ class SongSelect{ return a.id > b.id ? 1 : -1 } }) - this.songs.push({ - title: strings.back, - skin: this.songSkin.back, - action: "back" - }) - this.songs.push({ - title: strings.randomSong, - skin: this.songSkin.random, - action: "random", - category: strings.random, - canJump: true - }) + if(assets.songs.length){ + this.songs.push({ + title: strings.back, + skin: this.songSkin.back, + action: "back" + }) + this.songs.push({ + title: strings.randomSong, + skin: this.songSkin.random, + action: "random", + category: strings.random, + canJump: true + }) + } if(touchEnabled){ if(fromTutorial === "tutorial"){ fromTutorial = false @@ -287,7 +289,8 @@ class SongSelect{ options: 0, selLock: false, catJump: false, - focused: true + focused: true, + waitPreview: 0 } this.songSelecting = { speed: 400, @@ -472,7 +475,7 @@ class SongSelect{ this.toAccount() }else if(p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603){ this.toSession() - }else if(!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket.readyState === 1 && !assets.customSongs){ + }else if(!p2.session && mouse.x > 641 && mouse.y > 603 && p2.socket && p2.socket.readyState === 1 && !assets.customSongs){ this.toSession() }else{ var moveBy = this.songSelMouse(mouse.x, mouse.y) @@ -508,7 +511,7 @@ class SongSelect{ event.preventDefault() } mouseWheel(event){ - if(this.state.screen === "song"){ + if(this.state.screen === "song" && this.state.focused){ this.wheelTimer = this.getMS() if(event.deltaY < 0) { @@ -809,7 +812,7 @@ class SongSelect{ this.selectedDiff = 1 do{ this.state.options = this.mod(this.optionsList.length, this.state.options + moveBy) - }while((p2.socket.readyState !== 1 || assets.customSongs) && this.state.options === 2) + }while((p2.socket && p2.socket.readyState !== 1 || assets.customSongs) && this.state.options === 2) } } toTitleScreen(){ @@ -913,12 +916,7 @@ class SongSelect{ } } } - - if(this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) { - this.moveToSong(this.wheelScrolls) - this.wheelScrolls -= this.wheelScrolls - } - + if(!this.redrawRunning){ return } @@ -944,8 +942,8 @@ class SongSelect{ var ratioY = winH / 720 var ratio = (ratioX < ratioY ? ratioX : ratioY) if(this.winW !== winW || this.winH !== winH){ - this.canvas.width = winW - this.canvas.height = winH + this.canvas.width = Math.max(1, winW) + this.canvas.height = Math.max(1, winH) ctx.scale(ratio, ratio) this.canvas.style.width = (winW / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px" @@ -1034,6 +1032,13 @@ class SongSelect{ var screen = this.state.screen var selectedWidth = this.songAsset.width + if(this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) { + this.state.move = this.wheelScrolls + this.state.waitPreview = ms + 400 + this.wheelScrolls = 0 + this.endPreview() + } + if(screen === "title" || screen === "titleFadeIn"){ if(ms > this.state.screenMS + 1000){ this.state.screen = "song" @@ -2439,7 +2444,7 @@ class SongSelect{ } startPreview(loadOnly){ - if(!loadOnly && this.state && this.state.showWarning){ + if(!loadOnly && this.state && this.state.showWarning || this.state.waitPreview > this.getMS()){ return } var currentSong = this.songs[this.selectedSong] diff --git a/public/src/js/soundbuffer.js b/public/src/js/soundbuffer.js index 76b01bb..fee3d23 100644 --- a/public/src/js/soundbuffer.js +++ b/public/src/js/soundbuffer.js @@ -73,7 +73,10 @@ } } class SoundGain{ - constructor(soundBuffer, channel){ + constructor(...args){ + this.init(...args) + } + init(soundBuffer, channel){ this.soundBuffer = soundBuffer this.gainNode = soundBuffer.context.createGain() if(channel){ @@ -121,7 +124,10 @@ class SoundGain{ } } class Sound{ - constructor(gain, buffer){ + constructor(...args){ + this.init(...args) + } + init(gain, buffer){ this.gain = gain this.buffer = buffer this.soundBuffer = gain.soundBuffer diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 1676904..f88a090 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -216,6 +216,10 @@ var translations = { ja: "曲「%s」を読み込むことができませんでした。(ID:%s)\n\n%s", en: "Could not load song %s with ID %s.\n\n%s" }, + accessNotGrantedError: { + ja: null, + en: "Permission to access the file was not granted" + }, loading: { ja: "ロード中...", en: "Loading...", diff --git a/public/src/js/tutorial.js b/public/src/js/tutorial.js index 5c96280..935f298 100644 --- a/public/src/js/tutorial.js +++ b/public/src/js/tutorial.js @@ -12,29 +12,85 @@ class Tutorial{ this.tutorialTitle = this.getElement("view-title") this.tutorialDiv = document.createElement("div") this.getElement("view-content").appendChild(this.tutorialDiv) + + this.items = [] + this.items.push(this.endButton) + this.selected = this.items.length - 1 + this.setStrings() - 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) - }) + pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) + pageEvents.add(this.formButton, ["mousedown", "touchstart"], this.linkButton.bind(this)) this.keyboard = new Keyboard({ - confirm: ["enter", "space", "esc", "don_l", "don_r"] - }, this.onEnd.bind(this)) + 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: ["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("tutorial") } getElement(name){ return loader.screen.getElementsByClassName(name)[0] } + 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() + 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){ + if(event.type === "touchstart"){ + event.preventDefault() + touched = true + }else if(event.which !== 1){ + return + } + } + this.clean() + assets.sounds["se_don"].play() + try{ + localStorage.setItem("tutorial", "true") + }catch(e){} + setTimeout(() => { + new SongSelect(this.fromSongSel ? "tutorial" : false, false, touched, this.songId) + }, 500) + } + getLink(target){ + return target.getElementsByTagName("a")[0] + } + linkButton(event){ + if(event.target === event.currentTarget && (event.type === "touchstart" || event.which === 1)){ + this.getLink(event.currentTarget).click() + assets.sounds["se_don"].play() + } + } insertText(text, parent){ parent.appendChild(document.createTextNode(text)) } diff --git a/public/src/js/view.js b/public/src/js/view.js index 45e97d1..694e767 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -199,8 +199,8 @@ this.ratio = ratio if(this.player !== 2){ - this.canvas.width = winW - this.canvas.height = winH + this.canvas.width = Math.max(1, winW) + this.canvas.height = Math.max(1, winH) ctx.scale(ratio, ratio) this.canvas.style.width = (winW / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px" @@ -1515,6 +1515,7 @@ } updateNoteFaces(){ var ms = this.getMS() + var lastNextBeat = this.nextBeat while(ms >= this.nextBeat){ this.nextBeat += this.beatInterval if(this.controller.getCombo() >= 50){ @@ -1529,6 +1530,9 @@ big: 3 } } + if(this.nextBeat <= lastNextBeat){ + break + } } } drawCircles(circles){ diff --git a/templates/index.html b/templates/index.html index 2f2a329..6c11397 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,6 +6,8 @@ + + @@ -18,14 +20,20 @@
-
+
{% if version.version and version.commit_short and version.commit %} taiko-web ver.{{version.version}} ({{version.commit_short}}) {% else %} - taiko-web (unknown version) + taiko-web (unknown version) {% endif %}
+ - \ No newline at end of file +