mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-30 11:26:53 -05:00
Working Blazin cutscene and fixed time travel
This commit is contained in:
parent
66c91d8b3e
commit
90360de0d0
4 changed files with 155 additions and 44 deletions
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 7d031153cf073e9d49ab59d7c72956cf4a68bcda
|
Subproject commit 1b0e097508ec694012043a4c059885b05a569e2a
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue