Decompiled source of SeedRescue v1.0.0

SeedRescue.dll

Decompiled 2 days ago
using 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;
			}
		}
	}
}