japanese-drum-game/public/src/js/debug.js
LoveEevee e81bf9b480 More bug fixes
- BPM and go go time change even when there are no notes after the change, seen in Ego Ego Atakushi and UFO Swingin'
- Maker URL can be added to a local tja
  - Example: `MAKER: Creator name <https://example.com/path>`
- Long list of settings scrolls more naturally with arrow keys
- When media engagement is low, gamepad users will not be able to proceed on the title screen until a mouse click or a key press unpauses the audio context
- Fix "Bad" note gauge judgement on easy difficulty
- In debug, use hash to tell apart songs
2020-03-12 07:59:28 +03:00

458 lines
13 KiB
JavaScript

class Debug{
constructor(){
if(!assets.pages["debug"]){
return
}
this.debugDiv = document.createElement("div")
this.debugDiv.id = "debug"
this.debugDiv.innerHTML = assets.pages["debug"]
document.body.appendChild(this.debugDiv)
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.volumeDiv = this.byClass("music-volume")
this.restartLabel = this.byClass("change-restart-label")
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
this.windowSymbol = Symbol()
pageEvents.add(window, ["mousedown", "mouseup", "touchstart", "touchend", "blur", "resize"], this.stopMove.bind(this), this.windowSymbol)
pageEvents.mouseAdd(this, this.onMove.bind(this))
pageEvents.add(window, "touchmove", this.onMove.bind(this))
pageEvents.add(this.titleDiv, ["mousedown", "touchstart"], this.startMove.bind(this))
pageEvents.add(this.minimiseDiv, ["click", "touchstart"], this.minimise.bind(this))
pageEvents.add(this.restartBtn, ["click", "touchstart"], this.restartSong.bind(this))
pageEvents.add(this.exitBtn, ["click", "touchstart"], this.clean.bind(this))
pageEvents.add(this.restartLabel, "touchstart", this.touchBox.bind(this))
pageEvents.add(this.autoplayLabel, "touchstart", this.touchBox.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", "touchstart"], this.branchReset.bind(this))
this.offsetSlider = new InputSlider(this.offsetDiv, -60, 60, 3)
this.offsetSlider.onchange(this.offsetChange.bind(this))
this.measureNumSlider = new InputSlider(this.measureNumDiv, 0, 1000, 0)
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()
pageEvents.send("debug")
}
byClass(name){
return this.debugDiv.getElementsByClassName(name)[0]
}
startMove(event){
if(event.which === 1 || event.type === "touchstart"){
event.stopPropagation()
var divPos = this.debugDiv.getBoundingClientRect()
var click = event.type === "touchstart" ? event.changedTouches[0] : event
var x = click.pageX - divPos.left
var y = click.pageY - divPos.top
this.moving = {x: x, y: y}
}
}
onMove(event){
if(this.moving){
var click = event.type === "touchmove" ? event.changedTouches[0] : event
var x = click.clientX - this.moving.x
var y = click.clientY - this.moving.y
this.moveTo(x, y)
}
}
stopMove(event){
if(!event || event.type === "resize"){
var divPos = this.debugDiv.getBoundingClientRect()
var x = divPos.left
var y = divPos.top
}else{
var click = event.type === "touchstart" || event.type === "touchend" ? event.changedTouches[0] : event
if(event.type == "blur"){
var x = this.moving.x
var y = this.moving.y
}else{
var x = click.clientX - this.moving.x
var y = click.clientY - this.moving.y
}
}
var w = this.debugDiv.offsetWidth
var h = this.debugDiv.offsetHeight
if(x + w > innerWidth){
x = innerWidth - w
}
if(y + h > lastHeight){
y = lastHeight - h
}
if(x < 0){
x = 0
}
if(y < 0){
y = 0
}
this.moveTo(x, y)
this.moving = false
}
moveTo(x, y){
this.debugDiv.style.transform = "translate(" + x + "px, " + y + "px)"
}
restore(){
debugObj.state = "open"
this.debugDiv.style.display = ""
this.stopMove()
}
minimise(){
debugObj.state = "minimised"
this.debugDiv.style.display = "none"
}
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"
}
var selectedSong = this.controller.selectedSong
this.defaultOffset = selectedSong.offset || 0
if(this.songHash === selectedSong.hash){
this.offsetChange(this.offsetSlider.get(), true)
this.branchChange(null, true)
this.volumeChange(this.volumeSlider.get(), true)
}else{
this.songHash = selectedSong.hash
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) => {
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
var game = this.controller.game
game.started = true
var timestamp = Date.now()
var currentDate = timestamp - measureMS
game.startDate = currentDate
game.sndTime = timestamp - snd.buffer.getTime() * 1000
var circles = game.songData.circles
for(var i in circles){
game.currentCircle = i
if(circles[i].endTime >= measureMS){
break
}
game.skipNote(circles[i])
}
if(game.mainMusicPlaying){
game.mainMusicPlaying = false
game.mainAsset.stop()
}
}
this.autoplayCheckbox.checked = this.controller.autoPlayEnabled
}
if(this.controller && !debugObj.controller){
this.restartBtn.style.display = ""
this.autoplayLabel.style.display = ""
this.branchHideDiv.style.display = ""
this.controller = null
}
this.stopMove()
}
offsetChange(value, noRestart){
if(this.controller){
var offset = (this.defaultOffset - value) * 1000
var songData = this.controller.parsedSongData
songData.circles.forEach(circle => {
circle.ms = circle.originalMS + offset
circle.endTime = circle.originalEndTime + offset
})
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()
}
}
}
measureNumChange(value){
this.measureNum = value
if(this.restartCheckbox.checked){
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()
}
}
toggleAutoplay(event){
if(this.controller){
this.controller.autoPlayEnabled = this.autoplayCheckbox.checked
if(this.controller.autoPlayEnabled){
this.controller.saveScore = false
}else{
var keyboard = debugObj.controller.keyboard
keyboard.setKey(false, "don_l")
keyboard.setKey(false, "don_r")
keyboard.setKey(false, "ka_l")
keyboard.setKey(false, "ka_r")
}
}
}
branchChange(event, noRestart){
if(this.controller){
var game = this.controller.game
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){
this.restartSong()
}
}
}
branchReset(event, noRestart){
this.branchSelect.value = "auto"
this.branchChange(null, noRestart)
}
touchBox(event){
event.currentTarget.click()
}
clean(){
this.offsetSlider.clean()
this.measureNumSlider.clean()
this.volumeSlider.clean()
pageEvents.remove(window, ["mousedown", "mouseup", "touchstart", "touchend", "blur", "resize"], this.windowSymbol)
pageEvents.mouseRemove(this)
pageEvents.remove(window, "touchmove")
pageEvents.remove(this.titleDiv, ["mousedown", "touchstart"])
pageEvents.remove(this.minimiseDiv, ["click", "touchstart"])
pageEvents.remove(this.restartBtn, ["click", "touchstart"])
pageEvents.remove(this.exitBtn, ["click", "touchstart"])
pageEvents.remove(this.restartLabel, "touchstart")
pageEvents.remove(this.autoplayLabel, "touchstart")
pageEvents.remove(this.autoplayCheckbox, "change")
pageEvents.remove(this.branchSelect, "change")
pageEvents.remove(this.branchResetBtn, ["click", "touchstart"])
delete this.offsetSlider
delete this.measureNumSlider
delete this.volumeSlider
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.volumeDiv
delete this.restartCheckbox
delete this.autoplayLabel
delete this.autoplayCheckbox
delete this.restartBtn
delete this.exitBtn
delete this.controller
delete this.windowSymbol
debugObj.state = "closed"
debugObj.debug = null
document.body.removeChild(this.debugDiv)
delete this.debugDiv
}
}
class InputSlider{
constructor(sliderDiv, min, max, fixedPoint){
this.fixedPoint = fixedPoint
this.mul = Math.pow(10, fixedPoint)
this.min = min * this.mul
this.max = max * this.mul
this.input = sliderDiv.getElementsByTagName("input")[0]
this.reset = sliderDiv.getElementsByClassName("reset")[0]
this.plus = sliderDiv.getElementsByClassName("plus")[0]
this.minus = sliderDiv.getElementsByClassName("minus")[0]
this.value = null
this.defaultValue = null
this.callbacks = []
this.touchEnd = []
this.windowSymbol = Symbol()
pageEvents.add(this.input, ["touchstart", "touchend"], event => {
event.stopPropagation()
})
pageEvents.add(window, ["mouseup", "touchstart", "touchend", "blur"], event => {
if(event.type !== "touchstart"){
this.touchEnd.forEach(func => func(event))
}else if(event.target !== this.input){
this.input.blur()
}
}, this.windowSymbol)
this.addTouchRepeat(this.plus, this.add.bind(this))
this.addTouchRepeat(this.minus, this.subtract.bind(this))
this.addTouch(this.reset, this.resetValue.bind(this))
pageEvents.add(this.input, "change", this.manualSet.bind(this))
pageEvents.add(this.input, "keydown", this.captureKeys.bind(this))
}
update(noCallback, force){
var oldValue = this.input.value
if(this.value === null){
this.input.value = ""
this.input.readOnly = true
}else{
if(this.value > this.max){
this.value = this.max
}
if(this.value < this.min){
this.value = this.min
}
this.input.value = this.get().toFixed(this.fixedPoint)
this.input.readOnly = false
}
if(force || !noCallback && oldValue !== this.input.value){
this.callbacks.forEach(callback => {
callback(this.get())
})
}
}
set(number){
this.value = Math.floor(number * this.mul)
this.defaultValue = this.value
this.update(true)
}
setMinMax(min, max){
this.min = min
this.max = max
this.update()
}
get(){
if(this.value === null){
return null
}else{
return Math.floor(this.value) / this.mul
}
}
add(event){
if(this.value !== null){
var newValue = this.value + this.eventNumber(event)
if(newValue <= this.max){
this.value = newValue
this.update()
}
}
}
subtract(event){
if(this.value !== null){
var newValue = this.value - this.eventNumber(event)
if(newValue >= this.min){
this.value = newValue
this.update()
}
}
}
eventNumber(event){
return (event.ctrlKey ? 10 : 1) * (event.shiftKey ? 10 : 1) * (event.altKey ? 10 : 1) * 1
}
resetValue(){
this.value = this.defaultValue
this.update()
}
onchange(callback){
this.callbacks.push(callback)
}
manualSet(){
var number = parseFloat(this.input.value) * this.mul
if(Number.isFinite(number) && this.min <= number && number <= this.max){
this.value = number
}
this.update(false, true)
}
captureKeys(event){
event.stopPropagation()
}
addTouch(element, callback){
pageEvents.add(element, ["mousedown", "touchstart"], event => {
if(event.type === "touchstart"){
event.preventDefault()
}else if(event.which !== 1){
return
}
callback(event)
})
}
addTouchRepeat(element, callback){
this.addTouch(element, event => {
var active = true
var func = () => {
active = false
this.touchEnd.splice(this.touchEnd.indexOf(func), 1)
}
this.touchEnd.push(func)
var repeat = delay => {
if(active && this.touchEnd){
callback(event)
setTimeout(() => repeat(50), delay)
}
}
repeat(400)
})
}
removeTouch(element){
pageEvents.remove(element, ["mousedown", "touchstart"])
}
clean(){
this.removeTouch(this.plus)
this.removeTouch(this.minus)
this.removeTouch(this.reset)
pageEvents.remove(this.input, ["touchstart", "touchend"])
pageEvents.remove(window, ["mouseup", "touchstart", "touchend", "blur"], this.windowSymbol)
pageEvents.remove(this.input, ["touchstart", "change", "keydown"])
delete this.input
delete this.reset
delete this.plus
delete this.minus
delete this.windowSymbol
delete this.touchEnd
}
}