From eccfd12d8e80b616569b88b99185674f31c42455 Mon Sep 17 00:00:00 2001 From: psyGamer Date: Fri, 21 Feb 2025 16:06:11 +0100 Subject: [PATCH] fix: Playback not accounting for TAS recording --- .../CommunicationAdapterCeleste.cs | 2 +- .../Source/ModInterop/TASRecorderInterop.cs | 40 ++++++++++++++---- .../TAS/Input/Commands/RecordingCommand.cs | 42 +++++++------------ .../Source/TAS/Manager.cs | 12 ++++++ .../Source/TAS/Savestates.cs | 2 +- 5 files changed, 59 insertions(+), 39 deletions(-) diff --git a/CelesteTAS-EverestInterop/Source/Communication/CommunicationAdapterCeleste.cs b/CelesteTAS-EverestInterop/Source/Communication/CommunicationAdapterCeleste.cs index 7402efbb0..c746be027 100644 --- a/CelesteTAS-EverestInterop/Source/Communication/CommunicationAdapterCeleste.cs +++ b/CelesteTAS-EverestInterop/Source/Communication/CommunicationAdapterCeleste.cs @@ -313,7 +313,7 @@ private void ProcessRecordTAS(string fileName) { WriteRecordingFailed(RecordingFailedReason.TASRecorderNotInstalled); return; } - if (!TASRecorderInterop.FFmpegInstalled) { + if (!TASRecorderInterop.IsFFmpegInstalled) { WriteRecordingFailed(RecordingFailedReason.FFmpegNotInstalled); return; } diff --git a/CelesteTAS-EverestInterop/Source/ModInterop/TASRecorderInterop.cs b/CelesteTAS-EverestInterop/Source/ModInterop/TASRecorderInterop.cs index 2a48884f2..f2b3e184e 100644 --- a/CelesteTAS-EverestInterop/Source/ModInterop/TASRecorderInterop.cs +++ b/CelesteTAS-EverestInterop/Source/ModInterop/TASRecorderInterop.cs @@ -4,25 +4,47 @@ namespace TAS.ModInterop; -public static class TASRecorderInterop { +internal static class TASRecorderInterop { public static bool Installed => installed.Value; private static readonly Lazy installed = new(() => ModUtils.IsInstalled("TASRecorder")); - public static void StartRecording(string fileName = null) { - if (installed.Value) startRecording(fileName); + /// Starts a recording. + /// If is true or is false, this shouldn't be called. + /// The file name of the recording. If null, it's generated from "dd-MM-yyyy_HH-mm-ss" + public static void StartRecording(string? fileName = null) { + if (Installed) { + startRecording(fileName); + } } + + /// Stops a recording which was previously started. + /// If or is false, this shouldn't be called. public static void StopRecording() { - if (installed.Value) stopRecording(); + if (Installed) { + stopRecording(); + } } - public static void SetDurationEstimate(int frames) { - if (installed.Value) setDurationEstimate(frames); + + /// Sets the estimated amount of total frames. + /// This is used for the progress bar, but doesn't actually interact with the recording. + /// If or is false, this shouldn't be called. + /// The total amount of frames, excluding loading times. If set to null, there isn't a progress bar. + public static void SetDurationEstimate(int? frames) { + if (Installed) { + setDurationEstimate(frames ?? TASRecorderAPI.NoEstimate); + } } - public static bool Recording => installed.Value && isRecording(); - public static bool FFmpegInstalled => installed.Value && isFFmpegInstalled(); + /// Whether TAS Recorder is currently recording. + public static bool IsRecording => Installed && isRecording(); + + /// Whether TAS Recorder could properly load FFmpeg. + public static bool IsFFmpegInstalled => Installed && isFFmpegInstalled(); + + // These methods **must not** be called if 'Installed == false' [MethodImpl(MethodImplOptions.NoInlining)] - private static void startRecording(string fileName = null) => TASRecorderAPI.StartRecording(fileName); + private static void startRecording(string? fileName = null) => TASRecorderAPI.StartRecording(fileName); [MethodImpl(MethodImplOptions.NoInlining)] private static void stopRecording() => TASRecorderAPI.StopRecording(); [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/CelesteTAS-EverestInterop/Source/TAS/Input/Commands/RecordingCommand.cs b/CelesteTAS-EverestInterop/Source/TAS/Input/Commands/RecordingCommand.cs index 1a9600807..91ac0569a 100644 --- a/CelesteTAS-EverestInterop/Source/TAS/Input/Commands/RecordingCommand.cs +++ b/CelesteTAS-EverestInterop/Source/TAS/Input/Commands/RecordingCommand.cs @@ -8,37 +8,25 @@ namespace TAS.Input.Commands; -public static class RecordingCommand { +/// Manages recording TASes to video files with TAS Recorder +internal static class RecordingCommand { internal record RecordingTime { public int StartFrame = int.MaxValue; public int StopFrame = int.MaxValue; + public int Duration => StopFrame - StartFrame; } internal static readonly Dictionary RecordingTimes = new(); - // workaround the first few frames get skipped when there is a breakpoint after StartRecording command - public static bool StopFastForward { - get { - if (TASRecorderInterop.Recording) { - return true; - } - - return RecordingTimes.Values.Any(time => { - int currentFrame = Manager.Controller.CurrentFrameInTas; - return currentFrame > time.StartFrame - 60 && currentFrame <= time.StopFrame; - }); - } - } - - // "StartRecording" + /// "StartRecording" [TasCommand("StartRecording", ExecuteTiming = ExecuteTiming.Parse | ExecuteTiming.Runtime)] private static void StartRecording(CommandLine commandLine, int studioLine, string filePath, int fileLine) { if (ParsingCommand) { if (CommunicationWrapper.Connected && Manager.Running) { if (!TASRecorderInterop.Installed) { CommunicationWrapper.SendRecordingFailed(RecordingFailedReason.TASRecorderNotInstalled); - } else if (!TASRecorderInterop.FFmpegInstalled) { + } else if (!TASRecorderInterop.IsFFmpegInstalled) { CommunicationWrapper.SendRecordingFailed(RecordingFailedReason.FFmpegNotInstalled); } } @@ -47,8 +35,8 @@ private static void StartRecording(CommandLine commandLine, int studioLine, stri AbortTas("TAS Recorder isn't installed"); return; } - if (!TASRecorderInterop.FFmpegInstalled) { - AbortTas("FFmpeg libraries aren't properly installed"); + if (!TASRecorderInterop.IsFFmpegInstalled) { + AbortTas("FFmpeg isn't properly installed"); return; } @@ -61,17 +49,16 @@ private static void StartRecording(CommandLine commandLine, int studioLine, stri } } - RecordingTime time = new() { StartFrame = Manager.Controller.Inputs.Count }; + var time = new RecordingTime { StartFrame = Manager.Controller.Inputs.Count }; RecordingTimes[time.StartFrame] = time; } else { - if (TASRecorderInterop.Recording) { + if (TASRecorderInterop.IsRecording) { AbortTas("Tried to start recording, while already recording"); return; } TASRecorderInterop.StartRecording(); - if (RecordingTimes.TryGetValue(Manager.Controller.CurrentFrameInTas, out RecordingTime time) && - time.StartFrame != int.MaxValue && time.StopFrame != int.MaxValue) { + if (RecordingTimes.TryGetValue(Manager.Controller.CurrentFrameInTas, out var time) && time.StartFrame != int.MaxValue && time.StopFrame != int.MaxValue) { TASRecorderInterop.SetDurationEstimate(time.Duration); } @@ -79,7 +66,7 @@ private static void StartRecording(CommandLine commandLine, int studioLine, stri } } - // "StopRecording" + /// "StopRecording" [TasCommand("StopRecording", ExecuteTiming = ExecuteTiming.Parse | ExecuteTiming.Runtime)] private static void StopRecording(CommandLine commandLine, int studioLine, string filePath, int fileLine) { if (ParsingCommand) { @@ -89,8 +76,7 @@ private static void StopRecording(CommandLine commandLine, int studioLine, strin return; } - RecordingTime last = RecordingTimes.Last().Value; - + var last = RecordingTimes.Last().Value; if (last.StopFrame != int.MaxValue) { if (last.StopFrame == int.MaxValue) { AbortTas($"{errorText}StartRecording is required before another StopRecording"); @@ -110,7 +96,7 @@ private static void ParseFileEnd() { return; } - RecordingTime last = RecordingTimes.Last().Value; + var last = RecordingTimes.Last().Value; if (last.StopFrame == int.MaxValue) { last.StopFrame = Manager.Controller.Inputs.Count; } @@ -123,7 +109,7 @@ private static void Clear() { [DisableRun] private static void DisableRun() { - if (TASRecorderInterop.Recording) { + if (TASRecorderInterop.IsRecording) { TASRecorderInterop.StopRecording(); } } diff --git a/CelesteTAS-EverestInterop/Source/TAS/Manager.cs b/CelesteTAS-EverestInterop/Source/TAS/Manager.cs index 0b6b8b222..f830fdc84 100644 --- a/CelesteTAS-EverestInterop/Source/TAS/Manager.cs +++ b/CelesteTAS-EverestInterop/Source/TAS/Manager.cs @@ -12,6 +12,7 @@ using TAS.EverestInterop; using TAS.Input; using TAS.Input.Commands; +using TAS.ModInterop; using TAS.Module; using TAS.Utils; @@ -218,6 +219,13 @@ public static void UpdateMeta() { return; } + if (TASRecorderInterop.IsRecording) { + // Force recording at 1x playback + NextState = State.Running; + PlaybackSpeed = 1.0f; + return; + } + switch (CurrState) { case State.Running: if (Hotkeys.PauseResume.Pressed || Hotkeys.FrameAdvance.Pressed) { @@ -305,6 +313,10 @@ overworld.Next is OuiChapterSelect && UserIO.Saving || /// Determine if current TAS file is a draft private static bool IsDraft() { + if (TASRecorderInterop.IsRecording) { + return false; + } + // Require any FileTime or ChapterTime, alternatively MidwayFileTime or MidwayChapterTime at the end for the TAS to be counted as finished return Controller.Commands.Values .SelectMany(commands => commands) diff --git a/CelesteTAS-EverestInterop/Source/TAS/Savestates.cs b/CelesteTAS-EverestInterop/Source/TAS/Savestates.cs index 4b9e21e9b..41e2fb76c 100644 --- a/CelesteTAS-EverestInterop/Source/TAS/Savestates.cs +++ b/CelesteTAS-EverestInterop/Source/TAS/Savestates.cs @@ -129,7 +129,7 @@ public static void SaveState(bool byBreakpoint) { public static void LoadState() { // Don't load save-states while recording - if (TASRecorderInterop.Recording) { + if (TASRecorderInterop.IsRecording) { return; }