Skip to content

Commit

Permalink
feat: Add save-state support for cycle commands
Browse files Browse the repository at this point in the history
  • Loading branch information
psyGamer committed Feb 4, 2025
1 parent bac825e commit 1f7f384
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static class SpeedrunToolInterop {
private static bool disallowUnsafeInput;
private static Random auraRandom;
private static bool betterInvincible = false;
private static Dictionary<string, (float StartTimeActive, float StartRawDeltaTime, int CurrentIndex, float[] DeltaTimes)>? cycleData;

[Load]
private static void Load() {
Expand All @@ -64,6 +65,7 @@ public static void AddSaveLoadAction() {
disallowUnsafeInput = SafeCommand.DisallowUnsafeInput;
auraRandom = DesyncFixer.AuraHelperSharedRandom.DeepCloneShared();
betterInvincible = Manager.Running && BetterInvincible.Invincible;
cycleData = CycleCommands.CycleData.DeepCloneShared();
};
Action<Dictionary<Type, Dictionary<string, object>>, Level> load = (_, _) => {
EntityDataHelper.CachedEntityData = savedEntityData.DeepCloneShared();
Expand All @@ -86,6 +88,10 @@ public static void AddSaveLoadAction() {
SafeCommand.DisallowUnsafeInput = disallowUnsafeInput;
DesyncFixer.AuraHelperSharedRandom = auraRandom.DeepCloneShared();
BetterInvincible.Invincible = Manager.Running && betterInvincible;
CycleCommands.CycleData.Clear();
foreach ((string key, var value) in cycleData) {
CycleCommands.CycleData[key] = value.DeepCloneShared();
}
};
Action clear = () => {
savedEntityData = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ private enum CycleType { TimeActiveInterval }

// The delta time of every frame between a WaitCycle and RequireCycle needs to be tracked,
// to accurately calculate the required wait frames for conditions using TimeActive
private static readonly Dictionary<string, (float StartTimeActive, float StartRawDeltaTime, int CurrentIndex, float[] DeltaTimes)> cycleData = new();
internal static readonly Dictionary<string, (float StartTimeActive, float StartRawDeltaTime, int CurrentIndex, float[] DeltaTimes)> CycleData = new();

[ClearInputs]
private static void ClearInputs() {
waitCycles.Clear();
cycleData.Clear();
CycleData.Clear();
}

[ParseFileEnd]
private static void ParseFileEnd() {
foreach (string cycle in waitCycles.Keys) {
if (!cycleData.ContainsKey(cycle)) {
if (!CycleData.ContainsKey(cycle)) {
// TODO: Display path / line of mismatching WaitCycle command
AbortTas("No matching RequireCycle for WaitCycle");
}
Expand All @@ -54,12 +54,12 @@ private static void ParseFileEnd() {
private static void PostSceneUpdate(Scene scene) {
var controller = Manager.Controller;

foreach (string cycle in cycleData.Keys) {
var data = cycleData[cycle];
foreach (string cycle in CycleData.Keys) {
var data = CycleData[cycle];

int startFrame = waitCycles[cycle];
int duration = data.DeltaTimes.Length;
if (controller.CurrentFrameInTas < startFrame || controller.CurrentFrameInTas >= startFrame + duration) {
if (controller.CurrentFrameInTas < startFrame || controller.CurrentFrameInTas >= startFrame + duration || data.CurrentIndex >= duration) {
continue;
}

Expand All @@ -70,12 +70,14 @@ private static void PostSceneUpdate(Scene scene) {

data.DeltaTimes[data.CurrentIndex++] = Engine.DeltaTime;

cycleData[cycle] = data;
CycleData[cycle] = data;
}
}

// WaitCycle, Name, Input
[TasCommand("WaitCycle", ExecuteTiming = ExecuteTiming.Parse, MetaDataProvider = typeof(WaitMeta))]
/// WaitCycle, Name, Input
///
/// Exclude from checksum, to avoid a cycle change causing a save-state clear
[TasCommand("WaitCycle", ExecuteTiming = ExecuteTiming.Parse, CalcChecksum = false, MetaDataProvider = typeof(WaitMeta))]
private static void WaitCycle(CommandLine commandLine, int studioLine, string filePath, int fileLine) {
if (commandLine.Arguments.Length < 1) {
AbortTas("Expected cycle name");
Expand All @@ -101,7 +103,7 @@ private static void WaitCycle(CommandLine commandLine, int studioLine, string fi
}
}

// RequireCycle, Name, TimeActiveInterval, Interval, Offset
/// RequireCycle, Name, TimeActiveInterval, Interval, Offset
[TasCommand("RequireCycle", ExecuteTiming = ExecuteTiming.Parse | ExecuteTiming.Runtime, MetaDataProvider = typeof(RequireMeta))]
private static void RequireCycle(CommandLine commandLine, int studioLine, string filePath, int fileLine) {
switch (commandLine.Arguments.Length) {
Expand All @@ -122,12 +124,12 @@ private static void RequireCycle(CommandLine commandLine, int studioLine, string
AbortTas($"Cycle '{name}' has no corresponding WaitCycle");
return;
}
if (cycleData.ContainsKey(name)) {
if (CycleData.ContainsKey(name)) {
AbortTas($"Cycle '{name}' has already used with a RequireCycle");
return;
}

cycleData[name] = (0.0f, 0.0f, 0, new float[controller.CurrentParsingFrame - waitCycles[name]]);
CycleData[name] = (0.0f, 0.0f, 0, new float[controller.CurrentParsingFrame - waitCycles[name]]);
}

if (!Enum.TryParse<CycleType>(commandLine.Arguments[1], out var type)) {
Expand Down Expand Up @@ -169,7 +171,7 @@ private static void RequireCycle(CommandLine commandLine, int studioLine, string
return;
}

var data = cycleData[name];
var data = CycleData[name];

// First guess the amount of frames we need to wait for the desired interval
// Then validate and adjust to the correct value by applying the entire delta-time chain since the WaitCycle
Expand Down

0 comments on commit 1f7f384

Please sign in to comment.