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.
*/

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.
// 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)
{
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.');
for (event in songEventsToActivate)
{
// If an event is trying to play, but it's over 5 seconds old, skip it.
if (event.time - Conductor.instance.songPosition < -5000)
// If an event is trying to play, but it's over 1 second old, skip it.
var eventAge:Float = Conductor.instance.songPosition - event.time;
if (eventAge > 1000)
{
event.activated = true;
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
@ -1761,7 +1767,7 @@ class PlayState extends MusicBeatSubState
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.
// 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;
@ -2041,6 +2047,7 @@ class PlayState extends MusicBeatSubState
}
}
// Respawns notes that were b
playerStrumline.handleSkippedNotes();
opponentStrumline.handleSkippedNotes();
}
@ -2303,7 +2310,7 @@ class PlayState extends MusicBeatSubState
#if (debug || FORCE_DEBUG_VERSION)
// 1: End the song immediately.
if (FlxG.keys.justPressed.ONE) endSong();
if (FlxG.keys.justPressed.ONE) endSong(true);
// 2: Gain 10% health.
if (FlxG.keys.justPressed.TWO) health += 0.1 * Constants.HEALTH_MAX;
@ -2497,16 +2504,35 @@ class PlayState extends MusicBeatSubState
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
// spitter for ravy, teehee!!
@ -2516,9 +2542,7 @@ class PlayState extends MusicBeatSubState
#end
deathCounter = 0;
mayPauseGame = false;
FlxG.sound.music.volume = 0;
vocals.volume = 0;
if (currentSong != null && currentSong.validScore)
{
// crackhead double thingie, sets whether was new highscore, AND saves the song!
@ -2605,7 +2629,14 @@ class PlayState extends MusicBeatSubState
}
else
{
moveToResultsScreen();
if (rightGoddamnNow)
{
moveToResultsScreen();
}
else
{
zoomIntoResultsScreen();
}
}
}
else
@ -2663,7 +2694,14 @@ class PlayState extends MusicBeatSubState
}
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!');
@ -2773,22 +2811,30 @@ class PlayState extends MusicBeatSubState
{
ease: FlxEase.expoIn,
onComplete: function(_) {
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);
moveToResultsScreen();
}
});
});
}
/**
* 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.
*/
@ -2818,14 +2864,18 @@ class PlayState extends MusicBeatSubState
*/
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);
// Don't go back in time to before the song started.
targetTimeMs = Math.max(0, targetTimeMs);
FlxG.sound.music.time = targetTimeMs;
handleSkippedNotes();
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
// regenNoteData(FlxG.sound.music.time);
Conductor.instance.update(FlxG.sound.music.time);

View file

@ -19,13 +19,22 @@ import hxcodec.flixel.FlxVideoSprite;
class VideoCutscene
{
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.
* 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 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;
@ -49,6 +58,8 @@ class VideoCutscene
blackScreen.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(blackScreen);
VideoCutscene.cutsceneType = cutsceneType;
#if html5
playVideoHTML5(filePath);
#elseif hxCodec
@ -68,8 +79,6 @@ class VideoCutscene
}
#if html5
static var vid:FlxVideo;
static function playVideoHTML5(filePath:String):Void
{
// Video displays OVER the FlxState.
@ -94,8 +103,6 @@ class VideoCutscene
#end
#if hxCodec
static var vid:FlxVideoSprite;
static function playVideoNative(filePath:String):Void
{
// Video displays OVER the FlxState.
@ -129,10 +136,17 @@ class VideoCutscene
}
#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
{
trace('ALERT: Finish video cutscene called!');
var cutsceneType:CutsceneType = VideoCutscene.cutsceneType;
#if html5
if (vid != null)
{
@ -168,8 +182,32 @@ class VideoCutscene
{
ease: FlxEase.quadInOut,
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.
}