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 ShipLightsTerminal v1.0.0
ShipLightsTerminal.dll
Decompiled 6 hours agousing 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 HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; 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("ShipLightsTerminal")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ShipLightsTerminal")] [assembly: AssemblyTitle("ShipLightsTerminal")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.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 ShipLightsTerminal { 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<string> Commands = new HashSet<string>(StringComparer.Ordinal) { "lights", "light", "ship lights", "lightson", "lights on", "light on", "onlights", "turn on lights", "lightsoff", "lights off", "light off", "offlights", "turn off lights", "togglelights", "toggle lights" }; 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 IsLightsCommand(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.IsLightsCommand(text)) { return true; } string text2 = LightActions.Run(text); Plugin.Log.LogInfo((object)("[OnSubmit] handled '" + text + "'")); __instance.LoadNewNode(LightActions.CreateDisplayNode(text2)); LightActions.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.IsLightsCommand(text)) { return true; } __result = LightActions.GetKeyword(text); Plugin.Log.LogInfo((object)("[ParseWord] returning lights 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.IsLightsCommand(text)) { return true; } string text2 = LightActions.Run(text); __result = LightActions.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 (!LightActions.TryGetCommandForNode(node, out string cmd)) { Plugin.V("[LoadNewNode] unrelated"); return; } Plugin.Log.LogInfo((object)("[LoadNewNode] lights keyword node for '" + cmd + "' — running action")); _reentry++; try { string text = LightActions.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"); LightActions.EnsureKeywordsRegistered(__instance); LightActions.EnsureHelpText(__instance); } public static void StartPostfix(Terminal __instance) { Plugin.Log.LogInfo((object)"[Terminal.Start] postfix hit"); LightActions.EnsureKeywordsRegistered(__instance); LightActions.EnsureHelpText(__instance); } } internal static class LightActions { private static readonly Dictionary<string, TerminalKeyword> Keywords = new Dictionary<string, TerminalKeyword>(); private static readonly Dictionary<TerminalNode, string> NodeCommands = new Dictionary<TerminalNode, string>(); private static bool _registered; private static bool _helpInjected; private const string HelpMarker = "[ShipLightsTerminal]"; private const string HelpFingerprint = ">LIGHTS"; private const string HelpBlock = ">LIGHTS\nToggle ship lights on/off.\nAlso: LIGHTSON / LIGHTSOFF\n\n"; internal static TerminalNode CreateDisplayNode(string text) { TerminalNode obj = ScriptableObject.CreateInstance<TerminalNode>(); 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 lights command:", StringComparison.OrdinalIgnoreCase)) { cmd = TerminalInput.Normalize(text.Substring("Ship lights command:".Length)); return TerminalInput.IsLightsCommand(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 lights command: " + word + "\n"); NodeCommands[val] = word; TerminalKeyword val2 = ScriptableObject.CreateInstance<TerminalKeyword>(); 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("lights"); EnsureKeyword("light"); EnsureKeyword("lightson"); EnsureKeyword("lightsoff"); EnsureKeyword("onlights"); EnsureKeyword("offlights"); EnsureKeyword("togglelights"); if (_registered) { Plugin.V("Keywords already registered"); return; } List<TerminalKeyword> list = new List<TerminalKeyword>(terminal.terminalNodes.allKeywords); foreach (KeyValuePair<string, TerminalKeyword> 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 lights 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 = StripLightsHelp(text); if (text2 != node.displayText) { node.displayText = text2; Plugin.Log.LogInfo((object)("[Help] stripped lights docs from non-list " + label)); } return false; } if (text.IndexOf(">LIGHTS", StringComparison.OrdinalIgnoreCase) >= 0) { if (text != node.displayText) { node.displayText = text; } return false; } node.displayText = InjectHelp(text); Plugin.Log.LogInfo((object)("[Help] injected lights docs into " + label)); return true; } private static string StripLegacyHelpHeader(string page) { if (string.IsNullOrEmpty(page) || page.IndexOf("[ShipLightsTerminal]", StringComparison.Ordinal) < 0) { return page; } return page.Replace("[ShipLightsTerminal]\n\n", "").Replace("[ShipLightsTerminal]\n", "").Replace("[ShipLightsTerminal]", ""); } private static string StripLightsHelp(string page) { if (string.IsNullOrEmpty(page) || page.IndexOf(">LIGHTS", StringComparison.OrdinalIgnoreCase) < 0) { return page; } if (page.Contains(">LIGHTS\nToggle ship lights on/off.\nAlso: LIGHTSON / LIGHTSOFF\n\n")) { return page.Replace(">LIGHTS\nToggle ship lights on/off.\nAlso: LIGHTSON / LIGHTSOFF\n\n", ""); } int num = page.IndexOf(">LIGHTS", 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) + ">LIGHTS\nToggle ship lights on/off.\nAlso: LIGHTSON / LIGHTSOFF\n\n" + page.Substring(num3); } } if (!page.EndsWith("\n")) { page += "\n"; } if (!page.EndsWith("\n\n")) { page += "\n"; } return page + ">LIGHTS\nToggle ship lights on/off.\nAlso: LIGHTSON / LIGHTSOFF\n\n"; } internal static string Run(string input) { switch (input) { case "lights": case "light": case "ship lights": case "togglelights": case "toggle lights": return Toggle(); case "light on": case "onlights": case "lightson": case "lights on": case "turn on lights": return Set(wantOn: true); case "light off": case "offlights": case "lightsoff": case "lights off": case "turn off lights": return Set(wantOn: false); default: return "Unknown lights command.\n"; } } private static ShipLights? ResolveLights() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.shipRoomLights != (Object)null) { return instance.shipRoomLights; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Lights] StartOfRound.shipRoomLights: " + ex.Message)); } try { return Object.FindObjectOfType<ShipLights>(); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[Lights] FindObjectOfType<ShipLights>: " + ex2.Message)); return null; } } private static string Toggle() { ShipLights val = ResolveLights(); if ((Object)(object)val == (Object)null) { return "Ship lights not available.\n"; } bool areLightsOn = val.areLightsOn; try { val.SetShipLightsServerRpc(!areLightsOn); Plugin.Log.LogInfo((object)$"[Lights] Toggle via SetShipLightsServerRpc({!areLightsOn}); wasOn={areLightsOn}"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Lights] SetShipLightsServerRpc toggle failed: " + ex.Message + "; trying ToggleShipLights")); try { val.ToggleShipLights(); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[Lights] ToggleShipLights: " + ex2.Message)); return "Ship lights not available.\n"; } } bool areLightsOn2 = val.areLightsOn; bool flag = !areLightsOn; try { flag = ((val.areLightsOn != areLightsOn) ? val.areLightsOn : (!areLightsOn)); } catch { } Plugin.V($"[Lights] after toggle areLightsOn={areLightsOn2} reportOn={flag}"); if (!flag) { return "Ship lights toggled (now OFF).\n"; } return "Ship lights toggled (now ON).\n"; } private static string Set(bool wantOn) { ShipLights val = ResolveLights(); if ((Object)(object)val == (Object)null) { return "Ship lights not available.\n"; } Plugin.Log.LogInfo((object)$"[Lights] Set wantOn={wantOn}, areLightsOn={val.areLightsOn}"); try { val.SetShipLightsServerRpc(wantOn); Plugin.Log.LogInfo((object)$"[Lights] SetShipLightsServerRpc({wantOn})"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Lights] SetShipLightsServerRpc failed: " + ex.Message + "; trying SetShipLightsBoolean")); try { val.SetShipLightsBoolean(wantOn); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[Lights] SetShipLightsBoolean: " + ex2.Message)); return "Ship lights not available.\n"; } } if (!wantOn) { return "Ship lights are now OFF.\n"; } return "Ship lights are now ON.\n"; } } [BepInPlugin("com.benhough.lethal.ShipLightsTerminal", "ShipLightsTerminal", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.benhough.lethal.ShipLightsTerminal"; public const string ModName = "ShipLightsTerminal"; public const string ModVersion = "1.0.0"; private readonly Harmony _harmony = new Harmony("com.benhough.lethal.ShipLightsTerminal"); internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static ConfigEntry<bool> Enabled { get; private set; } internal static ConfigEntry<bool> Verbose { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable terminal commands: lights / lightson / lightsoff / togglelights."); Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "VerboseLogging", false, "Log terminal/lights traces."); ManualPatches.Apply(_harmony); Log.LogInfo((object)string.Format("{0} v{1} loaded. Verbose={2}", "ShipLightsTerminal", "1.0.0", 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.ShipLightsTerminal"; public const string PLUGIN_NAME = "ShipLightsTerminal"; public const string PLUGIN_VERSION = "1.0.0"; } }