From 796a51325f92d303c281386056899173ffa50cd8 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sat, 8 Jul 2023 01:03:46 -0400 Subject: [PATCH] Hold note covers are in and properly positioned --- source/funkin/MusicBeatState.hx | 5 ++ source/funkin/play/PlayState.hx | 83 ++++++++++++------- source/funkin/play/character/BaseCharacter.hx | 11 ++- source/funkin/play/notes/NoteHoldCover.hx | 41 ++++++--- source/funkin/play/notes/NoteSprite.hx | 20 ++--- source/funkin/play/notes/Strumline.hx | 48 +++++++++-- source/funkin/play/notes/StrumlineNote.hx | 73 ++++++++++------ source/funkin/util/Constants.hx | 2 +- 8 files changed, 193 insertions(+), 90 deletions(-) diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 4b86d801c..9aad66773 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -60,6 +60,11 @@ class MusicBeatState extends FlxUIState implements IEventHandler { super.update(elapsed); + // Rebindable volume keys. + if (controls.VOLUME_MUTE) FlxG.sound.toggleMuted(); + else if (controls.VOLUME_UP) FlxG.sound.changeVolume(0.1); + else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1); + // Emergency exit button. if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState()); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 65a750e66..7d5dc48b9 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1367,13 +1367,14 @@ class PlayState extends MusicBeatState add(playerStrumline); add(opponentStrumline); - // Position the player strumline on the right - playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; + // Position the player strumline on the right half of the screen + playerStrumline.x = FlxG.width / 2 + Constants.STRUMLINE_X_OFFSET; // Classic style + // playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; // Centered style playerStrumline.y = PreferencesMenu.getPref('downscroll') ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET; playerStrumline.zIndex = 200; playerStrumline.cameras = [camHUD]; - // Position the opponent strumline on the left + // Position the opponent strumline on the left half of the screen opponentStrumline.x = Constants.STRUMLINE_X_OFFSET; opponentStrumline.y = PreferencesMenu.getPref('downscroll') ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET; opponentStrumline.zIndex = 100; @@ -1642,13 +1643,18 @@ class PlayState extends MusicBeatState if (Conductor.songPosition > hitWindowEnd) { + if (note.hasMissed) continue; + note.tooEarly = false; note.mayHit = false; note.hasMissed = true; + if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true; } else if (Conductor.songPosition > hitWindowCenter) { + if (note.hasBeenHit) continue; + // Call an event to allow canceling the note hit. // NOTE: This is what handles the character animations! var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, 0, true); @@ -1668,6 +1674,8 @@ class PlayState extends MusicBeatState } else if (Conductor.songPosition > hitWindowStart) { + if (note.hasBeenHit || note.hasMissed) continue; + note.tooEarly = false; note.mayHit = true; note.hasMissed = false; @@ -1682,6 +1690,25 @@ class PlayState extends MusicBeatState } } + // Process hold notes on the opponent's side. + for (holdNote in opponentStrumline.holdNotes.members) + { + if (holdNote == null || !holdNote.alive) continue; + + // While the hold note is being hit, and there is length on the hold note... + if (holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0) + { + // Make sure the opponent keeps singing while the note is held. + if (currentStage != null && currentStage.getDad() != null && currentStage.getDad().isSinging()) + { + currentStage.getDad().holdTimer = 0; + } + } + + // TODO: Potential penalty for dropping a hold note? + // if (holdNote.missedNote && !holdNote.handledMiss) { holdNote.handledMiss = true; } + } + // Process notes on the player's side. for (note in playerStrumline.notes.members) { @@ -1743,11 +1770,11 @@ class PlayState extends MusicBeatState if (holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0) { // Grant the player health. - trace(holdNote); - trace(holdNote.noteData); - trace(holdNote.sustainLength); health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed; } + + // TODO: Potential penalty for dropping a hold note? + // if (holdNote.missedNote && !holdNote.handledMiss) { holdNote.handledMiss = true; } } } @@ -1821,6 +1848,7 @@ class PlayState extends MusicBeatState targetNote.visible = false; targetNote.kill(); + notesInDirection.remove(targetNote); // Play the strumline animation. playerStrumline.playConfirm(input.noteDirection); @@ -1942,33 +1970,30 @@ class PlayState extends MusicBeatState function goodNoteHit(note:NoteSprite, input:PreciseInputEvent):Void { - if (!note.hasBeenHit) + var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, Highscore.tallies.combo + 1, true); + dispatchEvent(event); + + // Calling event.cancelEvent() skips all the other logic! Neat! + if (event.eventCanceled) return; + + if (!note.isHoldNote) { - var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, Highscore.tallies.combo + 1, true); - dispatchEvent(event); + Highscore.tallies.combo++; + Highscore.tallies.totalNotesHit++; - // Calling event.cancelEvent() skips all the other logic! Neat! - if (event.eventCanceled) return; + if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo; - if (!note.isHoldNote) - { - Highscore.tallies.combo++; - Highscore.tallies.totalNotesHit++; - - if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo; - - popUpScore(note, input); - } - - playerStrumline.hitNote(note); - - if (note.holdNoteSprite != null) - { - playerStrumline.playNoteHoldCover(note.holdNoteSprite); - } - - vocals.playerVolume = 1; + popUpScore(note, input); } + + playerStrumline.hitNote(note); + + if (note.holdNoteSprite != null) + { + playerStrumline.playNoteHoldCover(note.holdNoteSprite); + } + + vocals.playerVolume = 1; } /** diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index b27a46a0f..fa0a502fb 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -359,7 +359,7 @@ class BaseCharacter extends Bopper } // Handle character note hold time. - if (getCurrentAnimation().startsWith('sing')) + if (isSinging()) { // TODO: Rework this code (and all character animations ugh) // such that the hold time is handled by padding frames, @@ -405,6 +405,11 @@ class BaseCharacter extends Bopper } } + public function isSinging():Bool + { + return getCurrentAnimation().startsWith('sing'); + } + override function dance(force:Bool = false):Void { // Prevent default dancing behavior. @@ -412,13 +417,13 @@ class BaseCharacter extends Bopper if (!force) { - if (getCurrentAnimation().startsWith('sing')) return; + if (isSinging()) return; if (['hey', 'cheer'].contains(getCurrentAnimation()) && !isAnimationFinished()) return; } // Prevent dancing while another animation is playing. - if (!force && getCurrentAnimation().startsWith('sing')) return; + if (!force && isSinging()) return; // Otherwise, fallback to the super dance() method, which handles playing the idle animation. super.dance(); diff --git a/source/funkin/play/notes/NoteHoldCover.hx b/source/funkin/play/notes/NoteHoldCover.hx index b68de3946..a2041fb83 100644 --- a/source/funkin/play/notes/NoteHoldCover.hx +++ b/source/funkin/play/notes/NoteHoldCover.hx @@ -47,7 +47,7 @@ class NoteHoldCover extends FlxTypedSpriteGroup glow.animation.finishCallback = this.onAnimationFinished; - if (glow.animation.getAnimationList().length < 2) + if (glow.animation.getAnimationList().length < 3) { trace('WARNING: NoteHoldCover failed to initialize all animations.'); } @@ -56,35 +56,54 @@ class NoteHoldCover extends FlxTypedSpriteGroup public override function update(elapsed):Void { super.update(elapsed); - if (!holdNote.alive && !glow.animation.curAnim.name.startsWith('holdCoverEnd')) + if ((!holdNote.alive || holdNote.missedNote) && !glow.animation.curAnim.name.startsWith('holdCoverEnd')) { - this.visible = false; - this.kill(); - } - else - { - this.visible = true; + // If alive is false, the hold note was held to completion. + // If missedNote is true, the hold note was "dropped". + + playEnd(); } } public function playStart():Void { - // glow.animation.play('holdCoverStart${noteDirection.colorName.toTitleCase()}');\ + // glow.animation.play('holdCoverStart${noteDirection.colorName.toTitleCase()}'); glow.animation.play('holdCoverStartRed'); } public function playContinue():Void { - // glow.animation.play('holdCover${noteDirection.colorName.toTitleCase()}');\ + // glow.animation.play('holdCover${noteDirection.colorName.toTitleCase()}'); glow.animation.play('holdCoverRed'); } public function playEnd():Void { - // glow.animation.play('holdCoverEnd${noteDirection.colorName.toTitleCase()}');\ + // glow.animation.play('holdCoverEnd${noteDirection.colorName.toTitleCase()}'); glow.animation.play('holdCoverEndRed'); } + public override function kill():Void + { + super.kill(); + + this.visible = false; + + if (glow != null) glow.visible = false; + if (sparks != null) sparks.visible = false; + } + + public override function revive():Void + { + super.revive(); + + this.visible = true; + this.alpha = 1.0; + + if (glow != null) glow.visible = true; + if (sparks != null) sparks.visible = true; + } + public function onAnimationFinished(animationName:String):Void { if (animationName.startsWith('holdCoverStart')) diff --git a/source/funkin/play/notes/NoteSprite.hx b/source/funkin/play/notes/NoteSprite.hx index 8955f9d42..b407e7f74 100644 --- a/source/funkin/play/notes/NoteSprite.hx +++ b/source/funkin/play/notes/NoteSprite.hx @@ -127,7 +127,7 @@ class NoteSprite extends FlxSprite if (noteFrames != null && !force) return noteFrames; - noteFrames = Paths.getSparrowAtlas('NOTE_assets'); + noteFrames = Paths.getSparrowAtlas('notes'); noteFrames.parent.persist = true; @@ -138,20 +138,10 @@ class NoteSprite extends FlxSprite { this.frames = buildNoteFrames(); - animation.addByPrefix('greenScroll', 'green instance'); - animation.addByPrefix('redScroll', 'red instance'); - animation.addByPrefix('blueScroll', 'blue instance'); - animation.addByPrefix('purpleScroll', 'purple instance'); - - animation.addByPrefix('purpleholdend', 'pruple end hold'); - animation.addByPrefix('greenholdend', 'green hold end'); - animation.addByPrefix('redholdend', 'red hold end'); - animation.addByPrefix('blueholdend', 'blue hold end'); - - animation.addByPrefix('purplehold', 'purple hold piece'); - animation.addByPrefix('greenhold', 'green hold piece'); - animation.addByPrefix('redhold', 'red hold piece'); - animation.addByPrefix('bluehold', 'blue hold piece'); + animation.addByPrefix('greenScroll', 'noteUp'); + animation.addByPrefix('redScroll', 'noteRight'); + animation.addByPrefix('blueScroll', 'noteDown'); + animation.addByPrefix('purpleScroll', 'noteLeft'); setGraphicSize(Strumline.STRUMLINE_SIZE); updateHitbox(); diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 60df77e69..7730073f8 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -20,7 +20,7 @@ import funkin.util.SortUtil; class Strumline extends FlxSpriteGroup { public static final DIRECTIONS:Array = [NoteDirection.LEFT, NoteDirection.DOWN, NoteDirection.UP, NoteDirection.RIGHT]; - public static final STRUMLINE_SIZE:Int = 112; + public static final STRUMLINE_SIZE:Int = 104; public static final NOTE_SPACING:Int = STRUMLINE_SIZE + 8; // Positional fixes for new strumline graphics. @@ -84,6 +84,8 @@ class Strumline extends FlxSpriteGroup this.noteSplashes.zIndex = 50; this.add(this.noteSplashes); + this.refresh(); + for (i in 0...KEY_COUNT) { var child:StrumlineNote = new StrumlineNote(isPlayer, DIRECTIONS[i]); @@ -102,6 +104,11 @@ class Strumline extends FlxSpriteGroup this.active = true; } + public function refresh():Void + { + sort(SortUtil.byZIndex, FlxSort.ASCENDING); + } + override function get_width():Float { return KEY_COUNT * Strumline.NOTE_SPACING; @@ -112,8 +119,25 @@ class Strumline extends FlxSpriteGroup super.update(elapsed); updateNotes(); + + #if debug + if (!isPlayer) + { + FlxG.watch.addQuick("strumlineAnim", strumlineNotes.members[3]?.animation?.curAnim?.name); + var curFrame = strumlineNotes.members[3]?.animation?.curAnim?.curFrame; + frameMax = (curFrame > frameMax) ? curFrame : frameMax; + FlxG.watch.addQuick("strumlineFrame", strumlineNotes.members[3]?.animation?.curAnim?.curFrame); + FlxG.watch.addQuick("strumlineFrameMax", frameMax); + animFinishedEver = animFinishedEver || strumlineNotes.members[3]?.animation?.curAnim?.finished; + FlxG.watch.addQuick("strumlineFinished", strumlineNotes.members[3]?.animation?.curAnim?.finished); + FlxG.watch.addQuick("strumlineFinishedEver", animFinishedEver); + } + #end } + var frameMax:Int; + var animFinishedEver:Bool; + /** * Get a list of notes within + or - the given strumtime. * @param strumTime The current time. @@ -253,7 +277,8 @@ class Strumline extends FlxSpriteGroup // Stopped pressing the hold note. playStatic(holdNote.noteDirection); holdNote.missedNote = true; - holdNote.alpha = 0.6; + holdNote.visible = true; + holdNote.alpha = 0.0; } } @@ -347,6 +372,15 @@ class Strumline extends FlxSpriteGroup } } } + + // Update rendering of pressed keys. + for (dir in DIRECTIONS) + { + if (isKeyHeld(dir) && getByDirection(dir).getCurrentAnimation() == "static") + { + playPress(dir); + } + } } public function onBeatHit():Void @@ -405,7 +439,7 @@ class Strumline extends FlxSpriteGroup if (note.holdNoteSprite != null) { note.holdNoteSprite.missedNote = true; - note.holdNoteSprite.alpha = 0.6; + note.holdNoteSprite.visible = false; } } @@ -483,12 +517,12 @@ class Strumline extends FlxSpriteGroup cover.x += getXPos(holdNote.noteDirection); cover.x += STRUMLINE_SIZE / 2; cover.x -= cover.width / 2; - // cover.x += INITIAL_OFFSET * 2; + cover.x += -12; // Manual tweaking because fuck. + cover.y = this.y; cover.y += INITIAL_OFFSET; cover.y += STRUMLINE_SIZE / 2; - // cover.y -= cover.height / 2; - // cover.y += STRUMLINE_SIZE / 2; + cover.y += -96; // Manual tweaking because fuck. } } @@ -525,7 +559,7 @@ class Strumline extends FlxSpriteGroup holdNoteSprite.sustainLength = note.length; holdNoteSprite.missedNote = false; holdNoteSprite.hitNote = false; - + holdNoteSprite.visible = true; holdNoteSprite.alpha = 1.0; holdNoteSprite.x = this.x; diff --git a/source/funkin/play/notes/StrumlineNote.hx b/source/funkin/play/notes/StrumlineNote.hx index 2f2b41374..6361f607e 100644 --- a/source/funkin/play/notes/StrumlineNote.hx +++ b/source/funkin/play/notes/StrumlineNote.hx @@ -13,6 +13,10 @@ class StrumlineNote extends FlxSprite public var direction(default, set):NoteDirection; + var confirmHoldTimer:Float = -1; + + static final CONFIRM_HOLD_TIME:Float = 0.1; + public function updatePosition(parentNote:NoteSprite) { this.x = parentNote.x; @@ -49,9 +53,12 @@ class StrumlineNote extends FlxSprite function onAnimationFinished(name:String):Void { - if (!isPlayer && name.startsWith('confirm')) + // Run a timer before we stop playing the confirm animation. + // On opponent, this prevent issues with hold notes. + // On player, this allows holding the confirm key to fall back to press. + if (name == 'confirm') { - playStatic(); + confirmHoldTimer = 0; } } @@ -60,37 +67,49 @@ class StrumlineNote extends FlxSprite super.update(elapsed); centerOrigin(); + + if (confirmHoldTimer >= 0) + { + confirmHoldTimer += elapsed; + + // Ensure the opponent stops holding the key after a certain amount of time. + if (confirmHoldTimer >= CONFIRM_HOLD_TIME) + { + confirmHoldTimer = -1; + playStatic(); + } + } } function setup():Void { - this.frames = Paths.getSparrowAtlas('StrumlineNotes'); + this.frames = Paths.getSparrowAtlas('noteStrumline'); switch (this.direction) { case NoteDirection.LEFT: - this.animation.addByIndices('static', 'left confirm', [6, 7], '', 24, false, false, false); - this.animation.addByPrefix('press', 'left press', 24, false, false, false); - this.animation.addByIndices('confirm', 'left confirm', [0, 1, 2, 3], '', 24, false, false, false); - this.animation.addByIndices('confirm-hold', 'left confirm', [2, 3, 4, 5], '', 24, true, false, false); + this.animation.addByPrefix('static', 'staticLeft0', 24, false, false, false); + this.animation.addByPrefix('press', 'pressLeft0', 24, false, false, false); + this.animation.addByPrefix('confirm', 'confirmLeft0', 24, false, false, false); + this.animation.addByPrefix('confirm-hold', 'confirmHoldLeft0', 24, true, false, false); case NoteDirection.DOWN: - this.animation.addByIndices('static', 'down confirm', [6, 7], '', 24, false, false, false); - this.animation.addByPrefix('press', 'down press', 24, false, false, false); - this.animation.addByIndices('confirm', 'down confirm', [0, 1, 2, 3], '', 24, false, false, false); - this.animation.addByIndices('confirm-hold', 'down confirm', [2, 3, 4, 5], '', 24, true, false, false); + this.animation.addByPrefix('static', 'staticDown0', 24, false, false, false); + this.animation.addByPrefix('press', 'pressDown0', 24, false, false, false); + this.animation.addByPrefix('confirm', 'confirmDown0', 24, false, false, false); + this.animation.addByPrefix('confirm-hold', 'confirmHoldDown0', 24, true, false, false); case NoteDirection.UP: - this.animation.addByIndices('static', 'up confirm', [6, 7], '', 24, false, false, false); - this.animation.addByPrefix('press', 'up press', 24, false, false, false); - this.animation.addByIndices('confirm', 'up confirm', [0, 1, 2, 3], '', 24, false, false, false); - this.animation.addByIndices('confirm-hold', 'up confirm', [2, 3, 4, 5], '', 24, true, false, false); + this.animation.addByPrefix('static', 'staticUp0', 24, false, false, false); + this.animation.addByPrefix('press', 'pressUp0', 24, false, false, false); + this.animation.addByPrefix('confirm', 'confirmUp0', 24, false, false, false); + this.animation.addByPrefix('confirm-hold', 'confirmHoldUp0', 24, true, false, false); case NoteDirection.RIGHT: - this.animation.addByIndices('static', 'right confirm', [6, 7], '', 24, false, false, false); - this.animation.addByPrefix('press', 'right press', 24, false, false, false); - this.animation.addByIndices('confirm', 'right confirm', [0, 1, 2, 3], '', 24, false, false, false); - this.animation.addByIndices('confirm-hold', 'right confirm', [2, 3, 4, 5], '', 24, true, false, false); + this.animation.addByPrefix('static', 'staticRight0', 24, false, false, false); + this.animation.addByPrefix('press', 'pressRight0', 24, false, false, false); + this.animation.addByPrefix('confirm', 'confirmRight0', 24, false, false, false); + this.animation.addByPrefix('confirm-hold', 'confirmHoldRight0', 24, true, false, false); } this.setGraphicSize(Std.int(Strumline.STRUMLINE_SIZE * 1.55)); @@ -133,16 +152,22 @@ class StrumlineNote extends FlxSprite { this.active = true; - if (getCurrentAnimation() == "confirm-hold") return; - if (getCurrentAnimation() == "confirm") + if (getCurrentAnimation() == "confirm-hold") + { + return; + } + else if (getCurrentAnimation() == "confirm") { if (isAnimationFinished()) { - this.playAnimation('confirm-hold', true, false); + this.confirmHoldTimer = -1; + this.playAnimation('confirm-hold', false, false); } - return; } - this.playAnimation('confirm', false, false); + else + { + this.playAnimation('confirm', false, false); + } } /** diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index c6a6d0265..bc280f176 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -199,7 +199,7 @@ class Constants * If true, the player will not receive the ghost miss penalty if there are no notes within the hit window. * This is the thing people have been begging for forever lolol. */ - public static final GHOST_TAPPING:Bool = true; + public static final GHOST_TAPPING:Bool = false; /** * OTHER