diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index bc1d4fb30..7f3c137dc 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1014,8 +1014,9 @@ class PlayState extends MusicBeatState // super.stepHit() returns false if a module cancelled the event. if (!super.stepHit()) return false; - if (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200 - || Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200) + if (FlxG.sound.music != null + && (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200 + || Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200)) { trace("VOCALS NEED RESYNC"); if (vocals != null) trace(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)); @@ -1473,7 +1474,6 @@ class PlayState extends MusicBeatState // } // Reset the notes on each strumline. - var noteData:Array = currentChart.notes; var playerNoteData:Array = []; var opponentNoteData:Array = []; @@ -1698,6 +1698,9 @@ class PlayState extends MusicBeatState onNoteMiss(note); } } + + // Process hold notes on the player's side. + // This handles scoring so we don't need it on the opponent's side. } /** @@ -1737,6 +1740,8 @@ class PlayState extends MusicBeatState { var input:PreciseInputEvent = inputPressQueue.shift(); + playerStrumline.pressKey(input.noteDirection); + var notesInDirection:Array = notesByDirection[input.noteDirection]; if (canMiss && notesInDirection.length == 0) @@ -1777,6 +1782,8 @@ class PlayState extends MusicBeatState // Play the strumline animation. playerStrumline.playStatic(input.noteDirection); + + playerStrumline.releaseKey(input.noteDirection); } } @@ -2004,10 +2011,6 @@ class PlayState extends MusicBeatState note.active = false; note.visible = false; - - // Kill the note. - // NOTE: This is what handles recycling the note graphic. - playerStrumline.killNote(note); } /** diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 3cd503b3b..2408025ce 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -53,6 +53,8 @@ class Strumline extends FlxSpriteGroup var noteData:Array = []; var nextNoteIndex:Int = -1; + var heldKeys:Array = []; + public function new(isPlayer:Bool) { super(); @@ -60,19 +62,23 @@ class Strumline extends FlxSpriteGroup this.isPlayer = isPlayer; this.strumlineNotes = new FlxTypedSpriteGroup(); + this.strumlineNotes.zIndex = 10; this.add(this.strumlineNotes); // Hold notes are added first so they render behind regular notes. this.holdNotes = new FlxTypedSpriteGroup(); + this.holdNotes.zIndex = 20; this.add(this.holdNotes); this.notes = new FlxTypedSpriteGroup(); + this.notes.zIndex = 30; this.add(this.notes); this.noteSplashes = new FlxTypedSpriteGroup(0, 0, NOTE_SPLASH_CAP); + this.noteSplashes.zIndex = 40; this.add(this.noteSplashes); - for (i in 0...DIRECTIONS.length) + for (i in 0...KEY_COUNT) { var child:StrumlineNote = new StrumlineNote(isPlayer, DIRECTIONS[i]); child.x = getXPos(DIRECTIONS[i]); @@ -81,13 +87,18 @@ class Strumline extends FlxSpriteGroup this.strumlineNotes.add(child); } + for (i in 0...KEY_COUNT) + { + heldKeys.push(false); + } + // This MUST be true for children to update! this.active = true; } override function get_width():Float { - return 4 * Strumline.NOTE_SPACING; + return KEY_COUNT * Strumline.NOTE_SPACING; } public override function update(elapsed:Float):Void @@ -183,11 +194,11 @@ class Strumline extends FlxSpriteGroup if (note == null) continue; if (note.time > renderWindowStart) break; - buildNoteSprite(note); + var noteSprite = buildNoteSprite(note); if (note.length > 0) { - buildHoldNoteSprite(note); + noteSprite.holdNoteSprite = buildHoldNoteSprite(note); } nextNoteIndex++; // Increment the nextNoteIndex rather than splicing the array, because splicing is slow. @@ -198,7 +209,8 @@ class Strumline extends FlxSpriteGroup { if (note == null || note.hasBeenHit) continue; - note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime); + var vwoosh:Bool = note.holdNoteSprite == null; + note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime, vwoosh); // Check if the note is outside the hit window, and if so, mark it as missed. // TODO: Check to make sure this doesn't happen when the note is on screen because it'll probably get deleted. @@ -221,6 +233,17 @@ class Strumline extends FlxSpriteGroup { if (holdNote == null || !holdNote.alive) continue; + if (Conductor.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote) + { + if (isPlayer && !isKeyHeld(holdNote.noteDirection)) + { + // Stopped pressing the hold note. + playStatic(holdNote.noteDirection); + holdNote.missedNote = true; + holdNote.alpha = 0.6; + } + } + var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Conductor.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8; if (holdNote.missedNote && Conductor.songPosition >= renderWindowEnd) @@ -241,6 +264,28 @@ class Strumline extends FlxSpriteGroup // TODO: Better handle the weird edge case where the hold note is almost completed. holdNote.visible = false; } + else if (holdNote.missedNote && (holdNote.fullSustainLength > holdNote.sustainLength)) + { + // Hold note was dropped before completing, keep it in its clipped state. + holdNote.visible = true; + + var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Conductor.PIXELS_PER_MS; + + trace('yOffset: ' + yOffset); + trace('holdNote.fullSustainLength: ' + holdNote.fullSustainLength); + trace('holdNote.sustainLength: ' + holdNote.sustainLength); + + var vwoosh:Bool = false; + + if (PreferencesMenu.getPref('downscroll')) + { + holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2; + } + else + { + holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + yOffset + STRUMLINE_SIZE / 2; + } + } else if (Conductor.songPosition > holdNote.strumTime && holdNote.hitNote) { // Hold note is currently being hit, clip it off. @@ -258,38 +303,19 @@ class Strumline extends FlxSpriteGroup holdNote.y = this.y - INITIAL_OFFSET + STRUMLINE_SIZE / 2; } } - else if (holdNote.missedNote && (holdNote.fullSustainLength > holdNote.sustainLength)) - { - // Hold note was dropped before completing, keep it in its clipped state. - holdNote.visible = true; - - var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Conductor.PIXELS_PER_MS; - - trace('yOffset: ' + yOffset); - trace('holdNote.fullSustainLength: ' + holdNote.fullSustainLength); - trace('holdNote.sustainLength: ' + holdNote.sustainLength); - - if (PreferencesMenu.getPref('downscroll')) - { - holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime) - holdNote.height + STRUMLINE_SIZE / 2; - } - else - { - holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime) + yOffset + STRUMLINE_SIZE / 2; - } - } else { // Hold note is new, render it normally. holdNote.visible = true; + var vwoosh:Bool = false; if (PreferencesMenu.getPref('downscroll')) { - holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, false) - holdNote.height + STRUMLINE_SIZE / 2; + holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2; } else { - holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, false) + STRUMLINE_SIZE / 2; + holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + STRUMLINE_SIZE / 2; } } } @@ -302,6 +328,21 @@ class Strumline extends FlxSpriteGroup if (holdNotes.members.length > 1) holdNotes.members.insertionSort(compareHoldNoteSprites.bind(FlxSort.ASCENDING)); } + public function pressKey(dir:NoteDirection):Void + { + heldKeys[dir] = true; + } + + public function releaseKey(dir:NoteDirection):Void + { + heldKeys[dir] = false; + } + + public function isKeyHeld(dir:NoteDirection):Bool + { + return heldKeys[dir]; + } + public function applyNoteData(data:Array):Void { this.notes.clear(); @@ -395,7 +436,7 @@ class Strumline extends FlxSpriteGroup } } - public function buildNoteSprite(note:SongNoteData):Void + public function buildNoteSprite(note:SongNoteData):NoteSprite { var noteSprite:NoteSprite = constructNoteSprite(); @@ -411,9 +452,11 @@ class Strumline extends FlxSpriteGroup // noteSprite.x += INITIAL_OFFSET; noteSprite.y = -9999; } + + return noteSprite; } - public function buildHoldNoteSprite(note:SongNoteData):Void + public function buildHoldNoteSprite(note:SongNoteData):SustainTrail { var holdNoteSprite:SustainTrail = constructHoldNoteSprite(); @@ -427,13 +470,16 @@ class Strumline extends FlxSpriteGroup holdNoteSprite.missedNote = false; holdNoteSprite.hitNote = false; + holdNoteSprite.alpha = 1.0; + holdNoteSprite.x = this.x; holdNoteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]); - // holdNoteSprite.x += INITIAL_OFFSET; holdNoteSprite.x += STRUMLINE_SIZE / 2; holdNoteSprite.x -= holdNoteSprite.width / 2; holdNoteSprite.y = -9999; } + + return holdNoteSprite; } /** diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index b42c8e7c4..4cbf1ade3 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -283,8 +283,9 @@ class SongDifficulty return timeChanges[0].bpm; } - public function getPlayableChar(id:String):SongPlayableChar + public function getPlayableChar(id:String):Null { + if (id == null || id == '') return null; return chars.get(id); } @@ -300,9 +301,10 @@ class SongDifficulty public inline function cacheInst(?currentPlayerId:String = null):Void { - if (currentPlayerId != null) + var currentPlayer:Null = getPlayableChar(currentPlayerId); + if (currentPlayer != null) { - FlxG.sound.cache(Paths.inst(this.song.songId, getPlayableChar(currentPlayerId).inst)); + FlxG.sound.cache(Paths.inst(this.song.songId, currentPlayer.inst)); } else {