1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-11-25 08:13:45 +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;
Conductor.songPosition = swagSong.getTimeWithDiff() - Conductor.offset;
Conductor.update(swagSong.getTimeWithDiff() - Conductor.offset);
// Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
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!
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()

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});
}
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;
// do controls.PAUSE | controls.ACCEPT instead?

View file

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

View file

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

View file

@ -88,6 +88,10 @@ typedef PlayStateParams =
* @default `false`
*/
?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 startTimestamp:Float = 0.0;
public var isSubState(get, null):Bool;
function get_isSubState():Bool
@ -471,6 +477,7 @@ class PlayState extends MusicBeatSubState
if (params.targetCharacter != null) currentPlayerId = params.targetCharacter;
isPracticeMode = params.practiceMode ?? 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.
}
@ -560,7 +567,7 @@ class PlayState extends MusicBeatSubState
// Prepare the Conductor.
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.
initCameras();
@ -669,7 +676,7 @@ class PlayState extends MusicBeatSubState
FlxG.sound.music.pause();
vocals.pause();
FlxG.sound.music.time = 0;
FlxG.sound.music.time = (startTimestamp);
vocals.time = 0;
FlxG.sound.music.volume = 1;
@ -700,8 +707,8 @@ class PlayState extends MusicBeatSubState
{
if (isInCountdown)
{
Conductor.songPosition += elapsed * 1000;
if (Conductor.songPosition >= 0) startSong();
Conductor.update(Conductor.songPosition + elapsed * 1000);
if (Conductor.songPosition >= startTimestamp) startSong();
}
}
else
@ -1067,7 +1074,8 @@ class PlayState extends MusicBeatSubState
// super.stepHit() returns false if a module cancelled the event.
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(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.
*/
function regenNoteData():Void
function regenNoteData(startTime:Float = 0):Void
{
Highscore.tallies.combo = 0;
Highscore.tallies = new Tallies();
@ -1531,6 +1539,8 @@ class PlayState extends MusicBeatSubState
for (songNote in currentChart.notes)
{
var strumTime:Float = songNote.time;
if (strumTime < startTime) continue; // Skip notes that are before the start time.
var noteData:Int = songNote.getDirection();
var playerNote:Bool = true;
@ -1617,14 +1627,22 @@ class PlayState extends MusicBeatSubState
}
FlxG.sound.music.onComplete = endSong;
FlxG.sound.music.play(false, startTimestamp);
trace('Playing vocals...');
add(vocals);
vocals.play();
resyncVocals();
#if discord_rpc
// Updating Discord Rich Presence (with Time Left)
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
#end
if (startTimestamp > 0)
{
FlxG.sound.music.time = startTimestamp;
handleSkippedNotes();
}
}
/**
@ -1640,7 +1658,7 @@ class PlayState extends MusicBeatSubState
Conductor.update();
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> = [];
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,
* and then processed here.
@ -2064,11 +2099,10 @@ class PlayState extends MusicBeatSubState
if (event.eventCanceled) return;
health -= Constants.HEALTH_MISS_PENALTY;
songScore -= 10;
if (!isPracticeMode)
{
songScore -= 10;
// messy copy paste rn lol
var pressArray:Array<Bool> = [
controls.NOTE_LEFT_P,
@ -2139,11 +2173,10 @@ class PlayState extends MusicBeatSubState
if (event.eventCanceled) return;
health += event.healthChange;
songScore += event.scoreChange;
if (!isPracticeMode)
{
songScore += event.scoreChange;
var pressArray:Array<Bool> = [
controls.NOTE_LEFT_P,
controls.NOTE_DOWN_P,
@ -2282,11 +2315,10 @@ class PlayState extends MusicBeatSubState
playerStrumline.playNoteSplash(daNote.noteData.getDirection());
}
// Only add the score if you're not on practice mode
songScore += score;
if (!isPracticeMode)
{
songScore += score;
// TODO: Input splitter uses old input system, make it pull from the precise input queue directly.
var pressArray:Array<Bool> = [
controls.NOTE_LEFT_P,
@ -2643,30 +2675,16 @@ class PlayState extends MusicBeatSubState
{
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);
/**
*
// 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();
}
#end

View file

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

View file

@ -400,6 +400,11 @@ class ChartEditorState extends HaxeUIState
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.
*/
@ -1305,6 +1310,9 @@ class ChartEditorState extends HaxeUIState
addUIChangeListener('menubarItemDownscroll', event -> isViewDownscroll = event.value);
setUICheckboxSelected('menubarItemDownscroll', isViewDownscroll);
addUIChangeListener('menubarItemPlaytestStartTime', event -> playtestStartTime = event.value);
setUICheckboxSelected('menubarItemPlaytestStartTime', playtestStartTime);
addUIChangeListener('menuBarItemThemeLight', function(event:UIEvent) {
if (event.target.value) currentTheme = ChartEditorTheme.Light;
});
@ -3049,6 +3057,9 @@ class ChartEditorState extends HaxeUIState
*/
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);
subStateClosed.add(fixCamera);
@ -3061,6 +3072,7 @@ class ChartEditorState extends HaxeUIState
// targetCharacter: targetCharacter,
practiceMode: true,
minimalMode: minimal,
startTimestamp: startTimestamp,
}));
}