1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-04-07 20:54:43 +00:00

Added ability to start song at a specific timestamp

This commit is contained in:
EliteMasterEric 2023-07-26 20:03:31 -04:00
parent 5ff546bacc
commit 2048e65bf2
8 changed files with 88 additions and 41 deletions

View file

@ -191,7 +191,7 @@ class LatencyState extends MusicBeatSubState
if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed; if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed;
Conductor.songPosition = swagSong.getTimeWithDiff() - Conductor.offset; Conductor.update(swagSong.getTimeWithDiff() - Conductor.offset);
// Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp; // Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
songPosVis.x = songPosToX(Conductor.songPosition); songPosVis.x = songPosToX(Conductor.songPosition);

View file

@ -61,6 +61,15 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
// This can now be used in EVERY STATE YAY! // This can now be used in EVERY STATE YAY!
if (FlxG.keys.justPressed.F5) debug_refreshModules(); if (FlxG.keys.justPressed.F5) debug_refreshModules();
// Display Conductor info in the watch window.
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
FlxG.watch.addQuick("bpm", Conductor.bpm);
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime);
dispatchEvent(new UpdateScriptEvent(elapsed));
} }
function debug_refreshModules() function debug_refreshModules()

View file

@ -256,7 +256,7 @@ class TitleState extends MusicBeatState
FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG}); FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG});
} }
if (FlxG.sound.music != null) Conductor.songPosition = FlxG.sound.music.time; if (FlxG.sound.music != null) Conductor.update(FlxG.sound.music.time);
if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen; if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen;
// do controls.PAUSE | controls.ACCEPT instead? // do controls.PAUSE | controls.ACCEPT instead?

View file

@ -37,7 +37,7 @@ class Countdown
stopCountdown(); stopCountdown();
PlayState.instance.isInCountdown = true; PlayState.instance.isInCountdown = true;
Conductor.songPosition = Conductor.beatLengthMs * -5; Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5);
// Handle onBeatHit events manually // Handle onBeatHit events manually
@:privateAccess @:privateAccess
PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
@ -46,6 +46,12 @@ class Countdown
countdownTimer = new FlxTimer(); countdownTimer = new FlxTimer();
countdownTimer.start(Conductor.beatLengthMs / 1000, function(tmr:FlxTimer) { countdownTimer.start(Conductor.beatLengthMs / 1000, function(tmr:FlxTimer) {
if (PlayState.instance == null)
{
tmr.cancel();
return;
}
countdownStep = decrement(countdownStep); countdownStep = decrement(countdownStep);
// Handle onBeatHit events manually // Handle onBeatHit events manually
@ -146,7 +152,7 @@ class Countdown
{ {
stopCountdown(); stopCountdown();
// This will trigger PlayState.startSong() // This will trigger PlayState.startSong()
Conductor.songPosition = 0; Conductor.update(0);
// PlayState.isInCountdown = false; // PlayState.isInCountdown = false;
} }

View file

@ -117,7 +117,7 @@ class GameOverSubState extends MusicBeatSubState
gameOverMusic.stop(); gameOverMusic.stop();
// The conductor now represents the BPM of the game over music. // The conductor now represents the BPM of the game over music.
Conductor.songPosition = 0; Conductor.update(0);
} }
var hasStartedAnimation:Bool = false; var hasStartedAnimation:Bool = false;
@ -183,7 +183,7 @@ class GameOverSubState extends MusicBeatSubState
{ {
// Match the conductor to the music. // Match the conductor to the music.
// This enables the stepHit and beatHit events. // This enables the stepHit and beatHit events.
Conductor.songPosition = gameOverMusic.time; Conductor.update(gameOverMusic.time);
} }
else else
{ {

View file

@ -88,6 +88,10 @@ typedef PlayStateParams =
* @default `false` * @default `false`
*/ */
?minimalMode:Bool, ?minimalMode:Bool,
/**
* If specified, the game will jump to the specified timestamp after the countdown ends.
*/
?startTimestamp:Float,
} }
/** /**
@ -236,6 +240,8 @@ class PlayState extends MusicBeatSubState
*/ */
public var disableKeys:Bool = false; public var disableKeys:Bool = false;
public var startTimestamp:Float = 0.0;
public var isSubState(get, null):Bool; public var isSubState(get, null):Bool;
function get_isSubState():Bool function get_isSubState():Bool
@ -471,6 +477,7 @@ class PlayState extends MusicBeatSubState
if (params.targetCharacter != null) currentPlayerId = params.targetCharacter; if (params.targetCharacter != null) currentPlayerId = params.targetCharacter;
isPracticeMode = params.practiceMode ?? false; isPracticeMode = params.practiceMode ?? false;
isMinimalMode = params.minimalMode ?? false; isMinimalMode = params.minimalMode ?? false;
startTimestamp = params.startTimestamp ?? 0.0;
// Don't do anything else here! Wait until create() when we attach to the camera. // Don't do anything else here! Wait until create() when we attach to the camera.
} }
@ -560,7 +567,7 @@ class PlayState extends MusicBeatSubState
// Prepare the Conductor. // Prepare the Conductor.
Conductor.mapTimeChanges(currentChart.timeChanges); Conductor.mapTimeChanges(currentChart.timeChanges);
Conductor.update(-5000); Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp);
// The song is now loaded. We can continue to initialize the play state. // The song is now loaded. We can continue to initialize the play state.
initCameras(); initCameras();
@ -669,7 +676,7 @@ class PlayState extends MusicBeatSubState
FlxG.sound.music.pause(); FlxG.sound.music.pause();
vocals.pause(); vocals.pause();
FlxG.sound.music.time = 0; FlxG.sound.music.time = (startTimestamp);
vocals.time = 0; vocals.time = 0;
FlxG.sound.music.volume = 1; FlxG.sound.music.volume = 1;
@ -700,8 +707,8 @@ class PlayState extends MusicBeatSubState
{ {
if (isInCountdown) if (isInCountdown)
{ {
Conductor.songPosition += elapsed * 1000; Conductor.update(Conductor.songPosition + elapsed * 1000);
if (Conductor.songPosition >= 0) startSong(); if (Conductor.songPosition >= startTimestamp) startSong();
} }
} }
else else
@ -1067,7 +1074,8 @@ class PlayState extends MusicBeatSubState
// super.stepHit() returns false if a module cancelled the event. // super.stepHit() returns false if a module cancelled the event.
if (!super.stepHit()) return false; if (!super.stepHit()) return false;
if (FlxG.sound.music != null if (!startingSong
&& FlxG.sound.music != null
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200 && (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200
|| Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200)) || Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200))
{ {
@ -1515,7 +1523,7 @@ class PlayState extends MusicBeatSubState
/** /**
* Read note data from the chart and generate the notes. * Read note data from the chart and generate the notes.
*/ */
function regenNoteData():Void function regenNoteData(startTime:Float = 0):Void
{ {
Highscore.tallies.combo = 0; Highscore.tallies.combo = 0;
Highscore.tallies = new Tallies(); Highscore.tallies = new Tallies();
@ -1531,6 +1539,8 @@ class PlayState extends MusicBeatSubState
for (songNote in currentChart.notes) for (songNote in currentChart.notes)
{ {
var strumTime:Float = songNote.time; var strumTime:Float = songNote.time;
if (strumTime < startTime) continue; // Skip notes that are before the start time.
var noteData:Int = songNote.getDirection(); var noteData:Int = songNote.getDirection();
var playerNote:Bool = true; var playerNote:Bool = true;
@ -1617,14 +1627,22 @@ class PlayState extends MusicBeatSubState
} }
FlxG.sound.music.onComplete = endSong; FlxG.sound.music.onComplete = endSong;
FlxG.sound.music.play(false, startTimestamp);
trace('Playing vocals...'); trace('Playing vocals...');
add(vocals); add(vocals);
vocals.play(); vocals.play();
resyncVocals();
#if discord_rpc #if discord_rpc
// Updating Discord Rich Presence (with Time Left) // Updating Discord Rich Presence (with Time Left)
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs); DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
#end #end
if (startTimestamp > 0)
{
FlxG.sound.music.time = startTimestamp;
handleSkippedNotes();
}
} }
/** /**
@ -1640,7 +1658,7 @@ class PlayState extends MusicBeatSubState
Conductor.update(); Conductor.update();
vocals.time = FlxG.sound.music.time; vocals.time = FlxG.sound.music.time;
vocals.play(); vocals.play(false, FlxG.sound.music.time);
} }
/** /**
@ -1836,6 +1854,23 @@ class PlayState extends MusicBeatSubState
*/ */
var inputSpitter:Array<ScoreInput> = []; var inputSpitter:Array<ScoreInput> = [];
function handleSkippedNotes():Void
{
for (note in playerStrumline.notes.members)
{
var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS;
var hitWindowCenter = note.strumTime;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
if (Conductor.songPosition > hitWindowEnd)
{
// We have passed this note.
// Flag the note for deletion without actually penalizing the player.
note.handledMiss = true;
}
}
}
/** /**
* PreciseInputEvents are put into a queue between update() calls, * PreciseInputEvents are put into a queue between update() calls,
* and then processed here. * and then processed here.
@ -2064,11 +2099,10 @@ class PlayState extends MusicBeatSubState
if (event.eventCanceled) return; if (event.eventCanceled) return;
health -= Constants.HEALTH_MISS_PENALTY; health -= Constants.HEALTH_MISS_PENALTY;
songScore -= 10;
if (!isPracticeMode) if (!isPracticeMode)
{ {
songScore -= 10;
// messy copy paste rn lol // messy copy paste rn lol
var pressArray:Array<Bool> = [ var pressArray:Array<Bool> = [
controls.NOTE_LEFT_P, controls.NOTE_LEFT_P,
@ -2139,11 +2173,10 @@ class PlayState extends MusicBeatSubState
if (event.eventCanceled) return; if (event.eventCanceled) return;
health += event.healthChange; health += event.healthChange;
songScore += event.scoreChange;
if (!isPracticeMode) if (!isPracticeMode)
{ {
songScore += event.scoreChange;
var pressArray:Array<Bool> = [ var pressArray:Array<Bool> = [
controls.NOTE_LEFT_P, controls.NOTE_LEFT_P,
controls.NOTE_DOWN_P, controls.NOTE_DOWN_P,
@ -2282,11 +2315,10 @@ class PlayState extends MusicBeatSubState
playerStrumline.playNoteSplash(daNote.noteData.getDirection()); playerStrumline.playNoteSplash(daNote.noteData.getDirection());
} }
// Only add the score if you're not on practice mode songScore += score;
if (!isPracticeMode) if (!isPracticeMode)
{ {
songScore += score;
// TODO: Input splitter uses old input system, make it pull from the precise input queue directly. // TODO: Input splitter uses old input system, make it pull from the precise input queue directly.
var pressArray:Array<Bool> = [ var pressArray:Array<Bool> = [
controls.NOTE_LEFT_P, controls.NOTE_LEFT_P,
@ -2643,30 +2675,16 @@ class PlayState extends MusicBeatSubState
{ {
FlxG.sound.music.pause(); FlxG.sound.music.pause();
FlxG.sound.music.time += sections * Conductor.measureLengthMs; var targetTimeSteps:Float = Conductor.currentStepTime + (Conductor.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections);
var targetTimeMs:Float = Conductor.getStepTimeInMs(targetTimeSteps);
FlxG.sound.music.time = targetTimeMs;
handleSkippedNotes();
// regenNoteData(FlxG.sound.music.time);
Conductor.update(FlxG.sound.music.time); Conductor.update(FlxG.sound.music.time);
/**
*
// TODO: Redo this for the new conductor.
var daBPM:Float = Conductor.bpm;
var daPos:Float = 0;
for (i in 0...(Std.int(Conductor.currentStep / 16 + sec)))
{
var section = .getSong()[i];
if (section == null) continue;
if (section.changeBPM)
{
daBPM = .getSong()[i].bpm;
}
daPos += 4 * (1000 * 60 / daBPM);
}
Conductor.songPosition = FlxG.sound.music.time = daPos;
Conductor.songPosition += Conductor.offset;
*/
resyncVocals(); resyncVocals();
} }
#end #end

View file

@ -220,6 +220,7 @@ class Strumline extends FlxSpriteGroup
{ {
if (noteData.length == 0) return; if (noteData.length == 0) return;
var songStart:Float = PlayState.instance.startTimestamp ?? 0.0;
var renderWindowStart:Float = Conductor.songPosition + RENDER_DISTANCE_MS; var renderWindowStart:Float = Conductor.songPosition + RENDER_DISTANCE_MS;
for (noteIndex in nextNoteIndex...noteData.length) for (noteIndex in nextNoteIndex...noteData.length)
@ -227,6 +228,7 @@ class Strumline extends FlxSpriteGroup
var note:Null<SongNoteData> = noteData[noteIndex]; var note:Null<SongNoteData> = noteData[noteIndex];
if (note == null) continue; if (note == null) continue;
if (note.time < songStart) continue;
if (note.time > renderWindowStart) break; if (note.time > renderWindowStart) break;
var noteSprite = buildNoteSprite(note); var noteSprite = buildNoteSprite(note);

View file

@ -400,6 +400,11 @@ class ChartEditorState extends HaxeUIState
return isViewDownscroll; return isViewDownscroll;
} }
/**
* If true, playtesting a chart will skip to the current playhead position.
*/
var playtestStartTime:Bool = false;
/** /**
* Whether hitsounds are enabled for at least one character. * Whether hitsounds are enabled for at least one character.
*/ */
@ -1305,6 +1310,9 @@ class ChartEditorState extends HaxeUIState
addUIChangeListener('menubarItemDownscroll', event -> isViewDownscroll = event.value); addUIChangeListener('menubarItemDownscroll', event -> isViewDownscroll = event.value);
setUICheckboxSelected('menubarItemDownscroll', isViewDownscroll); setUICheckboxSelected('menubarItemDownscroll', isViewDownscroll);
addUIChangeListener('menubarItemPlaytestStartTime', event -> playtestStartTime = event.value);
setUICheckboxSelected('menubarItemPlaytestStartTime', playtestStartTime);
addUIChangeListener('menuBarItemThemeLight', function(event:UIEvent) { addUIChangeListener('menuBarItemThemeLight', function(event:UIEvent) {
if (event.target.value) currentTheme = ChartEditorTheme.Light; if (event.target.value) currentTheme = ChartEditorTheme.Light;
}); });
@ -3049,6 +3057,9 @@ class ChartEditorState extends HaxeUIState
*/ */
public function testSongInPlayState(?minimal:Bool = false):Void public function testSongInPlayState(?minimal:Bool = false):Void
{ {
var startTimestamp:Float = 0;
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false); var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false);
subStateClosed.add(fixCamera); subStateClosed.add(fixCamera);
@ -3061,6 +3072,7 @@ class ChartEditorState extends HaxeUIState
// targetCharacter: targetCharacter, // targetCharacter: targetCharacter,
practiceMode: true, practiceMode: true,
minimalMode: minimal, minimalMode: minimal,
startTimestamp: startTimestamp,
})); }));
} }