Add global offset

Adds new settings for controlling the note offset while playing. It can be either an actual offset (it is called "Audio Latency" in the settings) or just the visual offset ("Video Latency").
With higher audio latency it means you have to press the button sooner than what you hear, similarly with higher video latency it is sooner than what you see. By offsetting these events the game would play better, however, the sound effect of you hitting the drum would still play at the wrong time, the code cannot anticipate you to hit the drum in the future so to work around this issue a new option that disables drum sounds is also included.
These settings could be set through trial and error but it would be better to get the correct values through the automated latency calibration, where you can hit the drum as you hear sounds or see a blinking animation. I tried making one by measuring latency from user input, adding all the latency up, and dividing, but that gives unreliable results. I hope someone suggests to me what I should be doing during the calibration to get better results, as I cannot figure what to do on my own.
This commit is contained in:
LoveEevee 2019-11-28 09:04:40 +03:00
parent 7a50dec558
commit ff09cb83bd
20 changed files with 1579 additions and 313 deletions

Binary file not shown.

View File

@ -16,14 +16,6 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
#cursor{
position: fixed;
width: 1px;
height: 1px;
cursor: none;
pointer-events: none;
z-index: 1;
}
#touch-drum{ #touch-drum{
display: none; display: none;
position: absolute; position: absolute;

View File

@ -158,8 +158,7 @@ kbd{
.setting-box:first-child{ .setting-box:first-child{
margin-top: 0; margin-top: 0;
} }
.settings-outer .view-content:not(:hover) .setting-box.selected, .view-content:not(:hover) .setting-box.selected,
.view-outer:not(.settings-outer) .setting-box.selected,
.setting-box:hover{ .setting-box:hover{
background: #ffb547; background: #ffb547;
animation: 2s linear border-pulse infinite; animation: 2s linear border-pulse infinite;
@ -177,7 +176,6 @@ kbd{
overflow: hidden; overflow: hidden;
} }
.view-content:not(:hover) .setting-box.selected .setting-name, .view-content:not(:hover) .setting-box.selected .setting-name,
.view-outer:not(.settings-outer) .setting-box.selected .setting-name,
.setting-box:hover .setting-name, .setting-box:hover .setting-name,
.setting-box:hover #gamepad-value{ .setting-box:hover #gamepad-value{
color: #fff; color: #fff;
@ -193,6 +191,8 @@ kbd{
border-radius: 0.2em; border-radius: 0.2em;
padding: 0.5em; padding: 0.5em;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
white-space: nowrap;
} }
.setting-value.selected{ .setting-value.selected{
width: calc(50% + 0.2em); width: calc(50% + 0.2em);
@ -215,27 +215,26 @@ kbd{
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.5);
z-index: 1; z-index: 1;
} }
#settings-gamepad{ #settings-gamepad,
#settings-latency{
display: none; display: none;
} }
#settings-gamepad .view{ #settings-gamepad .view{
position: absolute; width: 29.9em;
margin: auto; max-width: 100vw;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 574px;
height: 428px;
max-height: calc(100vh - 14em + 88px);
} }
#settings-gamepad .setting-box{ #settings-gamepad .setting-box{
height: auto; height: auto;
overflow: hidden;
}
#gamepad-bg,
#gamepad-buttons{
background-size: 20.53em;
} }
#gamepad-bg{ #gamepad-bg{
position: relative; position: relative;
width: 550px; width: 20.53em;
height: 317px; height: 11.83em;
max-height: none; max-height: none;
background-repeat: no-repeat; background-repeat: no-repeat;
text-align: center; text-align: center;
@ -244,11 +243,11 @@ kbd{
} }
#gamepad-buttons{ #gamepad-buttons{
position: absolute; position: absolute;
left: 141px; left: 5.26em;
top: 120px; top: 4.48em;
width: 282px; width: 10.52em;
height: 131px; height: 4.89em;
background-position: 0 -318px; background-position: 0 -11.87em;
background-repeat: no-repeat; background-repeat: no-repeat;
pointer-events: none; pointer-events: none;
} }
@ -259,3 +258,36 @@ kbd{
#gamepad-value::before{ #gamepad-value::before{
left: auto; left: auto;
} }
#settings-latency .view{
width: 30em;
}
#settings-latency .setting-value{
position: relative;
}
.setting-value:not(.selected) .latency-buttons{
display: none;
}
.setting-value .latency-buttons{
position: absolute;
top: 0;
right: 0;
bottom: 0;
padding: 0;
}
.latency-buttons span{
display: inline-block;
width: 2em;
height: 100%;
text-align: center;
background-color: #c3862a;
color: #fff;
line-height: 2em;
outline: none;
}
.latency-buttons span:hover,
.latency-buttons span:active{
background-color: #946013;
}
.left-buttons .taibtn{
z-index: 1;
}

View File

@ -29,23 +29,30 @@
this.endButton.innerText = strings.tutorial.ok this.endButton.innerText = strings.tutorial.ok
this.endButton.setAttribute("alt", strings.tutorial.ok) this.endButton.setAttribute("alt", strings.tutorial.ok)
this.items = []
var versionUrl = gameConfig._version.url var versionUrl = gameConfig._version.url
this.getLink(this.linkIssues).href = versionUrl + "issues" this.getLink(this.linkIssues).href = versionUrl + "issues"
this.items.push(this.linkIssues)
var contactEmail = gameConfig.email var contactEmail = gameConfig.email
if (typeof contactEmail === 'string') { this.hasEmail = typeof contactEmail === "string"
if(this.hasEmail){
this.linkEmail.setAttribute("alt", contactEmail) this.linkEmail.setAttribute("alt", contactEmail)
this.getLink(this.linkEmail).href = "mailto:" + contactEmail this.getLink(this.linkEmail).href = "mailto:" + contactEmail
this.getLink(this.linkEmail).text = contactEmail this.getLink(this.linkEmail).innerText = contactEmail
} else { this.items.push(this.linkEmail)
this.linkEmail.style.display = "none" }else{
this.linkEmail.parentNode.removeChild(this.linkEmail)
} }
pageEvents.add(this.linkIssues, ["click", "touchend"], this.linkButton.bind(this)) pageEvents.add(this.linkIssues, ["click", "touchend"], this.linkButton.bind(this))
pageEvents.add(this.linkEmail, ["click", "touchend"], this.linkButton.bind(this)) if(this.hasEmail){
pageEvents.add(this.linkEmail, ["click", "touchend"], this.linkButton.bind(this))
}
pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this))
this.items = [this.linkIssues, this.linkEmail, this.endButton] this.items.push(this.endButton)
this.selected = 2 this.selected = this.items.length - 1
this.keyboard = new Keyboard({ this.keyboard = new Keyboard({
confirm: ["enter", "space", "don_l", "don_r"], confirm: ["enter", "space", "don_l", "don_r"],
@ -146,6 +153,8 @@
} }
} }
diag.push("Language: " + strings.id + userLangStr) diag.push("Language: " + strings.id + userLangStr)
var latency = settings.getItem("latency")
diag.push("Audio Latency: " + (latency.audio > 0 ? "+" : "") + latency.audio.toString() + "ms, Video Latency: " + (latency.video > 0 ? "+" : "") + latency.video.toString() + "ms")
var errorObj = {} var errorObj = {}
if(localStorage["lastError"]){ if(localStorage["lastError"]){
try{ try{
@ -195,7 +204,9 @@
} }
var issueBody = strings.about.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, "<br>\r\n")) if(this.hasEmail){
this.getLink(this.linkEmail).href += "?body=" + encodeURIComponent(issueBody.replace(/\n/g, "<br>\r\n"))
}
return diag return diag
} }
@ -214,7 +225,9 @@
this.keyboard.clean() this.keyboard.clean()
this.gamepad.clean() this.gamepad.clean()
pageEvents.remove(this.linkIssues, ["click", "touchend"]) pageEvents.remove(this.linkIssues, ["click", "touchend"])
pageEvents.remove(this.linkEmail, ["click", "touchend"]) if(this.hasEmail){
pageEvents.remove(this.linkEmail, ["click", "touchend"])
}
pageEvents.remove(this.endButton, ["mousedown", "touchstart"]) pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
if(this.textarea){ if(this.textarea){
pageEvents.remove(this.textarea, ["focus", "blur"]) pageEvents.remove(this.textarea, ["focus", "blur"])

View File

@ -114,7 +114,8 @@ var assets = {
"v_sanka.wav", "v_sanka.wav",
"v_songsel.wav", "v_songsel.wav",
"v_start.wav", "v_start.wav",
"v_title.wav" "v_title.wav",
"calibration.wav"
], ],
"audioSfxLR": [ "audioSfxLR": [
"neiro_1_don.wav", "neiro_1_don.wav",

View File

@ -1,5 +1,6 @@
class CanvasCache{ class CanvasCache{
constructor(w, h, scale){ constructor(noSmoothing, w, h, scale){
this.noSmoothing = noSmoothing
if(w){ if(w){
this.resize(w, h, scale) this.resize(w, h, scale)
} }
@ -11,6 +12,9 @@ class CanvasCache{
this.map = new Map() this.map = new Map()
this.canvas = document.createElement("canvas") this.canvas = document.createElement("canvas")
this.ctx = this.canvas.getContext("2d") this.ctx = this.canvas.getContext("2d")
if(this.noSmoothing){
this.ctx.imageSmoothingEnabled = false
}
} }
this.scale = scale this.scale = scale
this.x = 0 this.x = 0

View File

@ -1,5 +1,5 @@
class CanvasDraw{ class CanvasDraw{
constructor(){ constructor(noSmoothing){
this.diffStarPath = new Path2D(vectors.diffStar) this.diffStarPath = new Path2D(vectors.diffStar)
this.longVowelMark = new Path2D(vectors.longVowelMark) this.longVowelMark = new Path2D(vectors.longVowelMark)
@ -68,7 +68,8 @@
emCap: /[MW]/, emCap: /[MW]/,
rWidth: /[abdfIjo-rtv-]/, rWidth: /[abdfIjo-rtv-]/,
lWidth: /[il]/, lWidth: /[il]/,
ura: /\s*[\(]裏[\)]$/ ura: /\s*[\(]裏[\)]$/,
cjk: /[\u3040-ゞ゠-ヾ一-\u9ffe]/
} }
var numbersFull = "" var numbersFull = ""
@ -78,10 +79,12 @@
this.numbersFullToHalf[numbersFull[i]] = numbersHalf[i] this.numbersFullToHalf[numbersFull[i]] = numbersHalf[i]
this.numbersFullToHalf[numbersHalf[i]] = numbersHalf[i] this.numbersFullToHalf[numbersHalf[i]] = numbersHalf[i]
} }
this.wrapOn = [" ", "\n", "%s"]
this.stickySymbols = "!,.:;?~‐–‼、。々〜ぁぃぅぇぉっゃゅょァィゥェォッャュョ・ーヽヾ!:;?"
this.songFrameCache = new CanvasCache() this.songFrameCache = new CanvasCache(noSmoothing)
this.diffStarCache = new CanvasCache() this.diffStarCache = new CanvasCache(noSmoothing)
this.crownCache = new CanvasCache() this.crownCache = new CanvasCache(noSmoothing)
this.tmpCanvas = document.createElement("canvas") this.tmpCanvas = document.createElement("canvas")
this.tmpCtx = this.tmpCanvas.getContext("2d") this.tmpCtx = this.tmpCanvas.getContext("2d")
@ -818,6 +821,163 @@
ctx.restore() ctx.restore()
} }
wrappingText(config){
var ctx = config.ctx
var inputText = config.text.toString()
var words = []
var start = 0
var substituteIndex = 0
while(start < inputText.length){
var character = inputText.slice(start, start + 1)
if(words.length !== 0){
var previous = words[words.length - 1]
if(!previous.substitute && previous !== "\n" && this.stickySymbols.indexOf(character) !== -1){
words[words.length - 1] += character
start++
continue
}
}
var index = Infinity
var currentIndex = inputText.slice(start).search(this.regex.cjk)
if(currentIndex !== -1){
index = start + currentIndex
var on = inputText.charAt(index)
}
for(var i = 0; i < this.wrapOn.length; i++){
var currentIndex = inputText.indexOf(this.wrapOn[i], start)
if(currentIndex !== -1 && currentIndex < index){
var on = this.wrapOn[i]
index = currentIndex
}
}
if(index === Infinity){
if(start !== inputText.length){
words.push(inputText.slice(start, inputText.length))
}
break
}
var end = index + (on === " " ? 1 : 0)
if(start !== end){
words.push(inputText.slice(start, end))
}
if(on === "%s" && config.substitute){
words.push({
substitute: true,
index: substituteIndex,
width: config.substitute(config, substituteIndex, true) || 0
})
substituteIndex++
}else if(on !== " "){
words.push(on)
}
start = index + on.length
}
ctx.save()
var bold = this.bold(config.fontFamily)
ctx.font = bold + config.fontSize + "px " + config.fontFamily
ctx.textBaseline = config.baseline || "top"
ctx.textAlign = "left"
ctx.fillStyle = config.fill
var lineHeight = config.lineHeight || config.fontSize
var x = 0
var y = 0
var totalW = 0
var totalH = 0
var line = ""
var toDraw = []
var lastWidth = 0
var addToDraw = obj => {
toDraw.push(obj)
if(x + lastWidth > totalW){
totalW = x + lastWidth
}
if(y + lineHeight > totalH){
totalH = y + lineHeight
}
}
var recenter = () => {
if(config.textAlign === "center"){
for(var j in toDraw){
if(toDraw[j].y === y){
toDraw[j].x += (config.width - x - lastWidth) / 2
}
}
}
}
for(var i in words){
var skip = words[i].substitute || words[i] === "\n"
if(!skip){
var currentWidth = ctx.measureText(line + words[i]).width
}
if(skip || (x !== 0 || line) && x + currentWidth > config.width){
if(line){
addToDraw({
text: line,
x: x, y: y
})
}
if(words[i].substitute){
line = ""
var currentWidth = words[i].width
if(x + lastWidth + currentWidth > config.width){
recenter()
x = 0
y += lineHeight
lastWidth = 0
}
addToDraw({
substitute: true,
index: words[i].index,
x: x + lastWidth, y: y
})
x += lastWidth + currentWidth
lastWidth = currentWidth
}else{
recenter()
x = 0
y += lineHeight
line = words[i] === "\n" ? "" : words[i]
lastWidth = ctx.measureText(line).width
}
}else{
line += words[i]
lastWidth = currentWidth
}
}
if(line){
addToDraw({
text: line,
x: x, y: y
})
recenter()
}
var addX = 0
var addY = 0
if(config.verticalAlign === "middle"){
addY = ((config.height || 0) - totalH) / 2
}
for(var i in toDraw){
var x = config.x + toDraw[i].x + addX
var y = config.y + toDraw[i].y + addY
if(toDraw[i].text){
ctx.fillText(toDraw[i].text, x, y)
}else if(toDraw[i].substitute){
ctx.save()
ctx.translate(x, y)
config.substitute(config, toDraw[i].index)
ctx.restore()
}
}
ctx.restore()
}
diffIcon(config){ diffIcon(config){
var ctx = config.ctx var ctx = config.ctx
var scale = config.scale var scale = config.scale

View File

@ -7,6 +7,16 @@ class Controller{
this.touchEnabled = touchEnabled this.touchEnabled = touchEnabled
this.snd = this.multiplayer ? "_p" + this.multiplayer : "" this.snd = this.multiplayer ? "_p" + this.multiplayer : ""
this.calibrationMode = selectedSong.folder === "calibration"
this.audioLatency = 0
this.videoLatency = 0
if(!this.calibrationMode){
var latency = settings.getItem("latency")
if(!autoPlayEnabled){
this.audioLatency = Math.round(latency.audio) || 0
}
this.videoLatency = Math.round(latency.video) || 0 + this.audioLatency
}
if(this.multiplayer !== 2){ if(this.multiplayer !== 2){
loader.changePage("game", false) loader.changePage("game", false)
} }
@ -18,18 +28,23 @@ class Controller{
} }
this.offset = this.parsedSongData.soundOffset this.offset = this.parsedSongData.soundOffset
assets.songs.forEach(song => { if(this.calibrationMode){
if(song.id == this.selectedSong.folder){ this.volume = 1
this.mainAsset = song.sound }else{
this.volume = song.volume || 1 assets.songs.forEach(song => {
} if(song.id == this.selectedSong.folder){
}) this.mainAsset = song.sound
this.volume = song.volume || 1
}
})
}
this.game = new Game(this, this.selectedSong, this.parsedSongData) this.game = new Game(this, this.selectedSong, this.parsedSongData)
this.view = new View(this) this.view = new View(this)
this.mekadon = new Mekadon(this, this.game) this.mekadon = new Mekadon(this, this.game)
this.keyboard = new GameInput(this) this.keyboard = new GameInput(this)
this.drumSounds = settings.getItem("latency").drumSounds
this.playedSounds = {} this.playedSounds = {}
} }
run(syncWith){ run(syncWith){
@ -72,8 +87,8 @@ class Controller{
} }
stopMainLoop(){ stopMainLoop(){
this.mainLoopRunning = false this.mainLoopRunning = false
if(this.mainAsset){ if(this.game.mainAsset){
this.mainAsset.stop() this.game.mainAsset.stop()
} }
if(this.multiplayer !== 2){ if(this.multiplayer !== 2){
clearInterval(this.gameInterval) clearInterval(this.gameInterval)
@ -90,13 +105,18 @@ class Controller{
if(this.game.musicFadeOut < 3){ if(this.game.musicFadeOut < 3){
this.keyboard.checkMenuKeys() this.keyboard.checkMenuKeys()
} }
if(this.calibrationMode){
this.game.calibration()
}
if(!this.game.isPaused()){ if(!this.game.isPaused()){
this.keyboard.checkGameKeys() this.keyboard.checkGameKeys()
if(ms < 0){ if(ms < 0){
this.game.updateTime() this.game.updateTime()
}else{ }else{
this.game.update() if(!this.calibrationMode){
this.game.update()
}
if(!this.mainLoopRunning){ if(!this.mainLoopRunning){
return return
} }
@ -158,7 +178,11 @@ class Controller{
if(!fadeIn){ if(!fadeIn){
this.clean() this.clean()
} }
new SongSelect(false, fadeIn, this.touchEnabled) if(this.calibrationMode){
new SettingsView(this.touchEnabled, false, null, "latency")
}else{
new SongSelect(false, fadeIn, this.touchEnabled)
}
} }
restartSong(){ restartSong(){
this.clean() this.clean()
@ -166,20 +190,24 @@ class Controller{
new LoadSong(this.selectedSong, false, true, this.touchEnabled) new LoadSong(this.selectedSong, false, true, this.touchEnabled)
}else{ }else{
new Promise(resolve => { new Promise(resolve => {
var songObj = assets.songs.find(song => song.id === this.selectedSong.folder) if(this.calibrationMode){
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() resolve()
}else{
var songObj = assets.songs.find(song => song.id === this.selectedSong.folder)
if(songObj.chart && songObj.chart !== "blank"){
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(() => { }).then(() => {
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled) var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
@ -187,10 +215,13 @@ class Controller{
}) })
} }
} }
playSound(id, time){ playSound(id, time, noSnd){
if(!this.drumSounds && (id === "neiro_1_don" || id === "neiro_1_ka" || id === "se_don" || id === "se_ka")){
return
}
var ms = Date.now() + (time || 0) * 1000 var ms = Date.now() + (time || 0) * 1000
if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){ if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){
assets.sounds[id + this.snd].play(time) assets.sounds[id + (noSnd ? "" : this.snd)].play(time)
this.playedSounds[id] = ms this.playedSounds[id] = ms
} }
} }
@ -201,11 +232,11 @@ class Controller{
} }
this.playSound(soundID + meka, time) this.playSound(soundID + meka, time)
} }
togglePause(){ togglePause(forcePause, pauseMove, noSound){
if(this.multiplayer === 1){ if(this.multiplayer === 1){
this.syncWith.game.togglePause() this.syncWith.game.togglePause(forcePause, pauseMove, noSound)
} }
this.game.togglePause() this.game.togglePause(forcePause, pauseMove, noSound)
} }
getKeys(){ getKeys(){
return this.keyboard.getKeys() return this.keyboard.getKeys()

View File

@ -45,7 +45,12 @@ class Game{
} }
initTiming(){ initTiming(){
// Date when the chrono is started (before the game begins) // Date when the chrono is started (before the game begins)
var offsetTime = Math.max(0, this.timeForDistanceCircle - this.songData.circles[0].ms) |0 var firstCircle = this.songData.circles[0]
if(this.controller.calibrationMode){
var offsetTime = 0
}else{
var offsetTime = Math.max(0, this.timeForDistanceCircle - (firstCircle ? firstCircle.ms : 0)) |0
}
if(this.controller.multiplayer){ if(this.controller.multiplayer){
var syncWith = this.controller.syncWith var syncWith = this.controller.syncWith
var syncCircles = syncWith.game.songData.circles var syncCircles = syncWith.game.songData.circles
@ -57,8 +62,8 @@ class Game{
this.startDate = Date.now() + offsetTime this.startDate = Date.now() + offsetTime
} }
update(){ update(){
// Main operations
this.updateTime() this.updateTime()
// Main operations
this.updateCirclesStatus() this.updateCirclesStatus()
this.checkPlays() this.checkPlays()
// Event operations // Event operations
@ -82,10 +87,10 @@ class Game{
if(circle && (!circle.branch || circle.branch.active) && !circle.isPlayed){ if(circle && (!circle.branch || circle.branch.active) && !circle.isPlayed){
var type = circle.type var type = circle.type
var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll" var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll"
var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad) var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad) + this.controller.audioLatency
if(ms >= circle.ms){ if(ms >= circle.ms + this.controller.audioLatency){
if(drumrollNotes && !circle.rendaPlayed && ms < endTime){ if(drumrollNotes && !circle.rendaPlayed && ms < endTime + this.controller.audioLatency){
circle.rendaPlayed = true circle.rendaPlayed = true
if(this.rules.difficulty === "easy"){ if(this.rules.difficulty === "easy"){
assets.sounds["v_renda" + this.controller.snd].stop() assets.sounds["v_renda" + this.controller.snd].stop()
@ -109,7 +114,7 @@ class Game{
this.updateCurrentCircle() this.updateCurrentCircle()
if(this.controller.multiplayer === 1){ if(this.controller.multiplayer === 1){
var value = { var value = {
pace: (ms - circle.ms) / circle.timesHit pace: (ms - circle.ms - this.controller.audioLatency) / circle.timesHit
} }
if(type === "drumroll" || type === "daiDrumroll"){ if(type === "drumroll" || type === "daiDrumroll"){
value.kaAmount = circle.timesKa / circle.timesHit value.kaAmount = circle.timesKa / circle.timesHit
@ -211,7 +216,7 @@ class Game{
for(var i = this.currentCircle + 1; i < circles.length; i++){ for(var i = this.currentCircle + 1; i < circles.length; i++){
var circle = circles[i] var circle = circles[i]
var relative = ms - circle.ms var relative = ms - circle.ms - this.controller.audioLatency
if(!circle.branch || circle.branch.active){ if(!circle.branch || circle.branch.active){
if((!circleIsNote(circle) || relative < -this.rules.bad)){ if((!circleIsNote(circle) || relative < -this.rules.bad)){
break break
@ -310,7 +315,7 @@ class Game{
var keyTime = this.controller.getKeyTime() var keyTime = this.controller.getKeyTime()
var currentTime = keysDon ? keyTime["don"] : keyTime["ka"] var currentTime = keysDon ? keyTime["don"] : keyTime["ka"]
var relative = currentTime - circle.ms var relative = currentTime - circle.ms - this.controller.audioLatency
if(relative >= this.rules.ok){ if(relative >= this.rules.ok){
var fixedNote = this.fixNoteStream(keysDon) var fixedNote = this.fixNoteStream(keysDon)
@ -366,7 +371,7 @@ class Game{
if(this.controller.multiplayer === 1){ if(this.controller.multiplayer === 1){
var value = { var value = {
score: score, score: score,
ms: circle.ms - currentTime, ms: circle.ms - currentTime - this.controller.audioLatency,
dai: typeDai ? (keyDai ? 2 : 1) : 0 dai: typeDai ? (keyDai ? 2 : 1) : 0
} }
if((!keysDon || !typeDon) && (!keysKa || !typeKa)){ if((!keysDon || !typeDon) && (!keysKa || !typeKa)){
@ -375,7 +380,7 @@ class Game{
p2.send("note", value) p2.send("note", value)
} }
}else{ }else{
if(circle.ms > currentTime || currentTime > circle.endTime){ if(circle.ms + this.controller.audioLatency > currentTime || currentTime > circle.endTime + this.controller.audioLatency){
return true return true
} }
if(keysDon && type === "balloon"){ if(keysDon && type === "balloon"){
@ -400,7 +405,7 @@ class Game{
circle.played(score) circle.played(score)
if(this.controller.multiplayer == 1){ if(this.controller.multiplayer == 1){
p2.send("drumroll", { p2.send("drumroll", {
pace: (this.elapsedTime - circle.ms) / circle.timesHit pace: (this.elapsedTime - circle.ms + this.controller.audioLatency) / circle.timesHit
}) })
} }
}else{ }else{
@ -447,17 +452,19 @@ class Game{
var ms = this.elapsedTime var ms = this.elapsedTime
if(!this.lastCircle){ if(!this.lastCircle){
var circles = this.songData.circles var circles = this.songData.circles
this.lastCircle = circles[circles.length - 1].endTime var circle = circles[circles.length - 1]
this.lastCircle = circle ? circle.endTime : 0
if(this.controller.multiplayer){ if(this.controller.multiplayer){
var syncWith = this.controller.syncWith var syncWith = this.controller.syncWith
var syncCircles = syncWith.game.songData.circles var syncCircles = syncWith.game.songData.circles
var syncLastCircle = syncCircles[syncCircles.length - 1].endTime circle = syncCircles[syncCircles.length - 1]
var syncLastCircle = circle ? circle.endTime : 0
if(syncLastCircle > this.lastCircle){ if(syncLastCircle > this.lastCircle){
this.lastCircle = syncLastCircle this.lastCircle = syncLastCircle
} }
} }
} }
if(!this.fadeOutStarted && ms >= this.lastCircle + 2000){ if(!this.fadeOutStarted && ms >= this.lastCircle + 2000 + this.controller.audioLatency){
this.fadeOutStarted = ms this.fadeOutStarted = ms
if(this.controller.multiplayer){ if(this.controller.multiplayer){
this.controller.syncWith.game.fadeOutStarted = ms this.controller.syncWith.game.fadeOutStarted = ms
@ -495,28 +502,51 @@ class Game{
playMainMusic(){ playMainMusic(){
var ms = this.elapsedTime + this.controller.offset var ms = this.elapsedTime + this.controller.offset
if(!this.mainMusicPlaying && (!this.fadeOutStarted || ms < this.fadeOutStarted + 1600)){ if(!this.mainMusicPlaying && (!this.fadeOutStarted || ms < this.fadeOutStarted + 1600)){
if(this.controller.multiplayer !== 2 && this.mainAsset){ if(this.calibrationState === "audio"){
var beatInterval = this.controller.view.beatInterval
var startAt = ms % beatInterval
var duration = this.mainAsset.duration * 1000
if(startAt < duration){
this.mainAsset.playLoop(0, false, startAt / 1000, 0, beatInterval / 1000)
}else{
this.mainAsset.playLoop((startAt - duration) / 1000, false, 0, 0, beatInterval / 1000)
}
}else if(this.controller.multiplayer !== 2 && this.mainAsset){
this.mainAsset.play((ms < 0 ? -ms : 0) / 1000, false, Math.max(0, ms / 1000)) this.mainAsset.play((ms < 0 ? -ms : 0) / 1000, false, Math.max(0, ms / 1000))
} }
this.mainMusicPlaying = true this.mainMusicPlaying = true
} }
} }
togglePause(){ togglePause(forcePause, pauseMove, noSound){
if(!this.paused){ if(!this.paused){
assets.sounds["se_pause"].play() if(forcePause === false){
return
}
if(!noSound){
this.controller.playSound("se_pause", 0, true)
}
this.paused = true this.paused = true
this.latestDate = Date.now() this.latestDate = Date.now()
if(this.mainAsset){ if(this.mainAsset){
this.mainAsset.stop() this.mainAsset.stop()
} }
this.mainMusicPlaying = false this.mainMusicPlaying = false
this.view.pauseMove(0, true) this.view.pauseMove(pauseMove || 0, true)
this.view.gameDiv.classList.add("game-paused") this.view.gameDiv.classList.add("game-paused")
this.view.lastMousemove = this.view.getMS() this.view.lastMousemove = this.view.getMS()
this.view.cursorHidden = false this.view.cursorHidden = false
pageEvents.send("pause") pageEvents.send("pause")
}else{ }else if(!forcePause){
assets.sounds["se_cancel"].play() if(forcePause !== false && this.calibrationState && ["audioHelp", "audioComplete", "videoHelp", "videoComplete", "results"].indexOf(this.calibrationState) !== -1){
return
}
if(this.calibrationState === "audioHelp" || this.calibrationState === "videoHelp"){
this.calibrationState = this.calibrationState === "audioHelp" ? "audio" : "video"
this.controller.view.pauseOptions = strings.pauseOptions
this.controller.playSound("se_don", 0, true)
}else if(!noSound){
this.controller.playSound("se_cancel", 0, true)
}
this.paused = false this.paused = false
var currentDate = Date.now() var currentDate = Date.now()
this.startDate += currentDate - this.latestDate this.startDate += currentDate - this.latestDate
@ -683,7 +713,7 @@ class Game{
if(!circle || circle.branch === currentBranch[pastActive]){ if(!circle || circle.branch === currentBranch[pastActive]){
var ms = this.elapsedTime var ms = this.elapsedTime
var closestCircle = circles.findIndex(circle => { var closestCircle = circles.findIndex(circle => {
return (!circle.branch || circle.branch.active) && circle.endTime >= ms return (!circle.branch || circle.branch.active) && circle.endTime + this.controller.audioLatency >= ms
}) })
if(closestCircle !== -1){ if(closestCircle !== -1){
this.currentCircle = closestCircle this.currentCircle = closestCircle
@ -701,4 +731,104 @@ class Game{
this.sectionNotes = [] this.sectionNotes = []
this.sectionDrumroll = 0 this.sectionDrumroll = 0
} }
clearKeyTime(){
var keyboard = this.controller.keyboard
for(var key in keyboard.keyTime){
keyboard.keys[key] = null
keyboard.keyTime[key] = -Infinity
}
}
calibration(){
var view = this.controller.view
if(!this.calibrationState){
this.controller.parsedSongData.measures = []
this.calibrationProgress = {
audio: 0,
video: 0,
requirement: 40
}
this.calibrationReset("audio", true)
}
var progress = this.calibrationProgress
var state = this.calibrationState
switch(state){
case "audio":
case "video":
if(state === "audio" && !this.mainAsset){
this.mainAsset = assets.sounds["calibration"]
this.mainMusicPlaying = false
}
if(progress.hit >= progress.requirement){
var reduced = 0
for(var i = 2; i < progress.offsets.length; i++){
reduced += progress.offsets[i]
}
progress[state] = Math.max(0, Math.round(reduced / progress.offsets.length - 2))
this.calibrationState += "Complete"
view.pauseOptions = []
this.clearKeyTime()
this.togglePause(true, 1)
this.mainAsset = null
}
break
case "audioComplete":
case "videoComplete":
if(Date.now() - this.latestDate > 3000){
var audioComplete = this.calibrationState === "audioComplete"
this.controller.playSound("se_pause", 0, true)
if(audioComplete){
this.calibrationReset("video")
}else{
view.pauseOptions = [
strings.calibration.retryPrevious,
strings.calibration.finish
]
}
this.calibrationState = audioComplete ? "videoHelp" : "results"
}
break
}
}
calibrationHit(ms){
var progress = this.calibrationProgress
var beatInterval = this.controller.view.beatInterval
var current = Math.floor((ms + 100) / beatInterval)
if(current !== progress.last){
var offset = ((ms + 100) % beatInterval) - 100
var offsets = progress.offsets
if(offsets.length >= progress.requirement){
offsets.shift()
}
offsets.push(offset)
progress.hit++
progress.last = current
this.globalScore.gauge = 100 / (progress.requirement / progress.hit)
}
}
calibrationReset(to, togglePause){
var view = this.controller.view
this.songData.circles = []
view.pauseOptions = [
to === "audio" ? strings.calibration.back : strings.calibration.retryPrevious,
strings.calibration.start
]
this.calibrationState = to + "Help"
var progress = this.calibrationProgress
progress.offsets = []
progress.hit = 0
progress.last = null
this.globalScore.gauge = 0
if(to === "video"){
this.clearKeyTime()
this.initTiming()
this.latestDate = this.startDate
this.elapsedTime = 0
view.ms = 0
}
if(togglePause){
this.togglePause(true, 1, true)
}else{
view.pauseMove(1, true)
}
}
} }

View File

@ -94,7 +94,7 @@ class GameInput{
} }
} }
checkMenuKeys(){ checkMenuKeys(){
if(!this.controller.multiplayer && !this.locked){ if(!this.controller.multiplayer && !this.locked && this.controller.view.pauseOptions.length !== 0){
var moveMenu = 0 var moveMenu = 0
var ms = this.game.getAccurateTime() var ms = this.game.getAccurateTime()
this.gamepadMenu.play((pressed, name) => { this.gamepadMenu.play((pressed, name) => {
@ -146,7 +146,7 @@ class GameInput{
this.checkKey("don_l", "menu", moveMenuConfirm) this.checkKey("don_l", "menu", moveMenuConfirm)
this.checkKey("don_r", "menu", moveMenuConfirm) this.checkKey("don_r", "menu", moveMenuConfirm)
if(moveMenu && this.game.isPaused()){ if(moveMenu && this.game.isPaused()){
assets.sounds["se_ka"].play() this.controller.playSound("se_ka", 0, true)
this.controller.view.pauseMove(moveMenu) this.controller.view.pauseMove(moveMenu)
} }
} }
@ -197,11 +197,19 @@ class GameInput{
return return
} }
this.keyTime[name] = ms this.keyTime[name] = ms
var calibrationState = this.game.calibrationState
var calibration = calibrationState && !this.game.paused
if(name == "don_l" || name == "don_r"){ if(name == "don_l" || name == "don_r"){
this.checkKeySound(name, "don") if(calibration){
this.game.calibrationHit(ms)
}else{
this.checkKeySound(name, "don")
}
this.keyboardEvents++ this.keyboardEvents++
}else if(name == "ka_l" || name == "ka_r"){ }else if(name == "ka_l" || name == "ka_r"){
this.checkKeySound(name, "ka") if(!calibration){
this.checkKeySound(name, "ka")
}
this.keyboardEvents++ this.keyboardEvents++
} }
}else{ }else{

View File

@ -35,14 +35,20 @@ class LoadSong{
var song = this.selectedSong var song = this.selectedSong
var id = song.folder var id = song.folder
var promises = [] var promises = []
assets.sounds["v_start"].play() if(song.folder !== "calibration"){
assets.sounds["v_start"].play()
var songObj = assets.songs.find(song => song.id === id)
}else{
var songObj = {
"music": "muted",
"chart": "blank"
}
}
song.songBg = this.randInt(1, 5) song.songBg = this.randInt(1, 5)
song.songStage = this.randInt(1, 3) song.songStage = this.randInt(1, 3)
song.donBg = this.randInt(1, 6) song.donBg = this.randInt(1, 6)
var songObj = assets.songs.find(song => song.id === id)
if(song.songSkin && song.songSkin.name){ if(song.songSkin && song.songSkin.name){
var imgLoad = [] var imgLoad = []
for(var type in song.songSkin){ for(var type in song.songSkin){
@ -117,14 +123,18 @@ class LoadSong{
} }
})) }))
if(songObj.chart){ if(songObj.chart){
var reader = new FileReader() if(songObj.chart === "blank"){
promises.push(pageEvents.load(reader).then(event => { this.songData = ""
this.songData = event.target.result.replace(/\0/g, "").split("\n")
}))
if(song.type === "tja"){
reader.readAsText(songObj.chart, "sjis")
}else{ }else{
reader.readAsText(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{ }else{
promises.push(loader.ajax(this.getSongPath(song)).then(data => { promises.push(loader.ajax(this.getSongPath(song)).then(data => {

View File

@ -122,7 +122,7 @@
return [string.slice(0, index), string.slice(index + delimiter.length)] return [string.slice(0, index), string.slice(index + delimiter.length)]
} }
parseCircles(){ parseCircles(){
var meta = this.metadata[this.difficulty] var meta = this.metadata[this.difficulty] || {}
var ms = (meta.offset || 0) * -1000 + this.offset var ms = (meta.offset || 0) * -1000 + this.offset
var bpm = Math.abs(meta.bpm) || 120 var bpm = Math.abs(meta.bpm) || 120
var scroll = 1 var scroll = 1

View File

@ -10,6 +10,14 @@ class Scoresheet{
this.canvas = document.getElementById("canvas") this.canvas = document.getElementById("canvas")
this.ctx = this.canvas.getContext("2d") this.ctx = this.canvas.getContext("2d")
var resolution = settings.getItem("resolution")
var noSmoothing = resolution === "low" || resolution === "lowest"
if(noSmoothing){
this.ctx.imageSmoothingEnabled = false
}
if(resolution === "lowest"){
this.canvas.style.imageRendering = "pixelated"
}
this.game = document.getElementById("game") this.game = document.getElementById("game")
this.fadeScreen = document.createElement("div") this.fadeScreen = document.createElement("div")
@ -28,8 +36,8 @@ class Scoresheet{
this.frame = 1000 / 60 this.frame = 1000 / 60
this.numbers = "001122334455667788900112233445".split("") this.numbers = "001122334455667788900112233445".split("")
this.draw = new CanvasDraw() this.draw = new CanvasDraw(noSmoothing)
this.canvasCache = new CanvasCache() this.canvasCache = new CanvasCache(noSmoothing)
this.keyboard = new Keyboard({ this.keyboard = new Keyboard({
confirm: ["enter", "space", "esc", "don_l", "don_r"] confirm: ["enter", "space", "esc", "don_l", "don_r"]
@ -105,7 +113,7 @@ class Scoresheet{
if(!p2.session){ if(!p2.session){
this.state.screen = "scoresShown" this.state.screen = "scoresShown"
this.state.screenMS = this.getMS() this.state.screenMS = this.getMS()
assets.sounds["neiro_1_don"].play() this.controller.playSound("neiro_1_don", 0, true)
} }
} }
toSongsel(fromP2){ toSongsel(fromP2){
@ -114,7 +122,7 @@ class Scoresheet{
this.state.screen = "fadeOut" this.state.screen = "fadeOut"
this.state.screenMS = this.getMS() this.state.screenMS = this.getMS()
if(!fromP2){ if(!fromP2){
assets.sounds["neiro_1_don"].play() this.controller.playSound("neiro_1_don", 0, true)
} }
} }
} }

View File

@ -2,11 +2,15 @@ class Settings{
constructor(){ constructor(){
var ios = /iPhone|iPad/.test(navigator.userAgent) var ios = /iPhone|iPad/.test(navigator.userAgent)
var phone = /Android|iPhone|iPad/.test(navigator.userAgent) var phone = /Android|iPhone|iPad/.test(navigator.userAgent)
this.allLanguages = []
for(var i in allStrings){
this.allLanguages.push(i)
}
this.items = { this.items = {
language: { language: {
type: "language", type: "language",
options: ["ja", "en", "cn", "tw", "ko"], options: this.allLanguages,
default: this.getLang() default: this.getLang()
}, },
resolution: { resolution: {
@ -34,6 +38,14 @@ class Settings{
options: ["a", "b", "c"], options: ["a", "b", "c"],
default: "a", default: "a",
gamepad: true gamepad: true
},
latency: {
type: "latency",
default: {
"audio": 0,
"video": 0,
"drumSounds": true
}
} }
} }
@ -61,6 +73,22 @@ class Settings{
} }
} }
this.storage[i] = obj this.storage[i] = obj
}else if(current.type === "latency"){
var obj = {}
for(var j in current.default){
if(storage[i] && j in storage[i]){
if(j === "drumSounds"){
obj[j] = !!storage[i][j]
continue
}else if(!isNaN(storage[i][j])){
obj[j] = Math.round(parseFloat(storage[i][j]) || 0)
continue
}
}
obj = null
break
}
this.storage[i] = obj
}else{ }else{
this.storage[i] = storage[i] this.storage[i] = storage[i]
} }
@ -107,7 +135,7 @@ class Settings{
} }
} }
} }
return "ja" return this.allLanguages[0]
} }
setLang(lang, noEvent){ setLang(lang, noEvent){
strings = lang strings = lang
@ -122,7 +150,7 @@ class Settings{
} }
class SettingsView{ class SettingsView{
constructor(touchEnabled, tutorial, songId){ constructor(touchEnabled, tutorial, songId, toSetting){
this.touchEnabled = touchEnabled this.touchEnabled = touchEnabled
this.tutorial = tutorial this.tutorial = tutorial
this.songId = songId this.songId = songId
@ -130,9 +158,15 @@ class SettingsView{
loader.changePage("settings", tutorial) loader.changePage("settings", tutorial)
assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992)
this.defaultButton = document.getElementById("settings-default") this.defaultButton = document.getElementById("settings-default")
this.viewOuter = this.getElement("view-outer")
if(touchEnabled){ if(touchEnabled){
this.getElement("view-outer").classList.add("touch-enabled") this.viewOuter.classList.add("touch-enabled")
} }
this.touchEnd = []
pageEvents.add(this.viewOuter, ["mouseup", "touchend"], event => {
this.touchEnd.forEach(func => func(event))
})
var gamepadEnabled = false var gamepadEnabled = false
if("getGamepads" in navigator){ if("getGamepads" in navigator){
var gamepads = navigator.getGamepads() var gamepads = navigator.getGamepads()
@ -145,19 +179,22 @@ class SettingsView{
} }
this.mode = "settings" this.mode = "settings"
this.pressedKeys = {}
this.keyboard = new Keyboard({ this.keyboard = new Keyboard({
"confirm": ["enter", "space", "don_l", "don_r"], "confirm": ["enter", "space", "don_l", "don_r"],
"up": ["up"], "up": ["up"],
"previous": ["left", "ka_l"], "right": ["right", "ka_r"],
"next": ["right", "down", "ka_r"], "down": ["down"],
"left": ["left", "ka_l"],
"back": ["esc"], "back": ["esc"],
"other": ["wildcard"] "other": ["wildcard"]
}, this.keyPressed.bind(this)) }, this.keyPressed.bind(this))
this.gamepad = new Gamepad({ this.gamepad = new Gamepad({
"confirm": ["b", "ls", "rs"], "confirm": ["b", "ls", "rs"],
"up": ["u", "lsu"], "up": ["u", "lsu"],
"previous": ["l", "lb", "lt", "lsl"], "right": ["r", "rb", "rt", "lsr"],
"next": ["d", "r", "rb", "rt", "lsd", "lsr"], "down": ["d", "lsd"],
"left": ["l", "lb", "lt", "lsl"],
"back": ["start", "a"] "back": ["start", "a"]
}, this.keyPressed.bind(this)) }, this.keyPressed.bind(this))
@ -182,15 +219,15 @@ class SettingsView{
var nameDiv = document.createElement("div") var nameDiv = document.createElement("div")
nameDiv.classList.add("setting-name", "stroke-sub") nameDiv.classList.add("setting-name", "stroke-sub")
var name = strings.settings[i].name var name = strings.settings[i].name
nameDiv.innerText = name this.setAltText(nameDiv, name)
nameDiv.setAttribute("alt", name)
settingBox.appendChild(nameDiv) settingBox.appendChild(nameDiv)
var valueDiv = document.createElement("div") var valueDiv = document.createElement("div")
valueDiv.classList.add("setting-value") valueDiv.classList.add("setting-value")
this.getValue(i, valueDiv) this.getValue(i, valueDiv)
settingBox.appendChild(valueDiv) settingBox.appendChild(valueDiv)
content.appendChild(settingBox) content.appendChild(settingBox)
if(this.items.length === this.selected){ if(!toSetting && this.items.length === this.selected || toSetting === i){
this.selected = this.items.length
settingBox.classList.add("selected") settingBox.classList.add("selected")
} }
this.addTouch(settingBox, event => this.setValue(i)) this.addTouch(settingBox, event => this.setValue(i))
@ -226,8 +263,99 @@ class SettingsView{
this.gamepadButtons = document.getElementById("gamepad-buttons") this.gamepadButtons = document.getElementById("gamepad-buttons")
this.gamepadValue = document.getElementById("gamepad-value") this.gamepadValue = document.getElementById("gamepad-value")
this.latencySettings = document.getElementById("settings-latency")
this.addTouch(this.latencySettings, event => {
if(event.target === event.currentTarget){
this.latencyBack()
}
})
this.latencyTitle = this.latencySettings.getElementsByClassName("view-title")[0]
this.latencyItems = []
this.latencySelected = 0
var latencyContent = this.latencySettings.getElementsByClassName("view-content")[0]
var latencyWindow = ["calibration", "audio", "video", "drumSounds"]
for(let i in latencyWindow){
let current = latencyWindow[i]
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.latency[current]
this.setAltText(nameDiv, name)
settingBox.appendChild(nameDiv)
let outputObject = {
id: current,
settingBox: settingBox,
nameDiv: nameDiv
}
if(current === "calibration"){
nameDiv.style.width = "100%"
}else{
var valueDiv = document.createElement("div")
valueDiv.classList.add("setting-value")
settingBox.appendChild(valueDiv)
var valueText = document.createTextNode("")
valueDiv.appendChild(valueText)
this.latencyGetValue(current, valueText)
if(current !== "drumSounds"){
var buttons = document.createElement("div")
buttons.classList.add("latency-buttons")
var buttonMinus = document.createElement("span")
buttonMinus.innerText = "-"
buttons.appendChild(buttonMinus)
this.addTouchRepeat(buttonMinus, event => {
this.latencySetAdjust(outputObject, -1)
})
var buttonPlus = document.createElement("span")
buttonPlus.innerText = "+"
buttons.appendChild(buttonPlus)
this.addTouchRepeat(buttonPlus, event => {
this.latencySetAdjust(outputObject, 1)
})
valueDiv.appendChild(buttons)
}
}
latencyContent.appendChild(settingBox)
if(this.latencyItems.length === this.latencySelected){
settingBox.classList.add("selected")
}
this.addTouch(settingBox, event => {
if(event.target.tagName !== "SPAN"){
this.latencySetValue(current, event.type === "touchstart")
}
})
if(current !== "calibration"){
outputObject.valueDiv = valueDiv
outputObject.valueText = valueText
outputObject.buttonMinus = buttonMinus
outputObject.buttonPlus = buttonPlus
}
this.latencyItems.push(outputObject)
}
this.latencyDefaultButton = document.getElementById("latency-default")
this.latencyItems.push({
id: "default",
settingBox: this.latencyDefaultButton
})
this.addTouch(this.latencyDefaultButton, event => this.latencyDefault())
this.latencyEndButton = this.latencySettings.getElementsByClassName("view-end-button")[0]
this.latencyItems.push({
id: "back",
settingBox: this.latencyEndButton
})
this.addTouch(this.latencyEndButton, event => this.latencyBack(true))
this.setStrings() this.setStrings()
this.drumSounds = settings.getItem("latency").drumSounds
this.playedSounds = {}
this.redrawRunning = true
this.redrawBind = this.redraw.bind(this)
this.redraw()
if(toSetting === "latency"){
this.mode = "latency"
this.latencySet()
}
pageEvents.send("settings") pageEvents.send("settings")
} }
getElement(name){ getElement(name){
@ -246,6 +374,23 @@ class SettingsView{
callback(event) 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){
callback()
setTimeout(() => repeat(50), delay)
}
}
repeat(400)
})
}
removeTouch(element){ removeTouch(element){
pageEvents.remove(element, ["mousedown", "touchstart"]) pageEvents.remove(element, ["mousedown", "touchstart"])
} }
@ -274,6 +419,17 @@ class SettingsView{
valueDiv.appendChild(keyDiv) valueDiv.appendChild(keyDiv)
} }
return return
}else if(current.type === "latency"){
var audioVideo = [Math.round(value.audio), Math.round(value.video)]
var latencyValue = strings.settings[name].value.split("%s")
var latencyIndex = 0
value = ""
latencyValue.forEach((string, i) => {
if(i !== 0){
value += this.addMs(audioVideo[latencyIndex++])
}
value += string
})
} }
valueDiv.innerText = value valueDiv.innerText = value
} }
@ -285,6 +441,7 @@ class SettingsView{
if(this.mode !== "settings"){ if(this.mode !== "settings"){
if(this.selected === selectedIndex){ if(this.selected === selectedIndex){
this.keyboardBack(selected) this.keyboardBack(selected)
this.playSound("se_don")
} }
return return
} }
@ -303,24 +460,37 @@ class SettingsView{
selected.valueDiv.classList.add("selected") selected.valueDiv.classList.add("selected")
this.keyboardKeys = {} this.keyboardKeys = {}
this.keyboardSet() this.keyboardSet()
assets.sounds["se_don"].play() this.playSound("se_don")
return return
}else if(current.type === "gamepad"){ }else if(current.type === "gamepad"){
this.mode = "gamepad" this.mode = "gamepad"
this.gamepadSelected = current.options.indexOf(value) this.gamepadSelected = current.options.indexOf(value)
this.gamepadSet() this.gamepadSet()
assets.sounds["se_don"].play() this.playSound("se_don")
return
}else if(current.type === "latency"){
this.mode = "latency"
this.latencySet()
this.playSound("se_don")
return return
} }
settings.setItem(name, value) settings.setItem(name, value)
this.getValue(name, this.items[this.selected].valueDiv) this.getValue(name, this.items[this.selected].valueDiv)
assets.sounds["se_ka"].play() this.playSound("se_ka")
if(current.type === "language"){ if(current.type === "language"){
this.setLang(allStrings[value]) this.setLang(allStrings[value])
} }
} }
keyPressed(pressed, name, event){ keyPressed(pressed, name, event, repeat){
if(!pressed){ if(pressed){
if(!this.pressedKeys[name]){
this.pressedKeys[name] = this.getMS() + 300
}
}else{
this.pressedKeys[name] = 0
return
}
if(repeat && name !== "up" && name !== "right" && name !== "down" && name !== "left"){
return return
} }
this.touched = false this.touched = false
@ -334,31 +504,31 @@ class SettingsView{
}else{ }else{
this.setValue(selected.id) this.setValue(selected.id)
} }
}else if(name === "up" || name === "previous" || name === "next"){ }else if(name === "up" || name === "right" || name === "down" || name === "left"){
selected.settingBox.classList.remove("selected") selected.settingBox.classList.remove("selected")
do{ do{
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1)) this.selected = this.mod(this.items.length, this.selected + ((name === "right" || name === "down") ? 1 : -1))
}while(this.items[this.selected].id === "default" && name !== "previous") }while(this.items[this.selected].id === "default" && name !== "left")
selected = this.items[this.selected] selected = this.items[this.selected]
selected.settingBox.classList.add("selected") selected.settingBox.classList.add("selected")
selected.settingBox.scrollIntoView() selected.settingBox.scrollIntoView()
assets.sounds["se_ka"].play() this.playSound("se_ka")
}else if(name === "back"){ }else if(name === "back"){
this.onEnd() this.onEnd()
} }
}else if(this.mode === "gamepad"){ }else if(this.mode === "gamepad"){
if(name === "confirm"){ if(name === "confirm"){
this.gamepadBack(true) this.gamepadBack(true)
}else if(name === "up" || name === "previous" || name === "next"){ }else if(name === "up" || name === "right" || name === "down" || name === "left"){
this.gamepadSet(name === "next" ? 1 : -1) this.gamepadSet((name === "right" || name === "down") ? 1 : -1)
}else if(name === "back"){ }else if(name === "back"){
this.gamepadBack() this.gamepadBack()
} }
}else if(this.mode === "keyboard"){ }else if(this.mode === "keyboard"){
if(name === "back"){ if(name === "back"){
this.keyboardBack(selected) this.keyboardBack(selected)
assets.sounds["se_cancel"].play() this.playSound("se_cancel")
}else{ }else if(event){
event.preventDefault() event.preventDefault()
var currentKey = event.key.toLowerCase() var currentKey = event.key.toLowerCase()
for(var i in this.keyboardKeys){ for(var i in this.keyboardKeys){
@ -367,10 +537,40 @@ class SettingsView{
} }
} }
var current = this.keyboardCurrent var current = this.keyboardCurrent
assets.sounds[current === "ka_l" || current === "ka_r" ? "se_ka" : "se_don"].play() this.playSound(current === "ka_l" || current === "ka_r" ? "se_ka" : "se_don")
this.keyboardKeys[current] = [currentKey] this.keyboardKeys[current] = [currentKey]
this.keyboardSet() this.keyboardSet()
} }
}else if(this.mode === "latency"){
var latencySelected = this.latencyItems[this.latencySelected]
if(name === "confirm"){
if(latencySelected.id === "back"){
this.latencyBack(true)
}else if(latencySelected.id === "default"){
this.latencyDefault()
}else{
this.latencySetValue(latencySelected.id)
}
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
latencySelected.settingBox.classList.remove("selected")
do{
this.latencySelected = this.mod(this.latencyItems.length, this.latencySelected + ((name === "right" || name === "down") ? 1 : -1))
}while(this.latencyItems[this.latencySelected].id === "default" && name !== "left")
latencySelected = this.latencyItems[this.latencySelected]
latencySelected.settingBox.classList.add("selected")
latencySelected.settingBox.scrollIntoView()
this.playSound("se_ka")
}else if(name === "back"){
this.latencyBack()
}
}else if(this.mode === "latencySet"){
var latencySelected = this.latencyItems[this.latencySelected]
if(name === "confirm" || name === "back"){
this.latencySetBack(latencySelected)
this.playSound(name === "confirm" ? "se_don" : "se_cancel")
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
this.latencySetAdjust(latencySelected, (name === "up" || name === "right") ? 1 : -1)
}
} }
} }
keyboardSet(){ keyboardSet(){
@ -416,14 +616,13 @@ class SettingsView{
var current = settings.items[selected.id] var current = settings.items[selected.id]
if(diff){ if(diff){
this.gamepadSelected = this.mod(current.options.length, this.gamepadSelected + diff) this.gamepadSelected = this.mod(current.options.length, this.gamepadSelected + diff)
assets.sounds["se_ka"].play() this.playSound("se_ka")
} }
var opt = current.options[this.gamepadSelected] var opt = current.options[this.gamepadSelected]
var value = strings.settings[selected.id][opt] var value = strings.settings[selected.id][opt]
this.gamepadValue.innerText = value this.setAltText(this.gamepadValue, value)
this.gamepadValue.setAttribute("alt", value) this.gamepadButtons.style.backgroundPosition = "0 " + (-11.87 - 4.93 * this.gamepadSelected) + "em"
this.gamepadButtons.style.backgroundPosition = "0 " + (-318 - 132 * this.gamepadSelected) + "px" this.gamepadSettings.style.display = "flex"
this.gamepadSettings.style.display = "block"
} }
gamepadBack(confirm){ gamepadBack(confirm){
if(this.mode !== "gamepad"){ if(this.mode !== "gamepad"){
@ -433,10 +632,142 @@ class SettingsView{
var current = settings.items[selected.id] var current = settings.items[selected.id]
settings.setItem(selected.id, current.options[this.gamepadSelected]) settings.setItem(selected.id, current.options[this.gamepadSelected])
this.getValue(selected.id, selected.valueDiv) this.getValue(selected.id, selected.valueDiv)
assets.sounds[confirm ? "se_don" : "se_cancel"].play() this.playSound(confirm ? "se_don" : "se_cancel")
this.gamepadSettings.style.display = "" this.gamepadSettings.style.display = ""
this.mode = "settings" this.mode = "settings"
} }
latencySet(){
if(this.mode !== "latency"){
return
}
var selected = this.items[this.selected]
var current = settings.items[selected.id]
this.latencySettings.style.display = "flex"
}
latencyGetValue(name, valueText){
var currentLatency = settings.getItem("latency")
if(name === "drumSounds"){
valueText.data = currentLatency[name] ? strings.settings.on : strings.settings.off
}else{
valueText.data = this.addMs(currentLatency[name] || 0)
}
}
latencySetValue(name, touched){
var selectedIndex = this.latencyItems.findIndex(item => item.id === name)
var selected = this.latencyItems[selectedIndex]
if(this.mode === "latencySet"){
this.latencySetBack(this.latencyItems[this.latencySelected])
if(this.latencySelected === selectedIndex){
this.playSound("se_don")
return
}
}else if(this.mode !== "latency"){
return
}
if(name === "calibration"){
this.playSound("se_don")
this.clean()
new LoadSong({
"title": strings.calibration.title,
"folder": "calibration",
"type": "tja",
"songSkin": {}
}, false, false, touched)
}else if(name === "drumSounds"){
this.drumSounds = !settings.getItem("latency")[name]
this.latencySave(name, this.drumSounds)
this.latencyGetValue(name, selected.valueText)
this.playSound("se_don")
}else{
var value = Math.round(settings.getItem("latency")[name] || 0)
if(this.latencySelected !== selectedIndex){
this.latencyItems[this.latencySelected].settingBox.classList.remove("selected")
this.latencySelected = selectedIndex
selected.settingBox.classList.add("selected")
}
this.mode = "latencySet"
selected.settingBox.style.animation = "none"
selected.valueDiv.classList.add("selected")
selected.value = value
this.playSound("se_don")
}
}
latencySetAdjust(selected, add){
selected.value += add
if(selected.value > 500){
selected.value = 500
}else if(selected.value < -200){
selected.value = -200
}else{
this.playSound("se_ka")
}
selected.valueText.data = this.addMs(selected.value)
}
latencySetBack(selected){
this.mode = "latency"
selected.settingBox.style.animation = ""
selected.valueDiv.classList.remove("selected")
this.latencySave(selected.id, selected.value)
this.latencyGetValue(selected.id, selected.valueText)
}
latencySave(id, value){
var input = settings.getItem("latency")
var output = {}
for(var i in input){
if(i === id){
output[i] = value
}else{
output[i] = input[i]
}
}
settings.setItem("latency", output)
}
latencyDefault(){
if(this.mode === "latencySet"){
this.latencySetBack(this.latencyItems[this.latencySelected])
}else if(this.mode !== "latency"){
return
}
settings.setItem("latency", null)
this.latencyItems.forEach(item => {
if(item.id === "audio" || item.id === "video" || item.id === "drumSounds"){
this.latencyGetValue(item.id, item.valueText)
}
})
this.drumSounds = settings.getItem("latency").drumSounds
this.playSound("se_don")
}
latencyBack(confirm){
if(this.mode === "latencySet"){
this.latencySetBack(this.latencyItems[this.latencySelected])
if(!confirm){
this.playSound("se_don")
return
}
}
if(this.mode !== "latency"){
return
}
var selected = this.items[this.selected]
var current = settings.items[selected.id]
this.getValue(selected.id, selected.valueDiv)
this.playSound(confirm ? "se_don" : "se_cancel")
this.latencySettings.style.display = ""
this.mode = "settings"
}
addMs(input){
var split = strings.calibration.ms.split("%s")
var index = 0
var output = ""
var inputStrings = [(input > 0 ? "+" : "") + input.toString()]
split.forEach((string, i) => {
if(i !== 0){
output += inputStrings[index++]
}
output += string
})
return output
}
defaultSettings(){ defaultSettings(){
if(this.mode === "keyboard"){ if(this.mode === "keyboard"){
this.keyboardBack(this.items[this.selected]) this.keyboardBack(this.items[this.selected])
@ -447,11 +778,17 @@ class SettingsView{
this.setLang(allStrings[settings.getItem("language")]) this.setLang(allStrings[settings.getItem("language")])
this.keyboard.update() this.keyboard.update()
pageEvents.setKbd() pageEvents.setKbd()
assets.sounds["se_don"].play() this.latencyItems.forEach(item => {
if(item.id === "audio" || item.id === "video" || item.id === "drumSounds"){
this.latencyGetValue(item.id, item.valueText)
}
})
this.drumSounds = settings.getItem("latency").drumSounds
this.playSound("se_don")
} }
onEnd(){ onEnd(){
this.clean() this.clean()
assets.sounds["se_don"].play() this.playSound("se_don")
setTimeout(() => { setTimeout(() => {
if(this.tutorial && !this.touched){ if(this.tutorial && !this.touched){
new Tutorial(false, this.songId) new Tutorial(false, this.songId)
@ -472,41 +809,94 @@ class SettingsView{
var item = this.items[i] var item = this.items[i]
if(item.valueDiv){ if(item.valueDiv){
var name = strings.settings[item.id].name var name = strings.settings[item.id].name
item.nameDiv.innerText = name this.setAltText(item.nameDiv, name)
item.nameDiv.setAttribute("alt", name)
this.getValue(item.id, item.valueDiv) this.getValue(item.id, item.valueDiv)
} }
} }
for(var i in this.latencyItems){
var current = this.latencyItems[i]
if(current.nameDiv){
this.setAltText(current.nameDiv, strings.settings.latency[current.id])
}
if(current.valueText){
this.latencyGetValue(current.id, current.valueText)
}
}
this.setStrings() this.setStrings()
} }
setStrings(){ setStrings(){
this.viewTitle.innerText = strings.gameSettings this.setAltText(this.viewTitle, strings.gameSettings)
this.viewTitle.setAttribute("alt", strings.gameSettings) this.setAltText(this.endButton, strings.settings.ok)
this.endButton.innerText = strings.settings.ok this.setAltText(this.gamepadTitle, strings.settings.gamepadLayout.name)
this.endButton.setAttribute("alt", strings.settings.ok) this.setAltText(this.gamepadEndButton, strings.settings.ok)
this.gamepadTitle.innerText = strings.settings.gamepadLayout.name this.setAltText(this.latencyTitle, strings.settings.latency.name)
this.gamepadTitle.setAttribute("alt", strings.settings.gamepadLayout.name) this.setAltText(this.latencyDefaultButton, strings.settings.default)
this.gamepadEndButton.innerText = strings.settings.ok this.setAltText(this.latencyEndButton, strings.settings.ok)
this.gamepadEndButton.setAttribute("alt", strings.settings.ok) this.setAltText(this.defaultButton, strings.settings.default)
this.defaultButton.innerText = strings.settings.default }
this.defaultButton.setAttribute("alt", strings.settings.default) setAltText(element, text){
element.innerText = text
element.setAttribute("alt", text)
} }
mod(length, index){ mod(length, index){
return ((index % length) + length) % length return ((index % length) + length) % length
} }
playSound(id, time){
if(!this.drumSounds && (id === "se_don" || id === "se_ka" || id === "se_cancel")){
return
}
var ms = Date.now() + (time || 0) * 1000
if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){
assets.sounds[id].play(time)
this.playedSounds[id] = ms
}
}
redraw(){
if(!this.redrawRunning){
return
}
requestAnimationFrame(this.redrawBind)
var ms = this.getMS()
for(var key in this.pressedKeys){
if(this.pressedKeys[key]){
if(ms >= this.pressedKeys[key] + 50){
this.keyPressed(true, key, null, true)
this.pressedKeys[key] = ms
}
}
}
}
getMS(){
return Date.now()
}
clean(){ clean(){
this.redrawRunning = false
this.keyboard.clean() this.keyboard.clean()
this.gamepad.clean() this.gamepad.clean()
assets.sounds["bgm_settings"].stop() assets.sounds["bgm_settings"].stop()
pageEvents.remove(this.viewOuter, ["mouseup", "touchend"])
for(var i in this.items){ for(var i in this.items){
this.removeTouch(this.items[i].settingBox) this.removeTouch(this.items[i].settingBox)
} }
for(var i in this.latencyItems){
this.removeTouch(this.latencyItems[i].settingBox)
if(this.latencyItems[i].buttonMinus){
this.removeTouch(this.latencyItems[i].buttonMinus)
this.removeTouch(this.latencyItems[i].buttonPlus)
}
}
if(this.defaultButton){ if(this.defaultButton){
delete this.defaultButton delete this.defaultButton
} }
this.removeTouch(this.gamepadSettings) this.removeTouch(this.gamepadSettings)
this.removeTouch(this.gamepadEndButton) this.removeTouch(this.gamepadEndButton)
this.removeTouch(this.gamepadBox) this.removeTouch(this.gamepadBox)
this.removeTouch(this.latencySettings)
this.removeTouch(this.latencyDefaultButton)
this.removeTouch(this.latencyEndButton)
delete this.viewOuter
delete this.touchEnd
delete this.tutorialTitle delete this.tutorialTitle
delete this.endButton delete this.endButton
delete this.items delete this.items
@ -516,6 +906,11 @@ class SettingsView{
delete this.gamepadBox delete this.gamepadBox
delete this.gamepadButtons delete this.gamepadButtons
delete this.gamepadValue delete this.gamepadValue
delete this.latencyItems
delete this.latencySettings
delete this.latencyTitle
delete this.latencyDefaultButton
delete this.latencyEndButton
if(this.resolution !== settings.getItem("resolution")){ if(this.resolution !== settings.getItem("resolution")){
for(var i in assets.image){ for(var i in assets.image){
if(i === "touch_drum" || 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_")){

View File

@ -5,6 +5,14 @@ class SongSelect{
loader.changePage("songselect", false) loader.changePage("songselect", false)
this.canvas = document.getElementById("song-sel-canvas") this.canvas = document.getElementById("song-sel-canvas")
this.ctx = this.canvas.getContext("2d") this.ctx = this.canvas.getContext("2d")
var resolution = settings.getItem("resolution")
var noSmoothing = resolution === "low" || resolution === "lowest"
if(noSmoothing){
this.ctx.imageSmoothingEnabled = false
}
if(resolution === "lowest"){
this.canvas.style.imageRendering = "pixelated"
}
this.songSkin = { this.songSkin = {
"selected": { "selected": {
@ -207,13 +215,13 @@ class SongSelect{
}] }]
this.optionsList = [strings.none, strings.auto, strings.netplay] this.optionsList = [strings.none, strings.auto, strings.netplay]
this.draw = new CanvasDraw() this.draw = new CanvasDraw(noSmoothing)
this.songTitleCache = new CanvasCache() this.songTitleCache = new CanvasCache(noSmoothing)
this.selectTextCache = new CanvasCache() this.selectTextCache = new CanvasCache(noSmoothing)
this.categoryCache = new CanvasCache() this.categoryCache = new CanvasCache(noSmoothing)
this.difficultyCache = new CanvasCache() this.difficultyCache = new CanvasCache(noSmoothing)
this.sessionCache = new CanvasCache() this.sessionCache = new CanvasCache(noSmoothing)
this.currentSongCache = new CanvasCache() this.currentSongCache = new CanvasCache(noSmoothing)
this.difficulty = [strings.easy, strings.normal, strings.hard, strings.oni] this.difficulty = [strings.easy, strings.normal, strings.hard, strings.oni]
this.difficultyId = ["easy", "normal", "hard", "oni", "ura"] this.difficultyId = ["easy", "normal", "hard", "oni", "ura"]
@ -234,6 +242,9 @@ class SongSelect{
fromTutorial = false fromTutorial = false
} }
this.drumSounds = settings.getItem("latency").drumSounds
this.playedSounds = {}
var songIdIndex = -1 var songIdIndex = -1
if(fromTutorial){ if(fromTutorial){
this.selectedSong = this.songs.findIndex(song => song.action === fromTutorial) this.selectedSong = this.songs.findIndex(song => song.action === fromTutorial)
@ -252,7 +263,7 @@ class SongSelect{
}else if((!p2.session || fadeIn) && "selectedSong" in localStorage){ }else if((!p2.session || fadeIn) && "selectedSong" in localStorage){
this.selectedSong = Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length - 1) this.selectedSong = Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length - 1)
} }
assets.sounds[songIdIndex !== -1 ? "v_diffsel" : "v_songsel"].play() this.playSound(songIdIndex !== -1 ? "v_diffsel" : "v_songsel")
snd.musicGain.fadeOut() snd.musicGain.fadeOut()
this.playBgm(false) this.playBgm(false)
} }
@ -436,7 +447,7 @@ class SongSelect{
window.open(this.songs[this.selectedSong].maker.url) window.open(this.songs[this.selectedSong].maker.url)
}else if(moveBy === this.diffOptions.length + 4){ }else if(moveBy === this.diffOptions.length + 4){
this.state.ura = !this.state.ura this.state.ura = !this.state.ura
assets.sounds["se_ka"].play() this.playSound("se_ka")
if(this.selectedDiff === this.diffOptions.length + 4 && !this.state.ura){ if(this.selectedDiff === this.diffOptions.length + 4 && !this.state.ura){
this.state.move = -1 this.state.move = -1
} }
@ -564,7 +575,7 @@ class SongSelect{
var soundsDelay = Math.abs((scroll + resize) / moveBy) var soundsDelay = Math.abs((scroll + resize) / moveBy)
for(var i = 0; i < Math.abs(moveBy) - 1; i++){ for(var i = 0; i < Math.abs(moveBy) - 1; i++){
assets.sounds["se_ka"].play((resize + i * soundsDelay) / 1000) this.playSound("se_ka", (resize + i * soundsDelay) / 1000)
} }
this.pointer(false) this.pointer(false)
} }
@ -574,7 +585,7 @@ class SongSelect{
this.state.move = moveBy this.state.move = moveBy
this.state.moveMS = this.getMS() - 500 this.state.moveMS = this.getMS() - 500
this.state.locked = 1 this.state.locked = 1
assets.sounds["se_ka"].play() this.playSound("se_ka")
} }
} }
@ -605,15 +616,15 @@ class SongSelect{
this.selectedDiff = this.diffOptions.length + 3 this.selectedDiff = this.diffOptions.length + 3
} }
assets.sounds["se_don"].play() this.playSound("se_don")
assets.sounds["v_songsel"].stop() assets.sounds["v_songsel"].stop()
assets.sounds["v_diffsel"].play(0.3) this.playSound("v_diffsel", 0.3)
pageEvents.send("song-select-difficulty", currentSong) pageEvents.send("song-select-difficulty", currentSong)
}else if(currentSong.action === "back"){ }else if(currentSong.action === "back"){
this.clean() this.clean()
this.toTitleScreen() this.toTitleScreen()
}else if(currentSong.action === "random"){ }else if(currentSong.action === "random"){
assets.sounds["se_don"].play() this.playSound("se_don")
this.state.locked = true this.state.locked = true
do{ do{
var i = Math.floor(Math.random() * this.songs.length) var i = Math.floor(Math.random() * this.songs.length)
@ -650,7 +661,7 @@ class SongSelect{
this.state.moveHover = null this.state.moveHover = null
assets.sounds["v_diffsel"].stop() assets.sounds["v_diffsel"].stop()
assets.sounds["se_cancel"].play() this.playSound("se_cancel")
} }
this.clearHash() this.clearHash()
pageEvents.send("song-select-back") pageEvents.send("song-select-back")
@ -659,7 +670,7 @@ class SongSelect{
this.clean() this.clean()
var selectedSong = this.songs[this.selectedSong] var selectedSong = this.songs[this.selectedSong]
assets.sounds["v_diffsel"].stop() assets.sounds["v_diffsel"].stop()
assets.sounds["se_don"].play() this.playSound("se_don")
try{ try{
if(assets.customSongs){ if(assets.customSongs){
@ -698,7 +709,7 @@ class SongSelect{
} }
toOptions(moveBy){ toOptions(moveBy){
if(!p2.session){ if(!p2.session){
assets.sounds["se_ka"].play() this.playSound("se_ka")
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)
@ -707,7 +718,7 @@ class SongSelect{
} }
toTitleScreen(){ toTitleScreen(){
if(!p2.session){ if(!p2.session){
assets.sounds["se_cancel"].play() this.playSound("se_cancel")
this.clean() this.clean()
setTimeout(() => { setTimeout(() => {
new Titlescreen() new Titlescreen()
@ -715,21 +726,21 @@ class SongSelect{
} }
} }
toTutorial(){ toTutorial(){
assets.sounds["se_don"].play() this.playSound("se_don")
this.clean() this.clean()
setTimeout(() => { setTimeout(() => {
new Tutorial(true) new Tutorial(true)
}, 500) }, 500)
} }
toAbout(){ toAbout(){
assets.sounds["se_don"].play() this.playSound("se_don")
this.clean() this.clean()
setTimeout(() => { setTimeout(() => {
new About(this.touchEnabled) new About(this.touchEnabled)
}, 500) }, 500)
} }
toSettings(){ toSettings(){
assets.sounds["se_don"].play() this.playSound("se_don")
this.clean() this.clean()
setTimeout(() => { setTimeout(() => {
new SettingsView(this.touchEnabled) new SettingsView(this.touchEnabled)
@ -744,7 +755,7 @@ class SongSelect{
}else{ }else{
localStorage["selectedSong"] = this.selectedSong localStorage["selectedSong"] = this.selectedSong
assets.sounds["se_don"].play() this.playSound("se_don")
this.clean() this.clean()
setTimeout(() => { setTimeout(() => {
new Session(this.touchEnabled) new Session(this.touchEnabled)
@ -755,7 +766,7 @@ class SongSelect{
if(assets.customSongs){ if(assets.customSongs){
assets.customSongs = false assets.customSongs = false
assets.songs = assets.songsDefault assets.songs = assets.songsDefault
assets.sounds["se_don"].play() this.playSound("se_don")
this.clean() this.clean()
setTimeout(() => { setTimeout(() => {
new SongSelect("browse", false, this.touchEnabled) new SongSelect("browse", false, this.touchEnabled)
@ -984,7 +995,7 @@ class SongSelect{
var scroll = resize2 - resize - scrollDelay * 2 var scroll = resize2 - resize - scrollDelay * 2
var elapsed = ms - this.state.moveMS var elapsed = ms - this.state.moveMS
if(this.state.move && ms > this.state.moveMS + resize2 - scrollDelay){ if(this.state.move && ms > this.state.moveMS + resize2 - scrollDelay){
assets.sounds["se_ka"].play() this.playSound("se_ka")
var previousSelectedSong = this.selectedSong var previousSelectedSong = this.selectedSong
this.selectedSong = this.mod(this.songs.length, this.selectedSong + this.state.move) this.selectedSong = this.mod(this.songs.length, this.selectedSong + this.state.move)
if(previousSelectedSong !== this.selectedSong){ if(previousSelectedSong !== this.selectedSong){
@ -2041,6 +2052,17 @@ class SongSelect{
} }
} }
playSound(id, time){
if(!this.drumSounds && (id === "se_don" || id === "se_ka" || id === "se_cancel")){
return
}
var ms = Date.now() + (time || 0) * 1000
if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){
assets.sounds[id].play(time)
this.playedSounds[id] = ms
}
}
getMS(){ getMS(){
return Date.now() return Date.now()
} }

View File

@ -126,11 +126,42 @@
b: "タイプB", b: "タイプB",
c: "タイプC" c: "タイプC"
}, },
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "オン", on: "オン",
off: "オフ", off: "オフ",
default: "既定値にリセット", default: "既定値にリセット",
ok: "OK" ok: "OK"
} }
this.calibration = {
title: "Latency Calibration",
ms: "%sms",
back: "Back to Settings",
retryPrevious: "Retry Previous",
start: "Start",
finish: "Finish",
audioHelp: {
title: "Audio Latency Calibration",
content: "Listen to a sound playing in the background.\n\nHit the surface of the drum (%s or %s) as you hear it!",
contentAlt: "Listen to a sound playing in the background.\n\nHit the surface of the drum as you hear it!"
},
audioComplete: "Audio Latency Calibration completed!",
videoHelp: {
title: "Video Latency Calibration",
content: "This time there will be no sounds.\n\nInstead, watch for notes blinking on the circle-shaped frame, hit the drum as they appear!"
},
videoComplete: "Video Latency Calibration completed!",
results: {
title: "Latency Calibration Results",
content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings."
}
}
this.browserSupport = { this.browserSupport = {
browserWarning: "サポートされていないブラウザを実行しています (%s)", browserWarning: "サポートされていないブラウザを実行しています (%s)",
details: "詳しく", details: "詳しく",
@ -270,11 +301,42 @@ function StringsEn(){
b: "Type B", b: "Type B",
c: "Type C" c: "Type C"
}, },
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "On", on: "On",
off: "Off", off: "Off",
default: "Reset to Defaults", default: "Reset to Defaults",
ok: "OK" ok: "OK"
} }
this.calibration = {
title: "Latency Calibration",
ms: "%sms",
back: "Back to Settings",
retryPrevious: "Retry Previous",
start: "Start",
finish: "Finish",
audioHelp: {
title: "Audio Latency Calibration",
content: "Listen to a sound playing in the background.\n\nHit the surface of the drum (%s or %s) as you hear it!",
contentAlt: "Listen to a sound playing in the background.\n\nHit the surface of the drum as you hear it!"
},
audioComplete: "Audio Latency Calibration completed!",
videoHelp: {
title: "Video Latency Calibration",
content: "This time there will be no sounds.\n\nInstead, watch for notes blinking on the circle-shaped frame, hit the drum as they appear!"
},
videoComplete: "Video Latency Calibration completed!",
results: {
title: "Latency Calibration Results",
content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings."
}
}
this.browserSupport = { this.browserSupport = {
browserWarning: "You are running an unsupported browser (%s)", browserWarning: "You are running an unsupported browser (%s)",
details: "Details...", details: "Details...",
@ -414,11 +476,42 @@ function StringsCn(){
b: "类型B", b: "类型B",
c: "类型C" c: "类型C"
}, },
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "开", on: "开",
off: "关", off: "关",
default: "重置为默认值", default: "重置为默认值",
ok: "确定" ok: "确定"
} }
this.calibration = {
title: "Latency Calibration",
ms: "%sms",
back: "Back to Settings",
retryPrevious: "Retry Previous",
start: "Start",
finish: "Finish",
audioHelp: {
title: "Audio Latency Calibration",
content: "Listen to a sound playing in the background.\n\nHit the surface of the drum (%s or %s) as you hear it!",
contentAlt: "Listen to a sound playing in the background.\n\nHit the surface of the drum as you hear it!"
},
audioComplete: "Audio Latency Calibration completed!",
videoHelp: {
title: "Video Latency Calibration",
content: "This time there will be no sounds.\n\nInstead, watch for notes blinking on the circle-shaped frame, hit the drum as they appear!"
},
videoComplete: "Video Latency Calibration completed!",
results: {
title: "Latency Calibration Results",
content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings."
}
}
this.browserSupport = { this.browserSupport = {
browserWarning: "You are running an unsupported browser (%s)", browserWarning: "You are running an unsupported browser (%s)",
details: "Details...", details: "Details...",
@ -558,11 +651,42 @@ function StringsTw(){
b: "類型B", b: "類型B",
c: "類型C" c: "類型C"
}, },
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "開", on: "開",
off: "關", off: "關",
default: "重置為默認值", default: "重置為默認值",
ok: "確定" ok: "確定"
} }
this.calibration = {
title: "Latency Calibration",
ms: "%sms",
back: "Back to Settings",
retryPrevious: "Retry Previous",
start: "Start",
finish: "Finish",
audioHelp: {
title: "Audio Latency Calibration",
content: "Listen to a sound playing in the background.\n\nHit the surface of the drum (%s or %s) as you hear it!",
contentAlt: "Listen to a sound playing in the background.\n\nHit the surface of the drum as you hear it!"
},
audioComplete: "Audio Latency Calibration completed!",
videoHelp: {
title: "Video Latency Calibration",
content: "This time there will be no sounds.\n\nInstead, watch for notes blinking on the circle-shaped frame, hit the drum as they appear!"
},
videoComplete: "Video Latency Calibration completed!",
results: {
title: "Latency Calibration Results",
content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings."
}
}
this.browserSupport = { this.browserSupport = {
browserWarning: "You are running an unsupported browser (%s)", browserWarning: "You are running an unsupported browser (%s)",
details: "Details...", details: "Details...",
@ -702,11 +826,42 @@ function StringsKo(){
b: "타입 B", b: "타입 B",
c: "타입 C" c: "타입 C"
}, },
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "온", on: "온",
off: "오프", off: "오프",
default: "기본값으로 재설정", default: "기본값으로 재설정",
ok: "확인" ok: "확인"
} }
this.calibration = {
title: "Latency Calibration",
ms: "%sms",
back: "Back to Settings",
retryPrevious: "Retry Previous",
start: "Start",
finish: "Finish",
audioHelp: {
title: "Audio Latency Calibration",
content: "Listen to a sound playing in the background.\n\nHit the surface of the drum (%s or %s) as you hear it!",
contentAlt: "Listen to a sound playing in the background.\n\nHit the surface of the drum as you hear it!"
},
audioComplete: "Audio Latency Calibration completed!",
videoHelp: {
title: "Video Latency Calibration",
content: "This time there will be no sounds.\n\nInstead, watch for notes blinking on the circle-shaped frame, hit the drum as they appear!"
},
videoComplete: "Video Latency Calibration completed!",
results: {
title: "Latency Calibration Results",
content: "Audio latency: %s\nVideo latency: %s\n\nYou can configure these latency values in the settings."
}
}
this.browserSupport = { this.browserSupport = {
browserWarning: "You are running an unsupported browser (%s)", browserWarning: "You are running an unsupported browser (%s)",
details: "Details...", details: "Details...",

View File

@ -4,8 +4,15 @@
this.canvas = document.getElementById("canvas") this.canvas = document.getElementById("canvas")
this.ctx = this.canvas.getContext("2d") this.ctx = this.canvas.getContext("2d")
var resolution = settings.getItem("resolution")
var noSmoothing = resolution === "low" || resolution === "lowest"
if(noSmoothing){
this.ctx.imageSmoothingEnabled = false
}
if(resolution === "lowest"){
this.canvas.style.imageRendering = "pixelated"
}
this.cursor = document.getElementById("cursor")
this.gameDiv = document.getElementById("game") this.gameDiv = document.getElementById("game")
this.songBg = document.getElementById("songbg") this.songBg = document.getElementById("songbg")
this.songStage = document.getElementById("song-stage") this.songStage = document.getElementById("song-stage")
@ -73,6 +80,7 @@
} }
this.nextBeat = 0 this.nextBeat = 0
this.gogoTime = 0 this.gogoTime = 0
this.gogoTimeStarted = -Infinity
this.drumroll = [] this.drumroll = []
this.touchEvents = 0 this.touchEvents = 0
if(this.controller.parsedSongData.branches){ if(this.controller.parsedSongData.branches){
@ -103,16 +111,20 @@
} }
} }
this.beatInterval = this.controller.parsedSongData.beatInfo.beatInterval if(this.controller.calibrationMode){
this.beatInterval = 512
}else{
this.beatInterval = this.controller.parsedSongData.beatInfo.beatInterval
}
this.font = strings.font this.font = strings.font
this.draw = new CanvasDraw() this.draw = new CanvasDraw(noSmoothing)
this.assets = new ViewAssets(this) this.assets = new ViewAssets(this)
this.titleCache = new CanvasCache() this.titleCache = new CanvasCache(noSmoothing)
this.comboCache = new CanvasCache() this.comboCache = new CanvasCache(noSmoothing)
this.pauseCache = new CanvasCache() this.pauseCache = new CanvasCache(noSmoothing)
this.branchCache = new CanvasCache() this.branchCache = new CanvasCache(noSmoothing)
this.multiplayer = this.controller.multiplayer this.multiplayer = this.controller.multiplayer
@ -120,6 +132,9 @@
this.touch = -Infinity this.touch = -Infinity
this.touchAnimation = settings.getItem("touchAnimation") this.touchAnimation = settings.getItem("touchAnimation")
versionDiv.classList.add("version-hide")
loader.screen.parentNode.insertBefore(versionDiv, loader.screen)
if(this.multiplayer !== 2){ if(this.multiplayer !== 2){
if(this.controller.touchEnabled){ if(this.controller.touchEnabled){
@ -134,7 +149,6 @@
pageEvents.add(this.canvas, "touchstart", this.ontouch.bind(this)) pageEvents.add(this.canvas, "touchstart", this.ontouch.bind(this))
this.gameDiv.classList.add("touch-visible") this.gameDiv.classList.add("touch-visible")
document.getElementById("version").classList.add("version-hide")
this.touchFullBtn = document.getElementById("touch-full-btn") this.touchFullBtn = document.getElementById("touch-full-btn")
pageEvents.add(this.touchFullBtn, "touchend", toggleFullscreen) pageEvents.add(this.touchFullBtn, "touchend", toggleFullscreen)
@ -444,12 +458,14 @@
ctx.fill() ctx.fill()
// Difficulty // Difficulty
ctx.drawImage(assets.image["difficulty"], if(this.controller.selectedSong.difficulty){
0, 144 * this.difficulty[this.controller.selectedSong.difficulty], ctx.drawImage(assets.image["difficulty"],
168, 143, 0, 144 * this.difficulty[this.controller.selectedSong.difficulty],
126, this.multiplayer === 2 ? 497 : 228, 168, 143,
62, 53 126, this.multiplayer === 2 ? 497 : 228,
) 62, 53
)
}
// Badges // Badges
if(this.controller.autoPlayEnabled && !this.controller.multiplayer){ if(this.controller.autoPlayEnabled && !this.controller.multiplayer){
@ -593,24 +609,26 @@
ctx.globalAlpha = 1 ctx.globalAlpha = 1
// Difficulty // Difficulty
ctx.drawImage(assets.image["difficulty"], if(this.controller.selectedSong.difficulty){
0, 144 * this.difficulty[this.controller.selectedSong.difficulty], ctx.drawImage(assets.image["difficulty"],
168, 143, 0, 144 * this.difficulty[this.controller.selectedSong.difficulty],
16, this.multiplayer === 2 ? 194 : 232, 168, 143,
141, 120 16, this.multiplayer === 2 ? 194 : 232,
) 141, 120
var diff = this.controller.selectedSong.difficulty )
var text = strings[diff === "ura" ? "oni" : diff] var diff = this.controller.selectedSong.difficulty
ctx.font = this.draw.bold(this.font) + "20px " + this.font var text = strings[diff === "ura" ? "oni" : diff]
ctx.textAlign = "center" ctx.font = this.draw.bold(this.font) + "20px " + this.font
ctx.textBaseline = "bottom" ctx.textAlign = "center"
ctx.strokeStyle = "#000" ctx.textBaseline = "bottom"
ctx.fillStyle = "#fff" ctx.strokeStyle = "#000"
ctx.lineWidth = 7 ctx.fillStyle = "#fff"
ctx.miterLimit = 1 ctx.lineWidth = 7
ctx.strokeText(text, 87, this.multiplayer === 2 ? 310 : 348) ctx.miterLimit = 1
ctx.fillText(text, 87, this.multiplayer === 2 ? 310 : 348) ctx.strokeText(text, 87, this.multiplayer === 2 ? 310 : 348)
ctx.miterLimit = 10 ctx.fillText(text, 87, this.multiplayer === 2 ? 310 : 348)
ctx.miterLimit = 10
}
// Badges // Badges
if(this.controller.autoPlayEnabled && !this.controller.multiplayer){ if(this.controller.autoPlayEnabled && !this.controller.multiplayer){
@ -947,6 +965,20 @@
ctx.clip() ctx.clip()
this.drawCircles(this.controller.getCircles()) this.drawCircles(this.controller.getCircles())
if(this.controller.game.calibrationState === "video"){
if(ms % this.beatInterval < 1000 / 60 * 5){
this.drawCircle({
ms: ms,
type: "don",
endTime: ms + 100,
speed: 0
}, {
x: this.slotPos.x,
y: this.slotPos.y
})
}
}
ctx.restore() ctx.restore()
// Hit notes explosion // Hit notes explosion
@ -1001,6 +1033,22 @@
ctx.translate(frameLeft, frameTop) ctx.translate(frameLeft, frameTop)
} }
var state = this.controller.game.calibrationState
if(state && state in strings.calibration){
var boldTitle = strings.calibration[state].title
}
if(boldTitle){
this.draw.layeredText({
ctx: ctx,
text: boldTitle,
fontSize: 35,
fontFamily: this.font,
x: 300,
y: 70
}, [
{outline: "#fff", letterBorder: 22}
])
}
var pauseRect = (ctx, mul) => { var pauseRect = (ctx, mul) => {
this.draw.roundedRect({ this.draw.roundedRect({
ctx: ctx, ctx: ctx,
@ -1025,86 +1073,284 @@
dx: 68, dx: 68,
dy: 11 dy: 11
}) })
if(boldTitle){
ctx.drawImage(assets.image["mimizu"], this.draw.layeredText({
313, 247, 136, 315
)
var _y = 108
var _w = 80
var _h = 464
for(var i = 0; i < this.pauseOptions.length; i++){
var _x = 520 + 110 * i
if(this.state.moveHover !== null){
var selected = i === this.state.moveHover
}else{
var selected = i === this.state.pausePos
}
if(selected){
ctx.fillStyle = "#ffb447"
this.draw.roundedRect({
ctx: ctx,
x: _x - _w / 2,
y: _y,
w: _w,
h: _h,
radius: 30
})
ctx.fill()
}
this.pauseCache.get({
ctx: ctx, ctx: ctx,
x: _x - _w / 2, text: boldTitle,
y: _y, fontSize: 35,
w: _w, fontFamily: this.font,
h: _h, x: 300,
id: this.pauseOptions[i] + (selected ? "1" : "0") y: 70
}, ctx => { }, [
var textConfig = { {outline: "#000", letterBorder: 10},
{fill: "#fff"}
])
}
switch(state){
case "audioHelp":
case "videoHelp":
case "results":
var content = state === "audioHelp" && this.touchEnabled ? "contentAlt" : "content"
if(state === "audioHelp"){
var kbdSettings = settings.getItem("keyboardSettings")
var keys = [
kbdSettings.don_l[0].toUpperCase(),
kbdSettings.don_r[0].toUpperCase()
]
var substitute = (config, index, width) => {
var ctx = config.ctx
var bold = this.draw.bold(config.fontFamily)
ctx.font = bold + (config.fontSize * 0.66) + "px " + config.fontFamily
var w = config.fontSize * 0.6 + ctx.measureText(keys[index]).width
if(width){
return w
}else{
var h = 30
ctx.lineWidth = 3
ctx.strokeStyle = "rgba(0, 0, 0, 0.2)"
this.draw.roundedRect({
ctx: ctx,
x: 0, y: 1, w: w, h: h,
radius: 3
})
ctx.stroke()
ctx.strokeStyle = "#ccc"
ctx.fillStyle = "#fff"
this.draw.roundedRect({
ctx: ctx,
x: 0, y: 0, w: w, h: h,
radius: 3
})
ctx.stroke()
ctx.fill()
ctx.fillStyle = "#f7f7f7"
ctx.fillRect(2, 2, w - 4, h - 4)
ctx.fillStyle = "#333"
ctx.textBaseline = "middle"
ctx.textAlign = "center"
ctx.fillText(keys[index], w / 2, h / 2)
}
}
}else if(state === "results"){
var progress = this.controller.game.calibrationProgress
var latency = [
progress.audio,
progress.video
]
var substitute = (config, index, width) => {
var ctx = config.ctx
var bold = this.draw.bold(config.fontFamily)
ctx.font = bold + (config.fontSize * 1.1) + "px " + config.fontFamily
var text = this.addMs(latency[index])
if(width){
return ctx.measureText(text).width
}else{
ctx.fillText(text, 0, 0)
}
}
}else{
var substitute = null
}
this.draw.wrappingText({
ctx: ctx, ctx: ctx,
text: this.pauseOptions[i], text: strings.calibration[state][content],
x: _w / 2, fontSize: 30,
y: 18, fontFamily: this.font,
width: _w, x: 300,
height: _h - 54, y: 130,
width: 680,
height: 240,
lineHeight: 35,
fill: "#000",
verticalAlign: "middle",
substitute: substitute
})
var _x = 640
var _w = 464
var _h = 80
for(var i = 0; i < this.pauseOptions.length; i++){
var text = this.pauseOptions[i]
var _y = 470 - 90 * (this.pauseOptions.length - i - 1)
if(this.state.moveHover !== null){
var selected = i === this.state.moveHover
}else{
var selected = i === this.state.pausePos
}
if(selected){
ctx.fillStyle = "#ffb447"
this.draw.roundedRect({
ctx: ctx,
x: _x - _w / 2,
y: _y,
w: _w,
h: _h,
radius: 30
})
ctx.fill()
}
if(selected){
var layers = [
{outline: "#000", letterBorder: 10},
{fill: "#fff"}
]
}else{
var layers = [
{fill: "#000"}
]
}
this.draw.layeredText({
ctx: ctx,
text: text,
x: _x,
y: _y + 18,
width: _w,
height: _h - 54,
fontSize: 40,
fontFamily: this.font,
letterSpacing: -1,
align: "center"
}, layers)
var highlight = 0
if(this.state.moveHover === i){
highlight = 2
}else if(selected){
highlight = 1
}
if(highlight){
this.draw.highlight({
ctx: ctx,
x: _x - _w / 2 - 3.5,
y: _y - 3.5,
w: _w + 7,
h: _h + 7,
animate: highlight === 1,
animateMS: this.state.moveMS,
opacity: highlight === 2 ? 0.8 : 1,
radius: 30
})
}
}
break
case "audioComplete":
case "videoComplete":
this.draw.wrappingText({
ctx: ctx,
text: strings.calibration[state],
fontSize: 40, fontSize: 40,
fontFamily: this.font, fontFamily: this.font,
letterSpacing: -1 x: 300,
} y: 130,
if(selected){ width: 680,
textConfig.fill = "#fff" height: 420,
textConfig.outline = "#000" lineHeight: 47,
textConfig.outlineSize = 10 fill: "#000",
}else{ verticalAlign: "middle",
textConfig.fill = "#000" textAlign: "center",
}
this.draw.verticalText(textConfig)
})
var highlight = 0
if(this.state.moveHover === i){
highlight = 2
}else if(selected){
highlight = 1
}
if(highlight){
this.draw.highlight({
ctx: ctx,
x: _x - _w / 2 - 3.5,
y: _y - 3.5,
w: _w + 7,
h: _h + 7,
animate: highlight === 1,
animateMS: this.state.moveMS,
opacity: highlight === 2 ? 0.8 : 1,
radius: 30
}) })
} break
default:
ctx.drawImage(assets.image["mimizu"],
313, 247, 136, 315
)
var _y = 108
var _w = 80
var _h = 464
for(var i = 0; i < this.pauseOptions.length; i++){
var text = this.pauseOptions[i]
if(this.controller.calibrationMode && i === this.pauseOptions.length - 1){
text = strings.calibration.back
}
var _x = 520 + 110 * i
if(this.state.moveHover !== null){
var selected = i === this.state.moveHover
}else{
var selected = i === this.state.pausePos
}
if(selected){
ctx.fillStyle = "#ffb447"
this.draw.roundedRect({
ctx: ctx,
x: _x - _w / 2,
y: _y,
w: _w,
h: _h,
radius: 30
})
ctx.fill()
}
this.pauseCache.get({
ctx: ctx,
x: _x - _w / 2,
y: _y,
w: _w,
h: _h,
id: text + (selected ? "1" : "0")
}, ctx => {
var textConfig = {
ctx: ctx,
text: text,
x: _w / 2,
y: 18,
width: _w,
height: _h - 54,
fontSize: 40,
fontFamily: this.font,
letterSpacing: -1
}
if(selected){
textConfig.fill = "#fff"
textConfig.outline = "#000"
textConfig.outlineSize = 10
}else{
textConfig.fill = "#000"
}
this.draw.verticalText(textConfig)
})
var highlight = 0
if(this.state.moveHover === i){
highlight = 2
}else if(selected){
highlight = 1
}
if(highlight){
this.draw.highlight({
ctx: ctx,
x: _x - _w / 2 - 3.5,
y: _y - 3.5,
w: _w + 7,
h: _h + 7,
animate: highlight === 1,
animateMS: this.state.moveMS,
opacity: highlight === 2 ? 0.8 : 1,
radius: 30
})
}
}
break
} }
ctx.restore() ctx.restore()
} }
} }
addMs(input){
var split = strings.calibration.ms.split("%s")
var index = 0
var output = ""
var inputStrings = [(input > 0 ? "+" : "") + input.toString()]
split.forEach((string, i) => {
if(i !== 0){
output += inputStrings[index++]
}
output += string
})
return output
}
setBackground(){ setBackground(){
var selectedSong = this.controller.selectedSong var selectedSong = this.controller.selectedSong
var songSkinName = selectedSong.songSkin.name var songSkinName = selectedSong.songSkin.name
@ -1219,10 +1465,10 @@
measures.forEach(measure => { measures.forEach(measure => {
var timeForDistance = this.posToMs(distanceForCircle, measure.speed) var timeForDistance = this.posToMs(distanceForCircle, measure.speed)
var startingTime = measure.ms - timeForDistance var startingTime = measure.ms - timeForDistance + this.controller.videoLatency
var finishTime = measure.ms + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + 3, measure.speed) var finishTime = measure.ms + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + 3, measure.speed) + this.controller.videoLatency
if(measure.visible && (!measure.branch || measure.branch.active) && 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) var measureX = this.slotPos.x + this.msToPos(measure.ms - ms + this.controller.videoLatency, measure.speed)
this.ctx.strokeStyle = measure.branchFirst ? "#ff0" : "#bdbdbd" this.ctx.strokeStyle = measure.branchFirst ? "#ff0" : "#bdbdbd"
this.ctx.lineWidth = 3 this.ctx.lineWidth = 3
this.ctx.beginPath() this.ctx.beginPath()
@ -1267,8 +1513,8 @@
var speed = circle.speed var speed = circle.speed
var timeForDistance = this.posToMs(distanceForCircle + this.slotPos.size / 2, speed) var timeForDistance = this.posToMs(distanceForCircle + this.slotPos.size / 2, speed)
var startingTime = circle.ms - timeForDistance var startingTime = circle.ms - timeForDistance + this.controller.videoLatency
var finishTime = circle.endTime + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed) var finishTime = circle.endTime + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed) + this.controller.videoLatency
if(circle.isPlayed <= 0 || circle.score === 0){ if(circle.isPlayed <= 0 || circle.score === 0){
if((!circle.branch || circle.branch.active) && ms >= startingTime && ms <= finishTime && circle.isPlayed !== -1){ if((!circle.branch || circle.branch.active) && ms >= startingTime && ms <= finishTime && circle.isPlayed !== -1){
@ -1350,7 +1596,7 @@
if(!circlePos){ if(!circlePos){
circlePos = { circlePos = {
x: this.slotPos.x + this.msToPos(circleMs - ms, speed), x: this.slotPos.x + this.msToPos(circleMs - ms + this.controller.videoLatency, speed),
y: this.slotPos.y y: this.slotPos.y
} }
} }
@ -1388,10 +1634,10 @@
size = circleSize size = circleSize
faceID = noteFace.small faceID = noteFace.small
var h = size * 1.8 var h = size * 1.8
if(circleMs < ms && ms <= endTime){ if(circleMs + this.controller.audioLatency < ms && ms <= endTime + this.controller.audioLatency){
circlePos.x = this.slotPos.x circlePos.x = this.slotPos.x
}else if(ms > endTime){ }else if(ms > endTime + this.controller.audioLatency){
circlePos.x = this.slotPos.x + this.msToPos(endTime - ms, speed) circlePos.x = this.slotPos.x + this.msToPos(endTime - ms + this.controller.audioLatency, speed)
} }
ctx.drawImage(assets.image["balloon"], ctx.drawImage(assets.image["balloon"],
circlePos.x + size - 4, circlePos.x + size - 4,
@ -1596,7 +1842,9 @@
} }
toggleGogoTime(circle){ toggleGogoTime(circle){
this.gogoTime = circle.gogoTime this.gogoTime = circle.gogoTime
this.gogoTimeStarted = circle.ms if(circle.gogoTime || this.gogoTimeStarted !== -Infinity){
this.gogoTimeStarted = circle.ms
}
if(this.gogoTime){ if(this.gogoTime){
this.assets.fireworks.forEach(fireworksAsset => { this.assets.fireworks.forEach(fireworksAsset => {
@ -1789,21 +2037,62 @@
if(typeof pos === "undefined"){ if(typeof pos === "undefined"){
pos = this.state.pausePos pos = this.state.pausePos
} }
var game = this.controller.game
var state = game.calibrationState
switch(state){
case "audioHelp":
pos = pos === 0 ? 2 : 0
break
case "videoHelp":
if(pos === 0){
assets.sounds["se_don"].play()
game.calibrationReset("audio")
return
}else{
pos = 0
}
break
case "results":
if(pos === 0){
assets.sounds["se_don"].play()
game.calibrationReset("video")
return
}else{
var input = settings.getItem("latency")
var output = {}
var progress = game.calibrationProgress
for(var i in input){
if(i === "audio" || i === "video"){
output[i] = progress[i]
}else{
output[i] = input[i]
}
}
settings.setItem("latency", output)
pos = 2
}
break
}
switch(pos){ switch(pos){
case 1: case 1:
assets.sounds["se_don"].play() this.controller.playSound("se_don", 0, true)
this.controller.restartSong() if(state === "video"){
game.calibrationReset(state)
}else{
this.controller.restartSong()
}
pageEvents.send("pause-restart") pageEvents.send("pause-restart")
break break
case 2: case 2:
assets.sounds["se_don"].play() this.controller.playSound("se_don", 0, true)
this.controller.songSelection() this.controller.songSelection()
pageEvents.send("pause-song-select") pageEvents.send("pause-song-select")
break break
default: default:
this.controller.togglePause() this.controller.togglePause(false)
break break
} }
return true
} }
onmousedown(event){ onmousedown(event){
if(this.controller.game.paused){ if(this.controller.game.paused){
@ -1855,23 +2144,30 @@
x = x * pauseScale + 257 x = x * pauseScale + 257
y = y * pauseScale - 328 y = y * pauseScale - 328
} }
if(104 <= y && y <= 575 && 465 <= x && x <= 465 + 110 * this.pauseOptions.length){ switch(this.controller.game.calibrationState){
return Math.floor((x - 465) / 110) case "audioHelp":
case "videoHelp":
case "results":
if(554 - 90 * this.pauseOptions.length <= y && y <= 554 && 404 <= x && x <= 876){
return Math.floor((y - 554 + 90 * this.pauseOptions.length) / 90)
}
break
default:
if(104 <= y && y <= 575 && 465 <= x && x <= 465 + 110 * this.pauseOptions.length){
return Math.floor((x - 465) / 110)
}
break
} }
return null return null
} }
mouseIdle(){ mouseIdle(){
var lastMouse = pageEvents.getMouse() var lastMouse = pageEvents.getMouse()
if(lastMouse && !this.cursorHidden){ if(lastMouse && !this.cursorHidden && !this.state.hasPointer){
if(this.getMS() >= this.lastMousemove + 2000){ if(this.getMS() >= this.lastMousemove + 2000){
this.cursor.style.top = lastMouse.clientY + "px" this.canvas.style.cursor = "none"
this.cursor.style.left = lastMouse.clientX + "px"
this.cursor.style.pointerEvents = "auto"
this.cursorHidden = true this.cursorHidden = true
}else{ }else{
this.cursor.style.top = "" this.canvas.style.cursor = ""
this.cursor.style.left = ""
this.cursor.style.pointerEvents = ""
} }
} }
} }
@ -1890,12 +2186,13 @@
this.pauseCache.clean() this.pauseCache.clean()
this.branchCache.clean() this.branchCache.clean()
versionDiv.classList.remove("version-hide")
loader.screen.parentNode.appendChild(versionDiv)
if(this.multiplayer !== 2){ if(this.multiplayer !== 2){
if(this.touchEnabled){ if(this.touchEnabled){
pageEvents.remove(this.canvas, "touchstart") pageEvents.remove(this.canvas, "touchstart")
pageEvents.remove(this.touchPauseBtn, "touchend") pageEvents.remove(this.touchPauseBtn, "touchend")
this.gameDiv.classList.add("touch-results") this.gameDiv.classList.add("touch-results")
document.getElementById("version").classList.remove("version-hide")
this.touchDrumDiv.parentNode.removeChild(this.touchDrumDiv) this.touchDrumDiv.parentNode.removeChild(this.touchDrumDiv)
delete this.touchDrumDiv delete this.touchDrumDiv
delete this.touchDrumImg delete this.touchDrumImg
@ -1915,7 +2212,6 @@
pageEvents.mouseRemove(this) pageEvents.mouseRemove(this)
delete this.pauseMenu delete this.pauseMenu
delete this.cursor
delete this.gameDiv delete this.gameDiv
delete this.canvas delete this.canvas
delete this.ctx delete this.ctx

View File

@ -7,8 +7,8 @@
<div id="link-issues" class="taibtn stroke-sub link-btn" alt="Issues"> <div id="link-issues" class="taibtn stroke-sub link-btn" alt="Issues">
<a target="_blank">Issues</a> <a target="_blank">Issues</a>
</div> </div>
<div id="link-email" class="taibtn stroke-sub link-btn" alt="taiko@bui.pm"> <div id="link-email" class="taibtn stroke-sub link-btn">
<a href="mailto:taiko@bui.pm">taiko@bui.pm</a> <a></a>
</div> </div>
</div> </div>
<div class="view-end-button taibtn stroke-sub selected"></div> <div class="view-end-button taibtn stroke-sub selected"></div>

View File

@ -11,5 +11,4 @@
<div id="touch-buttons"> <div id="touch-buttons">
<div id="touch-full-btn"></div><div id="touch-pause-btn"></div> <div id="touch-full-btn"></div><div id="touch-pause-btn"></div>
</div> </div>
<div id="cursor"></div>
</div> </div>

View File

@ -20,5 +20,15 @@
<div class="view-end-button taibtn stroke-sub selected"></div> <div class="view-end-button taibtn stroke-sub selected"></div>
</div> </div>
</div> </div>
<div class="view-outer shadow-outer" id="settings-latency">
<div class="view">
<div class="view-title stroke-sub"></div>
<div class="view-content"></div>
<div class="left-buttons">
<div id="latency-default" class="taibtn stroke-sub"></div>
</div>
<div class="view-end-button taibtn stroke-sub"></div>
</div>
</div>
</div> </div>
</div> </div>