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
This commit is contained in:
KatieFrogs 2022-02-17 23:50:07 +03:00
parent eab03369c7
commit 0655b79293
21 changed files with 286 additions and 112 deletions

11
app.py
View File

@ -371,12 +371,15 @@ def route_admin_users_post():
max_level = admin['user_level'] - 1 max_level = admin['user_level'] - 1
username = request.form.get('username') 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: if not user:
flash('Error: User was not found.') flash('Error: User was not found.')
elif admin_name == username: elif admin['username'] == user['username']:
flash('Error: You cannot modify your own level.') flash('Error: You cannot modify your own level.')
else: else:
user_level = user['user_level'] user_level = user['user_level']
@ -386,7 +389,7 @@ def route_admin_users_post():
flash('Error: This user has higher level than you.') flash('Error: This user has higher level than you.')
else: else:
output = {'user_level': level} output = {'user_level': level}
db.users.update_one({'username': username}, {'$set': output}) db.users.update_one({'username': user['username']}, {'$set': output})
flash('User updated.') flash('User updated.')
return render_template('admin_users.html', config=get_config(), max_level=max_level, username=username, level=level) return render_template('admin_users.html', config=get_config(), max_level=max_level, username=username, level=level)

View File

@ -201,6 +201,20 @@ kbd{
.setting-name::before{ .setting-name::before{
padding-left: 0.3em; 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{ .setting-value{
display: flex; display: flex;
background: #fff; background: #fff;
@ -403,6 +417,7 @@ kbd{
} }
.customdon-canvas{ .customdon-canvas{
width: 13em; width: 13em;
cursor: pointer;
} }
.customdon-div label{ .customdon-div label{
display: block; display: block;

View File

@ -13,7 +13,7 @@ function filePermission(file){
if(response === "granted"){ if(response === "granted"){
return file return file
}else{ }else{
return Promise.reject(file) return Promise.reject(strings.accessNotGrantedError)
} }
}) })
} }

View File

@ -46,6 +46,8 @@ class Account{
this.inputForms.push(this.displayname) this.inputForms.push(this.displayname)
this.redrawRunning = true this.redrawRunning = true
this.redrawPaused = matchMedia("(prefers-reduced-motion: reduce)").matches
this.redrawForce = true
this.customdonRedrawBind = this.customdonRedraw.bind(this) this.customdonRedrawBind = this.customdonRedraw.bind(this)
this.start = new Date().getTime() this.start = new Date().getTime()
this.frames = [ this.frames = [
@ -57,6 +59,7 @@ class Account{
this.customdonCache = new CanvasCache() this.customdonCache = new CanvasCache()
this.customdonCache.resize(723 * 2, 1858, 1) this.customdonCache.resize(723 * 2, 1858, 1)
this.customdonCanvas = this.getElement("customdon-canvas") this.customdonCanvas = this.getElement("customdon-canvas")
pageEvents.add(this.customdonCanvas, "click", this.customdonPause.bind(this))
this.customdonCtx = this.customdonCanvas.getContext("2d") this.customdonCtx = this.customdonCanvas.getContext("2d")
this.customdonBodyFill = this.getElement("customdon-bodyfill") this.customdonBodyFill = this.getElement("customdon-bodyfill")
this.customdonBodyFill.value = account.don.body_fill 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)) 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(){ customdonChange(){
var ctx = this.customdonCtx var ctx = this.customdonCtx
this.customdonCache.clear() this.customdonCache.clear()
@ -148,6 +156,7 @@ class Account{
id: "bodyFill" id: "bodyFill"
}) })
}) })
this.redrawForce = true
} }
customdonReset(event){ customdonReset(event){
if(event.type === "touchstart"){ if(event.type === "touchstart"){
@ -162,12 +171,16 @@ class Account{
return return
} }
requestAnimationFrame(this.customdonRedrawBind) requestAnimationFrame(this.customdonRedrawBind)
if(!document.hasFocus()){ if(!document.hasFocus() || this.redrawPaused && !this.redrawForce){
return return
} }
var ms = new Date().getTime() var ms = new Date().getTime()
var ctx = this.customdonCtx 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 w = 360
var h = 184 var h = 184
var sx = Math.floor(frame / 10) * (w + 2) var sx = Math.floor(frame / 10) * (w + 2)
@ -183,6 +196,7 @@ class Account{
sx, sy, w, h, sx, sy, w, h,
-26, 0, w, h -26, 0, w, h
) )
this.redrawForce = false
} }
showDiv(event, div){ showDiv(event, div){
if(event){ if(event){
@ -318,6 +332,7 @@ class Account{
onFormPress(event){ onFormPress(event){
event.stopPropagation() event.stopPropagation()
if(event.type === "keypress" && event.keyCode === 13){ if(event.type === "keypress" && event.keyCode === 13){
event.preventDefault()
if(this.mode === "account"){ if(this.mode === "account"){
this.onSave() this.onSave()
}else{ }else{
@ -611,6 +626,7 @@ class Account{
} }
this.redrawRunning = false this.redrawRunning = false
this.customdonCache.clean() this.customdonCache.clean()
pageEvents.remove(this.customdonCanvas, "click")
pageEvents.remove(this.customdonBodyFill, ["change", "input"]) pageEvents.remove(this.customdonBodyFill, ["change", "input"])
pageEvents.remove(this.customdonFaceFill, ["change", "input"]) pageEvents.remove(this.customdonFaceFill, ["change", "input"])
pageEvents.remove(this.customdonResetBtn, ["click", "touchstart"]) pageEvents.remove(this.customdonResetBtn, ["click", "touchstart"])

View File

@ -27,8 +27,8 @@ class CanvasCache{
this.h = h this.h = h
this.lastW = 0 this.lastW = 0
this.lastH = 0 this.lastH = 0
this.canvas.width = this.w * this.scale this.canvas.width = Math.max(1, this.w * this.scale)
this.canvas.height = this.h * this.scale this.canvas.height = Math.max(1, this.h * this.scale)
this.ctx.scale(this.scale, this.scale) this.ctx.scale(this.scale, this.scale)
} }
get(config, callback, setOnly){ get(config, callback, setOnly){

View File

@ -157,7 +157,7 @@
ctx.fillRect(0, 0, w, h) ctx.fillRect(0, 0, w, h)
} }
if(config.cached){ 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.resize(config.frameCache.w, config.frameCache.h, config.frameCache.ratio)
} }
this.songFrameCache.get({ this.songFrameCache.get({
@ -1680,8 +1680,8 @@
if(amount >= 1){ if(amount >= 1){
return callback(ctx) return callback(ctx)
}else if(amount >= 0){ }else if(amount >= 0){
this.tmpCanvas.width = winW || ctx.canvas.width this.tmpCanvas.width = Math.max(1, winW || ctx.canvas.width)
this.tmpCanvas.height = winH || ctx.canvas.height this.tmpCanvas.height = Math.max(1, winH || ctx.canvas.height)
callback(this.tmpCtx) callback(this.tmpCtx)
ctx.save() ctx.save()
ctx.globalAlpha = amount ctx.globalAlpha = amount

View File

@ -7,8 +7,8 @@ class CanvasTest{
var pixelRatio = window.devicePixelRatio || 1 var pixelRatio = window.devicePixelRatio || 1
var width = innerWidth * pixelRatio var width = innerWidth * pixelRatio
var height = innerHeight * pixelRatio var height = innerHeight * pixelRatio
this.canvas.width = width this.canvas.width = Math.max(1, width)
this.canvas.height = height this.canvas.height = Math.max(1, height)
this.ctx = this.canvas.getContext("2d") this.ctx = this.canvas.getContext("2d")
this.ctx.scale(pixelRatio, pixelRatio) this.ctx.scale(pixelRatio, pixelRatio)
this.ratio = pixelRatio this.ratio = pixelRatio

View File

@ -231,6 +231,9 @@ class Controller{
this.view.displayScore(score, notPlayed, bigNote) this.view.displayScore(score, notPlayed, bigNote)
} }
songSelection(fadeIn, showWarning){ songSelection(fadeIn, showWarning){
if(this.cleaned){
return
}
if(!fadeIn){ if(!fadeIn){
this.clean() this.clean()
} }
@ -241,6 +244,9 @@ class Controller{
} }
} }
restartSong(){ restartSong(){
if(this.cleaned){
return
}
this.clean() this.clean()
if(this.multiplayer){ if(this.multiplayer){
new LoadSong(this.selectedSong, false, true, this.touchEnabled) new LoadSong(this.selectedSong, false, true, this.touchEnabled)
@ -363,6 +369,7 @@ class Controller{
return true return true
} }
clean(){ clean(){
this.cleaned = true
if(this.multiplayer === 1){ if(this.multiplayer === 1){
this.syncWith.clean() this.syncWith.clean()
} }

View File

@ -506,10 +506,10 @@ class Game{
p2.send("gameend") p2.send("gameend")
} }
this.musicFadeOut++ 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.controller.displayResults()
this.musicFadeOut++ 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() this.controller.clean()
if(this.controller.scoresheet){ if(this.controller.scoresheet){
this.controller.scoresheet.startRedraw() this.controller.scoresheet.startRedraw()

View File

@ -111,10 +111,7 @@
var plugin = plugins.add(obj.data, obj.name) var plugin = plugins.add(obj.data, obj.name)
if(plugin){ if(plugin){
pluginAmount++ pluginAmount++
plugins.imported.push({ plugin.imported = true
name: plugin.name,
plugin: plugin
})
startPromises.push(plugin.start()) startPromises.push(plugin.start())
} }
}) })

View File

@ -239,8 +239,8 @@ class LoadSong{
var canvas = document.createElement("canvas") var canvas = document.createElement("canvas")
var w = Math.floor(img.width * scale) var w = Math.floor(img.width * scale)
var h = Math.floor(img.height * scale) var h = Math.floor(img.height * scale)
canvas.width = w canvas.width = Math.max(1, w)
canvas.height = h canvas.height = Math.max(1, h)
var ctx = canvas.getContext("2d") var ctx = canvas.getContext("2d")
ctx.drawImage(img, 0, 0, w, h) ctx.drawImage(img, 0, 0, w, h)
var saveScaled = url => { var saveScaled = url => {

View File

@ -64,8 +64,8 @@
var pixelRatio = window.devicePixelRatio || 1 var pixelRatio = window.devicePixelRatio || 1
var winW = this.canvas.offsetWidth * pixelRatio var winW = this.canvas.offsetWidth * pixelRatio
var winH = this.canvas.offsetHeight * pixelRatio var winH = this.canvas.offsetHeight * pixelRatio
this.canvas.width = winW this.canvas.width = Math.max(1, winW)
this.canvas.height = winH this.canvas.height = Math.max(1, winH)
ctx.scale(winW / this.width, winH / this.height) ctx.scale(winW / this.width, winH / this.height)
ctx.lineJoin = "round" ctx.lineJoin = "round"

View File

@ -28,16 +28,18 @@ class P2Connection{
} }
} }
open(){ open(){
this.closed = false if(this.closed && !this.disabled){
var wsProtocol = location.protocol == "https:" ? "wss:" : "ws:" this.closed = false
this.socket = new WebSocket(wsProtocol + "//" + location.host + "/p2") var wsProtocol = location.protocol == "https:" ? "wss:" : "ws:"
pageEvents.race(this.socket, "open", "close").then(response => { this.socket = new WebSocket(wsProtocol + "//" + location.host + "/p2")
if(response.type === "open"){ pageEvents.race(this.socket, "open", "close").then(response => {
return this.openEvent() if(response.type === "open"){
} return this.openEvent()
return this.closeEvent() }
}) return this.closeEvent()
pageEvents.add(this.socket, "message", this.messageEvent.bind(this)) })
pageEvents.add(this.socket, "message", this.messageEvent.bind(this))
}
} }
openEvent(){ openEvent(){
var addedType = this.allEvents.get("open") var addedType = this.allEvents.get("open")
@ -46,8 +48,12 @@ class P2Connection{
} }
} }
close(){ close(){
this.closed = true if(!this.closed){
this.socket.close() this.closed = true
if(this.socket){
this.socket.close()
}
}
} }
closeEvent(){ closeEvent(){
this.removeEventListener(onmessage) this.removeEventListener(onmessage)
@ -250,4 +256,12 @@ class P2Connection{
this.notes.shift() this.notes.shift()
} }
} }
enable(){
this.disabled = false
this.open()
}
disable(){
this.disabled = true
this.close()
}
} }

View File

@ -3,10 +3,10 @@ class Plugins{
this.init(...args) this.init(...args)
} }
init(){ init(){
this.imported = []
this.allPlugins = [] this.allPlugins = []
this.pluginMap = {} this.pluginMap = {}
this.hashes = [] this.hashes = []
this.startOrder = []
} }
add(script, name){ add(script, name){
var hash = md5.base64(script.toString()) var hash = md5.base64(script.toString())
@ -16,7 +16,7 @@ class Plugins{
} }
name = name || "plugin" name = name || "plugin"
var baseName = name 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() name = baseName + i.toString()
} }
var plugin = new PluginLoader(script, name, hash) var plugin = new PluginLoader(script, name, hash)
@ -37,10 +37,6 @@ class Plugins{
} }
} }
this.unload(name) 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) var index = this.allPlugins.findIndex(obj => obj.name === name)
if(index !== -1){ if(index !== -1){
this.allPlugins.splice(index, 1) this.allPlugins.splice(index, 1)
@ -67,21 +63,33 @@ class Plugins{
this.pluginMap[name].stop() this.pluginMap[name].stop()
} }
stopAll(){ stopAll(){
for(var i = this.allPlugins.length; i--;){ for(var i = this.startOrder.length; i--;){
this.allPlugins[i].plugin.stop() this.pluginMap[this.startOrder[i]].stop()
} }
} }
unload(name){ unload(name){
this.pluginMap[name].unload() this.pluginMap[name].unload()
} }
unloadAll(){ unloadAll(){
for(var i = this.startOrder.length; i--;){
this.pluginMap[this.startOrder[i]].unload()
}
for(var i = this.allPlugins.length; i--;){ for(var i = this.allPlugins.length; i--;){
this.allPlugins[i].plugin.unload() this.allPlugins[i].plugin.unload()
} }
} }
unloadImported(){ unloadImported(){
for(var i = this.imported.length; i--;){ for(var i = this.startOrder.length; i--;){
this.imported[i].plugin.unload() 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, getItem: () => plugin.started,
setItem: value => { setItem: value => {
if(plugin.started && !value){ if(plugin.started && !value){
plugin.stop() this.stop(plugin.name)
}else if(!plugin.started && value){ }else if(!plugin.started && value){
plugin.start() this.start(plugin.name)
} }
} }
} }
@ -188,10 +196,17 @@ class PluginLoader{
console.error(e) console.error(e)
this.error() 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(() => { return this.load().then(() => {
if(!this.started && this.module){ if(!this.started && this.module){
this.started = true this.started = true
@ -209,8 +224,18 @@ class PluginLoader{
} }
}) })
} }
stop(error){ stop(orderChange, error){
if(this.loaded && this.started){ 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 this.started = false
try{ try{
if(this.module.beforeStop){ if(this.module.beforeStop){
@ -225,12 +250,18 @@ class PluginLoader{
this.error() this.error()
} }
} }
if(!orderChange && stopIndex !== -1){
for(var i = stopIndex; i < plugins.startOrder.length; i++){
plugins.pluginMap[plugins.startOrder[i]].start(true)
}
}
} }
} }
unload(error){ unload(error){
if(this.loaded){ if(this.loaded){
if(this.started){ if(this.started){
this.stop(error) this.stop(false, error)
} }
this.loaded = false this.loaded = false
plugins.remove(this.name) plugins.remove(this.name)
@ -267,7 +298,6 @@ class EditValue{
} }
init(parent, name){ init(parent, name){
if(name){ if(name){
this.original = parent[name]
this.name = [parent, name] this.name = [parent, name]
this.delete = !(name in parent) this.delete = !(name in parent)
}else{ }else{
@ -275,18 +305,21 @@ class EditValue{
} }
} }
load(callback){ load(callback){
var output = callback(this.original) this.loadCallback = callback
if(typeof output === "undefined"){
throw new Error("A value is expected to be returned")
}
this.edited = output
return this return this
} }
start(){ start(){
if(this.name){ 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(){ stop(){
if(this.name){ if(this.name){
@ -300,20 +333,26 @@ class EditValue{
} }
unload(){ unload(){
delete this.name delete this.name
delete this.edited
delete this.original delete this.original
delete this.loadCallback
} }
} }
class EditFunction extends EditValue{ class EditFunction extends EditValue{
load(callback){ start(){
var output = callback(plugins.strFromFunc(this.original)) 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"){ if(typeof output === "undefined"){
throw new Error("A value is expected to be returned") throw new Error("A value is expected to be returned")
} }
var args = plugins.argsFromFunc(this.original) var output = Function(...args, output)
this.edited = Function(...args, output) if(this.name){
return this this.name[0][this.name[1]] = output
}
return output
} }
} }

View File

@ -215,8 +215,8 @@ class Scoresheet{
if(this.redrawing){ if(this.redrawing){
if(this.winW !== winW || this.winH !== winH){ if(this.winW !== winW || this.winH !== winH){
this.canvas.width = winW this.canvas.width = Math.max(1, winW)
this.canvas.height = winH this.canvas.height = Math.max(1, winH)
ctx.scale(ratio, ratio) ctx.scale(ratio, ratio)
this.canvas.style.width = (winW / this.pixelRatio) + "px" this.canvas.style.width = (winW / this.pixelRatio) + "px"
this.canvas.style.height = (winH / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px"

View File

@ -104,18 +104,20 @@ class SongSelect{
return a.id > b.id ? 1 : -1 return a.id > b.id ? 1 : -1
} }
}) })
this.songs.push({ if(assets.songs.length){
title: strings.back, this.songs.push({
skin: this.songSkin.back, title: strings.back,
action: "back" skin: this.songSkin.back,
}) action: "back"
this.songs.push({ })
title: strings.randomSong, this.songs.push({
skin: this.songSkin.random, title: strings.randomSong,
action: "random", skin: this.songSkin.random,
category: strings.random, action: "random",
canJump: true category: strings.random,
}) canJump: true
})
}
if(touchEnabled){ if(touchEnabled){
if(fromTutorial === "tutorial"){ if(fromTutorial === "tutorial"){
fromTutorial = false fromTutorial = false
@ -287,7 +289,8 @@ class SongSelect{
options: 0, options: 0,
selLock: false, selLock: false,
catJump: false, catJump: false,
focused: true focused: true,
waitPreview: 0
} }
this.songSelecting = { this.songSelecting = {
speed: 400, speed: 400,
@ -472,7 +475,7 @@ class SongSelect{
this.toAccount() this.toAccount()
}else if(p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603){ }else if(p2.session && 438 < mouse.x && mouse.x < 834 && mouse.y > 603){
this.toSession() 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() this.toSession()
}else{ }else{
var moveBy = this.songSelMouse(mouse.x, mouse.y) var moveBy = this.songSelMouse(mouse.x, mouse.y)
@ -508,7 +511,7 @@ class SongSelect{
event.preventDefault() event.preventDefault()
} }
mouseWheel(event){ mouseWheel(event){
if(this.state.screen === "song"){ if(this.state.screen === "song" && this.state.focused){
this.wheelTimer = this.getMS() this.wheelTimer = this.getMS()
if(event.deltaY < 0) { if(event.deltaY < 0) {
@ -809,7 +812,7 @@ class SongSelect{
this.selectedDiff = 1 this.selectedDiff = 1
do{ do{
this.state.options = this.mod(this.optionsList.length, this.state.options + moveBy) 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(){ 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){ if(!this.redrawRunning){
return return
} }
@ -944,8 +942,8 @@ class SongSelect{
var ratioY = winH / 720 var ratioY = winH / 720
var ratio = (ratioX < ratioY ? ratioX : ratioY) var ratio = (ratioX < ratioY ? ratioX : ratioY)
if(this.winW !== winW || this.winH !== winH){ if(this.winW !== winW || this.winH !== winH){
this.canvas.width = winW this.canvas.width = Math.max(1, winW)
this.canvas.height = winH this.canvas.height = Math.max(1, winH)
ctx.scale(ratio, ratio) ctx.scale(ratio, ratio)
this.canvas.style.width = (winW / this.pixelRatio) + "px" this.canvas.style.width = (winW / this.pixelRatio) + "px"
this.canvas.style.height = (winH / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px"
@ -1034,6 +1032,13 @@ class SongSelect{
var screen = this.state.screen var screen = this.state.screen
var selectedWidth = this.songAsset.width 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(screen === "title" || screen === "titleFadeIn"){
if(ms > this.state.screenMS + 1000){ if(ms > this.state.screenMS + 1000){
this.state.screen = "song" this.state.screen = "song"
@ -2439,7 +2444,7 @@ class SongSelect{
} }
startPreview(loadOnly){ startPreview(loadOnly){
if(!loadOnly && this.state && this.state.showWarning){ if(!loadOnly && this.state && this.state.showWarning || this.state.waitPreview > this.getMS()){
return return
} }
var currentSong = this.songs[this.selectedSong] var currentSong = this.songs[this.selectedSong]

View File

@ -73,7 +73,10 @@
} }
} }
class SoundGain{ class SoundGain{
constructor(soundBuffer, channel){ constructor(...args){
this.init(...args)
}
init(soundBuffer, channel){
this.soundBuffer = soundBuffer this.soundBuffer = soundBuffer
this.gainNode = soundBuffer.context.createGain() this.gainNode = soundBuffer.context.createGain()
if(channel){ if(channel){
@ -121,7 +124,10 @@ class SoundGain{
} }
} }
class Sound{ class Sound{
constructor(gain, buffer){ constructor(...args){
this.init(...args)
}
init(gain, buffer){
this.gain = gain this.gain = gain
this.buffer = buffer this.buffer = buffer
this.soundBuffer = gain.soundBuffer this.soundBuffer = gain.soundBuffer

View File

@ -216,6 +216,10 @@ var translations = {
ja: "曲「%s」を読み込むことができませんでした。ID%s\n\n%s", ja: "曲「%s」を読み込むことができませんでした。ID%s\n\n%s",
en: "Could not load song %s with 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: { loading: {
ja: "ロード中...", ja: "ロード中...",
en: "Loading...", en: "Loading...",

View File

@ -12,29 +12,85 @@ class Tutorial{
this.tutorialTitle = this.getElement("view-title") this.tutorialTitle = this.getElement("view-title")
this.tutorialDiv = document.createElement("div") this.tutorialDiv = document.createElement("div")
this.getElement("view-content").appendChild(this.tutorialDiv) this.getElement("view-content").appendChild(this.tutorialDiv)
this.items = []
this.items.push(this.endButton)
this.selected = this.items.length - 1
this.setStrings() this.setStrings()
pageEvents.add(this.endButton, ["mousedown", "touchstart"], event => { pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this))
if(event.type === "touchstart"){ pageEvents.add(this.formButton, ["mousedown", "touchstart"], this.linkButton.bind(this))
event.preventDefault()
this.touched = true
}else if(event.type === "mousedown" && event.which !== 1){
return
}
this.onEnd(true)
})
this.keyboard = new Keyboard({ this.keyboard = new Keyboard({
confirm: ["enter", "space", "esc", "don_l", "don_r"] confirm: ["enter", "space", "don_l", "don_r"],
}, this.onEnd.bind(this)) previous: ["left", "up", "ka_l"],
next: ["right", "down", "ka_r"],
back: ["escape"]
}, this.keyPressed.bind(this))
this.gamepad = new Gamepad({ this.gamepad = new Gamepad({
confirm: ["start", "b", "ls", "rs"] "confirm": ["b", "ls", "rs"],
}, this.onEnd.bind(this)) "previous": ["u", "l", "lb", "lt", "lsu", "lsl"],
"next": ["d", "r", "rb", "rt", "lsd", "lsr"],
"back": ["start", "a"]
}, this.keyPressed.bind(this))
pageEvents.send("tutorial") pageEvents.send("tutorial")
} }
getElement(name){ getElement(name){
return loader.screen.getElementsByClassName(name)[0] 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){ insertText(text, parent){
parent.appendChild(document.createTextNode(text)) parent.appendChild(document.createTextNode(text))
} }

View File

@ -199,8 +199,8 @@
this.ratio = ratio this.ratio = ratio
if(this.player !== 2){ if(this.player !== 2){
this.canvas.width = winW this.canvas.width = Math.max(1, winW)
this.canvas.height = winH this.canvas.height = Math.max(1, winH)
ctx.scale(ratio, ratio) ctx.scale(ratio, ratio)
this.canvas.style.width = (winW / this.pixelRatio) + "px" this.canvas.style.width = (winW / this.pixelRatio) + "px"
this.canvas.style.height = (winH / this.pixelRatio) + "px" this.canvas.style.height = (winH / this.pixelRatio) + "px"
@ -1515,6 +1515,7 @@
} }
updateNoteFaces(){ updateNoteFaces(){
var ms = this.getMS() var ms = this.getMS()
var lastNextBeat = this.nextBeat
while(ms >= this.nextBeat){ while(ms >= this.nextBeat){
this.nextBeat += this.beatInterval this.nextBeat += this.beatInterval
if(this.controller.getCombo() >= 50){ if(this.controller.getCombo() >= 50){
@ -1529,6 +1530,9 @@
big: 3 big: 3
} }
} }
if(this.nextBeat <= lastNextBeat){
break
}
} }
} }
drawCircles(circles){ drawCircles(circles){

View File

@ -6,6 +6,8 @@
<link rel="icon" href="{{config.assets_baseurl}}img/favicon.png" type="image/png"> <link rel="icon" href="{{config.assets_baseurl}}img/favicon.png" type="image/png">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="description" content="パソコンとスマホのブラウザ向けの太鼓の達人シミュレータ 🥁 Taiko no Tatsujin rhythm game simulator for desktop and mobile browsers"> <meta name="description" content="パソコンとスマホのブラウザ向けの太鼓の達人シミュレータ 🥁 Taiko no Tatsujin rhythm game simulator for desktop and mobile browsers">
<meta name="keywords" content="taiko no tatsujin, taiko, don chan, online, rhythm, browser, html5, game, for browsers, pc, arcade, emulator, free, download, website, 太鼓の達人, 太鼓ウェブ, 太鼓之達人, 太鼓達人, 太鼓网页, 网页版, 太鼓網頁, 網頁版, 태고의 달인, 태고 웹">
<meta name="color-scheme" content="only light">
<link rel="stylesheet" href="/src/css/loader.css?{{version.commit_short}}"> <link rel="stylesheet" href="/src/css/loader.css?{{version.commit_short}}">
@ -18,14 +20,20 @@
<body> <body>
<div id="assets"></div> <div id="assets"></div>
<div id="screen" class="pattern-bg"></div> <div id="screen" class="pattern-bg"></div>
<div id="version"> <div data-nosnippet id="version">
{% if version.version and version.commit_short and version.commit %} {% if version.version and version.commit_short and version.commit %}
<a href="{{version.url}}commit/{{version.commit}}" target="_blank" id="version-link" class="stroke-sub" alt="taiko-web ver.{{version.version}} ({{version.commit_short}})">taiko-web ver.{{version.version}} ({{version.commit_short}})</a> <a href="{{version.url}}commit/{{version.commit}}" target="_blank" id="version-link" class="stroke-sub" alt="taiko-web ver.{{version.version}} ({{version.commit_short}})">taiko-web ver.{{version.version}} ({{version.commit_short}})</a>
{% else %} {% else %}
<a href="{{version.url}}" target="_blank" id="version-link" class="stroke-sub" alt="taiko-web (unknown version)">taiko-web (unknown version)</a> <a href="{{version.url}}" target="_blank" rel="noopener" id="version-link" class="stroke-sub" alt="taiko-web (unknown version)">taiko-web (unknown version)</a>
{% endif %} {% endif %}
</div> </div>
<script src="/src/js/browsersupport.js?{{version.commit_short}}"></script> <script src="/src/js/browsersupport.js?{{version.commit_short}}"></script>
<script src="/src/js/main.js?{{version.commit_short}}"></script> <script src="/src/js/main.js?{{version.commit_short}}"></script>
<noscript>
<div data-nosnippet id="unsupportedBrowser">
<div id="unsupportedWarn">!</div>
<span>Taiko Web requires JavaScript to be enabled in the browser</span>
</div>
</noscript>
</body> </body>
</html> </html>