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%;
height: 100%;
}
#cursor{
position: fixed;
width: 1px;
height: 1px;
cursor: none;
pointer-events: none;
z-index: 1;
}
#touch-drum{
display: none;
position: absolute;

View File

@ -158,8 +158,7 @@ kbd{
.setting-box:first-child{
margin-top: 0;
}
.settings-outer .view-content:not(:hover) .setting-box.selected,
.view-outer:not(.settings-outer) .setting-box.selected,
.view-content:not(:hover) .setting-box.selected,
.setting-box:hover{
background: #ffb547;
animation: 2s linear border-pulse infinite;
@ -177,7 +176,6 @@ kbd{
overflow: hidden;
}
.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 #gamepad-value{
color: #fff;
@ -193,6 +191,8 @@ kbd{
border-radius: 0.2em;
padding: 0.5em;
box-sizing: border-box;
overflow: hidden;
white-space: nowrap;
}
.setting-value.selected{
width: calc(50% + 0.2em);
@ -215,27 +215,26 @@ kbd{
background: rgba(0, 0, 0, 0.5);
z-index: 1;
}
#settings-gamepad{
#settings-gamepad,
#settings-latency{
display: none;
}
#settings-gamepad .view{
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 574px;
height: 428px;
max-height: calc(100vh - 14em + 88px);
width: 29.9em;
max-width: 100vw;
}
#settings-gamepad .setting-box{
height: auto;
overflow: hidden;
}
#gamepad-bg,
#gamepad-buttons{
background-size: 20.53em;
}
#gamepad-bg{
position: relative;
width: 550px;
height: 317px;
width: 20.53em;
height: 11.83em;
max-height: none;
background-repeat: no-repeat;
text-align: center;
@ -244,11 +243,11 @@ kbd{
}
#gamepad-buttons{
position: absolute;
left: 141px;
top: 120px;
width: 282px;
height: 131px;
background-position: 0 -318px;
left: 5.26em;
top: 4.48em;
width: 10.52em;
height: 4.89em;
background-position: 0 -11.87em;
background-repeat: no-repeat;
pointer-events: none;
}
@ -259,3 +258,36 @@ kbd{
#gamepad-value::before{
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.setAttribute("alt", strings.tutorial.ok)
this.items = []
var versionUrl = gameConfig._version.url
this.getLink(this.linkIssues).href = versionUrl + "issues"
this.items.push(this.linkIssues)
var contactEmail = gameConfig.email
if (typeof contactEmail === 'string') {
this.hasEmail = typeof contactEmail === "string"
if(this.hasEmail){
this.linkEmail.setAttribute("alt", contactEmail)
this.getLink(this.linkEmail).href = "mailto:" + contactEmail
this.getLink(this.linkEmail).text = contactEmail
} else {
this.linkEmail.style.display = "none"
this.getLink(this.linkEmail).innerText = contactEmail
this.items.push(this.linkEmail)
}else{
this.linkEmail.parentNode.removeChild(this.linkEmail)
}
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))
this.items = [this.linkIssues, this.linkEmail, this.endButton]
this.selected = 2
this.items.push(this.endButton)
this.selected = this.items.length - 1
this.keyboard = new Keyboard({
confirm: ["enter", "space", "don_l", "don_r"],
@ -146,6 +153,8 @@
}
}
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 = {}
if(localStorage["lastError"]){
try{
@ -195,7 +204,9 @@
}
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
}
@ -214,7 +225,9 @@
this.keyboard.clean()
this.gamepad.clean()
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"])
if(this.textarea){
pageEvents.remove(this.textarea, ["focus", "blur"])

View File

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

View File

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

View File

@ -1,5 +1,5 @@
class CanvasDraw{
constructor(){
constructor(noSmoothing){
this.diffStarPath = new Path2D(vectors.diffStar)
this.longVowelMark = new Path2D(vectors.longVowelMark)
@ -68,7 +68,8 @@
emCap: /[MW]/,
rWidth: /[abdfIjo-rtv-]/,
lWidth: /[il]/,
ura: /\s*[\(]裏[\)]$/
ura: /\s*[\(]裏[\)]$/,
cjk: /[\u3040-ゞ゠-ヾ一-\u9ffe]/
}
var numbersFull = ""
@ -78,10 +79,12 @@
this.numbersFullToHalf[numbersFull[i]] = numbersHalf[i]
this.numbersFullToHalf[numbersHalf[i]] = numbersHalf[i]
}
this.wrapOn = [" ", "\n", "%s"]
this.stickySymbols = "!,.:;?~‐–‼、。々〜ぁぃぅぇぉっゃゅょァィゥェォッャュョ・ーヽヾ!:;?"
this.songFrameCache = new CanvasCache()
this.diffStarCache = new CanvasCache()
this.crownCache = new CanvasCache()
this.songFrameCache = new CanvasCache(noSmoothing)
this.diffStarCache = new CanvasCache(noSmoothing)
this.crownCache = new CanvasCache(noSmoothing)
this.tmpCanvas = document.createElement("canvas")
this.tmpCtx = this.tmpCanvas.getContext("2d")
@ -818,6 +821,163 @@
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){
var ctx = config.ctx
var scale = config.scale

View File

@ -7,6 +7,16 @@ class Controller{
this.touchEnabled = touchEnabled
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){
loader.changePage("game", false)
}
@ -18,18 +28,23 @@ class Controller{
}
this.offset = this.parsedSongData.soundOffset
assets.songs.forEach(song => {
if(song.id == this.selectedSong.folder){
this.mainAsset = song.sound
this.volume = song.volume || 1
}
})
if(this.calibrationMode){
this.volume = 1
}else{
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.view = new View(this)
this.mekadon = new Mekadon(this, this.game)
this.keyboard = new GameInput(this)
this.drumSounds = settings.getItem("latency").drumSounds
this.playedSounds = {}
}
run(syncWith){
@ -72,8 +87,8 @@ class Controller{
}
stopMainLoop(){
this.mainLoopRunning = false
if(this.mainAsset){
this.mainAsset.stop()
if(this.game.mainAsset){
this.game.mainAsset.stop()
}
if(this.multiplayer !== 2){
clearInterval(this.gameInterval)
@ -90,13 +105,18 @@ class Controller{
if(this.game.musicFadeOut < 3){
this.keyboard.checkMenuKeys()
}
if(this.calibrationMode){
this.game.calibration()
}
if(!this.game.isPaused()){
this.keyboard.checkGameKeys()
if(ms < 0){
this.game.updateTime()
}else{
this.game.update()
if(!this.calibrationMode){
this.game.update()
}
if(!this.mainLoopRunning){
return
}
@ -158,7 +178,11 @@ class Controller{
if(!fadeIn){
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(){
this.clean()
@ -166,20 +190,24 @@ class Controller{
new LoadSong(this.selectedSong, false, true, this.touchEnabled)
}else{
new Promise(resolve => {
var songObj = assets.songs.find(song => song.id === this.selectedSong.folder)
if(songObj.chart){
var reader = new FileReader()
var promise = pageEvents.load(reader).then(event => {
this.songData = event.target.result.replace(/\0/g, "").split("\n")
resolve()
})
if(this.selectedSong.type === "tja"){
reader.readAsText(songObj.chart, "sjis")
}else{
reader.readAsText(songObj.chart)
}
}else{
if(this.calibrationMode){
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(() => {
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
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
}
}
@ -201,11 +232,11 @@ class Controller{
}
this.playSound(soundID + meka, time)
}
togglePause(){
togglePause(forcePause, pauseMove, noSound){
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(){
return this.keyboard.getKeys()

View File

@ -45,7 +45,12 @@ class Game{
}
initTiming(){
// 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){
var syncWith = this.controller.syncWith
var syncCircles = syncWith.game.songData.circles
@ -57,8 +62,8 @@ class Game{
this.startDate = Date.now() + offsetTime
}
update(){
// Main operations
this.updateTime()
// Main operations
this.updateCirclesStatus()
this.checkPlays()
// Event operations
@ -82,10 +87,10 @@ class Game{
if(circle && (!circle.branch || circle.branch.active) && !circle.isPlayed){
var type = circle.type
var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll"
var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad)
var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad) + this.controller.audioLatency
if(ms >= circle.ms){
if(drumrollNotes && !circle.rendaPlayed && ms < endTime){
if(ms >= circle.ms + this.controller.audioLatency){
if(drumrollNotes && !circle.rendaPlayed && ms < endTime + this.controller.audioLatency){
circle.rendaPlayed = true
if(this.rules.difficulty === "easy"){
assets.sounds["v_renda" + this.controller.snd].stop()
@ -109,7 +114,7 @@ class Game{
this.updateCurrentCircle()
if(this.controller.multiplayer === 1){
var value = {
pace: (ms - circle.ms) / circle.timesHit
pace: (ms - circle.ms - this.controller.audioLatency) / circle.timesHit
}
if(type === "drumroll" || type === "daiDrumroll"){
value.kaAmount = circle.timesKa / circle.timesHit
@ -211,7 +216,7 @@ class Game{
for(var i = this.currentCircle + 1; i < circles.length; 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((!circleIsNote(circle) || relative < -this.rules.bad)){
break
@ -310,7 +315,7 @@ class Game{
var keyTime = this.controller.getKeyTime()
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){
var fixedNote = this.fixNoteStream(keysDon)
@ -366,7 +371,7 @@ class Game{
if(this.controller.multiplayer === 1){
var value = {
score: score,
ms: circle.ms - currentTime,
ms: circle.ms - currentTime - this.controller.audioLatency,
dai: typeDai ? (keyDai ? 2 : 1) : 0
}
if((!keysDon || !typeDon) && (!keysKa || !typeKa)){
@ -375,7 +380,7 @@ class Game{
p2.send("note", value)
}
}else{
if(circle.ms > currentTime || currentTime > circle.endTime){
if(circle.ms + this.controller.audioLatency > currentTime || currentTime > circle.endTime + this.controller.audioLatency){
return true
}
if(keysDon && type === "balloon"){
@ -400,7 +405,7 @@ class Game{
circle.played(score)
if(this.controller.multiplayer == 1){
p2.send("drumroll", {
pace: (this.elapsedTime - circle.ms) / circle.timesHit
pace: (this.elapsedTime - circle.ms + this.controller.audioLatency) / circle.timesHit
})
}
}else{
@ -447,17 +452,19 @@ class Game{
var ms = this.elapsedTime
if(!this.lastCircle){
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){
var syncWith = this.controller.syncWith
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){
this.lastCircle = syncLastCircle
}
}
}
if(!this.fadeOutStarted && ms >= this.lastCircle + 2000){
if(!this.fadeOutStarted && ms >= this.lastCircle + 2000 + this.controller.audioLatency){
this.fadeOutStarted = ms
if(this.controller.multiplayer){
this.controller.syncWith.game.fadeOutStarted = ms
@ -495,28 +502,51 @@ class Game{
playMainMusic(){
var ms = this.elapsedTime + this.controller.offset
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.mainMusicPlaying = true
}
}
togglePause(){
togglePause(forcePause, pauseMove, noSound){
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.latestDate = Date.now()
if(this.mainAsset){
this.mainAsset.stop()
}
this.mainMusicPlaying = false
this.view.pauseMove(0, true)
this.view.pauseMove(pauseMove || 0, true)
this.view.gameDiv.classList.add("game-paused")
this.view.lastMousemove = this.view.getMS()
this.view.cursorHidden = false
pageEvents.send("pause")
}else{
assets.sounds["se_cancel"].play()
}else if(!forcePause){
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
var currentDate = Date.now()
this.startDate += currentDate - this.latestDate
@ -683,7 +713,7 @@ class Game{
if(!circle || circle.branch === currentBranch[pastActive]){
var ms = this.elapsedTime
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){
this.currentCircle = closestCircle
@ -701,4 +731,104 @@ class Game{
this.sectionNotes = []
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(){
if(!this.controller.multiplayer && !this.locked){
if(!this.controller.multiplayer && !this.locked && this.controller.view.pauseOptions.length !== 0){
var moveMenu = 0
var ms = this.game.getAccurateTime()
this.gamepadMenu.play((pressed, name) => {
@ -146,7 +146,7 @@ class GameInput{
this.checkKey("don_l", "menu", moveMenuConfirm)
this.checkKey("don_r", "menu", moveMenuConfirm)
if(moveMenu && this.game.isPaused()){
assets.sounds["se_ka"].play()
this.controller.playSound("se_ka", 0, true)
this.controller.view.pauseMove(moveMenu)
}
}
@ -197,11 +197,19 @@ class GameInput{
return
}
this.keyTime[name] = ms
var calibrationState = this.game.calibrationState
var calibration = calibrationState && !this.game.paused
if(name == "don_l" || name == "don_r"){
this.checkKeySound(name, "don")
if(calibration){
this.game.calibrationHit(ms)
}else{
this.checkKeySound(name, "don")
}
this.keyboardEvents++
}else if(name == "ka_l" || name == "ka_r"){
this.checkKeySound(name, "ka")
if(!calibration){
this.checkKeySound(name, "ka")
}
this.keyboardEvents++
}
}else{

View File

@ -35,14 +35,20 @@ class LoadSong{
var song = this.selectedSong
var id = song.folder
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.songStage = this.randInt(1, 3)
song.donBg = this.randInt(1, 6)
var songObj = assets.songs.find(song => song.id === id)
if(song.songSkin && song.songSkin.name){
var imgLoad = []
for(var type in song.songSkin){
@ -117,14 +123,18 @@ class LoadSong{
}
}))
if(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")
if(songObj.chart === "blank"){
this.songData = ""
}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{
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)]
}
parseCircles(){
var meta = this.metadata[this.difficulty]
var meta = this.metadata[this.difficulty] || {}
var ms = (meta.offset || 0) * -1000 + this.offset
var bpm = Math.abs(meta.bpm) || 120
var scroll = 1

View File

@ -10,6 +10,14 @@ class Scoresheet{
this.canvas = document.getElementById("canvas")
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.fadeScreen = document.createElement("div")
@ -28,8 +36,8 @@ class Scoresheet{
this.frame = 1000 / 60
this.numbers = "001122334455667788900112233445".split("")
this.draw = new CanvasDraw()
this.canvasCache = new CanvasCache()
this.draw = new CanvasDraw(noSmoothing)
this.canvasCache = new CanvasCache(noSmoothing)
this.keyboard = new Keyboard({
confirm: ["enter", "space", "esc", "don_l", "don_r"]
@ -105,7 +113,7 @@ class Scoresheet{
if(!p2.session){
this.state.screen = "scoresShown"
this.state.screenMS = this.getMS()
assets.sounds["neiro_1_don"].play()
this.controller.playSound("neiro_1_don", 0, true)
}
}
toSongsel(fromP2){
@ -114,7 +122,7 @@ class Scoresheet{
this.state.screen = "fadeOut"
this.state.screenMS = this.getMS()
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(){
var ios = /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 = {
language: {
type: "language",
options: ["ja", "en", "cn", "tw", "ko"],
options: this.allLanguages,
default: this.getLang()
},
resolution: {
@ -34,6 +38,14 @@ class Settings{
options: ["a", "b", "c"],
default: "a",
gamepad: true
},
latency: {
type: "latency",
default: {
"audio": 0,
"video": 0,
"drumSounds": true
}
}
}
@ -61,6 +73,22 @@ class Settings{
}
}
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{
this.storage[i] = storage[i]
}
@ -107,7 +135,7 @@ class Settings{
}
}
}
return "ja"
return this.allLanguages[0]
}
setLang(lang, noEvent){
strings = lang
@ -122,7 +150,7 @@ class Settings{
}
class SettingsView{
constructor(touchEnabled, tutorial, songId){
constructor(touchEnabled, tutorial, songId, toSetting){
this.touchEnabled = touchEnabled
this.tutorial = tutorial
this.songId = songId
@ -130,9 +158,15 @@ class SettingsView{
loader.changePage("settings", tutorial)
assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992)
this.defaultButton = document.getElementById("settings-default")
this.viewOuter = this.getElement("view-outer")
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
if("getGamepads" in navigator){
var gamepads = navigator.getGamepads()
@ -145,19 +179,22 @@ class SettingsView{
}
this.mode = "settings"
this.pressedKeys = {}
this.keyboard = new Keyboard({
"confirm": ["enter", "space", "don_l", "don_r"],
"up": ["up"],
"previous": ["left", "ka_l"],
"next": ["right", "down", "ka_r"],
"right": ["right", "ka_r"],
"down": ["down"],
"left": ["left", "ka_l"],
"back": ["esc"],
"other": ["wildcard"]
}, this.keyPressed.bind(this))
this.gamepad = new Gamepad({
"confirm": ["b", "ls", "rs"],
"up": ["u", "lsu"],
"previous": ["l", "lb", "lt", "lsl"],
"next": ["d", "r", "rb", "rt", "lsd", "lsr"],
"right": ["r", "rb", "rt", "lsr"],
"down": ["d", "lsd"],
"left": ["l", "lb", "lt", "lsl"],
"back": ["start", "a"]
}, this.keyPressed.bind(this))
@ -182,15 +219,15 @@ class SettingsView{
var nameDiv = document.createElement("div")
nameDiv.classList.add("setting-name", "stroke-sub")
var name = strings.settings[i].name
nameDiv.innerText = name
nameDiv.setAttribute("alt", name)
this.setAltText(nameDiv, name)
settingBox.appendChild(nameDiv)
var valueDiv = document.createElement("div")
valueDiv.classList.add("setting-value")
this.getValue(i, valueDiv)
settingBox.appendChild(valueDiv)
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")
}
this.addTouch(settingBox, event => this.setValue(i))
@ -226,8 +263,99 @@ class SettingsView{
this.gamepadButtons = document.getElementById("gamepad-buttons")
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.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")
}
getElement(name){
@ -246,6 +374,23 @@ class SettingsView{
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){
pageEvents.remove(element, ["mousedown", "touchstart"])
}
@ -274,6 +419,17 @@ class SettingsView{
valueDiv.appendChild(keyDiv)
}
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
}
@ -285,6 +441,7 @@ class SettingsView{
if(this.mode !== "settings"){
if(this.selected === selectedIndex){
this.keyboardBack(selected)
this.playSound("se_don")
}
return
}
@ -303,24 +460,37 @@ class SettingsView{
selected.valueDiv.classList.add("selected")
this.keyboardKeys = {}
this.keyboardSet()
assets.sounds["se_don"].play()
this.playSound("se_don")
return
}else if(current.type === "gamepad"){
this.mode = "gamepad"
this.gamepadSelected = current.options.indexOf(value)
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
}
settings.setItem(name, value)
this.getValue(name, this.items[this.selected].valueDiv)
assets.sounds["se_ka"].play()
this.playSound("se_ka")
if(current.type === "language"){
this.setLang(allStrings[value])
}
}
keyPressed(pressed, name, event){
if(!pressed){
keyPressed(pressed, name, event, repeat){
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
}
this.touched = false
@ -334,31 +504,31 @@ class SettingsView{
}else{
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")
do{
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
}while(this.items[this.selected].id === "default" && name !== "previous")
this.selected = this.mod(this.items.length, this.selected + ((name === "right" || name === "down") ? 1 : -1))
}while(this.items[this.selected].id === "default" && name !== "left")
selected = this.items[this.selected]
selected.settingBox.classList.add("selected")
selected.settingBox.scrollIntoView()
assets.sounds["se_ka"].play()
this.playSound("se_ka")
}else if(name === "back"){
this.onEnd()
}
}else if(this.mode === "gamepad"){
if(name === "confirm"){
this.gamepadBack(true)
}else if(name === "up" || name === "previous" || name === "next"){
this.gamepadSet(name === "next" ? 1 : -1)
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
this.gamepadSet((name === "right" || name === "down") ? 1 : -1)
}else if(name === "back"){
this.gamepadBack()
}
}else if(this.mode === "keyboard"){
if(name === "back"){
this.keyboardBack(selected)
assets.sounds["se_cancel"].play()
}else{
this.playSound("se_cancel")
}else if(event){
event.preventDefault()
var currentKey = event.key.toLowerCase()
for(var i in this.keyboardKeys){
@ -367,10 +537,40 @@ class SettingsView{
}
}
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.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(){
@ -416,14 +616,13 @@ class SettingsView{
var current = settings.items[selected.id]
if(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 value = strings.settings[selected.id][opt]
this.gamepadValue.innerText = value
this.gamepadValue.setAttribute("alt", value)
this.gamepadButtons.style.backgroundPosition = "0 " + (-318 - 132 * this.gamepadSelected) + "px"
this.gamepadSettings.style.display = "block"
this.setAltText(this.gamepadValue, value)
this.gamepadButtons.style.backgroundPosition = "0 " + (-11.87 - 4.93 * this.gamepadSelected) + "em"
this.gamepadSettings.style.display = "flex"
}
gamepadBack(confirm){
if(this.mode !== "gamepad"){
@ -433,10 +632,142 @@ class SettingsView{
var current = settings.items[selected.id]
settings.setItem(selected.id, current.options[this.gamepadSelected])
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.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(){
if(this.mode === "keyboard"){
this.keyboardBack(this.items[this.selected])
@ -447,11 +778,17 @@ class SettingsView{
this.setLang(allStrings[settings.getItem("language")])
this.keyboard.update()
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(){
this.clean()
assets.sounds["se_don"].play()
this.playSound("se_don")
setTimeout(() => {
if(this.tutorial && !this.touched){
new Tutorial(false, this.songId)
@ -472,41 +809,94 @@ class SettingsView{
var item = this.items[i]
if(item.valueDiv){
var name = strings.settings[item.id].name
item.nameDiv.innerText = name
item.nameDiv.setAttribute("alt", name)
this.setAltText(item.nameDiv, name)
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()
}
setStrings(){
this.viewTitle.innerText = strings.gameSettings
this.viewTitle.setAttribute("alt", strings.gameSettings)
this.endButton.innerText = strings.settings.ok
this.endButton.setAttribute("alt", strings.settings.ok)
this.gamepadTitle.innerText = strings.settings.gamepadLayout.name
this.gamepadTitle.setAttribute("alt", strings.settings.gamepadLayout.name)
this.gamepadEndButton.innerText = strings.settings.ok
this.gamepadEndButton.setAttribute("alt", strings.settings.ok)
this.defaultButton.innerText = strings.settings.default
this.defaultButton.setAttribute("alt", strings.settings.default)
this.setAltText(this.viewTitle, strings.gameSettings)
this.setAltText(this.endButton, strings.settings.ok)
this.setAltText(this.gamepadTitle, strings.settings.gamepadLayout.name)
this.setAltText(this.gamepadEndButton, strings.settings.ok)
this.setAltText(this.latencyTitle, strings.settings.latency.name)
this.setAltText(this.latencyDefaultButton, strings.settings.default)
this.setAltText(this.latencyEndButton, strings.settings.ok)
this.setAltText(this.defaultButton, strings.settings.default)
}
setAltText(element, text){
element.innerText = text
element.setAttribute("alt", text)
}
mod(length, index){
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(){
this.redrawRunning = false
this.keyboard.clean()
this.gamepad.clean()
assets.sounds["bgm_settings"].stop()
pageEvents.remove(this.viewOuter, ["mouseup", "touchend"])
for(var i in this.items){
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){
delete this.defaultButton
}
this.removeTouch(this.gamepadSettings)
this.removeTouch(this.gamepadEndButton)
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.endButton
delete this.items
@ -516,6 +906,11 @@ class SettingsView{
delete this.gamepadBox
delete this.gamepadButtons
delete this.gamepadValue
delete this.latencyItems
delete this.latencySettings
delete this.latencyTitle
delete this.latencyDefaultButton
delete this.latencyEndButton
if(this.resolution !== settings.getItem("resolution")){
for(var i in assets.image){
if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_")){

View File

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

View File

@ -126,11 +126,42 @@
b: "タイプB",
c: "タイプC"
},
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "オン",
off: "オフ",
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 = {
browserWarning: "サポートされていないブラウザを実行しています (%s)",
details: "詳しく",
@ -270,11 +301,42 @@ function StringsEn(){
b: "Type B",
c: "Type C"
},
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "On",
off: "Off",
default: "Reset to Defaults",
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 = {
browserWarning: "You are running an unsupported browser (%s)",
details: "Details...",
@ -414,11 +476,42 @@ function StringsCn(){
b: "类型B",
c: "类型C"
},
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "开",
off: "关",
default: "重置为默认值",
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 = {
browserWarning: "You are running an unsupported browser (%s)",
details: "Details...",
@ -558,11 +651,42 @@ function StringsTw(){
b: "類型B",
c: "類型C"
},
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "開",
off: "關",
default: "重置為默認值",
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 = {
browserWarning: "You are running an unsupported browser (%s)",
details: "Details...",
@ -702,11 +826,42 @@ function StringsKo(){
b: "타입 B",
c: "타입 C"
},
latency: {
name: "Latency",
value: "Audio: %s, Video: %s",
calibration: "Latency Calibration",
audio: "Audio",
video: "Video",
drumSounds: "Drum Sounds"
},
on: "온",
off: "오프",
default: "기본값으로 재설정",
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 = {
browserWarning: "You are running an unsupported browser (%s)",
details: "Details...",

View File

@ -4,8 +4,15 @@
this.canvas = document.getElementById("canvas")
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.songBg = document.getElementById("songbg")
this.songStage = document.getElementById("song-stage")
@ -73,6 +80,7 @@
}
this.nextBeat = 0
this.gogoTime = 0
this.gogoTimeStarted = -Infinity
this.drumroll = []
this.touchEvents = 0
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.draw = new CanvasDraw()
this.draw = new CanvasDraw(noSmoothing)
this.assets = new ViewAssets(this)
this.titleCache = new CanvasCache()
this.comboCache = new CanvasCache()
this.pauseCache = new CanvasCache()
this.branchCache = new CanvasCache()
this.titleCache = new CanvasCache(noSmoothing)
this.comboCache = new CanvasCache(noSmoothing)
this.pauseCache = new CanvasCache(noSmoothing)
this.branchCache = new CanvasCache(noSmoothing)
this.multiplayer = this.controller.multiplayer
@ -120,6 +132,9 @@
this.touch = -Infinity
this.touchAnimation = settings.getItem("touchAnimation")
versionDiv.classList.add("version-hide")
loader.screen.parentNode.insertBefore(versionDiv, loader.screen)
if(this.multiplayer !== 2){
if(this.controller.touchEnabled){
@ -134,7 +149,6 @@
pageEvents.add(this.canvas, "touchstart", this.ontouch.bind(this))
this.gameDiv.classList.add("touch-visible")
document.getElementById("version").classList.add("version-hide")
this.touchFullBtn = document.getElementById("touch-full-btn")
pageEvents.add(this.touchFullBtn, "touchend", toggleFullscreen)
@ -444,12 +458,14 @@
ctx.fill()
// Difficulty
ctx.drawImage(assets.image["difficulty"],
0, 144 * this.difficulty[this.controller.selectedSong.difficulty],
168, 143,
126, this.multiplayer === 2 ? 497 : 228,
62, 53
)
if(this.controller.selectedSong.difficulty){
ctx.drawImage(assets.image["difficulty"],
0, 144 * this.difficulty[this.controller.selectedSong.difficulty],
168, 143,
126, this.multiplayer === 2 ? 497 : 228,
62, 53
)
}
// Badges
if(this.controller.autoPlayEnabled && !this.controller.multiplayer){
@ -593,24 +609,26 @@
ctx.globalAlpha = 1
// Difficulty
ctx.drawImage(assets.image["difficulty"],
0, 144 * this.difficulty[this.controller.selectedSong.difficulty],
168, 143,
16, this.multiplayer === 2 ? 194 : 232,
141, 120
)
var diff = this.controller.selectedSong.difficulty
var text = strings[diff === "ura" ? "oni" : diff]
ctx.font = this.draw.bold(this.font) + "20px " + this.font
ctx.textAlign = "center"
ctx.textBaseline = "bottom"
ctx.strokeStyle = "#000"
ctx.fillStyle = "#fff"
ctx.lineWidth = 7
ctx.miterLimit = 1
ctx.strokeText(text, 87, this.multiplayer === 2 ? 310 : 348)
ctx.fillText(text, 87, this.multiplayer === 2 ? 310 : 348)
ctx.miterLimit = 10
if(this.controller.selectedSong.difficulty){
ctx.drawImage(assets.image["difficulty"],
0, 144 * this.difficulty[this.controller.selectedSong.difficulty],
168, 143,
16, this.multiplayer === 2 ? 194 : 232,
141, 120
)
var diff = this.controller.selectedSong.difficulty
var text = strings[diff === "ura" ? "oni" : diff]
ctx.font = this.draw.bold(this.font) + "20px " + this.font
ctx.textAlign = "center"
ctx.textBaseline = "bottom"
ctx.strokeStyle = "#000"
ctx.fillStyle = "#fff"
ctx.lineWidth = 7
ctx.miterLimit = 1
ctx.strokeText(text, 87, this.multiplayer === 2 ? 310 : 348)
ctx.fillText(text, 87, this.multiplayer === 2 ? 310 : 348)
ctx.miterLimit = 10
}
// Badges
if(this.controller.autoPlayEnabled && !this.controller.multiplayer){
@ -947,6 +965,20 @@
ctx.clip()
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()
// Hit notes explosion
@ -1001,6 +1033,22 @@
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) => {
this.draw.roundedRect({
ctx: ctx,
@ -1025,86 +1073,284 @@
dx: 68,
dy: 11
})
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 _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({
if(boldTitle){
this.draw.layeredText({
ctx: ctx,
x: _x - _w / 2,
y: _y,
w: _w,
h: _h,
id: this.pauseOptions[i] + (selected ? "1" : "0")
}, ctx => {
var textConfig = {
text: boldTitle,
fontSize: 35,
fontFamily: this.font,
x: 300,
y: 70
}, [
{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,
text: this.pauseOptions[i],
x: _w / 2,
y: 18,
width: _w,
height: _h - 54,
text: strings.calibration[state][content],
fontSize: 30,
fontFamily: this.font,
x: 300,
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,
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
x: 300,
y: 130,
width: 680,
height: 420,
lineHeight: 47,
fill: "#000",
verticalAlign: "middle",
textAlign: "center",
})
}
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()
}
}
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(){
var selectedSong = this.controller.selectedSong
var songSkinName = selectedSong.songSkin.name
@ -1219,10 +1465,10 @@
measures.forEach(measure => {
var timeForDistance = this.posToMs(distanceForCircle, measure.speed)
var startingTime = measure.ms - timeForDistance
var finishTime = measure.ms + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + 3, measure.speed)
var startingTime = measure.ms - timeForDistance + this.controller.videoLatency
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){
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.lineWidth = 3
this.ctx.beginPath()
@ -1267,8 +1513,8 @@
var speed = circle.speed
var timeForDistance = this.posToMs(distanceForCircle + this.slotPos.size / 2, speed)
var startingTime = circle.ms - timeForDistance
var finishTime = circle.endTime + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed)
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) + this.controller.videoLatency
if(circle.isPlayed <= 0 || circle.score === 0){
if((!circle.branch || circle.branch.active) && ms >= startingTime && ms <= finishTime && circle.isPlayed !== -1){
@ -1350,7 +1596,7 @@
if(!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
}
}
@ -1388,10 +1634,10 @@
size = circleSize
faceID = noteFace.small
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
}else if(ms > endTime){
circlePos.x = this.slotPos.x + this.msToPos(endTime - ms, speed)
}else if(ms > endTime + this.controller.audioLatency){
circlePos.x = this.slotPos.x + this.msToPos(endTime - ms + this.controller.audioLatency, speed)
}
ctx.drawImage(assets.image["balloon"],
circlePos.x + size - 4,
@ -1596,7 +1842,9 @@
}
toggleGogoTime(circle){
this.gogoTime = circle.gogoTime
this.gogoTimeStarted = circle.ms
if(circle.gogoTime || this.gogoTimeStarted !== -Infinity){
this.gogoTimeStarted = circle.ms
}
if(this.gogoTime){
this.assets.fireworks.forEach(fireworksAsset => {
@ -1789,21 +2037,62 @@
if(typeof pos === "undefined"){
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){
case 1:
assets.sounds["se_don"].play()
this.controller.restartSong()
this.controller.playSound("se_don", 0, true)
if(state === "video"){
game.calibrationReset(state)
}else{
this.controller.restartSong()
}
pageEvents.send("pause-restart")
break
case 2:
assets.sounds["se_don"].play()
this.controller.playSound("se_don", 0, true)
this.controller.songSelection()
pageEvents.send("pause-song-select")
break
default:
this.controller.togglePause()
this.controller.togglePause(false)
break
}
return true
}
onmousedown(event){
if(this.controller.game.paused){
@ -1855,23 +2144,30 @@
x = x * pauseScale + 257
y = y * pauseScale - 328
}
if(104 <= y && y <= 575 && 465 <= x && x <= 465 + 110 * this.pauseOptions.length){
return Math.floor((x - 465) / 110)
switch(this.controller.game.calibrationState){
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
}
mouseIdle(){
var lastMouse = pageEvents.getMouse()
if(lastMouse && !this.cursorHidden){
if(lastMouse && !this.cursorHidden && !this.state.hasPointer){
if(this.getMS() >= this.lastMousemove + 2000){
this.cursor.style.top = lastMouse.clientY + "px"
this.cursor.style.left = lastMouse.clientX + "px"
this.cursor.style.pointerEvents = "auto"
this.canvas.style.cursor = "none"
this.cursorHidden = true
}else{
this.cursor.style.top = ""
this.cursor.style.left = ""
this.cursor.style.pointerEvents = ""
this.canvas.style.cursor = ""
}
}
}
@ -1890,12 +2186,13 @@
this.pauseCache.clean()
this.branchCache.clean()
versionDiv.classList.remove("version-hide")
loader.screen.parentNode.appendChild(versionDiv)
if(this.multiplayer !== 2){
if(this.touchEnabled){
pageEvents.remove(this.canvas, "touchstart")
pageEvents.remove(this.touchPauseBtn, "touchend")
this.gameDiv.classList.add("touch-results")
document.getElementById("version").classList.remove("version-hide")
this.touchDrumDiv.parentNode.removeChild(this.touchDrumDiv)
delete this.touchDrumDiv
delete this.touchDrumImg
@ -1915,7 +2212,6 @@
pageEvents.mouseRemove(this)
delete this.pauseMenu
delete this.cursor
delete this.gameDiv
delete this.canvas
delete this.ctx

View File

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

View File

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

View File

@ -20,5 +20,15 @@
<div class="view-end-button taibtn stroke-sub selected"></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>