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 ShipTeleportTerminal v1.0.0
ShipTeleportTerminal.dll
Decompiled 3 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("ShipTeleportTerminal")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ShipTeleportTerminal")] [assembly: AssemblyTitle("ShipTeleportTerminal")] [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 ShipTeleportTerminal { 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) { "teleport", "tp", "beam", "teleporter", "ship teleport", "iteleport", "itp", "inverse", "inverse teleport", "inverse teleporter", "teleport inverse", "tp inverse" }; 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 IsTeleportCommand(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.IsTeleportCommand(text)) { return true; } string text2 = TeleportActions.Run(text); Plugin.Log.LogInfo((object)("[OnSubmit] teleport handled '" + text + "'")); __instance.LoadNewNode(TeleportActions.CreateDisplayNode(text2)); TeleportActions.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.IsTeleportCommand(text)) { return true; } __result = TeleportActions.GetKeyword(text); Plugin.Log.LogInfo((object)("[ParseWord] returning teleport 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.IsTeleportCommand(text)) { return true; } string text2 = TeleportActions.Run(text); __result = TeleportActions.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 (!TeleportActions.TryGetCommandForNode(node, out string cmd)) { Plugin.V("[LoadNewNode] unrelated"); return; } Plugin.Log.LogInfo((object)("[LoadNewNode] teleport keyword node for '" + cmd + "' — running action")); _reentry++; try { string text = TeleportActions.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"); TeleportActions.EnsureKeywordsRegistered(__instance); TeleportActions.EnsureHelpText(__instance); } public static void StartPostfix(Terminal __instance) { Plugin.Log.LogInfo((object)"[Terminal.Start] postfix hit"); TeleportActions.EnsureKeywordsRegistered(__instance); TeleportActions.EnsureHelpText(__instance); } } internal static class TeleportActions { 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 = "[ShipTeleportTerminal]"; private const string HelpFingerprint = ">TELEPORT"; private const string HelpBlock = ">TELEPORT\nBeam the radar-targeted player (same as the teleporter button).\nAlso: TP / BEAM / ITELEPORT / ITP (inverse)\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 teleport command:", StringComparison.OrdinalIgnoreCase)) { cmd = TerminalInput.Normalize(text.Substring("Ship teleport command:".Length)); return TerminalInput.IsTeleportCommand(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 teleport 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("teleport"); EnsureKeyword("tp"); EnsureKeyword("beam"); EnsureKeyword("teleporter"); EnsureKeyword("iteleport"); EnsureKeyword("itp"); EnsureKeyword("inverse"); 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 teleport 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 = StripTeleportHelp(text); if (text2 != node.displayText) { node.displayText = text2; Plugin.Log.LogInfo((object)("[Help] stripped teleport docs from non-list " + label)); } return false; } if (text.IndexOf(">TELEPORT", StringComparison.OrdinalIgnoreCase) >= 0) { if (text != node.displayText) { node.displayText = text; } return false; } node.displayText = InjectHelp(text); Plugin.Log.LogInfo((object)("[Help] injected teleport docs into " + label)); return true; } private static string StripLegacyHelpHeader(string page) { if (string.IsNullOrEmpty(page) || page.IndexOf("[ShipTeleportTerminal]", StringComparison.Ordinal) < 0) { return page; } return page.Replace("[ShipTeleportTerminal]\n\n", "").Replace("[ShipTeleportTerminal]\n", "").Replace("[ShipTeleportTerminal]", ""); } private static string StripTeleportHelp(string page) { if (string.IsNullOrEmpty(page) || page.IndexOf(">TELEPORT", StringComparison.OrdinalIgnoreCase) < 0) { return page; } if (page.Contains(">TELEPORT\nBeam the radar-targeted player (same as the teleporter button).\nAlso: TP / BEAM / ITELEPORT / ITP (inverse)\n\n")) { return page.Replace(">TELEPORT\nBeam the radar-targeted player (same as the teleporter button).\nAlso: TP / BEAM / ITELEPORT / ITP (inverse)\n\n", ""); } int num = page.IndexOf(">TELEPORT", 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) + ">TELEPORT\nBeam the radar-targeted player (same as the teleporter button).\nAlso: TP / BEAM / ITELEPORT / ITP (inverse)\n\n" + page.Substring(num3); } } if (!page.EndsWith("\n")) { page += "\n"; } if (!page.EndsWith("\n\n")) { page += "\n"; } return page + ">TELEPORT\nBeam the radar-targeted player (same as the teleporter button).\nAlso: TP / BEAM / ITELEPORT / ITP (inverse)\n\n"; } internal static string Run(string input) { switch (input) { case "teleporter": case "teleport": case "tp": case "beam": case "ship teleport": return Fire(inverse: false); case "tp inverse": case "inverse teleport": case "teleport inverse": case "iteleport": case "itp": case "inverse": case "inverse teleporter": return Fire(inverse: true); default: return "Unknown teleport command.\n"; } } private static ShipTeleporter? ResolveTeleporter(bool inverse) { try { ShipTeleporter[] array = Object.FindObjectsOfType<ShipTeleporter>(); if (array == null || array.Length == 0) { return null; } ShipTeleporter val = null; ShipTeleporter val2 = null; ShipTeleporter[] array2 = array; foreach (ShipTeleporter val3 in array2) { if (!((Object)(object)val3 == (Object)null)) { if (val2 == null) { val2 = val3; } if (val3.isInverseTeleporter == inverse) { val = val3; break; } } } return val ?? (inverse ? null : val2); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Teleport] FindObjectsOfType: " + ex.Message)); return null; } } private static string Fire(bool inverse) { if (inverse && Plugin.AllowInverse != null && !Plugin.AllowInverse.Value) { return "Inverse teleporter commands are disabled in config.\n"; } ShipTeleporter val = ResolveTeleporter(inverse); if ((Object)(object)val == (Object)null) { if (!inverse) { return "Ship teleporter not found (is it unlocked/bought?).\n"; } return "Inverse teleporter not found (is it unlocked/bought?).\n"; } try { if (val.cooldownTime > 0f) { int num = Mathf.CeilToInt(val.cooldownTime); return string.Format("{0} cooling down ({1}s).\n", inverse ? "Inverse teleporter" : "Teleporter", num); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Teleport] cooldown read: " + ex.Message)); } if (inverse) { try { if (!val.CanUseInverseTeleporter()) { return "Inverse teleporter cannot be used right now.\n"; } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[Teleport] CanUseInverseTeleporter: " + ex2.Message)); } } try { val.PressTeleportButtonOnLocalClient(); Plugin.Log.LogInfo((object)$"[Teleport] PressTeleportButtonOnLocalClient inverse={inverse} id={val.teleporterId}"); } catch (Exception ex3) { Plugin.Log.LogWarning((object)("[Teleport] PressTeleportButtonOnLocalClient failed: " + ex3.Message + "; trying ServerRpc")); try { val.PressTeleportButtonServerRpc(); } catch (Exception ex4) { Plugin.Log.LogWarning((object)("[Teleport] PressTeleportButtonServerRpc: " + ex4.Message)); return "Teleporter button press failed.\n"; } } if (!inverse) { return "Teleporter activated (radar target).\n"; } return "Inverse teleporter activated.\n"; } } [BepInPlugin("com.benhough.lethal.ShipTeleportTerminal", "ShipTeleportTerminal", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.benhough.lethal.ShipTeleportTerminal"; public const string ModName = "ShipTeleportTerminal"; public const string ModVersion = "1.0.0"; private readonly Harmony _harmony = new Harmony("com.benhough.lethal.ShipTeleportTerminal"); 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> AllowInverse { 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: teleport / tp (and inverse variants)."); AllowInverse = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AllowInverse", true, "Allow iteleport / itp / inverse for the inverse teleporter."); Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "VerboseLogging", false, "Log terminal/teleporter traces."); ManualPatches.Apply(_harmony); Log.LogInfo((object)string.Format("{0} v{1} loaded. Verbose={2}", "ShipTeleportTerminal", "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.ShipTeleportTerminal"; public const string PLUGIN_NAME = "ShipTeleportTerminal"; public const string PLUGIN_VERSION = "1.0.0"; } }