diff --git a/OemLoader/MainWindow.cs b/OemLoader/MainWindow.cs index 9f1796d..9570cb8 100644 --- a/OemLoader/MainWindow.cs +++ b/OemLoader/MainWindow.cs @@ -1,18 +1,61 @@ using Brutal.ImGuiApi; +using Brutal.Numerics; +using KSA; namespace OemLoader; public class MainWindow(string title) { private readonly ImInputString _filename = new(short.MaxValue); + private List? _oem; + private string? _errorMessage = null; - public void Render(double dt) + public void Render() { if (!ProgramPatch.WindowOpen) return; if (ImGui.Begin(title, ref ProgramPatch.WindowOpen)) { ImGui.InputTextWithHint("OEM file path", "/home/user/ephemeris.asc", _filename); + if (ImGui.Button("Load OEM file")) + { + try + { + _oem = OemDataLine.ReadFile(_filename.ToString()); + _errorMessage = null; + } + catch (Exception e) + { + _errorMessage = e.ToString(); + } + } + + if (_errorMessage is not null) + { + ImGui.TextColored(new float4(0.863f, 0.196f, 0.184f, 0f), _errorMessage); + } + + if (_oem is not null && Program.ControlledVehicle is not null) + { + ImGui.Text($"Loaded ephemeris valid from {_oem.First().Epoch} to {_oem.Last().Epoch}"); + var now = DateTime.UtcNow; + if (_oem.First().Epoch <= now && now <= _oem.Last().Epoch) + { + if (ImGui.Button("Go to now")) + { + var l = _oem.TakeWhile(line => line.Epoch < now).Last(); + + var orb = l.AsOrbit(Program.ControlledVehicle.Parent, + Program.ControlledVehicle.Orbit.OrbitLineColor); + var dt = (now - OemLoader.KsaEpoch).TotalSeconds - Universe.GetElapsedSeconds(); + Universe.CompleteAndApplyPreviousOrbitSolvers(); + Universe.ResetSimulationSpeed(); + Universe.ExecuteNextOrbitSolvers(dt); + Universe.CompleteAndApplyPreviousOrbitSolvers(); + Program.ControlledVehicle.Teleport(orb, null, null); + } + } + } } ImGui.End(); } diff --git a/OemLoader/OemDataLine.cs b/OemLoader/OemDataLine.cs new file mode 100644 index 0000000..210744f --- /dev/null +++ b/OemLoader/OemDataLine.cs @@ -0,0 +1,59 @@ +using System.Globalization; +using System.Text.RegularExpressions; +using Brutal.Numerics; +using KSA; + +namespace OemLoader; + +public partial struct OemDataLine +{ + public DateTime Epoch; + public double3 PositionKmS; + public double3 VelocityKmS; + + public OemDataLine(string line) + { + var components = line.Split(' ', '\t'); + Epoch = DateTime.ParseExact(components[0], "yyyy-MM-dd'T'HH:mm:ss.FFF", CultureInfo.InvariantCulture); + + PositionKmS = new double3( + double.Parse(components[1]), + double.Parse(components[2]), + double.Parse(components[3]) + ); + VelocityKmS = new double3( + double.Parse(components[4]), + double.Parse(components[5]), + double.Parse(components[6]) + ); + } + + public Orbit AsOrbit(IParentBody parentBody, byte4 colour) + { + return Orbit.CreateFromStateCci( + parentBody, + new SimTime((Epoch - OemLoader.KsaEpoch).TotalSeconds), + PositionKmS * 1000, + VelocityKmS * 1000, + colour + ); + } + + [GeneratedRegex(@"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d{3})?(\s+-?\d+(.\d+)){6,}$")] + private static partial Regex OemLineRegex { get; } + + public static List ReadFile(string path) + { + var data = new List(); + var lines = File.ReadAllLines(path); + foreach (var line in lines) + { + if (OemLineRegex.IsMatch(line)) + { + data.Add(new OemDataLine(line)); + } + } + + return data; + } +} \ No newline at end of file diff --git a/OemLoader/OemLoader.cs b/OemLoader/OemLoader.cs index a4641b2..364791c 100644 --- a/OemLoader/OemLoader.cs +++ b/OemLoader/OemLoader.cs @@ -11,6 +11,8 @@ namespace OemLoader; [StarMapMod] public class OemLoader { + public static readonly DateTime KsaEpoch = new(2025, 11, 30, 0, 0, 0); + private readonly string _version; private readonly MainWindow _window; private static Harmony? _harmony = new("OemLoader"); @@ -36,7 +38,7 @@ public class OemLoader [StarMapAfterGui] public void AfterGui(double dt) { - _window.Render(dt); + _window.Render(); } [UsedImplicitly] diff --git a/OemLoader/ProgramPatch.cs b/OemLoader/ProgramPatch.cs index 5514b93..82a12a1 100644 --- a/OemLoader/ProgramPatch.cs +++ b/OemLoader/ProgramPatch.cs @@ -19,10 +19,10 @@ internal static class ProgramPatch [UsedImplicitly] [HarmonyPatch(nameof(Program.DrawMenuBar))] [HarmonyTranspiler] - private static IEnumerable DrawMenuBarTranspiler(IEnumerable instructions, ILGenerator generator) + private static IEnumerable DrawMenuBarTranspiler(IEnumerable instructions) { // Find the menu item that we want to add ours after - var matcher = new CodeMatcher(instructions, generator); + var matcher = new CodeMatcher(instructions); matcher.MatchEndForward( CodeMatch.Calls(() => Staging.Toggle()), new CodeMatch(OpCodes.Nop) diff --git a/OemLoader/UniversePatch.cs b/OemLoader/UniversePatch.cs new file mode 100644 index 0000000..6bb0c38 --- /dev/null +++ b/OemLoader/UniversePatch.cs @@ -0,0 +1,31 @@ +using HarmonyLib; +using JetBrains.Annotations; +using KSA; + +namespace OemLoader; + +[UsedImplicitly] +[HarmonyPatch(typeof(Universe))] +internal static class UniversePatch +{ + + // Patch to allow going back in time + // Without this patch, Universe._achievedSpeedFraction will end up as a large negative value after attempting to + // go backwards in time, and stuff will start breaking. + [UsedImplicitly] + [HarmonyPatch(nameof(Universe.CompleteAndApplyPreviousOrbitSolvers))] + [HarmonyTranspiler] + private static IEnumerable CompleteAndApplyPreviousOrbitSolversTranspiler( + IEnumerable instructions) + { + var matcher = new CodeMatcher(instructions); + return matcher.MatchStartForward( + CodeMatch.LoadsField(AccessTools.Field(typeof(Universe), "_nextPlayerDeltaTime")) + ) + .InsertAfter( + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + CodeInstruction.Call(() => Math.Abs(0.0d)) + ) + .InstructionEnumeration(); + } +} \ No newline at end of file