Files
taiko-web2/public/src/js/game.js
LoveEevee ff09cb83bd 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.
2019-11-28 09:04:40 +03:00

835 lines
24 KiB
JavaScript

class Game{
constructor(controller, selectedSong, songData){
this.controller = controller
this.selectedSong = selectedSong
this.songData = songData
this.elapsedTime = 0
this.currentCircle = 0
this.combo = 0
this.rules = new GameRules(this)
this.globalScore = {
points: 0,
good: 0,
ok: 0,
bad: 0,
maxCombo: 0,
drumroll: 0,
gauge: 0,
title: selectedSong.title,
difficulty: this.rules.difficulty
}
this.HPGain = 100 / this.songData.circles.filter(circle => {
var type = circle.type
return (type === "don" || type === "ka" || type === "daiDon" || type === "daiKa") && (!circle.branch || circle.branch.active)
}).length
this.paused = false
this.started = false
this.mainMusicPlaying = false
this.musicFadeOut = 0
this.fadeOutStarted = false
this.currentTimingPoint = 0
this.branchNames = ["normal", "advanced", "master"]
this.resetSection()
this.gameLagSync = !this.controller.touchEnabled && !(/Firefox/.test(navigator.userAgent))
assets.songs.forEach(song => {
if(song.id == selectedSong.folder){
this.mainAsset = song.sound
}
})
}
run(){
this.timeForDistanceCircle = 2500
this.initTiming()
this.view = this.controller.view
}
initTiming(){
// Date when the chrono is started (before the game begins)
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
var syncOffsetTime = Math.max(0, this.timeForDistanceCircle - syncCircles[0].ms) |0
offsetTime = Math.max(offsetTime, syncOffsetTime)
}
this.elapsedTime = -offsetTime
// The real start for the game will start when chrono will reach 0
this.startDate = Date.now() + offsetTime
}
update(){
this.updateTime()
// Main operations
this.updateCirclesStatus()
this.checkPlays()
// Event operations
this.whenFadeoutMusic()
if(this.controller.multiplayer !== 2){
this.whenLastCirclePlayed()
}
}
getCircles(){
return this.songData.circles
}
updateCirclesStatus(){
var nextSet = false
var ms = this.elapsedTime
var circles = this.songData.circles
var startIndex = this.currentCircle === 0 ? 0 : this.currentCircle - 1
var index = 0
for(var i = startIndex; i < circles.length; i++){
var circle = circles[i]
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) + this.controller.audioLatency
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()
this.controller.playSoundMeka("v_renda")
}
}
if(!circle.beatMSCopied){
if(this.view.beatInterval !== circle.beatMS){
this.view.changeBeatInterval(circle.beatMS)
}
circle.beatMSCopied = true
}
}
if(ms > endTime){
if(!this.controller.autoPlayEnabled){
if(drumrollNotes){
if(circle.section && circle.timesHit === 0){
this.resetSection()
}
circle.played(-1, false)
this.updateCurrentCircle()
if(this.controller.multiplayer === 1){
var value = {
pace: (ms - circle.ms - this.controller.audioLatency) / circle.timesHit
}
if(type === "drumroll" || type === "daiDrumroll"){
value.kaAmount = circle.timesKa / circle.timesHit
}
p2.send("drumroll", value)
}
}else{
this.skipNote(circle)
this.updateCurrentCircle()
}
}
}else if(!this.controller.autoPlayEnabled && !nextSet){
nextSet = true
this.currentCircle = i
}
if(index++ > 1){
break
}
}
}
var branches = this.songData.branches
if(branches){
var force = this.controller.multiplayer === 2 ? p2 : this
var measures = this.songData.measures
if(this.controller.multiplayer === 2 || force.branch){
if(!force.branchSet){
force.branchSet = true
if(branches.length){
this.setBranch(branches[0], force.branch)
}
var view = this.controller.view
var currentMeasure = view.branch
for(var i = measures.length; i--;){
var measure = measures[i]
if(measure.nextBranch && measure.ms <= ms){
currentMeasure = measure.nextBranch.active
}
}
if(view.branch !== currentMeasure){
if(!this.branchStatic){
view.branchAnimate = {
ms: ms,
fromBranch: view.branch
}
}
this.branchStatic = false
view.branch = currentMeasure
}
}
}
for(var i = 0; i < measures.length; i++){
var measure = measures[i]
if(measure.ms > ms){
break
}else if(measure.nextBranch && !measure.gameChecked){
measure.gameChecked = true
var branch = measure.nextBranch
if(branch.type){
var accuracy = 0
if(branch.type === "drumroll"){
if(force.branch){
var accuracy = Math.max(0, branch.requirement[force.branch])
}else{
var accuracy = this.sectionDrumroll
}
}else if(this.sectionNotes.length !== 0){
if(force.branch){
var accuracy = Math.max(0, Math.min(100, branch.requirement[force.branch]))
}else{
var accuracy = this.sectionNotes.reduce((a, b) => a + b) / this.sectionNotes.length * 100
}
}
if(accuracy >= branch.requirement.master){
this.setBranch(branch, "master")
}else if(accuracy >= branch.requirement.advanced){
this.setBranch(branch, "advanced")
}else{
this.setBranch(branch, "normal")
}
}else if(this.controller.multiplayer === 1){
p2.send("branch", "normal")
}
}
}
}
}
fixNoteStream(keysDon){
var circleIsNote = circle => {
var type = circle.type
return type === "don" || type === "ka" || type === "daiDon" || type === "daiKa"
}
var correctNote = circle => {
var type = circle.type
return keysDon ? (type === "don" || type === "daiDon") : (type === "ka" || type === "daiKa")
}
var ms = this.elapsedTime
var circles = this.songData.circles
for(var i = this.currentCircle + 1; i < circles.length; i++){
var circle = circles[i]
var relative = ms - circle.ms - this.controller.audioLatency
if(!circle.branch || circle.branch.active){
if((!circleIsNote(circle) || relative < -this.rules.bad)){
break
}else if(Math.abs(relative) < this.rules.ok && correctNote(circle)){
for(var j = this.currentCircle; j < i; j++){
var circle = circles[j]
if(circle && !circle.branch || circle.branch.active){
this.skipNote(circles[j])
}
}
this.currentCircle = i
return circles[i]
}
}
}
}
skipNote(circle){
if(circle.section){
this.resetSection()
}
circle.played(-1, circle.type === "daiDon" || circle.type === "daiKa")
this.sectionNotes.push(0)
this.controller.displayScore(0, true)
this.updateCombo(0)
this.updateGlobalScore(0, 1)
if(this.controller.multiplayer === 1){
p2.send("note", {
score: -1
})
}
}
checkPlays(){
var circles = this.songData.circles
var circle = circles[this.currentCircle]
if(this.controller.autoPlayEnabled){
while(circle && this.controller.autoPlay(circle)){
circle = circles[this.currentCircle]
}
return
}
var keys = this.controller.getKeys()
var don_l = keys["don_l"] && !this.controller.isWaiting("don_l", "score")
var don_r = keys["don_r"] && !this.controller.isWaiting("don_r", "score")
var ka_l = keys["ka_l"] && !this.controller.isWaiting("ka_l", "score")
var ka_r = keys["ka_r"] && !this.controller.isWaiting("ka_r", "score")
var checkDon = () => {
if(don_l && don_r){
this.checkKey(["don_l", "don_r"], circle, "daiDon")
}else if(don_l){
this.checkKey(["don_l"], circle, "don")
}else if(don_r){
this.checkKey(["don_r"], circle, "don")
}
}
var checkKa = () => {
if(ka_l && ka_r){
this.checkKey(["ka_l", "ka_r"], circle, "daiKa")
}else if(ka_l){
this.checkKey(["ka_l"], circle, "ka")
}else if(ka_r){
this.checkKey(["ka_r"], circle, "ka")
}
}
var keyTime = this.controller.getKeyTime()
if(keyTime["don"] >= keyTime["ka"]){
checkDon()
checkKa()
}else{
checkKa()
checkDon()
}
}
checkKey(keyCodes, circle, check){
if(circle && !circle.isPlayed){
if(!this.checkScore(circle, check)){
return
}
}
keyCodes.forEach(keyCode => {
this.controller.waitForKeyup(keyCode, "score")
})
}
checkScore(circle, check){
var ms = this.elapsedTime
var type = circle.type
var keysDon = check === "don" || check === "daiDon"
var keysKa = check === "ka" || check === "daiKa"
var keyDai = check === "daiDon" || check === "daiKa"
var typeDon = type === "don" || type === "daiDon"
var typeKa = type === "ka" || type === "daiKa"
var typeDai = type === "daiDon" || type === "daiKa"
var keyTime = this.controller.getKeyTime()
var currentTime = keysDon ? keyTime["don"] : keyTime["ka"]
var relative = currentTime - circle.ms - this.controller.audioLatency
if(relative >= this.rules.ok){
var fixedNote = this.fixNoteStream(keysDon)
if(fixedNote){
return this.checkScore(fixedNote, check)
}
}
if(typeDon || typeKa){
if(-this.rules.bad >= relative || relative >= this.rules.bad){
return true
}
var score = 0
if(keysDon && typeDon || keysKa && typeKa){
if(typeDai && !keyDai){
if(!circle.daiFailed){
circle.daiFailed = ms
return false
}else if(ms < circle.daiFailed + this.rules.daiLeniency){
return false
}
}
var circleStatus = -1
relative = Math.abs(relative)
if(relative < this.rules.good){
circleStatus = 450
}else if(relative < this.rules.ok){
circleStatus = 230
}else if(relative < this.rules.bad){
circleStatus = 0
}
if(circleStatus === 230 || circleStatus === 450){
score = circleStatus
}
circle.played(score, score === 0 ? typeDai : keyDai)
this.controller.displayScore(score, false, typeDai && keyDai)
}else{
var keyTime = this.controller.getKeyTime()
var keyTimeRelative = Math.abs(keyTime.don - keyTime.ka)
if(Math.abs(relative) >= (keyTimeRelative <= 25 ? this.rules.bad : this.rules.good)){
return true
}
circle.played(-1, typeDai)
this.controller.displayScore(score, true, false)
}
this.updateCombo(score)
this.updateGlobalScore(score, typeDai && keyDai ? 2 : 1, circle.gogoTime)
this.updateCurrentCircle()
if(circle.section){
this.resetSection()
}
this.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0))
if(this.controller.multiplayer === 1){
var value = {
score: score,
ms: circle.ms - currentTime - this.controller.audioLatency,
dai: typeDai ? (keyDai ? 2 : 1) : 0
}
if((!keysDon || !typeDon) && (!keysKa || !typeKa)){
value.reverse = true
}
p2.send("note", value)
}
}else{
if(circle.ms + this.controller.audioLatency > currentTime || currentTime > circle.endTime + this.controller.audioLatency){
return true
}
if(keysDon && type === "balloon"){
this.checkBalloon(circle)
if(check === "daiDon" && !circle.isPlayed){
this.checkBalloon(circle)
}
}else if((keysDon || keysKa) && (type === "drumroll" || type === "daiDrumroll")){
this.checkDrumroll(circle, keysKa)
if(keyDai){
this.checkDrumroll(circle, keysKa)
}
}
}
return true
}
checkBalloon(circle){
if(circle.timesHit >= circle.requiredHits - 1){
var score = 5000
this.updateCurrentCircle()
circle.hit()
circle.played(score)
if(this.controller.multiplayer == 1){
p2.send("drumroll", {
pace: (this.elapsedTime - circle.ms + this.controller.audioLatency) / circle.timesHit
})
}
}else{
var score = 300
circle.hit()
}
this.globalScore.drumroll++
this.sectionDrumroll++
this.globalScore.points += score
this.view.setDarkBg(false)
}
checkDrumroll(circle, keysKa){
var ms = this.elapsedTime
var dai = circle.type === "daiDrumroll"
var score = 100
if(circle.section && circle.timesHit === 0){
this.resetSection()
}
circle.hit(keysKa)
var keyTime = this.controller.getKeyTime()
if(circle.type === "drumroll"){
var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka"
}else{
var sound = keyTime["don"] > keyTime["ka"] ? "daiDon" : "daiKa"
}
var circleAnim = new Circle({
id: 0,
start: ms,
type: sound,
txt: "",
speed: circle.speed,
gogoTime: circle.gogoTime,
fixedPos: document.hasFocus()
})
circleAnim.played(score, dai)
circleAnim.animate(ms)
this.view.drumroll.push(circleAnim)
this.globalScore.drumroll++
this.sectionDrumroll++
this.globalScore.points += score * (dai ? 2 : 1)
this.view.setDarkBg(false)
}
whenLastCirclePlayed(){
var ms = this.elapsedTime
if(!this.lastCircle){
var circles = this.songData.circles
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
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 + this.controller.audioLatency){
this.fadeOutStarted = ms
if(this.controller.multiplayer){
this.controller.syncWith.game.fadeOutStarted = ms
}
}
}
whenFadeoutMusic(){
var started = this.fadeOutStarted
if(started){
var ms = this.elapsedTime
var duration = this.mainAsset ? this.mainAsset.duration : 0
var musicDuration = duration * 1000 - this.controller.offset
if(this.musicFadeOut === 0){
if(this.controller.multiplayer === 1){
p2.send("gameresults", this.getGlobalScore())
}
this.musicFadeOut++
}else if(this.musicFadeOut === 1 && ms >= started + 1600){
this.controller.gameEnded()
if(!p2.session && this.controller.multiplayer === 1){
p2.send("gameend")
}
this.musicFadeOut++
}else if(this.musicFadeOut === 2 && (ms >= started + 8600 && ms >= musicDuration + 250)){
this.controller.displayResults()
this.musicFadeOut++
}else if(this.musicFadeOut === 3 && (ms >= started + 9600 && ms >= musicDuration + 1250)){
this.controller.clean()
if(this.controller.scoresheet){
this.controller.scoresheet.startRedraw()
}
}
}
}
playMainMusic(){
var ms = this.elapsedTime + this.controller.offset
if(!this.mainMusicPlaying && (!this.fadeOutStarted || ms < this.fadeOutStarted + 1600)){
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(forcePause, pauseMove, noSound){
if(!this.paused){
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(pauseMove || 0, true)
this.view.gameDiv.classList.add("game-paused")
this.view.lastMousemove = this.view.getMS()
this.view.cursorHidden = false
pageEvents.send("pause")
}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
this.sndTime = currentDate - snd.buffer.getTime() * 1000
this.view.gameDiv.classList.remove("game-paused")
this.view.pointer()
pageEvents.send("unpause", currentDate - this.latestDate)
}
}
isPaused(){
return this.paused
}
updateTime(){
// Refreshed date
var ms = this.elapsedTime
if(ms >= 0 && !this.started){
this.startDate = Date.now()
this.elapsedTime = this.getAccurateTime()
this.started = true
this.sndTime = this.startDate - snd.buffer.getTime() * 1000
}else if(ms < 0 || ms >= 0 && this.started){
var currentDate = Date.now()
if(this.gameLagSync){
var sndTime = currentDate - snd.buffer.getTime() * 1000
var lag = sndTime - this.sndTime
if(Math.abs(lag) >= 50){
this.startDate += lag
this.sndTime = sndTime
pageEvents.send("game-lag", lag)
}
}
this.elapsedTime = currentDate - this.startDate
}
}
getAccurateTime(){
if(this.isPaused()){
return this.elapsedTime
}else{
return Date.now() - this.startDate
}
}
getCircles(){
return this.songData.circles
}
updateCurrentCircle(){
var circles = this.songData.circles
do{
var circle = circles[++this.currentCircle]
}while(circle && circle.branch && !circle.branch.active)
}
getCurrentCircle(){
return this.currentCircle
}
updateCombo(score){
if(score !== 0){
this.combo++
}else{
this.combo = 0
}
if(this.combo > this.globalScore.maxCombo){
this.globalScore.maxCombo = this.combo
}
if(this.combo === 50 || this.combo > 0 && this.combo % 100 === 0 && this.combo < 1500 || this.combo > 0 && this.combo % 500 === 0){
this.controller.playSoundMeka("v_combo_" + (this.combo <= 1400 ? this.combo : "over1500"))
}
if (this.songData.scoremode == 2 && this.combo > 0 && this.combo % 100 == 0) {
this.globalScore.points += 10000;
}
this.view.updateCombo(this.combo)
}
getCombo(){
return this.combo
}
getGlobalScore(){
return this.globalScore
}
updateGlobalScore(score, multiplier, gogoTime){
// Circle score
switch(score){
case 450:
this.globalScore.good++
break
case 230:
this.globalScore.ok++
break
case 0:
this.globalScore.bad++
break
}
if (this.songData.scoremode) {
switch (score) {
case 450:
score = this.songData.scoreinit;
break;
case 230:
score = Math.floor(this.songData.scoreinit / 2);
break;
}
}
// Gauge update
if(score !== 0){
this.globalScore.gauge += this.HPGain
}else if(this.globalScore.gauge - this.HPGain > 0){
this.globalScore.gauge -= this.HPGain
}else{
this.globalScore.gauge = 0
}
// Points update
if (this.songData.scoremode == 2) {
var diff_mul = 0;
if (this.combo >= 100) {
diff_mul = 8;
} else if (this.combo >= 50) {
diff_mul = 4;
} else if (this.combo >= 30) {
diff_mul = 2;
} else if (this.combo >= 10) {
diff_mul = 1;
}
score += this.songData.scorediff * diff_mul;
} else {
score += Math.max(0, Math.floor((Math.min(this.combo, 100) - 1) / 10) * (this.songData.scoremode ? this.songData.scorediff : 100));
}
if(gogoTime){
multiplier *= 1.2
}
this.globalScore.points += Math.floor(score * multiplier / 10) * 10
}
setBranch(currentBranch, activeName){
var pastActive = currentBranch.active
var ms = currentBranch.ms
for(var i = 0; i < this.songData.branches.length; i++){
var branch = this.songData.branches[i]
if(branch.ms >= ms){
var relevantName = activeName
var req = branch.requirement
var noNormal = req.advanced <= 0
var noAdvanced = req.master <= 0 || req.advanced >= req.master || branch.type === "accuracy" && req.advanced > 100
var noMaster = branch.type === "accuracy" && req.master > 100
if(relevantName === "normal" && noNormal){
relevantName = noAdvanced ? "master" : "advanced"
}
if(relevantName === "advanced" && noAdvanced){
relevantName = noMaster ? "normal" : "master"
}
if(relevantName === "master" && noMaster){
relevantName = noAdvanced ? "normal" : "advanced"
}
for(var j in this.branchNames){
var name = this.branchNames[j]
if(name in branch){
branch[name].active = name === relevantName
}
}
if(branch === currentBranch){
activeName = relevantName
}
branch.active = relevantName
}
}
var circles = this.songData.circles
var circle = circles[this.currentCircle]
if(!circle || circle.branch === currentBranch[pastActive]){
var ms = this.elapsedTime
var closestCircle = circles.findIndex(circle => {
return (!circle.branch || circle.branch.active) && circle.endTime + this.controller.audioLatency >= ms
})
if(closestCircle !== -1){
this.currentCircle = closestCircle
}
}
this.HPGain = 100 / this.songData.circles.filter(circle => {
var type = circle.type
return (type === "don" || type === "ka" || type === "daiDon" || type === "daiKa") && (!circle.branch || circle.branch.active)
}).length
if(this.controller.multiplayer === 1){
p2.send("branch", activeName)
}
}
resetSection(){
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)
}
}
}