Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SeedRescue v1.0.0
SeedRescue.dll
Decompiled 2 days agousing System; using System.Collections; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SeedRescue")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("SeedRescue")] [assembly: AssemblyTitle("SeedRescue")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SeedRescue { [BepInPlugin("trepidrock.seedrescue", "SeedRescue", "1.0.0")] [BepInIncompatibility("daltoncurtis.seedrecoveryfix")] public class Plugin : BaseUnityPlugin { public const string Guid = "trepidrock.seedrescue"; public const string Name = "SeedRescue"; public const string Version = "1.0.0"; internal static Plugin Instance; internal static ManualLogSource Log; internal ConfigEntry<bool> AutoRecovery; internal ConfigEntry<float> LandingTimeout; internal ConfigEntry<int> AutoRetries; internal ConfigEntry<string> CommandWord; internal ConfigEntry<bool> AutoLand; internal ConfigEntry<float> RelandDelay; internal ConfigEntry<float> LeaveFallback; internal ConfigEntry<float> OrbitTimeout; private void Awake() { //IL_013e: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; AutoRecovery = ((BaseUnityPlugin)this).Config.Bind<bool>("Watchdog", "AutoRecovery", true, "Automatically force a reseed when the ship does not land in time (host only)."); LandingTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("Watchdog", "LandingTimeoutSeconds", 180f, "Seconds from pulling the lever until the ship must have landed. Raise it for very heavy modpacks."); AutoRetries = ((BaseUnityPlugin)this).Config.Bind<int>("Watchdog", "AutoRetries", 1, "Automatic retries (orbit + land again with a new seed) before only returning to orbit."); CommandWord = ((BaseUnityPlugin)this).Config.Bind<string>("Reseed", "TerminalCommand", "reseed", "Ship terminal command (host only). '<cmd>' = orbit + land again, '<cmd> orbit' = orbit only."); AutoLand = ((BaseUnityPlugin)this).Config.Bind<bool>("Reseed", "AutoLand", true, "Land again automatically after a reseed (never on deadline day)."); RelandDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Reseed", "RelandDelaySeconds", 6f, "Seconds to wait in orbit before landing again."); LeaveFallback = ((BaseUnityPlugin)this).Config.Bind<float>("Reseed", "LeaveFallbackSeconds", 12f, "If the ship-leave animation has not finished after this many seconds, unload the moon directly."); OrbitTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("Reseed", "OrbitTimeoutSeconds", 120f, "Maximum seconds to wait for everyone to get back to orbit."); ((Component)this).gameObject.AddComponent<Reseeder>(); new Harmony("trepidrock.seedrescue").PatchAll(typeof(Patches)); Log.LogInfo((object)("SeedRescue 1.0.0 loaded. Terminal command: '" + CommandWord.Value + "'.")); } internal static void Chat(string msg) { Log.LogInfo((object)("[Chat] " + msg)); try { if ((Object)(object)HUDManager.Instance != (Object)null && (Object)(object)StartOfRound.Instance != (Object)null && ((NetworkBehaviour)StartOfRound.Instance).IsServer) { HUDManager.Instance.AddTextToChatOnServer("[Seed Rescue] " + msg, -1); } } catch (Exception ex) { Log.LogDebug((object)("Chat failed: " + ex.Message)); } } } internal class Reseeder : MonoBehaviour { internal static Reseeder Instance; internal static bool ShipHasLeftCalled; private Coroutine routine; private float leverPulledAt = -1f; private int failedLandings; private bool wasInOrbit; internal bool Busy => routine != null; private void Awake() { Instance = this; } private void Update() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || !((NetworkBehaviour)instance).IsServer) { leverPulledAt = -1f; wasInOrbit = false; return; } bool inShipPhase = instance.inShipPhase; if (wasInOrbit && !inShipPhase && !Busy) { leverPulledAt = Time.realtimeSinceStartup; Plugin.Log.LogInfo((object)"Watchdog armed (landing started)."); } wasInOrbit = inShipPhase; if (leverPulledAt < 0f || Busy) { return; } if (instance.shipHasLanded) { leverPulledAt = -1f; failedLandings = 0; Plugin.Log.LogInfo((object)"Watchdog disarmed (ship landed)."); } else if (instance.inShipPhase) { leverPulledAt = -1f; Plugin.Log.LogInfo((object)"Watchdog disarmed (back in orbit)."); } else if (Plugin.Instance.AutoRecovery.Value && !(Time.realtimeSinceStartup - leverPulledAt < Mathf.Max(30f, Plugin.Instance.LandingTimeout.Value))) { leverPulledAt = -1f; failedLandings++; bool flag = failedLandings <= Mathf.Max(0, Plugin.Instance.AutoRetries.Value); Plugin.Log.LogWarning((object)$"Ship did not land in time. Auto recovery #{failedLandings} (reland={flag})."); if (!flag) { failedLandings = 0; } Begin(flag, "watchdog"); } } internal void Begin(bool reland, string source) { if (!Busy) { leverPulledAt = -1f; routine = ((MonoBehaviour)this).StartCoroutine(ReseedRoutine(reland, source)); } } private IEnumerator ReseedRoutine(bool reland, string source) { Plugin cfg = Plugin.Instance; StartOfRound so = StartOfRound.Instance; Plugin.Log.LogWarning((object)$"Forced reseed from {source}. Current seed: {so.randomMapSeed}"); Plugin.Chat("Stuck load: returning to orbit..."); ShipHasLeftCalled = false; try { int num = (int)(((Object)(object)so.localPlayerController != (Object)null) ? so.localPlayerController.playerClientId : 0); so.EndGameClientRpc(num); } catch (Exception ex) { Plugin.Log.LogWarning((object)("EndGameClientRpc failed: " + ex.Message)); } yield return WaitUntilOrTimeout(() => ShipHasLeftCalled || so.inShipPhase, cfg.LeaveFallback.Value); if (!ShipHasLeftCalled && !so.inShipPhase) { Plugin.Log.LogWarning((object)"Leave animation did not finish; calling ShipHasLeft() directly."); try { so.ShipHasLeft(); } catch (Exception ex2) { Plugin.Log.LogError((object)("ShipHasLeft failed: " + ex2)); } } yield return WaitUntilOrTimeout(() => so.inShipPhase && !so.shipIsLeaving && !so.newGameIsLoading, Mathf.Max(10f, cfg.OrbitTimeout.Value)); if (!so.inShipPhase) { Plugin.Chat("Could not return to orbit. A player may be desynced: restart the lobby."); Plugin.Log.LogError((object)"Timed out waiting for orbit."); routine = null; yield break; } int randomMapSeed = so.randomMapSeed; so.ChooseNewRandomMapSeed(); Plugin.Log.LogWarning((object)$"Back in orbit. Seed {randomMapSeed} -> {so.randomMapSeed}"); bool flag = (Object)(object)TimeOfDay.Instance != (Object)null && TimeOfDay.Instance.daysUntilDeadline <= 0; if (!reland || !cfg.AutoLand.Value || flag) { Plugin.Chat(flag ? "In orbit with a new seed (deadline day: pull the lever yourselves)." : "In orbit with a new seed. Pull the lever to land."); routine = null; yield break; } yield return (object)new WaitForSecondsRealtime(Mathf.Max(2f, cfg.RelandDelay.Value)); routine = null; if (!so.inShipPhase || so.travellingToNewLevel || so.newGameIsLoading) { Plugin.Chat("In orbit with a new seed. Pull the lever to land."); yield break; } Plugin.Chat("Landing again with a new seed..."); so.StartGameServerRpc(); } private static IEnumerator WaitUntilOrTimeout(Func<bool> condition, float seconds) { float end = Time.realtimeSinceStartup + Mathf.Max(1f, seconds); while (!condition() && Time.realtimeSinceStartup < end) { yield return null; } } internal string HandleCommand(string args) { StartOfRound instance = StartOfRound.Instance; string value = Plugin.Instance.CommandWord.Value; if ((Object)(object)instance == (Object)null) { return "Not in a lobby."; } if (!((NetworkBehaviour)instance).IsServer) { return "Only the HOST can use '" + value + "'."; } if (Busy) { return "A reseed is already in progress, please wait."; } if (instance.inShipPhase) { int randomMapSeed = instance.randomMapSeed; instance.ChooseNewRandomMapSeed(); return $"You are in orbit, nothing is stuck.\nSeed rerolled: {randomMapSeed} -> {instance.randomMapSeed}\nPull the lever to land."; } if (instance.shipHasLanded) { return "The ship already landed and the moon loaded fine.\nTake off with the lever if you want another seed."; } bool flag = args.IndexOf("orbit", StringComparison.OrdinalIgnoreCase) < 0 && Plugin.Instance.AutoLand.Value; Begin(flag, "terminal"); return "FORCING RESEED\n" + (flag ? "Returning to orbit, then landing again with a new seed." : "Returning to orbit. Pull the lever to land with a new seed.") + "\n(This counts as a finished day.)"; } } [HarmonyPatch] internal static class Patches { [HarmonyPatch(typeof(StartOfRound), "ShipHasLeft")] [HarmonyPostfix] private static void ShipHasLeftPostfix() { Reseeder.ShipHasLeftCalled = true; } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] [HarmonyPrefix] private static bool ParsePlayerSentencePrefix(Terminal __instance, ref TerminalNode __result) { try { string text = (Plugin.Instance.CommandWord.Value ?? "").Trim(); if (text.Length == 0 || (Object)(object)Reseeder.Instance == (Object)null) { return true; } string text2 = __instance.screenText.text; int num = Mathf.Clamp(__instance.textAdded, 0, text2.Length); string[] array = text2.Substring(text2.Length - num).Trim().Split(new char[1] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0 || !array[0].Equals(text, StringComparison.OrdinalIgnoreCase)) { return true; } TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>(); val.displayText = "\n\n" + Reseeder.Instance.HandleCommand((array.Length > 1) ? array[1] : "") + "\n\n"; val.clearPreviousText = true; __result = val; return false; } catch (Exception ex) { Plugin.Log.LogError((object)("Terminal command failed: " + ex)); return true; } } } }