using System.Linq; using System.Text; using HarmonyLib; using Model; using Model.Physics; using UI.Console; namespace RailroaderPhysicsOverhaul; /// /// Hooks into ConsoleCommandHandler._HandleSlashCommand to add /rpf commands /// before the game's switch block sees them. /// [HarmonyPatch(typeof(ConsoleCommandHandler))] [HarmonyPatch("_HandleSlashCommand")] static class ConsoleCommandPatch { static bool Prefix(string[] comps, ref string __result) { if (comps.Length == 0 || comps[0].ToLower() != "/rpf") return true; // not our command, let original handle it __result = RpfCommands.Handle(comps); return false; // swallow — don't pass to original } } static class RpfCommands { internal static string Handle(string[] comps) { if (comps.Length < 2) return Usage(); return comps[1].ToLower() switch { "freeze" => Freeze(), "unfreeze" => Unfreeze(), "dump" => Dump(), "timing" => PhysicsTimer.GetReport(), "overlay" => ToggleOverlay(), "forceactive" => ToggleForceActive(), "lod" => SetLod(comps), "help" => Usage(), _ => $"Unknown subcommand '{comps[1]}'. {Usage()}", }; } static string Freeze() { var (car, set) = GetSelection(); if (car == null) return "No car selected."; if (set == null) return $"{car.id} has no IntegrationSet."; // Zero velocities so cars stop cleanly before we freeze the solver. set.SetVelocity(0f, set.Cars.ToList()); bool added = ConsistFreezer.Freeze(set.Id); return added ? $"Froze set #{set.Id} ({set.NumberOfCars} cars). Use /rpf unfreeze to release." : $"Set #{set.Id} was already frozen."; } static string Unfreeze() { var (car, set) = GetSelection(); if (car == null) return "No car selected."; if (set == null) return $"{car.id} has no IntegrationSet."; bool removed = ConsistFreezer.Unfreeze(set.Id); return removed ? $"Unfroze set #{set.Id} — solver resumed." : $"Set #{set.Id} was not frozen."; } static string Dump() { var (car, set) = GetSelection(); if (car == null) return "No car selected."; if (set == null) return $"{car.id} has no IntegrationSet."; var sb = new StringBuilder(); sb.AppendLine($"=== IntegrationSet #{set.Id} | {set.NumberOfCars} cars | frozen={ConsistFreezer.IsFrozen(set.Id)} ==="); float totalWeightLbs = 0f; int idx = 0; foreach (Car c in set.Cars) { totalWeightLbs += c.Weight; float mph = c.velocity * 2.23694f; string coupled = c.EndGearA.IsCoupled ? "A" : ""; coupled += c.EndGearB.IsCoupled ? "B" : ""; if (coupled == "") coupled = "solo"; sb.AppendLine($" [{idx++}] {c.id} {c.DisplayName} | {mph:F1}mph | {c.Weight:F0}lb | coupled={coupled} | loc={c.WheelBoundsF.segment?.id}"); } sb.AppendLine($"Total: {totalWeightLbs / 2000f:F0} short tons"); return sb.ToString(); } static (Car car, IntegrationSet set) GetSelection() { Car car = TrainController.Shared?.SelectedCar; return (car, car?.set); } static string ToggleOverlay() { var gui = PhysicsOverlayGUI.Instance; if (gui == null) return "Overlay not initialized."; gui.Visible = !gui.Visible; return $"Overlay {(gui.Visible ? "shown" : "hidden")}."; } static string SetLod(string[] comps) { if (comps.Length < 3) { ConsistLOD.Enabled = !ConsistLOD.Enabled; return $"LOD {(ConsistLOD.Enabled ? "enabled" : "disabled")}. Threshold: {ConsistLOD.DistanceThreshold:F0}m. Usage: /rpf lod "; } if (comps[2].ToLower() == "off") { ConsistLOD.Enabled = false; return "LOD disabled."; } if (float.TryParse(comps[2], out float dist) && dist > 0f) { ConsistLOD.DistanceThreshold = dist; ConsistLOD.Enabled = true; return $"LOD enabled, fast path for interior cars beyond {dist:F0}m."; } return "Usage: /rpf lod or /rpf lod off"; } static string ToggleForceActive() { PhysicsTimer.ForceActive = !PhysicsTimer.ForceActive; return PhysicsTimer.ForceActive ? "ForceActive ON — stock optimizer bypassed, solver always runs." : "ForceActive OFF — stock optimizer active."; } static string Usage() => "Usage: /rpf "; }