diff --git a/public/src/css/debug.css b/public/src/css/debug.css index 7c838f0..f9e1478 100644 --- a/public/src/css/debug.css +++ b/public/src/css/debug.css @@ -44,7 +44,8 @@ box-sizing: border-box; } -#debug .input-slider{ +#debug .input-slider, +#debug .select{ display: flex; width: 100%; height: 30px; @@ -59,7 +60,8 @@ padding: 2px 4px; text-align: center; } -#debug .input-slider>span{ +#debug .input-slider>span, +#debug .select>span{ display: block; width: 10%; height: 100%; @@ -70,10 +72,19 @@ line-height: 2em; cursor: pointer; } -#debug .input-slider>span:hover{ +#debug .input-slider>span:hover, +#debug .select>span:hover{ opacity: 1; background: #333; } +#debug .select select{ + width: 90%; + height: 100%; + box-sizing: border-box; + font-size: 18px; + font-family: sans-serif; + padding: 2px 4px; +} #debug label{ display: block; @@ -111,6 +122,7 @@ margin-left: 3px; } -#debug .autoplay-label{ +#debug .autoplay-label, +#debug .branch-hide{ display: none; } diff --git a/public/src/js/canvasdraw.js b/public/src/js/canvasdraw.js index 472730c..8a072a4 100644 --- a/public/src/js/canvasdraw.js +++ b/public/src/js/canvasdraw.js @@ -1167,6 +1167,7 @@ var firstTop = config.multiplayer ? 0 : 30 var secondTop = config.multiplayer ? 0 : 8 + config.percentage = Math.max(0, Math.min(1, config.percentage)) var cleared = config.percentage - 1 / 50 >= config.clear var gaugeW = 14 * 50 diff --git a/public/src/js/circle.js b/public/src/js/circle.js index 6d3c650..261629e 100644 --- a/public/src/js/circle.js +++ b/public/src/js/circle.js @@ -1,6 +1,5 @@ class Circle{ constructor(config){ - // id, ms, type, text, speed, endTime, requiredHits this.id = config.id this.ms = config.start this.originalMS = this.ms @@ -23,38 +22,13 @@ class Circle{ this.gogoChecked = false this.beatMS = config.beatMS this.fixedPos = config.fixedPos - } - getMS(){ - return this.ms - } - getEndTime(){ - return this.endTime - } - getType(){ - return this.type - } - getLastFrame(){ - return this.lastFrame + this.branch = config.branch + this.section = config.section } animate(ms){ this.animating = true this.animT = ms } - isAnimated(){ - return this.animating - } - getAnimT(){ - return this.animT - } - getPlayed(){ - return this.isPlayed - } - isAnimationFinished(){ - return this.animationEnded - } - endAnimation(){ - this.animationEnded = true - } played(score, big){ this.score = score this.isPlayed = score <= 0 ? score - 1 : (big ? 2 : 1) @@ -65,16 +39,4 @@ class Circle{ this.timesKa++ } } - getScore(){ - return this.score - } - getID(){ - return this.id - } - getText(){ - return this.text - } - getSpeed(){ - return this.speed - } } \ No newline at end of file diff --git a/public/src/js/debug.js b/public/src/js/debug.js index 6b03534..607002d 100644 --- a/public/src/js/debug.js +++ b/public/src/js/debug.js @@ -8,15 +8,19 @@ class Debug{ this.debugDiv.innerHTML = assets.pages["debug"] document.body.appendChild(this.debugDiv) - this.titleDiv = this.debugDiv.getElementsByClassName("title")[0] - this.minimiseDiv = this.debugDiv.getElementsByClassName("minimise")[0] - this.offsetDiv = this.debugDiv.getElementsByClassName("offset")[0] - this.measureNumDiv = this.debugDiv.getElementsByClassName("measure-num")[0] - this.restartCheckbox = this.debugDiv.getElementsByClassName("change-restart")[0] - this.autoplayLabel = this.debugDiv.getElementsByClassName("autoplay-label")[0] - this.autoplayCheckbox = this.debugDiv.getElementsByClassName("autoplay")[0] - this.restartBtn = this.debugDiv.getElementsByClassName("restart-btn")[0] - this.exitBtn = this.debugDiv.getElementsByClassName("exit-btn")[0] + this.titleDiv = this.byClass("title") + this.minimiseDiv = this.byClass("minimise") + this.offsetDiv = this.byClass("offset") + this.measureNumDiv = this.byClass("measure-num") + this.branchHideDiv = this.byClass("branch-hide") + this.branchSelectDiv = this.byClass("branch-select") + this.branchSelect = this.branchSelectDiv.getElementsByTagName("select")[0] + this.branchResetBtn = this.branchSelectDiv.getElementsByClassName("reset")[0] + this.restartCheckbox = this.byClass("change-restart") + this.autoplayLabel = this.byClass("autoplay-label") + this.autoplayCheckbox = this.byClass("autoplay") + this.restartBtn = this.byClass("restart-btn") + this.exitBtn = this.byClass("exit-btn") this.moving = false pageEvents.add(window, ["mousedown", "mouseup", "blur"], this.stopMove.bind(this)) @@ -26,6 +30,8 @@ class Debug{ pageEvents.add(this.restartBtn, "click", this.restartSong.bind(this)) pageEvents.add(this.exitBtn, "click", this.clean.bind(this)) pageEvents.add(this.autoplayCheckbox, "change", this.toggleAutoplay.bind(this)) + pageEvents.add(this.branchSelect, "change", this.branchChange.bind(this)) + pageEvents.add(this.branchResetBtn, "click", this.branchReset.bind(this)) this.offsetSlider = new InputSlider(this.offsetDiv, -60, 60, 3) this.offsetSlider.onchange(this.offsetChange.bind(this)) @@ -39,6 +45,9 @@ class Debug{ this.updateStatus() pageEvents.send("debug") } + byClass(name){ + return this.debugDiv.getElementsByClassName(name)[0] + } startMove(event){ if(event.which === 1){ event.stopPropagation() @@ -88,17 +97,23 @@ class Debug{ } updateStatus(){ if(debugObj.controller && !this.controller){ + this.controller = debugObj.controller + this.restartBtn.style.display = "block" this.autoplayLabel.style.display = "block" + if(this.controller.parsedSongData.branches){ + this.branchHideDiv.style.display = "block" + } - this.controller = debugObj.controller var selectedSong = this.controller.selectedSong this.defaultOffset = selectedSong.offset || 0 if(this.songFolder === selectedSong.folder){ this.offsetChange(this.offsetSlider.get(), true) + this.branchChange(null, true) }else{ this.songFolder = selectedSong.folder this.offsetSlider.set(this.defaultOffset) + this.branchReset(null, true) } var measures = this.controller.parsedSongData.measures @@ -128,6 +143,7 @@ class Debug{ if(this.controller && !debugObj.controller){ this.restartBtn.style.display = "" this.autoplayLabel.style.display = "" + this.branchHideDiv.style.display = "" this.controller = null } } @@ -142,6 +158,11 @@ class Debug{ songData.measures.forEach(measure => { measure.ms = measure.originalMS + offset }) + if(songData.branches){ + songData.branches.forEach(branch => { + branch.ms = branch.originalMS + offset + }) + } if(this.restartCheckbox.checked && !noRestart){ this.restartSong() } @@ -171,21 +192,44 @@ class Debug{ } } } + branchChange(event, noRestart){ + if(this.controller){ + var game = this.controller.game + var name = this.branchSelect.value + game.branch = name === "auto" ? false : name + game.branchSet = false + if(this.restartCheckbox.checked && !noRestart){ + this.restartSong() + } + } + } + branchReset(event, noRestart){ + this.branchSelect.value = "auto" + this.branchChange(null, noRestart) + } clean(){ this.offsetSlider.clean() + this.measureNumSlider.clean() pageEvents.remove(window, ["mousedown", "mouseup", "blur"]) pageEvents.mouseRemove(this) + pageEvents.remove(this.titleDiv, "mousedown") pageEvents.remove(this.title, "mousedown") pageEvents.remove(this.minimiseDiv, "click") pageEvents.remove(this.restartBtn, "click") pageEvents.remove(this.exitBtn, "click") pageEvents.remove(this.autoplayCheckbox, "change") + pageEvents.remove(this.branchSelect, "change") + pageEvents.remove(this.branchResetBtn, "click") delete this.titleDiv delete this.minimiseDiv delete this.offsetDiv delete this.measureNumDiv + delete this.branchHideDiv + delete this.branchSelectDiv + delete this.branchSelect + delete this.branchResetBtn delete this.restartCheckbox delete this.autoplayLabel delete this.autoplayCheckbox diff --git a/public/src/js/game.js b/public/src/js/game.js index 76f7006..865b905 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -19,8 +19,8 @@ class Game{ difficulty: this.rules.difficulty } this.HPGain = 100 / this.songData.circles.filter(circle => { - var type = circle.getType() - return type === "don" || type === "ka" || type === "daiDon" || type === "daiKa" + 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 @@ -28,6 +28,8 @@ class Game{ this.musicFadeOut = 0 this.fadeOutStarted = false this.currentTimingPoint = 0 + this.sectionNotes = [] + this.sectionDrumroll = 0 assets.songs.forEach(song => { if(song.id == selectedSong.folder){ @@ -69,17 +71,18 @@ class Game{ } updateCirclesStatus(){ var nextSet = false + var ms = this.elapsedTime var circles = this.songData.circles var startIndex = this.currentCircle === 0 ? 0 : this.currentCircle - 1 - for(var i = startIndex; i < circles.length && i < this.currentCircle + 2; i++){ + var index = 0 + for(var i = startIndex; i < circles.length; i++){ var circle = circles[i] - if(!circle.getPlayed()){ - var ms = this.elapsedTime - var type = circle.getType() + if((!circle.branch || circle.branch.active) && !circle.isPlayed){ + var type = circle.type var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll" - var endTime = circle.getEndTime() + (drumrollNotes ? 0 : this.rules.bad) + var endTime = circle.endTime + (drumrollNotes ? 0 : this.rules.bad) - if(ms >= circle.getMS()){ + if(ms >= circle.ms){ if(drumrollNotes && !circle.rendaPlayed && ms < endTime){ circle.rendaPlayed = true if(this.rules.difficulty === "easy"){ @@ -101,7 +104,7 @@ class Game{ this.updateCurrentCircle() if(this.controller.multiplayer === 1){ var value = { - pace: (ms - circle.getMS()) / circle.timesHit + pace: (ms - circle.ms) / circle.timesHit } if(type === "drumroll" || type === "daiDrumroll"){ value.kaAmount = circle.timesKa / circle.timesHit @@ -111,6 +114,7 @@ class Game{ }else{ var currentScore = 0 circle.played(-1, type === "daiDon" || type === "daiKa") + this.sectionNotes.push(0) this.controller.displayScore(currentScore, true) this.updateCurrentCircle() this.updateCombo(currentScore) @@ -126,6 +130,56 @@ class Game{ nextSet = true this.currentCircle = i } + if(index++ > 1){ + break + } + } + } + var branches = this.songData.branches + if(branches){ + if(this.controller.multiplayer === 2 || this.branch){ + var parent = this.controller.multiplayer === 2 ? p2 : this + var view = this.controller.view + if(view.branch !== parent.branch){ + view.branchAnimate = { + ms: ms, + fromBranch: view.branch + } + view.branch = parent.branch + } + if(!parent.branchSet){ + parent.branchSet = true + for(var i = 0; i < branches.length; i++){ + this.setBranch(branches[i], parent.branch) + } + } + }else{ + var measures = this.songData.measures + 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){ + if(branch.type === "drumroll"){ + var accuracy = this.sectionDrumroll + }else{ + var accuracy = this.sectionNotes.reduce((a, b) => a + b) / this.sectionNotes.length * 100 + } + if(accuracy >= branch.requirement[1]){ + this.setBranch(branch, "master") + }else if(accuracy >= branch.requirement[0]){ + this.setBranch(branch, "advanced") + }else{ + this.setBranch(branch, "normal") + } + }else if(this.controller.multiplayer === 1){ + p2.send("branch", "normal") + } + } + } } } } @@ -160,7 +214,7 @@ class Game{ } } checkKey(keyCodes, circle, check){ - if(circle && !circle.getPlayed()){ + if(circle && !circle.isPlayed){ if(!this.checkScore(circle, check)){ return } @@ -171,7 +225,7 @@ class Game{ } checkScore(circle, check){ var ms = this.elapsedTime - var type = circle.getType() + var type = circle.type var keysDon = check === "don" || check === "daiDon" var keysKa = check === "ka" || check === "daiKa" @@ -182,7 +236,7 @@ class Game{ var keyTime = this.controller.getKeyTime() var currentTime = keysDon ? keyTime["don"] : keyTime["ka"] - var relative = currentTime - circle.getMS() + var relative = currentTime - circle.ms if(typeDon || typeKa){ if(-this.rules.bad >= relative || relative >= this.rules.bad){ @@ -219,10 +273,11 @@ class Game{ this.updateCombo(score) this.updateGlobalScore(score, typeDai && keyDai ? 2 : 1, circle.gogoTime) this.updateCurrentCircle() - if(this.controller.multiplayer == 1){ + this.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0)) + if(this.controller.multiplayer === 1){ var value = { score: score, - ms: circle.getMS() - currentTime, + ms: circle.ms - currentTime, dai: typeDai ? keyDai ? 2 : 1 : 0 } if((!keysDon || !typeDon) && (!keysKa || !typeKa)){ @@ -231,12 +286,12 @@ class Game{ p2.send("note", value) } }else{ - if(circle.getMS() > currentTime || currentTime > circle.getEndTime()){ + if(circle.ms > currentTime || currentTime > circle.endTime){ return true } if(keysDon && type === "balloon"){ this.checkBalloon(circle) - if(check === "daiDon" && !circle.getPlayed()){ + if(check === "daiDon" && !circle.isPlayed){ this.checkBalloon(circle) } }else if((keysDon || keysKa) && (type === "drumroll" || type === "daiDrumroll")){ @@ -256,24 +311,25 @@ class Game{ circle.played(score) if(this.controller.multiplayer == 1){ p2.send("drumroll", { - pace: (this.elapsedTime - circle.getMS()) / circle.timesHit + pace: (this.elapsedTime - circle.ms) / circle.timesHit }) } }else{ var score = 300 circle.hit() } - this.globalScore.drumroll ++ + this.globalScore.drumroll++ + this.sectionDrumroll++ this.globalScore.points += score this.view.setDarkBg(false) } checkDrumroll(circle, keysKa){ var ms = this.elapsedTime - var dai = circle.getType() === "daiDrumroll" + var dai = circle.type === "daiDrumroll" var score = 100 circle.hit(keysKa) var keyTime = this.controller.getKeyTime() - if(circle.getType() === "drumroll"){ + if(circle.type === "drumroll"){ var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka" }else{ var sound = keyTime["don"] > keyTime["ka"] ? "daiDon" : "daiKa" @@ -291,6 +347,9 @@ class Game{ circleAnim.animate(ms) this.view.drumroll.push(circleAnim) this.globalScore.drumroll++ + if(this.controller.multiplayer !== 2){ + this.sectionDrumroll++ + } this.globalScore.points += score * (dai ? 2 : 1) this.view.setDarkBg(false) } @@ -298,11 +357,11 @@ class Game{ var ms = this.elapsedTime if(!this.lastCircle){ var circles = this.songData.circles - this.lastCircle = circles[circles.length - 1].getEndTime() + this.lastCircle = circles[circles.length - 1].endTime if(this.controller.multiplayer){ var syncWith = this.controller.syncWith var syncCircles = syncWith.game.songData.circles - var syncLastCircle = syncCircles[syncCircles.length - 1].getEndTime() + var syncLastCircle = syncCircles[syncCircles.length - 1].endTime if(syncLastCircle > this.lastCircle){ this.lastCircle = syncLastCircle } @@ -410,7 +469,14 @@ class Game{ return this.songData.circles } updateCurrentCircle(){ - this.currentCircle++ + var circles = this.songData.circles + do{ + var circle = circles[++this.currentCircle] + }while(circle && circle.branch && !circle.branch.active) + if(circle && circle.section){ + this.sectionNotes = [] + this.sectionDrumroll = 0 + } } getCurrentCircle(){ return this.currentCircle @@ -464,4 +530,16 @@ class Game{ } this.globalScore.points += Math.floor(score * multiplier / 10) * 10 } + setBranch(branch, name){ + var names = ["normal", "advanced", "master"] + for(var i in names){ + if(names[i] in branch){ + branch[names[i]].active = names[i] === name + } + } + branch.active = name + if(this.controller.multiplayer === 1){ + p2.send("branch", name) + } + } } diff --git a/public/src/js/importsongs.js b/public/src/js/importsongs.js index 400f443..cb907f5 100644 --- a/public/src/js/importsongs.js +++ b/public/src/js/importsongs.js @@ -199,7 +199,7 @@ songObj.subtitle = songObj.subtitle_en = subtitle songObj.preview = meta.demostart ? Math.floor(meta.demostart * 1000) : 0 if(meta.level){ - songObj.stars[this.courseTypes[diff]] = meta.level + songObj.stars[this.courseTypes[diff]] = meta.level + (meta.branch ? " B" : "") } if(meta.wave){ songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] diff --git a/public/src/js/keyboard.js b/public/src/js/keyboard.js index 186c651..5925658 100644 --- a/public/src/js/keyboard.js +++ b/public/src/js/keyboard.js @@ -199,8 +199,8 @@ class Keyboard{ if( sound === "don" && circle - && !circle.getPlayed() - && circle.getType() === "balloon" + && !circle.isPlayed + && circle.type === "balloon" && circle.requiredHits - circle.timesHit <= 1 ){ this.controller.playSound("se_balloon") diff --git a/public/src/js/mekadon.js b/public/src/js/mekadon.js index cca80e2..22f7d73 100644 --- a/public/src/js/mekadon.js +++ b/public/src/js/mekadon.js @@ -6,12 +6,12 @@ class Mekadon{ this.lastHit = -Infinity } play(circle){ - var type = circle.getType() - if((type === "balloon" || type === "drumroll" || type === "daiDrumroll") && this.getMS() > circle.getEndTime()){ + var type = circle.type + if((type === "balloon" || type === "drumroll" || type === "daiDrumroll") && this.getMS() > circle.endTime){ circle.played(-1, false) this.game.updateCurrentCircle() } - type = circle.getType() + type = circle.type if(type === "balloon"){ this.playDrumrollAt(circle, 0, 30) }else if(type === "drumroll" || type === "daiDrumroll"){ @@ -21,7 +21,7 @@ class Mekadon{ } } playAt(circle, ms, score, dai, reverse){ - var currentMs = circle.getMS() - this.getMS() + var currentMs = circle.ms - this.getMS() if(ms > currentMs - 10){ return this.playNow(circle, score, dai, reverse) } @@ -36,18 +36,19 @@ class Mekadon{ } } miss(circle){ - var currentMs = circle.getMS() - this.getMS() + var currentMs = circle.ms - this.getMS() if(0 >= currentMs - 10){ this.controller.displayScore(0, true) this.game.updateCurrentCircle() this.game.updateCombo(0) this.game.updateGlobalScore(0, 1, circle.gogoTime) + this.game.sectionNotes.push(0) return true } } playNow(circle, score, dai, reverse){ var kbd = this.controller.getBindings() - var type = circle.getType() + var type = circle.type var keyDai = false var playDai = !dai || dai === 2 var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll" @@ -55,7 +56,7 @@ class Mekadon{ if(drumrollNotes){ var ms = this.getMS() }else{ - var ms = circle.getMS() + var ms = circle.ms } if(reverse){ @@ -95,6 +96,7 @@ class Mekadon{ this.game.updateGlobalScore(score, keyDai ? 2 : 1, circle.gogoTime) this.game.updateCurrentCircle() circle.played(score, keyDai) + this.game.sectionNotes.push(score === 450 ? 1 : (score === 230 ? 0.5 : 0)) } this.lastHit = ms return true diff --git a/public/src/js/p2.js b/public/src/js/p2.js index 1f6f97e..a0c266d 100644 --- a/public/src/js/p2.js +++ b/public/src/js/p2.js @@ -109,6 +109,7 @@ class P2Connection{ this.dai = 2 this.kaAmount = 0 this.results = false + this.branch = "normal" break case "gameend": this.otherConnected = false @@ -141,6 +142,10 @@ class P2Connection{ this.kaAmount = response.value.kaAmount } break + case "branch": + this.branch = response.value + this.branchSet = false + break case "session": this.clearMessage("users") this.otherConnected = true @@ -161,10 +166,10 @@ class P2Connection{ } play(circle, mekadon){ if(this.otherConnected || this.notes.length > 0){ - var type = circle.getType() + var type = circle.type var drumrollNotes = type === "balloon" || type === "drumroll" || type === "daiDrumroll" - if(drumrollNotes && mekadon.getMS() > circle.getEndTime()){ + if(drumrollNotes && mekadon.getMS() > circle.endTime){ circle.played(-1, false) mekadon.game.updateCurrentCircle() } @@ -177,7 +182,7 @@ class P2Connection{ var note = this.notes[0] if(note.score >= 0){ var dai = 1 - if(circle.getType() === "daiDon" || circle.getType() === "daiKa"){ + if(circle.type === "daiDon" || circle.type === "daiKa"){ dai = this.dai } if(mekadon.playAt(circle, note.ms, note.score, dai, note.reverse)){ diff --git a/public/src/js/parsetja.js b/public/src/js/parsetja.js index 0055b99..262c0b9 100644 --- a/public/src/js/parsetja.js +++ b/public/src/js/parsetja.js @@ -59,7 +59,9 @@ if(!(courseName in courses)){ courses[courseName] = {} } - courses[courseName][name] = currentCourse[name] + if(name !== "branch"){ + courses[courseName][name] = currentCourse[name] + } } courses[courseName].start = lineNum + 1 courses[courseName].end = this.data.length @@ -70,6 +72,8 @@ hasSong = true courses[courseName].end = lineNum } + }else if(name.startsWith("branchstart") && inSong){ + courses[courseName].branch = true } }else if(!inSong){ @@ -128,9 +132,13 @@ var balloons = meta.balloon || [] var lastDrumroll = false + var branch = false - var branchType - var branchPreference = "m" + var branchObj = {} + var currentBranch = false + var branchSettings = {} + var branchPushed = false + var sectionBegin = true var currentMeasure = [] var firstNote = true @@ -138,19 +146,19 @@ var circleID = 0 var pushMeasure = () => { - if(barLine){ - var note = currentMeasure[0] - if(note){ - var speed = note.bpm * note.scroll / 60 - }else{ - var speed = bpm * scroll / 60 - } - this.measures.push({ - ms: ms, - originalMS: ms, - speed: speed - }) + var note = currentMeasure[0] + if(note){ + var speed = note.bpm * note.scroll / 60 + }else{ + var speed = bpm * scroll / 60 } + this.measures.push({ + ms: ms, + originalMS: ms, + speed: speed, + visible: barLine, + branch: currentBranch + }) if(currentMeasure.length){ for(var i = 0; i < currentMeasure.length; i++){ var note = currentMeasure[i] @@ -182,7 +190,9 @@ gogoTime: note.gogo, endTime: note.endTime, requiredHits: note.requiredHits, - beatMS: 60000 / note.bpm + beatMS: 60000 / note.bpm, + branch: currentBranch, + section: note.section }) if(lastDrumroll === note){ lastDrumroll = circleObj @@ -204,59 +214,112 @@ var line = line.slice(1).toLowerCase() var [name, value] = this.split(line, " ") - if(!branch || branch && branchType === branchPreference){ - switch(name){ - case "gogostart": - gogo = true - break - case "gogoend": - gogo = false - break - case "bpmchange": - bpm = parseFloat(value) - break - case "scroll": - scroll = parseFloat(value) - break - case "measure": - var [numerator, denominator] = value.split("/") - measure = numerator / denominator * 4 - break - case "delay": - ms += (parseFloat(value) || 0) * 1000 - break - case "barlineon": - barLine = true - break - case "barlineoff": - barLine = false - break - } - } switch(name){ + case "gogostart": + gogo = true + break + case "gogoend": + gogo = false + break + case "bpmchange": + bpm = parseFloat(value) + break + case "scroll": + scroll = parseFloat(value) + break + case "measure": + var [numerator, denominator] = value.split("/") + measure = numerator / denominator * 4 + break + case "delay": + ms += (parseFloat(value) || 0) * 1000 + break + case "barlineon": + barLine = true + break + case "barlineoff": + barLine = false + break case "branchstart": branch = true - branchType = "" + currentBranch = false + branchPushed = false + branchSettings = { + ms: ms, + gogo: gogo, + bpm: bpm, + scroll: scroll, + sectionBegin: sectionBegin + } value = value.split(",") - var forkType = value[0].toLowerCase() - if(forkType === "r" || parseFloat(value[2]) <= 100){ - branchPreference = "m" - }else if(parseFloat(value[1]) <= 100){ - branchPreference = "e" - }else{ - branchPreference = "n" + if(!this.branches){ + this.branches = [] + } + branchObj = { + ms: ms, + originalMS: ms, + type: value[0].toLowerCase() === "r" ? "drumroll" : "perfect", + requirement: [ + parseFloat(value[1]), + parseFloat(value[2]) + ] } break case "branchend": - case "section": branch = false + currentBranch = false + if(this.measures.length !== 0){ + this.measures[this.measures.length - 1].nextBranch = { + ms: ms, + originalMS: ms, + active: "normal" + } + } + break + case "section": + sectionBegin = true + if(branch && !currentBranch){ + branchSettings.sectionBegin = true + } break case "n": case "e": case "m": - branchType = name + if(!branchPushed){ + branchPushed = true + this.branches.push(branchObj) + if(this.measures.length === 1 && branchObj.type === "drumroll"){ + for(var i = circles.length; i--;){ + var circle = circles[i] + if(circle.endTime && circle.type === "drumroll" || circle.type === "daiDrumroll" || circle.type === "balloon"){ + this.measures.push({ + ms: circle.endTime, + originalMS: circle.endTime, + speed: circle.bpm * circle.scroll / 60, + visible: false, + branch: circle.branch + }) + break + } + } + } + if(this.measures.length !== 0){ + this.measures[this.measures.length - 1].nextBranch = branchObj + } + } + ms = branchSettings.ms + gogo = branchSettings.gogo + bpm = branchSettings.bpm + scroll = branchSettings.scroll + sectionBegin = branchSettings.sectionBegin + var branchName = name === "m" ? "master" : (name === "e" ? "advanced" : "normal") + currentBranch = { + name: branchName, + active: branchName === "normal" + } + branchObj[branchName] = currentBranch break } - }else if(!branch || branch && branchType === branchPreference){ + }else{ var string = line.split("") @@ -278,8 +341,10 @@ txt: type.txt, gogo: gogo, bpm: bpm, - scroll: scroll + scroll: scroll, + section: sectionBegin } + sectionBegin = false if(lastDrumroll){ circleObj.endDrumroll = lastDrumroll lastDrumroll = false @@ -293,15 +358,19 @@ txt: type.txt, gogo: gogo, bpm: bpm, - scroll: scroll + scroll: scroll, + section: sectionBegin } + sectionBegin = false if(lastDrumroll){ if(symbol === "9"){ currentMeasure.push({ endDrumroll: lastDrumroll, bpm: bpm, - scroll: scroll + scroll: scroll, + section: sectionBegin }) + sectionBegin = false lastDrumroll = false }else{ currentMeasure.push({ @@ -327,8 +396,10 @@ currentMeasure.push({ endDrumroll: lastDrumroll, bpm: bpm, - scroll: scroll + scroll: scroll, + section: sectionBegin }) + sectionBegin = false lastDrumroll = false }else{ currentMeasure.push({ @@ -359,6 +430,10 @@ lastDrumroll.originalEndTime = ms } + if(this.branches){ + circles.sort((a, b) => a.ms > b.ms ? 1 : -1) + circles.forEach((circle, i) => circle.id = i + 1) + } return circles } } diff --git a/public/src/js/songselect.js b/public/src/js/songselect.js index 6dbe579..114a276 100644 --- a/public/src/js/songselect.js +++ b/public/src/js/songselect.js @@ -351,7 +351,7 @@ class SongSelect{ down: code == 40 // Down } - if(key.cancel && event){ + if(event && (code == 27 || code == 8)){ event.preventDefault() } if(this.state.screen === "song"){ @@ -1322,27 +1322,47 @@ class SongSelect{ outlineSize: currentUra ? this.songAsset.letterBorder : 0 }) }) - var songStars = currentUra ? currentSong.stars[4] : currentSong.stars[i] - for(var j = 0; j < 10; j++){ - if(songSel){ - var yPos = _y + 113 + j * 17 - }else{ - var yPos = _y + 178 + j * 19.5 - } - if(10 - j > songStars){ - ctx.fillStyle = currentUra ? "#187085" : (songSel ? "#e97526" : "#e7e7e7") - ctx.beginPath() - ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2) - ctx.fill() - }else{ - this.draw.diffStar({ - ctx: ctx, - songSel: songSel, - ura: currentUra, - x: _x, - y: yPos, - ratio: ratio - }) + var songStarsArray = (currentUra ? currentSong.stars[4] : currentSong.stars[i]).toString().split(" ") + var songStars = songStarsArray[0] + var songBranch = songStarsArray[1] === "B" + var elapsedMS = this.state.screenMS > this.state.moveMS ? this.state.screenMS : this.state.moveMS + var fade = ((ms - elapsedMS) % 2000) / 2000 + if(songBranch && fade > 0.25 && fade < 0.75){ + this.draw.verticalText({ + ctx: ctx, + text: strings.songBranch, + x: _x, + y: _y + (songSel ? 110 : 185), + width: songSel ? 44 : 56, + height: songSel ? 160 : 170, + fill: songSel && !currentUra ? "#c85200" : "#fff", + fontSize: songSel ? 25 : 27, + fontFamily: songSel ? "Meiryo, Microsoft YaHei, sans-serif" : this.font, + outline: songSel ? false : "#f22666", + outlineSize: songSel ? 0 : this.songAsset.letterBorder + }) + }else{ + for(var j = 0; j < 10; j++){ + if(songSel){ + var yPos = _y + 113 + j * 17 + }else{ + var yPos = _y + 178 + j * 19.5 + } + if(10 - j > songStars){ + ctx.fillStyle = currentUra ? "#187085" : (songSel ? "#e97526" : "#e7e7e7") + ctx.beginPath() + ctx.arc(_x, yPos, songSel ? 4.5 : 5, 0, Math.PI * 2) + ctx.fill() + }else{ + this.draw.diffStar({ + ctx: ctx, + songSel: songSel, + ura: currentUra, + x: _x, + y: yPos, + ratio: ratio + }) + } } } var currentDiff = this.selectedDiff - this.diffOptions.length diff --git a/public/src/js/soundbuffer.js b/public/src/js/soundbuffer.js index 0262a6b..f220863 100644 --- a/public/src/js/soundbuffer.js +++ b/public/src/js/soundbuffer.js @@ -86,7 +86,7 @@ class SoundGain{ this.volume = amount } setCrossfade(amount){ - this.setVolume(Math.pow(Math.sin(Math.PI / 2 * amount), 1 / 4)) + this.setVolume(Math.sqrt(Math.sin(Math.PI / 2 * amount))) } fadeIn(duration, time, absolute){ this.fadeVolume(0, this.volume * this.volume, duration, time, absolute) diff --git a/public/src/js/strings.js b/public/src/js/strings.js index 967d0a1..7c6b029 100644 --- a/public/src/js/strings.js +++ b/public/src/js/strings.js @@ -34,6 +34,7 @@ this.normal = "ふつう" this.hard = "むずかしい" this.oni = "おに" + this.songBranch = "譜面分岐あり" this.sessionStart = "オンラインセッションを開始する!" this.sessionEnd = "オンラインセッションを終了する" this.loading = "ロード中..." @@ -135,6 +136,7 @@ function StringsEn(){ this.normal = "Normal" this.hard = "Hard" this.oni = "Extreme" + this.songBranch = "Forked Paths" this.sessionStart = "Begin an Online Session!" this.sessionEnd = "End Online Session" this.loading = "Loading..." @@ -236,6 +238,7 @@ function StringsCn(){ this.normal = "普通" this.hard = "困难" this.oni = "魔王" + this.songBranch = "有分数分支" this.sessionStart = "开始在线会话!" this.sessionEnd = "结束在线会话" this.loading = "加载中..." @@ -337,6 +340,7 @@ function StringsTw(){ this.normal = "普通" this.hard = "困難" this.oni = "魔王" + this.songBranch = "有分數分支" this.sessionStart = "開始多人模式!" this.sessionEnd = "結束多人模式" this.loading = "讀取中..." @@ -438,6 +442,7 @@ function StringsKo(){ this.normal = "보통" this.hard = "어려움" this.oni = "귀신" + this.songBranch = "악보 분기 있습니다" this.sessionStart = "온라인 세션 시작!" this.sessionEnd = "온라인 세션 끝내기" this.loading = "로딩 중..." diff --git a/public/src/js/titlescreen.js b/public/src/js/titlescreen.js index 642aab9..9f95cdc 100644 --- a/public/src/js/titlescreen.js +++ b/public/src/js/titlescreen.js @@ -18,7 +18,11 @@ class Titlescreen{ this.setLang(allStrings[this.lang], true) if(songId){ - this.goNext() + if(localStorage.getItem("tutorial") === "true"){ + new SongSelect(false, false, this.touched, this.songId) + }else{ + new Tutorial(false, this.songId) + } }else{ this.addLangs() diff --git a/public/src/js/view.js b/public/src/js/view.js index fa0c534..f3f8383 100644 --- a/public/src/js/view.js +++ b/public/src/js/view.js @@ -75,6 +75,18 @@ this.gogoTime = 0 this.drumroll = [] this.touchEvents = 0 + if(this.controller.parsedSongData.branches){ + this.branch = "normal" + this.branchAnimate = { + ms: -Infinity, + fromBranch: "normal" + } + this.branchMap = { + "normal": "rgba(0, 0, 0, 0)", + "advanced": "rgba(29, 129, 189, 0.4)", + "master": "rgba(230, 29, 189, 0.4)" + } + } this.beatInterval = this.controller.parsedSongData.beatInfo.beatInterval this.font = strings.font @@ -693,6 +705,18 @@ } ctx.fillRect(padding, barY, winW - padding, barH) } + if(this.branchAnimate && ms <= this.branchAnimate.ms + 300){ + var alpha = Math.max(0, (ms - this.branchAnimate.ms) / 300) + ctx.globalAlpha = 1 - alpha + ctx.fillStyle = this.branchMap[this.branchAnimate.fromBranch] + ctx.fillRect(padding, barY, winW - padding, barH) + ctx.globalAlpha = alpha + } + if(this.branch){ + ctx.fillStyle = this.branchMap[this.branch] + ctx.fillRect(padding, barY, winW - padding, barH) + ctx.globalAlpha = 1 + } if(keyTime[sound] > ms - 130){ var gradients = { "don": "255, 0, 0", @@ -1102,7 +1126,7 @@ 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) - if(ms >= startingTime && ms <= finishTime){ + if(measure.visible && (!measure.branch || measure.branch.active) && ms >= startingTime && ms <= finishTime){ var measureX = this.slotPos.x + this.msToPos(measure.ms - ms, measure.speed) this.ctx.strokeStyle = "#bdbdbd" this.ctx.lineWidth = 3 @@ -1111,6 +1135,14 @@ this.ctx.lineTo(measureX, measureY + measureH) this.ctx.stroke() } + if(this.multiplayer !== 2 && ms >= measure.ms && measure.nextBranch && !measure.viewChecked && measure.gameChecked){ + measure.viewChecked = true + this.branchAnimate = { + ms: ms, + fromBranch: this.branch + } + this.branch = measure.nextBranch.active + } }) } updateNoteFaces(){ @@ -1137,17 +1169,17 @@ for(var i = circles.length; i--;){ var circle = circles[i] - var speed = circle.getSpeed() + var speed = circle.speed var timeForDistance = this.posToMs(distanceForCircle + this.slotPos.size / 2, speed) - var startingTime = circle.getMS() - timeForDistance - var finishTime = circle.getEndTime() + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + 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) - if(circle.getPlayed() <= 0 || circle.getScore() === 0){ - if(ms >= startingTime && ms <= finishTime && circle.getPlayed() !== -1){ + if(circle.isPlayed <= 0 || circle.score === 0){ + if((!circle.branch || circle.branch.active) && ms >= startingTime && ms <= finishTime && circle.isPlayed !== -1){ this.drawCircle(circle) } - }else if(!circle.isAnimated()){ + }else if(!circle.animating){ // Start animation to gauge circle.animate(ms) } @@ -1165,9 +1197,9 @@ for(var i = 0; i < circles.length; i++){ var circle = circles[i] - if(circle.isAnimated()){ + if(circle.animating){ - var animT = circle.getAnimT() + var animT = circle.animT if(ms < animT + 490){ if(circle.fixedPos){ @@ -1183,7 +1215,7 @@ var pos = this.animateBezier[3] this.drawCircle(circle, pos, (ms - animT - 490) / 160) }else{ - circle.endAnimation() + circle.animationEnded = true } } } @@ -1211,13 +1243,13 @@ var lyricsSize = 20 * mul var fill, size, faceID - var type = circle.getType() + var type = circle.type var ms = this.getMS() - var circleMs = circle.getMS() - var endTime = circle.getEndTime() - var animated = circle.isAnimated() - var speed = circle.getSpeed() - var played = circle.getPlayed() + var circleMs = circle.ms + var endTime = circle.endTime + var animated = circle.animating + var speed = circle.speed + var played = circle.isPlayed var drumroll = 0 var endX = 0 @@ -1323,9 +1355,9 @@ ctx.fill() ctx.globalAlpha = 1 } - if(!circle.isAnimated()){ + if(!circle.animating){ // Text - var text = circle.getText() + var text = circle.text var textX = circlePos.x var textY = circlePos.y + 83 * mul ctx.font = lyricsSize + "px Kozuka, Microsoft YaHei, sans-serif" @@ -1466,7 +1498,7 @@ if(this.gogoTime){ var circles = this.controller.parsedSongData.circles var lastCircle = circles[circles.length - 1] - var endTime = lastCircle.getEndTime() + 3000 + var endTime = lastCircle.endTime + 3000 if(ms >= endTime){ this.toggleGogoTime({ gogoTime: 0, diff --git a/public/src/views/debug.html b/public/src/views/debug.html index eba64ac..b681717 100644 --- a/public/src/views/debug.html +++ b/public/src/views/debug.html @@ -9,6 +9,17 @@
x-+
+
+
Branch:
+
+ x +
+
diff --git a/server.py b/server.py index b4b65ca..aafcad4 100644 --- a/server.py +++ b/server.py @@ -185,6 +185,7 @@ async def connection(ws, path): if "other_user" in user and "ws" in user["other_user"]: if type == "note"\ or type == "drumroll"\ + or type == "branch"\ or type == "gameresults": await user["other_user"]["ws"].send(msgobj(type, value)) elif type == "songsel" and user["session"]: