using System;
using System.Collections;
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;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
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("PersistentTerminal")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Terminal remembers your last screen")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: AssemblyInformationalVersion("1.3.0")]
[assembly: AssemblyProduct("PersistentTerminal")]
[assembly: AssemblyTitle("PersistentTerminal")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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 PersistentTerminal
{
[BepInPlugin("mrbub.persistentterminal", "PersistentTerminal", "1.3.0")]
public class Plugin : BaseUnityPlugin
{
internal static ManualLogSource Logger;
public static ConfigEntry<string> TerminalTheme;
public static ConfigEntry<string> CustomTextColor;
public static ConfigEntry<string> CustomBackgroundColor;
public static ConfigEntry<string> CustomScreenLightColor;
public static ConfigEntry<bool> AlwaysOnScreen;
public static ConfigEntry<bool> AlwaysOnLight;
private void Awake()
{
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Expected O, but got Unknown
//IL_0138: Unknown result type (might be due to invalid IL or missing references)
Logger = ((BaseUnityPlugin)this).Logger;
TerminalTheme = ((BaseUnityPlugin)this).Config.Bind<string>("Theme", "Preset", "Default", new ConfigDescription("Terminal color theme preset.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[7] { "Default", "Green", "Amber", "Blue", "Red", "Purple", "Cyan" }), Array.Empty<object>()));
CustomTextColor = ((BaseUnityPlugin)this).Config.Bind<string>("Custom Colors", "TextColor", "", "Custom text color in RGB format (0-255). Overrides the theme preset if set.\nExample: 0,255,0 for green\nLeave empty to use theme preset.");
CustomBackgroundColor = ((BaseUnityPlugin)this).Config.Bind<string>("Custom Colors", "BackgroundColor", "", "Custom background color in RGB format (0-255). Overrides the theme preset if set.\nExample: 0,20,0 for dark green\nLeave empty to use theme preset.");
CustomScreenLightColor = ((BaseUnityPlugin)this).Config.Bind<string>("Custom Colors", "ScreenLightColor", "", "Custom screen light color in RGB format (0-255). Overrides the theme preset if set.\nExample: 0,255,0 for green\nLeave empty to use theme preset.");
AlwaysOnScreen = ((BaseUnityPlugin)this).Config.Bind<bool>("Behavior", "AlwaysOnScreen", false, "Keep the terminal screen always on, even when not in use.");
AlwaysOnLight = ((BaseUnityPlugin)this).Config.Bind<bool>("Behavior", "AlwaysOnLight", false, "Keep the terminal light always on.");
TerminalPresets.InitializeConfig(((BaseUnityPlugin)this).Config);
new Harmony("mrbub.persistentterminal").PatchAll();
Logger.LogInfo((object)"PersistentTerminal v1.3.0 loaded!");
}
}
public static class TerminalPresets
{
public struct PresetItem
{
public string ItemName;
public int Quantity;
public int ItemIndex;
public int UnitCost;
public PresetItem(string name, int qty)
{
ItemName = name;
Quantity = qty;
ItemIndex = -1;
UnitCost = 0;
}
}
public static Dictionary<string, List<PresetItem>> Presets = new Dictionary<string, List<PresetItem>>(StringComparer.OrdinalIgnoreCase);
private static string pendingPresetName = null;
private static int pendingTotalCost = 0;
private static List<PresetItem> pendingItems = null;
public static ConfigEntry<string> Preset1;
public static ConfigEntry<string> Preset2;
public static ConfigEntry<string> Preset3;
public static ConfigEntry<string> Preset4;
public static ConfigEntry<string> Preset5;
public static void InitializeConfig(ConfigFile config)
{
Preset1 = config.Bind<string>("Presets", "Preset1", "ESSENTIALS=flashlight:4,shovel:2,walkie-talkie:4", "Preset format: NAME=item1:qty,item2:qty,item3:qty\nItem names must match store names (case-insensitive).\nLeave empty to disable.");
Preset2 = config.Bind<string>("Presets", "Preset2", "LIGHTS=flashlight:4,pro-flashlight:2", "Preset format: NAME=item1:qty,item2:qty,item3:qty");
Preset3 = config.Bind<string>("Presets", "Preset3", "", "Preset format: NAME=item1:qty,item2:qty,item3:qty");
Preset4 = config.Bind<string>("Presets", "Preset4", "", "Preset format: NAME=item1:qty,item2:qty,item3:qty");
Preset5 = config.Bind<string>("Presets", "Preset5", "", "Preset format: NAME=item1:qty,item2:qty,item3:qty");
ParsePresets();
}
public static void ParsePresets()
{
Presets.Clear();
ParsePresetString(Preset1.Value);
ParsePresetString(Preset2.Value);
ParsePresetString(Preset3.Value);
ParsePresetString(Preset4.Value);
ParsePresetString(Preset5.Value);
Plugin.Logger.LogInfo((object)$"[PRESETS] Loaded {Presets.Count} presets");
}
private static void ParsePresetString(string presetString)
{
if (string.IsNullOrWhiteSpace(presetString))
{
return;
}
try
{
int num = presetString.IndexOf('=');
if (num <= 0)
{
Plugin.Logger.LogWarning((object)("[PRESETS] Invalid preset format (no '='): " + presetString));
return;
}
string text = presetString.Substring(0, num).Trim().ToUpper();
string text2 = presetString.Substring(num + 1);
List<PresetItem> list = new List<PresetItem>();
string[] array = text2.Split(',');
for (int i = 0; i < array.Length; i++)
{
string text3 = array[i].Trim();
if (string.IsNullOrEmpty(text3))
{
continue;
}
int num2 = text3.LastIndexOf(':');
if (num2 <= 0)
{
Plugin.Logger.LogWarning((object)("[PRESETS] Invalid item format (no ':'): " + text3));
continue;
}
string text4 = text3.Substring(0, num2).Trim();
if (int.TryParse(text3.Substring(num2 + 1).Trim(), out var result) && result > 0)
{
list.Add(new PresetItem(text4, result));
Plugin.Logger.LogInfo((object)$"[PRESETS] Parsed item: {text4} x{result}");
}
}
if (list.Count > 0)
{
Presets[text] = list;
Plugin.Logger.LogInfo((object)$"[PRESETS] Added preset '{text}' with {list.Count} items");
}
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("[PRESETS] Error parsing preset '" + presetString + "': " + ex.Message));
}
}
public static bool TryHandlePresetCommand(Terminal terminal, string input, out string response)
{
response = null;
string text = input.Trim();
input = text.ToUpper();
if (string.IsNullOrEmpty(input))
{
return false;
}
if (pendingPresetName != null)
{
if ("CONFIRM".StartsWith(input) || "YES".StartsWith(input) || input == "Y")
{
response = ExecutePendingPreset(terminal, text);
return true;
}
if ("DENY".StartsWith(input) || "NO".StartsWith(input) || "CANCEL".StartsWith(input) || input == "N")
{
_ = pendingPresetName;
pendingPresetName = null;
pendingItems = null;
pendingTotalCost = 0;
response = ">" + text.ToUpper() + "\n\nCancelled order.\n\n";
return true;
}
}
if (input.Length >= 2 && ("PRESETS".StartsWith(input) || "PRESET".StartsWith(input)))
{
pendingPresetName = null;
pendingItems = null;
pendingTotalCost = 0;
response = GeneratePresetsListText(terminal, text);
return true;
}
if (input.Length >= 2)
{
foreach (KeyValuePair<string, List<PresetItem>> preset in Presets)
{
if (preset.Key.StartsWith(input))
{
response = GeneratePresetConfirmation(terminal, preset.Key, preset.Value, text);
return true;
}
}
}
if (input.StartsWith("BUY ") && input.Length > 5)
{
string value = input.Substring(4).Trim();
foreach (KeyValuePair<string, List<PresetItem>> preset2 in Presets)
{
if (preset2.Key.StartsWith(value))
{
response = GeneratePresetConfirmation(terminal, preset2.Key, preset2.Value, text);
return true;
}
}
}
return false;
}
private static string GeneratePresetsListText(Terminal terminal, string originalInput)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(">" + originalInput.ToUpper() + "\n\n");
stringBuilder.Append("Welcome to Terminal Presets.\n");
stringBuilder.Append("Type a preset name to order in bulk.\n");
stringBuilder.Append("Use CONFIRM and DENY to finalize.\n");
stringBuilder.Append("________________________________________\n\n");
if (Presets.Count == 0)
{
stringBuilder.Append("No presets configured.\nConfigure presets in the mod config file.\n\n");
return stringBuilder.ToString();
}
foreach (KeyValuePair<string, List<PresetItem>> preset in Presets)
{
int num = 0;
int num2 = 0;
foreach (PresetItem item in preset.Value)
{
int num3 = FindItemIndex(terminal, item.ItemName);
if (num3 >= 0)
{
int itemCost = GetItemCost(terminal, num3);
num += itemCost * item.Quantity;
num2 += item.Quantity;
}
}
stringBuilder.Append($"* {preset.Key} // ${num} ({num2} items)\n");
}
stringBuilder.Append("\n");
return stringBuilder.ToString();
}
private static string GeneratePresetConfirmation(Terminal terminal, string presetName, List<PresetItem> items, string originalInput)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(">" + originalInput.ToUpper() + "\n\n");
int num = 0;
int num2 = 0;
List<PresetItem> list = new List<PresetItem>();
bool flag = false;
stringBuilder.Append("You have requested to order " + presetName + ":\n\n");
foreach (PresetItem item2 in items)
{
int num3 = FindItemIndex(terminal, item2.ItemName);
if (num3 >= 0)
{
int itemCost = GetItemCost(terminal, num3);
int num4 = itemCost * item2.Quantity;
num += num4;
num2 += item2.Quantity;
PresetItem item = new PresetItem(item2.ItemName, item2.Quantity);
item.ItemIndex = num3;
item.UnitCost = itemCost;
list.Add(item);
stringBuilder.Append($"* {item2.ItemName} x{item2.Quantity} // ${num4}\n");
}
else
{
stringBuilder.Append($"* {item2.ItemName} x{item2.Quantity} // NOT IN STORE\n");
flag = true;
}
}
stringBuilder.Append($"\nTotal: ${num}\n");
stringBuilder.Append($"Your balance is ${terminal.groupCredits}.\n");
if (flag)
{
stringBuilder.Append("\nWARNING: Some items not found in store!\n");
}
if (num > terminal.groupCredits)
{
stringBuilder.Append("\nYou could not afford these items!\n\n");
return stringBuilder.ToString();
}
if (num2 + terminal.numberOfItemsInDropship > 12)
{
stringBuilder.Append("\nThe dropship cannot hold this many items!\n\n");
return stringBuilder.ToString();
}
pendingPresetName = presetName;
pendingTotalCost = num;
pendingItems = list;
stringBuilder.Append("\nPlease CONFIRM or DENY.\n\n");
return stringBuilder.ToString();
}
private static string ExecutePendingPreset(Terminal terminal, string originalInput)
{
if (pendingItems == null || pendingPresetName == null)
{
return ">" + originalInput.ToUpper() + "\n\nNo pending order.\n\n";
}
if (pendingTotalCost > terminal.groupCredits)
{
ClearPending();
return ">" + originalInput.ToUpper() + "\n\nYou could not afford these items!\n\n";
}
int num = 0;
foreach (PresetItem pendingItem in pendingItems)
{
num += pendingItem.Quantity;
}
if (num + terminal.numberOfItemsInDropship > 12)
{
ClearPending();
return ">" + originalInput.ToUpper() + "\n\nThe dropship cannot hold this many items!\n\n";
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(">" + originalInput.ToUpper() + "\n\n");
stringBuilder.Append("Ordered " + pendingPresetName + ":\n\n");
foreach (PresetItem pendingItem2 in pendingItems)
{
if (pendingItem2.ItemIndex >= 0)
{
for (int i = 0; i < pendingItem2.Quantity; i++)
{
terminal.orderedItemsFromTerminal.Add(pendingItem2.ItemIndex);
terminal.numberOfItemsInDropship++;
}
stringBuilder.Append($"* {pendingItem2.ItemName} x{pendingItem2.Quantity}\n");
}
}
terminal.groupCredits -= pendingTotalCost;
terminal.PlayTerminalAudioServerRpc(0);
if (!((NetworkBehaviour)terminal).IsServer)
{
terminal.SyncGroupCreditsServerRpc(terminal.groupCredits, terminal.numberOfItemsInDropship);
}
else
{
typeof(Terminal).GetMethod("SyncGroupCreditsClientRpc", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(terminal, new object[2] { terminal.groupCredits, terminal.numberOfItemsInDropship });
}
stringBuilder.Append($"\nYour new balance is ${terminal.groupCredits}.\n\n");
stringBuilder.Append("Our contractors enjoy fast, free shipping while on the job! Any purchased items will arrive within approximately one hour.\n\n");
ClearPending();
return stringBuilder.ToString();
}
private static void ClearPending()
{
pendingPresetName = null;
pendingTotalCost = 0;
pendingItems = null;
}
private static int FindItemIndex(Terminal terminal, string itemName)
{
if (terminal.buyableItemsList == null)
{
return -1;
}
itemName = itemName.ToLower().Replace("-", " ").Replace("_", " ");
for (int i = 0; i < terminal.buyableItemsList.Length; i++)
{
string text = terminal.buyableItemsList[i].itemName.ToLower();
if (text == itemName || text.Replace("-", " ") == itemName)
{
return i;
}
}
for (int j = 0; j < terminal.buyableItemsList.Length; j++)
{
string text2 = terminal.buyableItemsList[j].itemName.ToLower();
if (text2.Contains(itemName) || itemName.Contains(text2))
{
return j;
}
}
return -1;
}
private static int GetItemCost(Terminal terminal, int itemIndex)
{
if (itemIndex < 0 || itemIndex >= terminal.buyableItemsList.Length)
{
return 0;
}
int creditsWorth = terminal.buyableItemsList[itemIndex].creditsWorth;
int num = terminal.itemSalesPercentages[itemIndex];
return (int)((float)creditsWorth * ((float)num / 100f));
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "mrbub.persistentterminal";
public const string PLUGIN_NAME = "PersistentTerminal";
public const string PLUGIN_VERSION = "1.3.0";
}
}
namespace PersistentTerminal.Patches
{
[HarmonyPatch(typeof(Terminal))]
internal class TerminalPatches
{
[CompilerGenerated]
private sealed class <ForceScrollbarUp>d__27 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public Terminal terminal;
private int <i>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ForceScrollbarUp>d__27(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<i>5__2 = 0;
break;
case 1:
<>1__state = -1;
terminal.scrollBarVertical.value = 1f;
<i>5__2++;
break;
}
if (<i>5__2 < 5)
{
<>2__current = null;
<>1__state = 1;
return true;
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private static TerminalNode lastNode = null;
private static string lastScreenText = "";
private static int lastTextPosition = 0;
private static int lastSaveFileNum = -1;
private static bool isRestoring = false;
private static bool hasRestoredThisSession = false;
private static bool skipSoundPlayback = false;
private static bool isShowingPresetPage = false;
private static string lastPresetPageText = "";
private static List<string> commandHistory = new List<string>();
private static int historyIndex = -1;
private static string currentInput = "";
private static string savedInput = "";
private static bool themeApplied = false;
internal static Light terminalLight = null;
private static Image terminalBackground = null;
internal static bool isPlayerInShip = true;
internal static Terminal cachedTerminal = null;
[HarmonyPatch("BeginUsingTerminal")]
[HarmonyPrefix]
private static void BeginUsingTerminalPrefix(Terminal __instance)
{
if ((Object)(object)cachedTerminal == (Object)null)
{
cachedTerminal = __instance;
}
if (!((Object)(object)__instance == (Object)null) && !((Object)(object)GameNetworkManager.Instance == (Object)null) && !hasRestoredThisSession)
{
int saveFileNum = GameNetworkManager.Instance.saveFileNum;
if (saveFileNum != lastSaveFileNum)
{
ClearSavedState();
lastSaveFileNum = saveFileNum;
}
else if ((Object)(object)lastNode != (Object)null && !string.IsNullOrEmpty(lastScreenText))
{
isRestoring = true;
}
else if (isShowingPresetPage && !string.IsNullOrEmpty(lastPresetPageText))
{
isRestoring = true;
}
}
}
[HarmonyPatch("BeginUsingTerminal")]
[HarmonyPostfix]
private static void BeginUsingTerminalPostfix(Terminal __instance)
{
if (!themeApplied)
{
ApplyTheme(__instance);
themeApplied = true;
}
if (!isRestoring)
{
if (!string.IsNullOrEmpty(savedInput) && (Object)(object)__instance.screenText != (Object)null)
{
Plugin.Logger.LogInfo((object)("[INPUT] Restoring saved input: '" + savedInput + "'"));
TMP_InputField screenText = __instance.screenText;
screenText.text += savedInput;
__instance.textAdded = savedInput.Length;
__instance.currentText = __instance.screenText.text;
Plugin.Logger.LogInfo((object)$"[INPUT] Input restored, textAdded: {__instance.textAdded}");
savedInput = "";
}
return;
}
try
{
if (isShowingPresetPage && !string.IsNullOrEmpty(lastPresetPageText))
{
Plugin.Logger.LogInfo((object)"[RESTORE] Restoring preset page");
typeof(Terminal).GetField("modifyingText", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(__instance, true);
__instance.screenText.text = lastPresetPageText;
__instance.currentText = lastPresetPageText;
__instance.textAdded = 0;
__instance.screenText.ActivateInputField();
((Selectable)__instance.screenText).Select();
Plugin.Logger.LogInfo((object)"[RESTORE] Successfully restored preset page");
hasRestoredThisSession = true;
isRestoring = false;
}
else if (!((Object)(object)lastNode == (Object)null))
{
Plugin.Logger.LogInfo((object)("[RESTORE] Starting restoration of node: " + ((Object)lastNode).name));
skipSoundPlayback = true;
__instance.LoadNewNode(lastNode);
skipSoundPlayback = false;
if (lastTextPosition > 0 && (Object)(object)__instance.screenText != (Object)null)
{
__instance.textAdded = lastTextPosition;
Plugin.Logger.LogInfo((object)$"[RESTORE] Restored scroll position: {lastTextPosition}");
}
if (!string.IsNullOrEmpty(savedInput) && (Object)(object)__instance.screenText != (Object)null)
{
Plugin.Logger.LogInfo((object)("[INPUT] Restoring saved input after node restore: '" + savedInput + "'"));
TMP_InputField screenText2 = __instance.screenText;
screenText2.text += savedInput;
__instance.textAdded = savedInput.Length;
__instance.currentText = __instance.screenText.text;
Plugin.Logger.LogInfo((object)$"[INPUT] Input restored, textAdded: {__instance.textAdded}");
savedInput = "";
}
Plugin.Logger.LogInfo((object)("[RESTORE] Successfully restored to: " + ((Object)lastNode).name));
hasRestoredThisSession = true;
}
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Error restoring terminal state: " + ex.Message));
skipSoundPlayback = false;
}
finally
{
isRestoring = false;
}
}
[HarmonyPatch("LoadNewNode")]
[HarmonyPrefix]
private static bool LoadNewNodePrefix(Terminal __instance, TerminalNode node)
{
if (isRestoring && (Object)(object)node != (Object)null && ((Object)node).name == "Start")
{
return false;
}
return true;
}
[HarmonyPatch("LoadNewNode")]
[HarmonyPostfix]
private static void LoadNewNodePostfix(Terminal __instance, TerminalNode node)
{
if (isRestoring || skipSoundPlayback || !__instance.terminalInUse)
{
return;
}
if ((Object)(object)node != (Object)null)
{
isShowingPresetPage = false;
lastPresetPageText = "";
}
if ((Object)(object)node != (Object)null && (Object)(object)__instance.currentNode == (Object)(object)node)
{
lastNode = node;
if ((Object)(object)__instance.screenText != (Object)null)
{
lastScreenText = __instance.screenText.text;
lastTextPosition = __instance.textAdded;
}
}
}
[HarmonyPatch("PlayTerminalAudioServerRpc")]
[HarmonyPrefix]
private static bool PlayTerminalAudioServerRpcPrefix(int clipIndex)
{
if (skipSoundPlayback)
{
return false;
}
return true;
}
[HarmonyPatch("Update")]
[HarmonyPostfix]
private static void UpdatePostfix(Terminal __instance)
{
if (!__instance.terminalInUse || (Object)(object)__instance.screenText == (Object)null)
{
return;
}
Keyboard current = Keyboard.current;
if (current == null)
{
return;
}
if (((ButtonControl)current.upArrowKey).wasPressedThisFrame && commandHistory.Count > 0)
{
if (historyIndex == -1)
{
currentInput = GetCurrentInput(__instance);
Plugin.Logger.LogInfo((object)("[HISTORY] Saved current input: '" + currentInput + "'"));
}
if (historyIndex < commandHistory.Count - 1)
{
historyIndex++;
string text = commandHistory[commandHistory.Count - 1 - historyIndex];
Plugin.Logger.LogInfo((object)$"[HISTORY] Up arrow - showing command {historyIndex + 1}/{commandHistory.Count}: '{text}'");
SetCurrentInput(__instance, text);
}
else
{
Plugin.Logger.LogInfo((object)"[HISTORY] Already at oldest command");
}
}
else if (((ButtonControl)current.downArrowKey).wasPressedThisFrame && commandHistory.Count > 0)
{
if (historyIndex > 0)
{
historyIndex--;
string text2 = commandHistory[commandHistory.Count - 1 - historyIndex];
Plugin.Logger.LogInfo((object)$"[HISTORY] Down arrow - showing command {historyIndex + 1}/{commandHistory.Count}: '{text2}'");
SetCurrentInput(__instance, text2);
}
else if (historyIndex == 0)
{
Plugin.Logger.LogInfo((object)("[HISTORY] Down arrow - restoring current input: '" + currentInput + "'"));
historyIndex = -1;
SetCurrentInput(__instance, currentInput);
}
else
{
Plugin.Logger.LogInfo((object)"[HISTORY] Already at newest input");
}
}
}
[HarmonyPatch("OnSubmit")]
[HarmonyPrefix]
private static bool OnSubmitPrefix(Terminal __instance)
{
if ((Object)(object)__instance.screenText == (Object)null || __instance.textAdded <= 0)
{
return true;
}
string text = GetCurrentInput(__instance);
if (!string.IsNullOrWhiteSpace(text))
{
if (TerminalPresets.TryHandlePresetCommand(__instance, text, out var response))
{
Plugin.Logger.LogInfo((object)("[PRESETS] Handled preset command: '" + text + "'"));
if (commandHistory.Count == 0 || commandHistory[commandHistory.Count - 1] != text)
{
commandHistory.Add(text);
}
historyIndex = -1;
currentInput = "";
typeof(Terminal).GetField("modifyingText", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(__instance, true);
string text2 = "\n\n\n" + response;
__instance.screenText.text = text2;
__instance.currentText = text2;
__instance.textAdded = 0;
isShowingPresetPage = true;
lastPresetPageText = text2;
lastNode = null;
Plugin.Logger.LogInfo((object)$"[PRESETS] Set screen text, length: {__instance.screenText.text.Length}");
__instance.screenText.ActivateInputField();
((Selectable)__instance.screenText).Select();
FieldInfo field = typeof(Terminal).GetField("forceScrollbarCoroutine", BindingFlags.Instance | BindingFlags.NonPublic);
if (field != null)
{
object? value = field.GetValue(__instance);
Coroutine val = (Coroutine)((value is Coroutine) ? value : null);
if (val != null)
{
((MonoBehaviour)__instance).StopCoroutine(val);
}
Coroutine value2 = ((MonoBehaviour)__instance).StartCoroutine(ForceScrollbarUp(__instance));
field.SetValue(__instance, value2);
}
return false;
}
if (commandHistory.Count == 0 || commandHistory[commandHistory.Count - 1] != text)
{
commandHistory.Add(text);
Plugin.Logger.LogInfo((object)$"[HISTORY] Added command to history: '{text}' (total: {commandHistory.Count})");
if (commandHistory.Count > 50)
{
string text3 = commandHistory[0];
commandHistory.RemoveAt(0);
Plugin.Logger.LogInfo((object)("[HISTORY] Removed oldest command: '" + text3 + "'"));
}
}
else
{
Plugin.Logger.LogInfo((object)("[HISTORY] Skipped duplicate command: '" + text + "'"));
}
}
historyIndex = -1;
currentInput = "";
return true;
}
private static string GetCurrentInput(Terminal terminal)
{
if ((Object)(object)terminal.screenText == (Object)null || terminal.textAdded <= 0)
{
return "";
}
int num = terminal.screenText.text.Length - terminal.textAdded;
if (num < 0 || num >= terminal.screenText.text.Length)
{
return "";
}
return terminal.screenText.text.Substring(num);
}
private static void SetCurrentInput(Terminal terminal, string input)
{
if (!((Object)(object)terminal.screenText == (Object)null))
{
if (terminal.textAdded > 0)
{
terminal.screenText.text = terminal.screenText.text.Substring(0, terminal.screenText.text.Length - terminal.textAdded);
}
TMP_InputField screenText = terminal.screenText;
screenText.text += input;
terminal.textAdded = input.Length;
terminal.currentText = terminal.screenText.text;
}
}
[IteratorStateMachine(typeof(<ForceScrollbarUp>d__27))]
private static IEnumerator ForceScrollbarUp(Terminal terminal)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ForceScrollbarUp>d__27(0)
{
terminal = terminal
};
}
[HarmonyPatch("QuitTerminal")]
[HarmonyPrefix]
private static void QuitTerminalPrefix(Terminal __instance)
{
if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.currentNode == (Object)null)
{
return;
}
try
{
lastNode = __instance.currentNode;
if ((Object)(object)__instance.screenText != (Object)null)
{
lastScreenText = __instance.screenText.text;
lastTextPosition = __instance.textAdded;
savedInput = GetCurrentInput(__instance);
if (!string.IsNullOrEmpty(savedInput))
{
Plugin.Logger.LogInfo((object)("[INPUT] Saved player input: '" + savedInput + "'"));
}
}
if ((Object)(object)GameNetworkManager.Instance != (Object)null)
{
lastSaveFileNum = GameNetworkManager.Instance.saveFileNum;
}
Plugin.Logger.LogInfo((object)$"[HISTORY] Command history size: {commandHistory.Count}");
hasRestoredThisSession = false;
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Error saving terminal state: " + ex.Message));
}
}
[HarmonyPatch(typeof(StartOfRound), "ResetShip")]
[HarmonyPostfix]
private static void ResetShipPostfix()
{
ClearSavedState();
}
[HarmonyPatch(typeof(StartOfRound), "EndOfGame")]
[HarmonyPostfix]
private static void EndOfGamePostfix()
{
ClearSavedState();
}
[HarmonyPatch(typeof(GameNetworkManager), "Disconnect")]
[HarmonyPostfix]
private static void DisconnectPostfix()
{
Plugin.Logger.LogInfo((object)"[STATE] Disconnected - clearing terminal state");
ClearSavedState();
}
private static void ClearSavedState()
{
Plugin.Logger.LogInfo((object)"[STATE] Clearing all saved state");
lastNode = null;
lastScreenText = "";
lastTextPosition = 0;
isRestoring = false;
hasRestoredThisSession = false;
skipSoundPlayback = false;
isShowingPresetPage = false;
lastPresetPageText = "";
commandHistory.Clear();
historyIndex = -1;
currentInput = "";
savedInput = "";
themeApplied = false;
Plugin.Logger.LogInfo((object)"[STATE] State cleared");
}
[HarmonyPatch("Start")]
[HarmonyPostfix]
private static void StartPostfix(Terminal __instance)
{
//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
//IL_0200: Unknown result type (might be due to invalid IL or missing references)
//IL_021a: Unknown result type (might be due to invalid IL or missing references)
//IL_0234: Unknown result type (might be due to invalid IL or missing references)
//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
//IL_030b: Unknown result type (might be due to invalid IL or missing references)
//IL_031f: Unknown result type (might be due to invalid IL or missing references)
//IL_03e7: Unknown result type (might be due to invalid IL or missing references)
//IL_03f6: Unknown result type (might be due to invalid IL or missing references)
//IL_040a: Unknown result type (might be due to invalid IL or missing references)
//IL_041e: Unknown result type (might be due to invalid IL or missing references)
Plugin.Logger.LogInfo((object)"[INIT] ========== Terminal.Start - Full Object Discovery ==========");
Plugin.Logger.LogInfo((object)("[INIT] Terminal GameObject: " + ((Object)((Component)__instance).gameObject).name));
Plugin.Logger.LogInfo((object)("[INIT] Terminal transform: " + ((Object)((Component)__instance).transform).name));
if ((Object)(object)__instance.terminalLight != (Object)null)
{
terminalLight = __instance.terminalLight;
Plugin.Logger.LogInfo((object)$"[INIT] terminalLight: {((Object)((Component)__instance.terminalLight).gameObject).name} (enabled: {((Behaviour)__instance.terminalLight).enabled})");
Plugin.Logger.LogInfo((object)$"[INIT] terminalLight color: R:{__instance.terminalLight.color.r} G:{__instance.terminalLight.color.g} B:{__instance.terminalLight.color.b}");
if (Plugin.AlwaysOnLight.Value)
{
((Behaviour)terminalLight).enabled = true;
Plugin.Logger.LogInfo((object)"[ALWAYS-ON] Terminal light enabled");
}
}
else
{
Plugin.Logger.LogWarning((object)"[INIT] terminalLight is null");
}
if ((Object)(object)__instance.terminalUIScreen != (Object)null)
{
Plugin.Logger.LogInfo((object)("[INIT] terminalUIScreen: " + ((Object)__instance.terminalUIScreen).name));
LogGameObjectHierarchy(((Component)__instance.terminalUIScreen).gameObject, 0, 3);
}
else
{
Plugin.Logger.LogWarning((object)"[INIT] terminalUIScreen is null");
}
if ((Object)(object)__instance.screenText != (Object)null)
{
Plugin.Logger.LogInfo((object)("[INIT] screenText GameObject: " + ((Object)((Component)__instance.screenText).gameObject).name));
if ((Object)(object)__instance.screenText.textComponent != (Object)null)
{
Plugin.Logger.LogInfo((object)("[INIT] screenText.textComponent: " + ((object)__instance.screenText.textComponent).GetType().Name));
Plugin.Logger.LogInfo((object)$"[INIT] screenText color: R:{((Graphic)__instance.screenText.textComponent).color.r} G:{((Graphic)__instance.screenText.textComponent).color.g} B:{((Graphic)__instance.screenText.textComponent).color.b}");
}
}
else
{
Plugin.Logger.LogWarning((object)"[INIT] screenText is null");
}
Plugin.Logger.LogInfo((object)"[INIT] === Terminal GameObject Children ===");
LogGameObjectHierarchy(((Component)__instance).gameObject, 0, 3);
Plugin.Logger.LogInfo((object)"[INIT] === Searching for UI Image components ===");
Image[] componentsInChildren = ((Component)__instance.terminalUIScreen).GetComponentsInChildren<Image>(true);
Plugin.Logger.LogInfo((object)$"[INIT] Found {componentsInChildren.Length} Image components in Canvas");
Image[] array = componentsInChildren;
foreach (Image val in array)
{
Plugin.Logger.LogInfo((object)$"[INIT] Image: {((Object)((Component)val).gameObject).name} - Color: R:{((Graphic)val).color.r:F2} G:{((Graphic)val).color.g:F2} B:{((Graphic)val).color.b:F2} A:{((Graphic)val).color.a:F2}");
if (((Object)((Component)val).gameObject).name == "MainContainer" && (Object)(object)terminalBackground == (Object)null)
{
terminalBackground = val;
Plugin.Logger.LogInfo((object)"[INIT] *** Cached MainContainer as terminal background ***");
}
}
Plugin.Logger.LogInfo((object)"[INIT] === Searching for Light components ===");
Light[] componentsInChildren2 = ((Component)__instance).GetComponentsInChildren<Light>(true);
Plugin.Logger.LogInfo((object)$"[INIT] Found {componentsInChildren2.Length} Light components in Terminal");
Light[] array2 = componentsInChildren2;
foreach (Light val2 in array2)
{
Plugin.Logger.LogInfo((object)$"[INIT] Light: {((Object)((Component)val2).gameObject).name} - Type: {val2.type} - Color: R:{val2.color.r:F2} G:{val2.color.g:F2} B:{val2.color.b:F2} - Enabled: {((Behaviour)val2).enabled}");
}
Plugin.Logger.LogInfo((object)"[INIT] ========== End Terminal Object Discovery ==========");
if (Plugin.AlwaysOnScreen.Value)
{
Plugin.Logger.LogInfo((object)"[INIT] Always-on enabled, applying theme immediately");
ApplyTheme(__instance);
themeApplied = true;
}
if (Plugin.AlwaysOnScreen.Value && (Object)(object)__instance.terminalNodes != (Object)null && __instance.terminalNodes.specialNodes != null && __instance.terminalNodes.specialNodes.Count > 1)
{
Plugin.Logger.LogInfo((object)"[ALWAYS-ON] Loading store node for always-on screen");
__instance.LoadNewNode(__instance.terminalNodes.specialNodes[1]);
}
}
private static void LogGameObjectHierarchy(GameObject obj, int depth, int maxDepth)
{
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)obj == (Object)null || depth > maxDepth)
{
return;
}
string text = new string(' ', depth * 2);
Plugin.Logger.LogInfo((object)("[HIERARCHY] " + text + "└─ " + ((Object)obj).name));
Component[] components = obj.GetComponents<Component>();
foreach (Component val in components)
{
if ((Object)(object)val != (Object)null && !(val is Transform))
{
Plugin.Logger.LogInfo((object)("[HIERARCHY] " + text + " ├─ Component: " + ((object)val).GetType().Name));
}
}
foreach (Transform item in obj.transform)
{
LogGameObjectHierarchy(((Component)item).gameObject, depth + 1, maxDepth);
}
}
[HarmonyPatch("waitUntilFrameEndToSetActive")]
[HarmonyPrefix]
private static void WaitUntilFrameEndToSetActivePrefix(Terminal __instance, ref bool active)
{
if (!isPlayerInShip)
{
active = false;
}
else if (Plugin.AlwaysOnScreen.Value)
{
Plugin.Logger.LogInfo((object)$"[ALWAYS-ON] Forcing terminal canvas to stay active (was: {active})");
active = true;
}
}
[HarmonyPatch("SetTerminalInUseClientRpc")]
[HarmonyPostfix]
private static void SetTerminalInUseClientRpcPostfix(Terminal __instance, bool inUse)
{
if (!isPlayerInShip)
{
if ((Object)(object)terminalLight != (Object)null)
{
((Behaviour)terminalLight).enabled = false;
}
}
else if (Plugin.AlwaysOnLight.Value && (Object)(object)terminalLight != (Object)null)
{
((Behaviour)terminalLight).enabled = true;
Plugin.Logger.LogInfo((object)$"[ALWAYS-ON] Terminal light kept enabled (inUse: {inUse})");
}
}
private static void ApplyTheme(Terminal terminal)
{
//IL_00af: Unknown result type (might be due to invalid IL or missing references)
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
//IL_03d0: Unknown result type (might be due to invalid IL or missing references)
//IL_03db: Unknown result type (might be due to invalid IL or missing references)
//IL_03e6: Unknown result type (might be due to invalid IL or missing references)
//IL_0401: Unknown result type (might be due to invalid IL or missing references)
//IL_0406: Unknown result type (might be due to invalid IL or missing references)
//IL_00de: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
//IL_0414: Unknown result type (might be due to invalid IL or missing references)
//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
//IL_010d: Unknown result type (might be due to invalid IL or missing references)
//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
//IL_04d1: Unknown result type (might be due to invalid IL or missing references)
//IL_04e1: Unknown result type (might be due to invalid IL or missing references)
//IL_04ec: Unknown result type (might be due to invalid IL or missing references)
//IL_04f7: Unknown result type (might be due to invalid IL or missing references)
//IL_0112: Unknown result type (might be due to invalid IL or missing references)
//IL_052f: Unknown result type (might be due to invalid IL or missing references)
//IL_053f: Unknown result type (might be due to invalid IL or missing references)
//IL_054a: Unknown result type (might be due to invalid IL or missing references)
//IL_0555: Unknown result type (might be due to invalid IL or missing references)
//IL_048b: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)terminal.screenText == (Object)null || (Object)(object)terminal.screenText.textComponent == (Object)null)
{
Plugin.Logger.LogWarning((object)"[THEME] Cannot apply theme - screenText or textComponent is null");
return;
}
bool flag = !string.IsNullOrWhiteSpace(Plugin.CustomTextColor.Value);
bool flag2 = !string.IsNullOrWhiteSpace(Plugin.CustomScreenLightColor.Value);
bool flag3 = !string.IsNullOrWhiteSpace(Plugin.CustomBackgroundColor.Value);
Color val = default(Color);
Color val2 = default(Color);
Color val3 = default(Color);
if (flag || flag2 || flag3)
{
Plugin.Logger.LogInfo((object)"[THEME] Using custom colors (overriding theme)");
val = (Color)(flag ? ParseCustomColor(Plugin.CustomTextColor.Value) : new Color(0f, 1f, 0f, 1f));
val2 = (Color)(flag2 ? ParseCustomColor(Plugin.CustomScreenLightColor.Value) : new Color(0f, 1f, 0f, 1f));
val3 = (Color)(flag3 ? ParseCustomColor(Plugin.CustomBackgroundColor.Value) : new Color(0f, 0.08f, 0f, 1f));
}
else
{
string text = Plugin.TerminalTheme.Value.ToLower();
switch (text)
{
case "green":
((Color)(ref val))..ctor(0f, 1f, 0f, 1f);
((Color)(ref val2))..ctor(0f, 1f, 0f, 1f);
((Color)(ref val3))..ctor(0f, 0.08f, 0f, 1f);
break;
case "amber":
((Color)(ref val))..ctor(1f, 0.75f, 0f, 1f);
((Color)(ref val2))..ctor(1f, 0.75f, 0f, 1f);
((Color)(ref val3))..ctor(0.08f, 0.06f, 0f, 1f);
break;
case "blue":
((Color)(ref val))..ctor(0.3f, 0.7f, 1f, 1f);
((Color)(ref val2))..ctor(0.3f, 0.7f, 1f, 1f);
((Color)(ref val3))..ctor(0f, 0.03f, 0.08f, 1f);
break;
case "red":
((Color)(ref val))..ctor(1f, 0.2f, 0.2f, 1f);
((Color)(ref val2))..ctor(1f, 0.2f, 0.2f, 1f);
((Color)(ref val3))..ctor(0.08f, 0f, 0f, 1f);
break;
case "purple":
((Color)(ref val))..ctor(0.8f, 0.4f, 1f, 1f);
((Color)(ref val2))..ctor(0.8f, 0.4f, 1f, 1f);
((Color)(ref val3))..ctor(0.06f, 0f, 0.08f, 1f);
break;
case "cyan":
((Color)(ref val))..ctor(0f, 1f, 1f, 1f);
((Color)(ref val2))..ctor(0f, 1f, 1f, 1f);
((Color)(ref val3))..ctor(0f, 0.08f, 0.08f, 1f);
break;
default:
Plugin.Logger.LogInfo((object)"[THEME] Using default terminal colors");
return;
}
Plugin.Logger.LogInfo((object)("[THEME] Using theme preset: " + text));
}
((Graphic)terminal.screenText.textComponent).color = val;
Plugin.Logger.LogInfo((object)$"[THEME] Applied text color: R:{val.r:F2} G:{val.g:F2} B:{val.b:F2}");
if (terminal.screenText.caretColor != val)
{
terminal.screenText.caretColor = val;
Plugin.Logger.LogInfo((object)"[THEME] Applied caret color");
}
if ((Object)(object)terminal.terminalUIScreen != (Object)null)
{
TextMeshProUGUI[] componentsInChildren = ((Component)terminal.terminalUIScreen).GetComponentsInChildren<TextMeshProUGUI>(true);
Plugin.Logger.LogInfo((object)$"[THEME] Found {componentsInChildren.Length} TextMeshProUGUI components");
TextMeshProUGUI[] array = componentsInChildren;
foreach (TextMeshProUGUI val4 in array)
{
if (!((Object)(object)val4 == (Object)(object)terminal.screenText.textComponent))
{
((Graphic)val4).color = val;
Plugin.Logger.LogInfo((object)("[THEME] Colored text component: " + ((Object)((Component)val4).gameObject).name));
}
}
}
if ((Object)(object)terminalLight != (Object)null)
{
terminalLight.color = val2;
Plugin.Logger.LogInfo((object)$"[THEME] Applied light color: R:{val2.r:F2} G:{val2.g:F2} B:{val2.b:F2}");
}
else
{
Plugin.Logger.LogInfo((object)"[THEME] No terminal light found to color");
}
if ((Object)(object)terminalBackground != (Object)null)
{
((Graphic)terminalBackground).color = val3;
Plugin.Logger.LogInfo((object)$"[THEME] Applied background color: R:{val3.r:F2} G:{val3.g:F2} B:{val3.b:F2}");
}
else
{
Plugin.Logger.LogInfo((object)"[THEME] No terminal background found to color");
}
}
private static Color ParseCustomColor(string colorString)
{
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
try
{
string[] array = colorString.Split(',');
if (array.Length == 3)
{
float num = float.Parse(array[0].Trim()) / 255f;
float num2 = float.Parse(array[1].Trim()) / 255f;
float num3 = float.Parse(array[2].Trim()) / 255f;
return new Color(num, num2, num3, 1f);
}
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("[THEME] Failed to parse custom color '" + colorString + "': " + ex.Message));
}
Plugin.Logger.LogWarning((object)"[THEME] Using default green color due to parse error");
return new Color(0f, 1f, 0f, 1f);
}
}
[HarmonyPatch(typeof(PlayerControllerB))]
internal class PlayerControllerPatches
{
private static float updateTimer;
private const float UPDATE_INTERVAL = 0.5f;
[HarmonyPatch("Update")]
[HarmonyPostfix]
private static void UpdatePostfix(PlayerControllerB __instance)
{
if (((NetworkBehaviour)__instance).IsOwner && __instance.isPlayerControlled)
{
updateTimer += Time.deltaTime;
if (!(updateTimer < 0.5f))
{
updateTimer = 0f;
UpdateTerminalPerformanceState(__instance);
}
}
}
private static void UpdateTerminalPerformanceState(PlayerControllerB player)
{
if ((Object)(object)TerminalPatches.cachedTerminal == (Object)null)
{
Terminal val = Object.FindObjectOfType<Terminal>();
if (!((Object)(object)val != (Object)null))
{
return;
}
TerminalPatches.cachedTerminal = val;
}
bool isInHangarShipRoom = player.isInHangarShipRoom;
if (isInHangarShipRoom == TerminalPatches.isPlayerInShip)
{
return;
}
TerminalPatches.isPlayerInShip = isInHangarShipRoom;
if (isInHangarShipRoom)
{
if (Plugin.AlwaysOnScreen.Value && (Object)(object)TerminalPatches.cachedTerminal.terminalUIScreen != (Object)null)
{
GameObject gameObject = ((Component)TerminalPatches.cachedTerminal.terminalUIScreen).gameObject;
if ((Object)(object)gameObject != (Object)null)
{
gameObject.SetActive(true);
Plugin.Logger.LogInfo((object)"[PERFORMANCE] Player entered ship - terminal screen enabled");
}
}
if (Plugin.AlwaysOnLight.Value && (Object)(object)TerminalPatches.terminalLight != (Object)null)
{
((Behaviour)TerminalPatches.terminalLight).enabled = true;
Plugin.Logger.LogInfo((object)"[PERFORMANCE] Player entered ship - terminal light enabled");
}
return;
}
if ((Object)(object)TerminalPatches.cachedTerminal.terminalUIScreen != (Object)null)
{
GameObject gameObject2 = ((Component)TerminalPatches.cachedTerminal.terminalUIScreen).gameObject;
if ((Object)(object)gameObject2 != (Object)null)
{
gameObject2.SetActive(false);
Plugin.Logger.LogInfo((object)"[PERFORMANCE] Player exited ship - terminal screen disabled");
}
}
if ((Object)(object)TerminalPatches.terminalLight != (Object)null)
{
((Behaviour)TerminalPatches.terminalLight).enabled = false;
Plugin.Logger.LogInfo((object)"[PERFORMANCE] Player exited ship - terminal light disabled");
}
}
}
}