diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 74dcdb2d3..3a0a9fa45 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -23,7 +23,6 @@ class MusicBeatState extends FlxUIState private var curStep:Int = 0; private var curBeat:Int = 0; private var controls(get, never):Controls; - private var lastBeatHitTime:Float = 0; inline function get_controls():Controls return PlayerSettings.player1.controls; @@ -136,9 +135,7 @@ class MusicBeatState extends FlxUIState dispatchEvent(event); if (event.eventCanceled) - { return false; - } if (curStep % 4 == 0) beatHit(); @@ -153,11 +150,7 @@ class MusicBeatState extends FlxUIState dispatchEvent(event); if (event.eventCanceled) - { return false; - } - - lastBeatHitTime = Conductor.songPosition; return true; } @@ -178,9 +171,7 @@ class MusicBeatState extends FlxUIState dispatchEvent(event); if (event.eventCanceled) - { return false; - } return super.switchTo(nextState); } @@ -192,9 +183,7 @@ class MusicBeatState extends FlxUIState dispatchEvent(event); if (event.eventCanceled) - { return; - } super.openSubState(targetSubstate); } @@ -211,9 +200,7 @@ class MusicBeatState extends FlxUIState dispatchEvent(event); if (event.eventCanceled) - { return; - } super.closeSubState(); } diff --git a/source/funkin/MusicBeatSubstate.hx b/source/funkin/MusicBeatSubstate.hx index c3a9bed0e..72c201292 100644 --- a/source/funkin/MusicBeatSubstate.hx +++ b/source/funkin/MusicBeatSubstate.hx @@ -53,10 +53,19 @@ class MusicBeatSubstate extends FlxSubState curStep = lastChange.stepTime + Math.floor((Conductor.songPosition - lastChange.songTime) / Conductor.stepCrochet); } - public function stepHit():Void + public function stepHit():Bool { + var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep); + + dispatchEvent(event); + + if (event.eventCanceled) + return false; + if (curStep % 4 == 0) beatHit(); + + return true; } function dispatchEvent(event:ScriptEvent) @@ -64,8 +73,15 @@ class MusicBeatSubstate extends FlxSubState ModuleHandler.callEvent(event); } - public function beatHit():Void + public function beatHit():Bool { - // do literally nothing dumbass + var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep); + + dispatchEvent(event); + + if (event.eventCanceled) + return false; + + return true; } } diff --git a/source/funkin/TitleState.hx b/source/funkin/TitleState.hx index e65462232..8e7dd19c8 100644 --- a/source/funkin/TitleState.hx +++ b/source/funkin/TitleState.hx @@ -2,7 +2,6 @@ package funkin; import flixel.FlxSprite; import flixel.FlxState; -import flixel.addons.display.FlxRuntimeShader; import flixel.group.FlxGroup; import flixel.input.gamepad.FlxGamepad; import flixel.tweens.FlxEase; diff --git a/source/funkin/GameOverSubstate.hx b/source/funkin/play/GameOverSubstate.hx similarity index 69% rename from source/funkin/GameOverSubstate.hx rename to source/funkin/play/GameOverSubstate.hx index 24afd1e00..e6162999d 100644 --- a/source/funkin/GameOverSubstate.hx +++ b/source/funkin/play/GameOverSubstate.hx @@ -1,4 +1,4 @@ -package funkin; +package funkin.play; import flixel.FlxSprite; import flixel.FlxObject; @@ -21,6 +21,27 @@ using StringTools; */ class GameOverSubstate extends MusicBeatSubstate { + /** + * Which alternate animation on the character to use. + * You can set this via script. + * For example, playing a different animation when BF dies in Week 4 + * or Pico dies in Weekend 1. + */ + public static var animationSuffix:String = ""; + + /** + * Which alternate game over music to use. + * You can set this via script. + * For example, the bf-pixel script sets this to `-pixel` + * and the pico-playable script sets this to `Pico`. + */ + public static var musicSuffix:String = ""; + + /** + * Which alternate "blue ball" sound effect to use. + */ + public static var blueBallSuffix:String = ""; + /** * The boyfriend character. */ @@ -42,93 +63,99 @@ class GameOverSubstate extends MusicBeatSubstate */ var isEnding:Bool = false; - /** - * Music variant to use. - * TODO: De-hardcode this somehow. - */ - var musicVariant:String = ""; - public function new() { super(); } + /** + * Reset the game over configuration to the default. + */ + public static function reset() + { + animationSuffix = ""; + musicSuffix = ""; + } + override public function create() { super.create(); - FlxG.sound.list.add(gameOverMusic); - gameOverMusic.stop(); - Conductor.songPosition = 0; + // + // Set up the visuals + // - // TODO: Make SFX and music easily overriden by scripts. - playBlueBalledSFX(); - - switch (PlayState.instance.currentStageId) - { - case 'school' | 'schoolEvil': - musicVariant = "-pixel"; - default: - if (['pico', 'pico-playable'].contains(PlayState.instance.currentStage.getBoyfriend().characterId)) - { - musicVariant = "Pico"; - } - else - { - musicVariant = ""; - } - } - - // By adding a background we can make it transparent for testing. + // Add a black background to the screen. + // We make this transparent so that we can see the stage underneath during debugging. var bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); bg.alpha = 0.25; bg.scrollFactor.set(); add(bg); - // We have to remove boyfriend from the stage. Then we can add him back at the end. + // Pluck Boyfriend from the PlayState and place him (in the same position) in the GameOverSubstate. + // We can then play the character's `firstDeath` animation. boyfriend = PlayState.instance.currentStage.getBoyfriend(true); boyfriend.isDead = true; add(boyfriend); boyfriend.resetCharacter(); boyfriend.playAnimation('firstDeath', true, true); + // Assign a camera follow point to the boyfriend's position. cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1); cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y; add(cameraFollowPoint); - // FlxG.camera.scroll.set(); FlxG.camera.target = null; FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01); + + // + // Set up the audio + // + + // Prepare the game over music. + FlxG.sound.list.add(gameOverMusic); + gameOverMusic.stop(); + + // The conductor now represents the BPM of the game over music. + Conductor.songPosition = 0; + + // Play the "blue balled" sound. May play a variant if one has been assigned. + playBlueBalledSFX(); } override function update(elapsed:Float) { - // makes the lerp non-dependant on the framerate - // FlxG.camera.followLerp = CoolUtil.camLerpShit(0.01); - super.update(elapsed); + // + // Handle user inputs. + // + + // MOBILE ONLY: Restart the level when tapping Boyfriend. if (FlxG.onMobile) { var touch = FlxG.touches.getFirst(); if (touch != null) { if (touch.overlaps(boyfriend)) + { confirmDeath(); + } } } + // KEYBOARD ONLY: Restart the level when pressing the assigned key. if (controls.ACCEPT) { confirmDeath(); } + // KEYBOARD ONLY: Return to the menu when pressing the assigned key. if (controls.BACK) { PlayState.deathCounter = 0; PlayState.seenCutscene = false; - // FlxG.sound.music.stop(); gameOverMusic.stop(); if (PlayState.isStoryMode) @@ -137,30 +164,29 @@ class GameOverSubstate extends MusicBeatSubstate FlxG.switchState(new FreeplayState()); } - // Start panning the camera to BF after 12 frames. - // TODO: Should this be de-hardcoded? - //if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.animation.curAnim.curFrame == 12) - //{ -// - //} - if (gameOverMusic.playing) { + // Match the conductor to the music. + // This enables the stepHit and beatHit events. Conductor.songPosition = gameOverMusic.time; } else { + // Music hasn't started yet. switch (PlayState.storyWeek) { + // TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded. + // This will simplify the class and make it easier for mods to add death quotes. case 7: if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote) { playingJeffQuote = true; playJeffQuote(); - + // Start music at lower volume startDeathMusic(0.2); } default: + // Start music at normal volume once the initial death animation finishes. if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished()) { startDeathMusic(); @@ -168,9 +194,44 @@ class GameOverSubstate extends MusicBeatSubstate } } + // Dispatch the onUpdate event. dispatchEvent(new UpdateScriptEvent(elapsed)); } + /** + * Do behavior which occurs when you confirm and move to restart the level. + */ + function confirmDeath():Void + { + if (!isEnding) + { + isEnding = true; + startDeathMusic(); // isEnding changes this function's behavior. + + boyfriend.playAnimation('deathConfirm' + animationSuffix, true); + + // After the animation finishes... + new FlxTimer().start(0.7, function(tmr:FlxTimer) + { + // ...fade out the graphics. Then after that happens... + FlxG.camera.fade(FlxColor.BLACK, 2, false, function() + { + // ...close the GameOverSubstate. + FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); + PlayState.needsReset = true; + + // Readd Boyfriend to the stage. + boyfriend.isDead = false; + remove(boyfriend); + PlayState.instance.currentStage.addCharacter(boyfriend, BF); + + // Close the substate. + close(); + }); + }); + } + } + override function dispatchEvent(event:ScriptEvent) { super.dispatchEvent(event); @@ -186,13 +247,13 @@ class GameOverSubstate extends MusicBeatSubstate { if (!isEnding) { - gameOverMusic.loadEmbedded(Paths.music('gameOver' + musicVariant)); + gameOverMusic.loadEmbedded(Paths.music('gameOver' + musicSuffix)); gameOverMusic.volume = startingVolume; gameOverMusic.play(); } else { - gameOverMusic.loadEmbedded(Paths.music('gameOverEnd' + musicVariant)); + gameOverMusic.loadEmbedded(Paths.music('gameOverEnd' + musicSuffix)); gameOverMusic.volume = startingVolume; gameOverMusic.play(); } @@ -204,7 +265,7 @@ class GameOverSubstate extends MusicBeatSubstate */ function playBlueBalledSFX() { - FlxG.sound.play(Paths.sound('fnf_loss_sfx' + musicVariant)); + FlxG.sound.play(Paths.sound('fnf_loss_sfx' + blueBallSuffix)); } var playingJeffQuote:Bool = false; @@ -229,38 +290,4 @@ class GameOverSubstate extends MusicBeatSubstate } }); } - - /** - * Do behavior which occurs when you confirm and move to restart the level. - */ - function confirmDeath():Void - { - if (!isEnding) - { - isEnding = true; - startDeathMusic(); // isEnding changes this function's behavior. - - boyfriend.playAnimation('deathConfirm', true); - - // After the animation finishes... - new FlxTimer().start(0.7, function(tmr:FlxTimer) - { - // ...fade out the graphics. Then after that happens... - FlxG.camera.fade(FlxColor.BLACK, 2, false, function() - { - // ...close the GameOverSubstate. - FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); - PlayState.needsReset = true; - - // Readd Boyfriend to the stage. - boyfriend.isDead = false; - remove(boyfriend); - PlayState.instance.currentStage.addCharacter(boyfriend, BF); - - // Close the substate. - close(); - }); - }); - } - } } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 73c97ec00..bdb1770dc 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -5,7 +5,6 @@ import flixel.FlxObject; import flixel.FlxSprite; import flixel.FlxState; import flixel.FlxSubState; -import flixel.addons.display.FlxRuntimeShader; import flixel.addons.transition.FlxTransitionableState; import flixel.group.FlxGroup; import flixel.math.FlxMath; @@ -32,6 +31,7 @@ import funkin.play.character.CharacterData; import funkin.play.scoring.Scoring; import funkin.play.stage.Stage; import funkin.play.stage.StageData; +import funkin.play.GameOverSubstate; import funkin.ui.PopUpStuff; import funkin.ui.PreferencesMenu; import funkin.ui.stageBuildShit.StageOffsetSubstate; @@ -2102,6 +2102,8 @@ class PlayState extends MusicBeatState implements IHook currentStage = null; } + GameOverSubstate.reset(); + // Clear the static reference to this state. instance = null; } diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 0580e95ef..edd178629 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -297,6 +297,7 @@ class BaseCharacter extends Bopper if (isDead) { playDeathAnimation(); + return; } if (hasAnimation('idle-hold') && getCurrentAnimation() == "idle" && isAnimationFinished()) @@ -343,7 +344,7 @@ class BaseCharacter extends Bopper { if (force || (getCurrentAnimation().startsWith("firstDeath") && isAnimationFinished())) { - playAnimation("deathLoop"); + playAnimation("deathLoop" + GameOverSubstate.animationSuffix); } } @@ -353,6 +354,9 @@ class BaseCharacter extends Bopper if (debugMode) return; + if (isDead) + return; + if (!force) { if (getCurrentAnimation().startsWith("sing"))