using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [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("ShipDoorTerminal")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.7.0")] [assembly: AssemblyInformationalVersion("1.0.7")] [assembly: AssemblyProduct("ShipDoorTerminal")] [assembly: AssemblyTitle("ShipDoorTerminal")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.7.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 ShipDoorTerminal { internal static class ManualPatches { internal static void Apply(Harmony harmony) { try { Prefix(typeof(Terminal), "OnSubmit", typeof(OnSubmitPatch), "Prefix"); Prefix(typeof(Terminal), "ParseWord", typeof(ParseWordPatch), "Prefix", new Type[2] { typeof(string), typeof(int) }); Prefix(typeof(Terminal), "ParsePlayerSentence", typeof(ParseSentencePatch), "Prefix"); Prefix(typeof(Terminal), "LoadNewNode", typeof(LoadNewNodePatch), "Prefix"); Postfix(typeof(Terminal), "Awake", typeof(TerminalLifecyclePatch), "AwakePostfix"); Postfix(typeof(Terminal), "Start", typeof(TerminalLifecyclePatch), "StartPostfix"); } catch (Exception arg) { Plugin.Log.LogError((object)$"ManualPatches.Apply failed: {arg}"); } void Postfix(Type type, string name, Type patchType, string method) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(type, name, (Type[])null, (Type[])null); Plugin.Log.LogInfo((object)("Resolve " + type.Name + "." + name + " => " + ((methodInfo == null) ? "NULL" : "ok"))); if (!(methodInfo == null)) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(patchType, method, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("Patched " + type.Name + "." + name + " postfix " + method)); } } void Prefix(Type type, string name, Type patchType, string method, Type[]? args = null) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown MethodInfo methodInfo = ((args == null) ? AccessTools.Method(type, name, (Type[])null, (Type[])null) : AccessTools.Method(type, name, args, (Type[])null)); Plugin.Log.LogInfo((object)("Resolve " + type.Name + "." + name + " => " + ((methodInfo == null) ? "NULL" : (methodInfo.IsPublic ? "public" : "nonpublic")))); if (!(methodInfo == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(patchType, method, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("Patched " + type.Name + "." + name + " prefix " + method)); } } } } internal static class TerminalInput { internal static string? LastSubmitted; private static readonly HashSet Commands = new HashSet(StringComparer.Ordinal) { "door", "doors", "opendoor", "open door", "door open", "dooropen", "closedoor", "close door", "door close", "doorclose" }; internal static string Normalize(string? raw) { if (string.IsNullOrEmpty(raw)) { return ""; } return Regex.Replace(Regex.Replace(raw.ToLowerInvariant(), "[^a-z0-9\\s]", " "), "\\s+", " ").Trim(); } internal static string Extract(Terminal terminal) { try { string text = (((Object)(object)terminal.screenText != (Object)null) ? terminal.screenText.text : null); if (string.IsNullOrEmpty(text)) { return ""; } if (terminal.textAdded > 0 && text.Length >= terminal.textAdded) { return Normalize(text.Substring(text.Length - terminal.textAdded)); } int num = text.LastIndexOf('\n'); return Normalize(((num >= 0) ? text.Substring(num + 1) : text).Trim().TrimStart('>', ' ')); } catch { return ""; } } internal static bool IsDoorCommand(string input) { return Commands.Contains(input); } } internal static class OnSubmitPatch { public static bool Prefix(Terminal __instance) { try { string text = (TerminalInput.LastSubmitted = TerminalInput.Extract(__instance)); Plugin.Log.LogInfo((object)$"[OnSubmit] captured='{text}' enabled={Plugin.Enabled?.Value}"); if (Plugin.Enabled == null || !Plugin.Enabled.Value) { return true; } if (!TerminalInput.IsDoorCommand(text)) { return true; } string text2 = DoorActions.Run(text); Plugin.Log.LogInfo((object)("[OnSubmit] handled '" + text + "'")); __instance.LoadNewNode(DoorActions.CreateDisplayNode(text2)); DoorActions.ReadyForNextCommand(__instance); return false; } catch (Exception arg) { Plugin.Log.LogWarning((object)$"[OnSubmit] {arg}"); return true; } } } internal static class ParseWordPatch { public static bool Prefix(string playerWord, int specificityRequired, ref TerminalKeyword __result) { if (Plugin.Enabled == null || !Plugin.Enabled.Value) { return true; } try { string text = TerminalInput.Normalize(playerWord); Plugin.V($"[ParseWord] '{playerWord}' -> '{text}' spec={specificityRequired}"); if (!TerminalInput.IsDoorCommand(text)) { return true; } __result = DoorActions.GetKeyword(text); Plugin.Log.LogInfo((object)("[ParseWord] returning door keyword for '" + text + "'")); return false; } catch (Exception arg) { Plugin.Log.LogWarning((object)$"[ParseWord] {arg}"); return true; } } } internal static class ParseSentencePatch { public static bool Prefix(Terminal __instance, ref TerminalNode __result) { if (Plugin.Enabled == null || !Plugin.Enabled.Value) { return true; } try { string text = TerminalInput.LastSubmitted; if (string.IsNullOrEmpty(text)) { text = TerminalInput.Extract(__instance); } Plugin.V("[ParseSentence] input='" + text + "'"); if (string.IsNullOrEmpty(text) || !TerminalInput.IsDoorCommand(text)) { return true; } string text2 = DoorActions.Run(text); __result = DoorActions.CreateDisplayNode(text2); TerminalInput.LastSubmitted = null; Plugin.Log.LogInfo((object)("[ParseSentence] handled '" + text + "'")); return false; } catch (Exception arg) { Plugin.Log.LogWarning((object)$"[ParseSentence] {arg}"); return true; } } } internal static class LoadNewNodePatch { private static int _reentry; public static void Prefix(TerminalNode node) { if ((Object)(object)node == (Object)null || _reentry > 0) { return; } try { if (Plugin.Enabled == null || !Plugin.Enabled.Value) { return; } if (!DoorActions.TryGetCommandForNode(node, out string cmd)) { Plugin.V("[LoadNewNode] unrelated"); return; } Plugin.Log.LogInfo((object)("[LoadNewNode] door keyword node for '" + cmd + "' — running action")); _reentry++; try { string text = DoorActions.Run(cmd); string text2 = (text.EndsWith("\n") ? text : (text + "\n")); if (!text2.EndsWith("\n\n")) { text2 += "\n"; } node.displayText = text2; node.clearPreviousText = true; node.acceptAnything = false; node.overrideOptions = false; } finally { _reentry--; } } catch (Exception arg) { Plugin.Log.LogWarning((object)$"[LoadNewNode] {arg}"); } } } internal static class TerminalLifecyclePatch { public static void AwakePostfix(Terminal __instance) { Plugin.Log.LogInfo((object)"[Terminal.Awake] postfix hit"); DoorActions.EnsureKeywordsRegistered(__instance); DoorActions.EnsureHelpText(__instance); } public static void StartPostfix(Terminal __instance) { Plugin.Log.LogInfo((object)"[Terminal.Start] postfix hit"); DoorActions.EnsureKeywordsRegistered(__instance); DoorActions.EnsureHelpText(__instance); } } internal static class DoorActions { private static readonly Dictionary Keywords = new Dictionary(); private static readonly Dictionary NodeCommands = new Dictionary(); private static bool _registered; private static bool _helpInjected; private const string HelpMarker = "[ShipDoorTerminal]"; private const string HelpFingerprint = ">DOOR"; private const string HelpBlock = ">DOOR\nToggle the ship hangar doors.\nAlso: OPENDOOR / CLOSEDOOR\n\n"; internal static TerminalNode CreateDisplayNode(string text) { TerminalNode obj = ScriptableObject.CreateInstance(); string text2 = text ?? ""; if (!text2.EndsWith("\n")) { text2 += "\n"; } if (!text2.EndsWith("\n\n")) { text2 += "\n"; } obj.displayText = text2; obj.clearPreviousText = true; obj.maxCharactersToType = 80; obj.acceptAnything = false; obj.overrideOptions = false; return obj; } internal static void ReadyForNextCommand(Terminal terminal) { try { if (!((Object)(object)terminal?.screenText == (Object)null)) { terminal.screenText.ActivateInputField(); ((Selectable)terminal.screenText).Select(); int num = ((terminal.screenText.text != null) ? terminal.screenText.text.Length : 0); terminal.screenText.caretPosition = num; terminal.screenText.selectionAnchorPosition = num; terminal.screenText.selectionFocusPosition = num; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[ReadyForNextCommand] " + ex.Message)); } } internal static bool TryGetCommandForNode(TerminalNode node, out string cmd) { if (NodeCommands.TryGetValue(node, out cmd)) { return true; } string text = node.displayText ?? ""; if (text.StartsWith("Ship door command:", StringComparison.OrdinalIgnoreCase)) { cmd = TerminalInput.Normalize(text.Substring("Ship door command:".Length)); return TerminalInput.IsDoorCommand(cmd); } cmd = ""; return false; } internal static TerminalKeyword GetKeyword(string word) { string text = (word.Contains(" ") ? word.Split(' ')[0] : word); if (Keywords.TryGetValue(text, out TerminalKeyword value) && (Object)(object)value != (Object)null) { return value; } EnsureKeyword(text); return Keywords[text]; } private static void EnsureKeyword(string word) { if (!Keywords.ContainsKey(word)) { TerminalNode val = CreateDisplayNode("Ship door command: " + word + "\n"); NodeCommands[val] = word; TerminalKeyword val2 = ScriptableObject.CreateInstance(); val2.word = word; val2.isVerb = false; val2.specialKeywordResult = val; Keywords[word] = val2; } } internal static void EnsureKeywordsRegistered(Terminal terminal) { try { if (terminal?.terminalNodes?.allKeywords == null) { Plugin.Log.LogInfo((object)"Keyword register skipped: allKeywords null"); return; } EnsureKeyword("door"); EnsureKeyword("doors"); EnsureKeyword("opendoor"); EnsureKeyword("closedoor"); EnsureKeyword("dooropen"); EnsureKeyword("doorclose"); if (_registered) { Plugin.V("Keywords already registered"); return; } List list = new List(terminal.terminalNodes.allKeywords); foreach (KeyValuePair kv in Keywords) { if (!list.Exists((TerminalKeyword k) => (Object)(object)k != (Object)null && k.word == kv.Key)) { list.Add(kv.Value); } } terminal.terminalNodes.allKeywords = list.ToArray(); _registered = true; Plugin.Log.LogInfo((object)$"Registered door keywords (allKeywords={list.Count}, trackedNodes={NodeCommands.Count})"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("EnsureKeywordsRegistered failed: " + ex.Message)); } } internal static void EnsureHelpText(Terminal terminal) { try { if (terminal?.terminalNodes?.specialNodes == null) { return; } int num = 0; for (int i = 0; i < terminal.terminalNodes.specialNodes.Count; i++) { if (TryApplyHelpToNode(terminal.terminalNodes.specialNodes[i], $"specialNodes[{i}]")) { num++; } } TerminalKeyword[] allKeywords = terminal.terminalNodes.allKeywords; if (allKeywords != null) { TerminalKeyword[] array = allKeywords; foreach (TerminalKeyword val in array) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.word)) { string word = val.word; if ((string.Equals(word, "help", StringComparison.OrdinalIgnoreCase) || string.Equals(word, "other", StringComparison.OrdinalIgnoreCase) || string.Equals(word, "others", StringComparison.OrdinalIgnoreCase)) && TryApplyHelpToNode(val.specialKeywordResult, "keyword:" + word)) { num++; } } } } if (num > 0) { _helpInjected = true; } else if (!_helpInjected) { Plugin.Log.LogInfo((object)"[Help] no command-list page found yet"); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Help] " + ex.Message)); } } private static bool IsCommandListPage(string page) { if (string.IsNullOrEmpty(page)) { return false; } bool num = page.IndexOf(">STORE", StringComparison.OrdinalIgnoreCase) >= 0; bool flag = page.IndexOf(">BESTIARY", StringComparison.OrdinalIgnoreCase) >= 0; bool flag2 = page.IndexOf(">STORAGE", StringComparison.OrdinalIgnoreCase) >= 0; bool flag3 = page.IndexOf(">OTHER", StringComparison.OrdinalIgnoreCase) >= 0; if (!num || !(flag || flag2 || flag3)) { return false; } return true; } private static bool TryApplyHelpToNode(TerminalNode? node, string label) { if ((Object)(object)node == (Object)null || string.IsNullOrEmpty(node.displayText)) { return false; } string text = StripLegacyHelpHeader(node.displayText); if (!IsCommandListPage(text)) { string text2 = StripDoorHelp(text); if (text2 != node.displayText) { node.displayText = text2; Plugin.Log.LogInfo((object)("[Help] stripped door docs from non-list " + label)); } return false; } if (text.IndexOf(">DOOR", StringComparison.OrdinalIgnoreCase) >= 0) { if (text != node.displayText) { node.displayText = text; } return false; } node.displayText = InjectHelp(text); Plugin.Log.LogInfo((object)("[Help] injected door docs into " + label)); return true; } private static string StripLegacyHelpHeader(string page) { if (string.IsNullOrEmpty(page) || page.IndexOf("[ShipDoorTerminal]", StringComparison.Ordinal) < 0) { return page; } return page.Replace("[ShipDoorTerminal]\n\n", "").Replace("[ShipDoorTerminal]\n", "").Replace("[ShipDoorTerminal]", ""); } private static string StripDoorHelp(string page) { if (string.IsNullOrEmpty(page) || page.IndexOf(">DOOR", StringComparison.OrdinalIgnoreCase) < 0) { return page; } if (page.Contains(">DOOR\nToggle the ship hangar doors.\nAlso: OPENDOOR / CLOSEDOOR\n\n")) { return page.Replace(">DOOR\nToggle the ship hangar doors.\nAlso: OPENDOOR / CLOSEDOOR\n\n", ""); } int num = page.IndexOf(">DOOR", StringComparison.OrdinalIgnoreCase); if (num < 0) { return page; } int num2 = page.IndexOf("\n\n", num); num2 = ((num2 >= 0) ? (num2 + 2) : page.Length); int num3 = num; if (num3 >= 2 && page[num3 - 2] == '\n' && page[num3 - 1] == '\n') { num3 -= 2; } else if (num3 >= 1 && page[num3 - 1] == '\n') { num3--; } return page.Substring(0, num3) + page.Substring(num2); } private static string InjectHelp(string page) { page = StripLegacyHelpHeader(page); int num = page.IndexOf(">OTHER", StringComparison.OrdinalIgnoreCase); if (num < 0) { num = page.IndexOf("OTHER", StringComparison.OrdinalIgnoreCase); } if (num >= 0) { int num2 = page.IndexOf("\n\n", num); if (num2 > num) { int num3 = num2 + 2; return page.Substring(0, num3) + ">DOOR\nToggle the ship hangar doors.\nAlso: OPENDOOR / CLOSEDOOR\n\n" + page.Substring(num3); } } if (!page.EndsWith("\n")) { page += "\n"; } if (!page.EndsWith("\n\n")) { page += "\n"; } return page + ">DOOR\nToggle the ship hangar doors.\nAlso: OPENDOOR / CLOSEDOOR\n\n"; } internal static string Run(string input) { switch (input) { case "door": case "doors": return Toggle(); case "dooropen": case "opendoor": case "door open": case "open door": return Set(wantClosed: false); case "closedoor": case "doorclose": case "close door": case "door close": return Set(wantClosed: true); default: return "Unknown door command.\n"; } } private static string Toggle() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return "Ship doors unavailable.\n"; } return Set(!instance.hangarDoorsClosed); } private static string Set(bool wantClosed) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return "Ship doors unavailable.\n"; } if (instance.inShipPhase) { return "Ship doors unavailable while in orbit.\n"; } HangarShipDoor val = Object.FindObjectOfType(); Plugin.Log.LogInfo((object)($"[Door] wantClosed={wantClosed}, hangarDoorsClosed={instance.hangarDoorsClosed}, " + $"shipDoorsEnabled={instance.shipDoorsEnabled}, doorNull={(Object)(object)val == (Object)null}, " + $"overheated={val?.overheated}, buttonsEnabled={val?.buttonsEnabled}, doorPower={val?.doorPower}")); if ((Object)(object)val != (Object)null && val.overheated) { return "Ship doors are overheated and cannot be toggled.\n"; } if (instance.hangarDoorsClosed == wantClosed) { if (!wantClosed) { return "Ship doors are already open.\n"; } return "Ship doors are already closed.\n"; } string text = (wantClosed ? "StopButton" : "StartButton"); if (TryPressDoorButton(text)) { Plugin.Log.LogInfo((object)$"[Door] pressed {text}; hangarDoorsClosed now={instance.hangarDoorsClosed}"); if (!wantClosed) { return "Opening ship doors...\n"; } return "Closing ship doors...\n"; } Plugin.Log.LogWarning((object)("[Door] " + text + " interact failed — trying HangarShipDoor + ServerRpc fallback")); try { if ((Object)(object)val != (Object)null) { if (wantClosed) { val.SetDoorClosed(); } else { val.SetDoorOpen(); } val.PlayDoorAnimation(wantClosed); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Door] HangarShipDoor fallback: " + ex.Message)); } try { instance.SetDoorsClosedServerRpc(wantClosed); Plugin.Log.LogInfo((object)"[Door] called SetDoorsClosedServerRpc"); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[Door] SetDoorsClosedServerRpc: " + ex2.Message)); try { instance.SetShipDoorsClosed(wantClosed); } catch (Exception ex3) { Plugin.Log.LogWarning((object)("[Door] SetShipDoorsClosed: " + ex3.Message)); } } Plugin.Log.LogInfo((object)$"[Door] after fallback: hangarDoorsClosed={instance.hangarDoorsClosed}"); if (!wantClosed) { return "Opening ship doors...\n"; } return "Closing ship doors...\n"; } private static bool TryPressDoorButton(string buttonName) { try { GameObject val = GameObject.Find(buttonName); if ((Object)(object)val == (Object)null) { val = GameObject.Find("Environment/HangarShip/AnimatedShipDoor/" + buttonName) ?? GameObject.Find("HangarShip/" + buttonName); } if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)("[Door] GameObject.Find('" + buttonName + "') returned null")); return false; } InteractTrigger componentInChildren = val.GetComponentInChildren(); if ((Object)(object)componentInChildren == (Object)null) { Plugin.Log.LogWarning((object)("[Door] no InteractTrigger on " + buttonName)); return false; } PlayerControllerB val2 = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning((object)"[Door] no local player for button interact"); return false; } UnityEvent onInteract = (UnityEvent)(object)componentInChildren.onInteract; if (onInteract != null) { onInteract.Invoke(val2); Plugin.Log.LogInfo((object)("[Door] invoked onInteract on " + buttonName)); return true; } componentInChildren.Interact(((Component)val2).transform); Plugin.Log.LogInfo((object)("[Door] called Interact() on " + buttonName)); return true; } catch (Exception arg) { Plugin.Log.LogWarning((object)$"[Door] TryPressDoorButton({buttonName}): {arg}"); return false; } } } [BepInPlugin("com.benhough.lethal.ShipDoorTerminal", "ShipDoorTerminal", "1.0.10")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.benhough.lethal.ShipDoorTerminal"; public const string ModName = "ShipDoorTerminal"; public const string ModVersion = "1.0.10"; private readonly Harmony _harmony = new Harmony("com.benhough.lethal.ShipDoorTerminal"); internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry Verbose { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Enable terminal commands: door / doors / opendoor / closedoor."); Verbose = ((BaseUnityPlugin)this).Config.Bind("General", "VerboseLogging", true, "Log terminal/door traces."); ManualPatches.Apply(_harmony); Log.LogInfo((object)string.Format("{0} v{1} loaded. Verbose={2}", "ShipDoorTerminal", "1.0.10", Verbose.Value)); } internal static void V(string msg) { if (Verbose != null && Verbose.Value) { Log.LogInfo((object)msg); } } } internal static class PluginInfo { public const string PLUGIN_GUID = "com.benhough.lethal.ShipDoorTerminal"; public const string PLUGIN_NAME = "ShipDoorTerminal"; public const string PLUGIN_VERSION = "1.0.10"; } }