Working Blazin cutscene and fixed time travel

This commit is contained in:
EliteMasterEric 2024-02-26 19:03:04 -05:00
parent 66c91d8b3e
commit 90360de0d0
4 changed files with 155 additions and 44 deletions

2
assets

@ -1 +1 @@
Subproject commit 7d031153cf073e9d49ab59d7c72956cf4a68bcda Subproject commit 1b0e097508ec694012043a4c059885b05a569e2a

View file

@ -148,6 +148,29 @@ class SongEventRegistry
}); });
} }
/**
* The currentTime has jumped far ahead or back.
* If we moved back in time, we need to reset all the events in that space.
* If we moved forward in time, we need to skip all the events in that space.
*/
public static function handleSkippedEvents(events:Array<SongEventData>, currentTime:Float):Void
{
for (event in events)
{
// Deactivate future events.
if (event.time > currentTime)
{
event.activated = false;
}
// Skip past events.
if (event.time < currentTime)
{
event.activated = true;
}
}
}
/** /**
* Reset activation of all the provided events. * Reset activation of all the provided events.
*/ */

View file

@ -987,8 +987,21 @@ class PlayState extends MusicBeatSubState
} }
} }
processSongEvents();
// Handle keybinds.
processInputQueue();
if (!isInCutscene && !disableKeys) debugKeyShit();
if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed);
// Moving notes into position is now done by Strumline.update().
processNotes(elapsed);
}
function processSongEvents():Void
{
// Query and activate song events. // Query and activate song events.
// TODO: Check that these work even when songPosition is less than 0. // TODO: Check that these work appropriately even when songPosition is less than 0, to play events during countdown.
if (songEvents != null && songEvents.length > 0) if (songEvents != null && songEvents.length > 0)
{ {
var songEventsToActivate:Array<SongEventData> = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition); var songEventsToActivate:Array<SongEventData> = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition);
@ -998,8 +1011,9 @@ class PlayState extends MusicBeatSubState
trace('Found ${songEventsToActivate.length} event(s) to activate.'); trace('Found ${songEventsToActivate.length} event(s) to activate.');
for (event in songEventsToActivate) for (event in songEventsToActivate)
{ {
// If an event is trying to play, but it's over 5 seconds old, skip it. // If an event is trying to play, but it's over 1 second old, skip it.
if (event.time - Conductor.instance.songPosition < -5000) var eventAge:Float = Conductor.instance.songPosition - event.time;
if (eventAge > 1000)
{ {
event.activated = true; event.activated = true;
continue; continue;
@ -1015,14 +1029,6 @@ class PlayState extends MusicBeatSubState
} }
} }
} }
// Handle keybinds.
processInputQueue();
if (!isInCutscene && !disableKeys) debugKeyShit();
if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed);
// Moving notes into position is now done by Strumline.update().
processNotes(elapsed);
} }
public override function dispatchEvent(event:ScriptEvent):Void public override function dispatchEvent(event:ScriptEvent):Void
@ -1761,7 +1767,7 @@ class PlayState extends MusicBeatSubState
currentChart.playInst(1.0, false); currentChart.playInst(1.0, false);
} }
FlxG.sound.music.onComplete = endSong; FlxG.sound.music.onComplete = endSong.bind(false);
// A negative instrumental offset means the song skips the first few milliseconds of the track. // A negative instrumental offset means the song skips the first few milliseconds of the track.
// This just gets added into the startTimestamp behavior so we don't need to do anything extra. // This just gets added into the startTimestamp behavior so we don't need to do anything extra.
FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
@ -2041,6 +2047,7 @@ class PlayState extends MusicBeatSubState
} }
} }
// Respawns notes that were b
playerStrumline.handleSkippedNotes(); playerStrumline.handleSkippedNotes();
opponentStrumline.handleSkippedNotes(); opponentStrumline.handleSkippedNotes();
} }
@ -2303,7 +2310,7 @@ class PlayState extends MusicBeatSubState
#if (debug || FORCE_DEBUG_VERSION) #if (debug || FORCE_DEBUG_VERSION)
// 1: End the song immediately. // 1: End the song immediately.
if (FlxG.keys.justPressed.ONE) endSong(); if (FlxG.keys.justPressed.ONE) endSong(true);
// 2: Gain 10% health. // 2: Gain 10% health.
if (FlxG.keys.justPressed.TWO) health += 0.1 * Constants.HEALTH_MAX; if (FlxG.keys.justPressed.TWO) health += 0.1 * Constants.HEALTH_MAX;
@ -2497,16 +2504,35 @@ class PlayState extends MusicBeatSubState
if (skipHeldTimer >= 1.5) if (skipHeldTimer >= 1.5)
{ {
VideoCutscene.finishVideo(); skipVideoCutscene();
} }
} }
/** /**
* End the song. Handle saving high scores and transitioning to the results screen. * Handle logic for actually skipping a video cutscene after it has been held.
*/ */
function endSong():Void function skipVideoCutscene():Void
{ {
dispatchEvent(new ScriptEvent(SONG_END)); VideoCutscene.finishVideo();
}
/**
* End the song. Handle saving high scores and transitioning to the results screen.
*
* Broadcasts an `onSongEnd` event, which can be cancelled to prevent the song from ending (for a cutscene or something).
* Remember to call `endSong` again when the song should actually end!
* @param rightGoddamnNow If true, don't play the fancy animation where you zoom onto Girlfriend. Used after a cutscene.
*/
public function endSong(rightGoddamnNow:Bool = false):Void
{
FlxG.sound.music.volume = 0;
vocals.volume = 0;
mayPauseGame = false;
// Check if any events want to prevent the song from ending.
var event = new ScriptEvent(SONG_END, true);
dispatchEvent(event);
if (event.eventCanceled) return;
#if sys #if sys
// spitter for ravy, teehee!! // spitter for ravy, teehee!!
@ -2516,9 +2542,7 @@ class PlayState extends MusicBeatSubState
#end #end
deathCounter = 0; deathCounter = 0;
mayPauseGame = false;
FlxG.sound.music.volume = 0;
vocals.volume = 0;
if (currentSong != null && currentSong.validScore) if (currentSong != null && currentSong.validScore)
{ {
// crackhead double thingie, sets whether was new highscore, AND saves the song! // crackhead double thingie, sets whether was new highscore, AND saves the song!
@ -2605,7 +2629,14 @@ class PlayState extends MusicBeatSubState
} }
else else
{ {
moveToResultsScreen(); if (rightGoddamnNow)
{
moveToResultsScreen();
}
else
{
zoomIntoResultsScreen();
}
} }
} }
else else
@ -2663,7 +2694,14 @@ class PlayState extends MusicBeatSubState
} }
else else
{ {
moveToResultsScreen(); if (rightGoddamnNow)
{
moveToResultsScreen();
}
else
{
zoomIntoResultsScreen();
}
} }
} }
} }
@ -2717,9 +2755,9 @@ class PlayState extends MusicBeatSubState
} }
/** /**
* Play the camera zoom animation and move to the results screen. * Play the camera zoom animation and then move to the results screen once it's done.
*/ */
function moveToResultsScreen():Void function zoomIntoResultsScreen():Void
{ {
trace('WENT TO RESULTS SCREEN!'); trace('WENT TO RESULTS SCREEN!');
@ -2773,22 +2811,30 @@ class PlayState extends MusicBeatSubState
{ {
ease: FlxEase.expoIn, ease: FlxEase.expoIn,
onComplete: function(_) { onComplete: function(_) {
persistentUpdate = false; moveToResultsScreen();
vocals.stop();
camHUD.alpha = 1;
var res:ResultState = new ResultState(
{
storyMode: PlayStatePlaylist.isStoryMode,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
tallies: Highscore.tallies,
});
res.camera = camHUD;
openSubState(res);
} }
}); });
}); });
} }
/**
* Move to the results screen right goddamn now.
*/
function moveToResultsScreen():Void
{
persistentUpdate = false;
vocals.stop();
camHUD.alpha = 1;
var res:ResultState = new ResultState(
{
storyMode: PlayStatePlaylist.isStoryMode,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
tallies: Highscore.tallies,
});
res.camera = camHUD;
openSubState(res);
}
/** /**
* Pauses music and vocals easily. * Pauses music and vocals easily.
*/ */
@ -2818,14 +2864,18 @@ class PlayState extends MusicBeatSubState
*/ */
function changeSection(sections:Int):Void function changeSection(sections:Int):Void
{ {
FlxG.sound.music.pause(); // FlxG.sound.music.pause();
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections); var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps); var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
// Don't go back in time to before the song started.
targetTimeMs = Math.max(0, targetTimeMs);
FlxG.sound.music.time = targetTimeMs; FlxG.sound.music.time = targetTimeMs;
handleSkippedNotes(); handleSkippedNotes();
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
// regenNoteData(FlxG.sound.music.time); // regenNoteData(FlxG.sound.music.time);
Conductor.instance.update(FlxG.sound.music.time); Conductor.instance.update(FlxG.sound.music.time);

View file

@ -19,13 +19,22 @@ import hxcodec.flixel.FlxVideoSprite;
class VideoCutscene class VideoCutscene
{ {
static var blackScreen:FlxSprite; static var blackScreen:FlxSprite;
static var cutsceneType:CutsceneType;
#if html5
static var vid:FlxVideo;
#end
#if hxCodec
static var vid:FlxVideoSprite;
#end
/** /**
* Play a video cutscene. * Play a video cutscene.
* TODO: Currently this is hardcoded to start the countdown after the video is done. * TODO: Currently this is hardcoded to start the countdown after the video is done.
* @param path The path to the video file. Use Paths.file(path) to get the correct path. * @param path The path to the video file. Use Paths.file(path) to get the correct path.
* @param cutseneType The type of cutscene to play, determines what the game does after. Defaults to `CutsceneType.STARTING`.
*/ */
public static function play(filePath:String):Void public static function play(filePath:String, ?cutsceneType:CutsceneType = STARTING):Void
{ {
if (PlayState.instance == null) return; if (PlayState.instance == null) return;
@ -49,6 +58,8 @@ class VideoCutscene
blackScreen.cameras = [PlayState.instance.camCutscene]; blackScreen.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(blackScreen); PlayState.instance.add(blackScreen);
VideoCutscene.cutsceneType = cutsceneType;
#if html5 #if html5
playVideoHTML5(filePath); playVideoHTML5(filePath);
#elseif hxCodec #elseif hxCodec
@ -68,8 +79,6 @@ class VideoCutscene
} }
#if html5 #if html5
static var vid:FlxVideo;
static function playVideoHTML5(filePath:String):Void static function playVideoHTML5(filePath:String):Void
{ {
// Video displays OVER the FlxState. // Video displays OVER the FlxState.
@ -94,8 +103,6 @@ class VideoCutscene
#end #end
#if hxCodec #if hxCodec
static var vid:FlxVideoSprite;
static function playVideoNative(filePath:String):Void static function playVideoNative(filePath:String):Void
{ {
// Video displays OVER the FlxState. // Video displays OVER the FlxState.
@ -129,10 +136,17 @@ class VideoCutscene
} }
#end #end
/**
* Finish the active video cutscene. Done when the video is finished or when the player skips the cutscene.
* @param transitionTime The duration of the transition to the next state. Defaults to 0.5 seconds (this time is always used when cancelling the video).
* @param finishCutscene The callback to call when the transition is finished.
*/
public static function finishVideo(?transitionTime:Float = 0.5):Void public static function finishVideo(?transitionTime:Float = 0.5):Void
{ {
trace('ALERT: Finish video cutscene called!'); trace('ALERT: Finish video cutscene called!');
var cutsceneType:CutsceneType = VideoCutscene.cutsceneType;
#if html5 #if html5
if (vid != null) if (vid != null)
{ {
@ -168,8 +182,32 @@ class VideoCutscene
{ {
ease: FlxEase.quadInOut, ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) { onComplete: function(twn:FlxTween) {
PlayState.instance.startCountdown(); onCutsceneFinish(cutsceneType);
} }
}); });
} }
/**
* The default callback used when a cutscene is finished.
* You can specify your own callback when calling `VideoCutscene#play()`.
*/
static function onCutsceneFinish(cutsceneType:CutsceneType):Void
{
switch (cutsceneType)
{
case CutsceneType.STARTING:
PlayState.instance.startCountdown();
case CutsceneType.ENDING:
PlayState.instance.endSong(true); // true = right goddamn now
case CutsceneType.MIDSONG:
throw "Not implemented!";
}
}
}
enum CutsceneType
{
STARTING; // The default cutscene type. Starts the countdown after the video is done.
MIDSONG; // TODO: Implement this!
ENDING; // Ends the song after the video is done.
} }