Decompiled source of ClickableTerminal v0.1.0

ClickableTerminal.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ClickableTerminal.Api;
using GameNetcodeStuff;
using HarmonyLib;
using LethalCompanyInputUtils.Glyphs;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.TextCore;
using UnityEngine.UI;
using UnityEngine.Video;

[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("ClickableTerminal")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Interactive terminal with mouse and controller support.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+6308c2a2b380ed7c9e020c0f20eeeaa84a3428c5")]
[assembly: AssemblyProduct("ClickableTerminal")]
[assembly: AssemblyTitle("ClickableTerminal")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ClickableTerminal
{
	internal class InteractiveMouse
	{
		private static readonly Regex Tags = new Regex("<[^>]+>", RegexOptions.Compiled);

		private readonly List<HotSpot> spots = new List<HotSpot>();

		private readonly StringBuilder builder = new StringBuilder();

		private string scanned;

		private object scannedMenu;

		private int scanIn;

		private bool screenChanged;

		internal bool TakeScreenChange()
		{
			bool result = screenChanged;
			screenChanged = false;
			return result;
		}

		internal void Reset()
		{
			spots.Clear();
			scanned = null;
			scannedMenu = null;
		}

		internal void Tick(string screen, TMP_Text text)
		{
			object app = InteractiveTerminal.Application();
			object obj = InteractiveTerminal.Menu(app);
			if (obj != scannedMenu)
			{
				screenChanged = true;
			}
			if (screen != scanned || obj != scannedMenu)
			{
				scanned = screen;
				scannedMenu = obj;
				scanIn = 2;
			}
			if (scanIn > 0 && --scanIn == 0)
			{
				Scan(text, app, obj);
			}
		}

		internal void Targets(RectTransform frame, List<Vector2> into)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = default(Vector2);
			foreach (HotSpot spot in spots)
			{
				((Vector2)(ref val))..ctor(Mathf.Lerp(((Rect)(ref spot.Area)).xMin, ((Rect)(ref spot.Area)).xMax, 0.12f), ((Rect)(ref spot.Area)).center.y);
				into.Add(Vector2.op_Implicit(((Transform)frame).InverseTransformPoint(((Transform)spot.Space).TransformPoint(Vector2.op_Implicit(val)))));
			}
		}

		internal HotSpot SpotAt(Vector3 world)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			foreach (HotSpot spot in spots)
			{
				if (((Rect)(ref spot.Area)).Contains(((Transform)spot.Space).InverseTransformPoint(world)))
				{
					return spot;
				}
			}
			return null;
		}

		private void Scan(TMP_Text text, object app, object menu)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			spots.Clear();
			Array array = InteractiveTerminal.Elements(menu);
			TMP_TextInfo textInfo = text.textInfo;
			if (array == null || textInfo == null || textInfo.lineInfo == null || textInfo.characterInfo == null)
			{
				return;
			}
			int num = Mathf.Min(textInfo.lineCount, textInfo.lineInfo.Length);
			string[] array2 = new string[num];
			for (int i = 0; i < num; i++)
			{
				array2[i] = LineText(textInfo, textInfo.lineInfo[i]);
			}
			List<int> list = new List<int>();
			List<int> list2 = new List<int>();
			int num2 = 0;
			for (int j = 0; j < array.Length; j++)
			{
				object value = array.GetValue(j);
				string text2 = ((value != null) ? InteractiveTerminal.TextOf(value) : null);
				string text3 = ((text2 != null) ? Key(text2) : "");
				if (text3.Length == 0)
				{
					continue;
				}
				for (int k = num2; k < num; k++)
				{
					if (array2[k].Contains(text3))
					{
						list.Add(k);
						list2.Add(j);
						num2 = k + 1;
						break;
					}
				}
			}
			for (int l = 0; l < list.Count; l++)
			{
				int index = list2[l];
				object element = array.GetValue(index);
				int num3 = list[l];
				int m = ((l + 1 < list.Count) ? (list[l + 1] - 1) : num3);
				if (l + 1 == list.Count)
				{
					for (; m + 1 < num && HasWords(array2[m + 1]) && !array2[m + 1].Contains("Page "); m++)
					{
					}
				}
				while (m > num3 && !HasWords(array2[m]))
				{
					m--;
				}
				bool num4 = InteractiveTerminal.CanSelect(element);
				HotSpot hotSpot = new HotSpot
				{
					Space = text.rectTransform,
					Area = AreaOf(text, textInfo, num3, m)
				};
				if (num4)
				{
					hotSpot.Hover = delegate
					{
						InteractiveTerminal.Select(menu, index);
					};
					hotSpot.Click = delegate
					{
						InteractiveTerminal.Select(menu, index);
						InteractiveTerminal.Submit(app);
					};
					if (InteractiveTerminal.HasCounter(element))
					{
						hotSpot.Scroll = delegate(bool up)
						{
							InteractiveTerminal.Count(element, up);
						};
					}
				}
				spots.Add(hotSpot);
			}
		}

		private static string Key(string name)
		{
			string text = Tags.Replace(name, "").Split('\n')[0].Trim();
			return ((text.Length > 14) ? text.Substring(0, 14) : text).TrimEnd();
		}

		private string LineText(TMP_TextInfo info, TMP_LineInfo line)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			builder.Clear();
			int num = Mathf.Min(line.lastCharacterIndex, info.characterInfo.Length - 1);
			for (int i = line.firstCharacterIndex; i <= num; i++)
			{
				builder.Append(info.characterInfo[i].character);
			}
			return builder.ToString();
		}

		private static bool HasWords(string line)
		{
			for (int i = 0; i < line.Length; i++)
			{
				if (char.IsLetterOrDigit(line[i]))
				{
					return true;
				}
			}
			return false;
		}

		private static Rect AreaOf(TMP_Text text, TMP_TextInfo info, int first, int last)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			float num = float.MaxValue;
			float num2 = float.MinValue;
			for (int i = first; i <= last; i++)
			{
				TMP_LineInfo val = info.lineInfo[i];
				int num3 = Mathf.Min(val.lastCharacterIndex, info.characterInfo.Length - 1);
				for (int j = val.firstCharacterIndex; j <= num3; j++)
				{
					if (!char.IsWhiteSpace(info.characterInfo[j].character))
					{
						num = Mathf.Min(num, info.characterInfo[j].bottomLeft.x);
						num2 = Mathf.Max(num2, info.characterInfo[j].topRight.x);
					}
				}
			}
			if (num > num2)
			{
				num = 0f;
				num2 = 0f;
			}
			float ascender = info.lineInfo[first].ascender;
			float descender = info.lineInfo[last].descender;
			return new Rect(num, descender, num2 - num, ascender - descender);
		}
	}
	internal static class InteractiveTerminal
	{
		private static bool looked;

		private static FieldInfo instance;

		private static FieldInfo applications;

		private static FieldInfo application;

		private static FieldInfo exitAction;

		private static InputControl<float> held;

		private static int releaseIn;

		private static int pressedAt = -100;

		internal static bool PressingKey => Time.frameCount - pressedAt < 6;

		internal static bool RunExitHandlers()
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			Find();
			try
			{
				InputAction val = (InputAction)((exitAction != null) ? /*isinst with value type is only supported in some contexts*/: null);
				object obj = ((val == null) ? null : AccessTools.Field(typeof(InputAction), "m_OnPerformed")?.GetValue(val));
				if (obj == null)
				{
					return false;
				}
				int num = (int)obj.GetType().GetProperty("length").GetValue(obj);
				PropertyInfo property = obj.GetType().GetProperty("Item");
				List<Action<CallbackContext>> list = new List<Action<CallbackContext>>();
				for (int i = 0; i < num; i++)
				{
					if (property.GetValue(obj, new object[1] { i }) is Action<CallbackContext> item)
					{
						list.Add(item);
					}
				}
				foreach (Action<CallbackContext> item2 in list)
				{
					item2(default(CallbackContext));
				}
				return list.Count > 0;
			}
			catch (Exception ex)
			{
				Plugin.mlg.LogWarning((object)("Interactive Terminal API: could not run its exit handlers: " + (ex.InnerException ?? ex).Message));
				return false;
			}
		}

		internal static bool PressExitKey()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			Find();
			try
			{
				InputAction val = (InputAction)((exitAction != null) ? /*isinst with value type is only supported in some contexts*/: null);
				InputControl<float> val2 = ((val != null && val.controls.Count > 0) ? (val.controls[0] as InputControl<float>) : null);
				if (val2 == null)
				{
					return false;
				}
				Queue(val2, 1f);
				held = val2;
				releaseIn = 2;
				pressedAt = Time.frameCount;
				return true;
			}
			catch (Exception ex)
			{
				Plugin.mlg.LogWarning((object)("Interactive Terminal API: could not press its exit key: " + ex.Message));
				return false;
			}
		}

		internal static void Tick()
		{
			if (held != null && --releaseIn <= 0)
			{
				try
				{
					Queue(held, 0f);
				}
				catch (Exception)
				{
				}
				held = null;
			}
		}

		private static void Queue(InputControl<float> control, float value)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			InputEventPtr val2 = default(InputEventPtr);
			NativeArray<byte> val = StateEvent.From(((InputControl)control).device, ref val2, (Allocator)2);
			try
			{
				InputControlExtensions.WriteValueIntoEvent<float>(control, value, val2);
				InputSystem.QueueEvent(val2);
			}
			finally
			{
				((IDisposable)val/*cast due to .constrained prefix*/).Dispose();
			}
		}

		internal static bool Running()
		{
			return Manager() != null;
		}

		internal static string Registered(string command)
		{
			foreach (string item in Commands())
			{
				if (string.Equals(item, command, StringComparison.OrdinalIgnoreCase))
				{
					return item;
				}
			}
			return null;
		}

		internal static IEnumerable<string> Commands()
		{
			Find();
			List<string> list = new List<string>();
			try
			{
				if (applications?.GetValue(null) is IDictionary dictionary)
				{
					foreach (object key in dictionary.Keys)
					{
						list.Add(key.ToString());
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.WarnOnce("Could not read the Interactive Terminal API applications: " + ex.Message);
			}
			return list;
		}

		internal static object Application()
		{
			object obj = Manager();
			if (obj == null || !(application != null))
			{
				return null;
			}
			return application.GetValue(obj);
		}

		internal static object Menu(object app)
		{
			if (app == null)
			{
				return null;
			}
			return Traverse.Create(app).Field("currentCursorMenu").GetValue();
		}

		internal static Array Elements(object menu)
		{
			if (menu == null)
			{
				return null;
			}
			return Traverse.Create(menu).Field("elements").GetValue() as Array;
		}

		internal static string TextOf(object element)
		{
			try
			{
				MethodInfo method = element.GetType().GetMethod("GetText", new Type[1] { typeof(int) });
				if (method != null && method.Invoke(element, new object[1] { 49 }) is string text && !string.IsNullOrWhiteSpace(text))
				{
					return text;
				}
			}
			catch (Exception)
			{
			}
			return Traverse.Create(element).Property("Name", (object[])null).GetValue() as string;
		}

		internal static bool CanSelect(object element)
		{
			if (element == null)
			{
				return false;
			}
			if (Traverse.Create(element).Property("Active", (object[])null).GetValue() is Delegate obj && !(bool)obj.DynamicInvoke(element))
			{
				return (Traverse.Create(element).Property("SelectInactive", (object[])null).GetValue() as bool?) ?? true;
			}
			return true;
		}

		internal static void Select(object menu, int index)
		{
			Traverse.Create(menu).Field("cursorIndex").SetValue((object)index);
		}

		internal static void Submit(object app)
		{
			Call(app, "Submit");
		}

		internal static bool HasPages(object app)
		{
			if (app == null)
			{
				return false;
			}
			object value = Traverse.Create(app).Field("currentPage").GetValue();
			if (value == null || !(Traverse.Create(value).Field("elements").GetValue() is Array { Length: >=2 } array))
			{
				return false;
			}
			object value2 = Traverse.Create(app).Field("currentScreen").GetValue();
			int valueOrDefault = (Traverse.Create(value).Field("pageIndex").GetValue() as int?).GetValueOrDefault();
			if (valueOrDefault >= 0 && valueOrDefault < array.Length)
			{
				return value2 == array.GetValue(valueOrDefault);
			}
			return false;
		}

		internal static bool Page(object app, bool forward)
		{
			if (HasPages(app))
			{
				return Call(app, forward ? "ChangeScreenForward" : "ChangeScreenBackward");
			}
			return false;
		}

		internal static bool HasCounter(object element)
		{
			if (element != null && element.GetType().GetMethod("IncrementCounter") != null)
			{
				return Traverse.Create(element).Property("ShowCounter", (object[])null).GetValue() as bool? == true;
			}
			return false;
		}

		internal static void Count(object element, bool up)
		{
			Call(element, up ? "IncrementCounter" : "DecrementCounter");
		}

		internal static void Exit(object app)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Call(app, "OnScreenExit", (object)default(CallbackContext));
		}

		private static bool Call(object target, string method, params object[] arguments)
		{
			try
			{
				MethodInfo methodInfo = target?.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (methodInfo == null)
				{
					return false;
				}
				methodInfo.Invoke(target, arguments);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.mlg.LogWarning((object)("Interactive Terminal API: " + method + " failed: " + (ex.InnerException ?? ex).Message));
				return false;
			}
		}

		private static object Manager()
		{
			Find();
			try
			{
				Object val = (Object)((instance != null) ? /*isinst with value type is only supported in some contexts*/: null);
				return (val != (Object)null) ? val : null;
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static void Find()
		{
			if (!looked)
			{
				looked = true;
				Type type = Reflect.Find("InteractiveTerminalAPI.UI.InteractiveTerminalManager");
				if (type != null)
				{
					instance = AccessTools.Field(type, "Instance");
					applications = AccessTools.Field(type, "registeredApplications");
					application = AccessTools.Field(type, "mainApplication");
				}
				Type type2 = Reflect.Find("InteractiveTerminalAPI.Input.Keybinds");
				if (type2 != null)
				{
					exitAction = AccessTools.Field(type2, "cursorExitAction");
				}
			}
		}
	}
	internal static class KnownCommands
	{
		internal class Group
		{
			internal string Plugin;

			internal string Title;

			internal string Description;

			internal string[] Claims;

			internal MenuEntry[] Entries;

			internal string[] OnlyIfRegistered;

			internal Func<string, bool> Owns;

			internal Func<MenuPage> Page;
		}

		private static readonly HashSet<string> Hidden = new HashSet<string> { "reset credits", "melanie", "upgrades", "decor", "simulate", "filter", "preview", "sort" };

		private static readonly MenuEntry[] Unexplained = new MenuEntry[2]
		{
			new MenuEntry
			{
				Label = "LOGS",
				Command = "sigurd",
				Description = "Read the log entries the crew has found."
			},
			new MenuEntry
			{
				Label = "EJECT",
				Command = "eject",
				Description = "Fire the crew out of the airlock and start over."
			}
		};

		private static readonly MenuEntry[] Singles = new MenuEntry[4]
		{
			new MenuEntry
			{
				Label = "SHIP INVENTORY",
				Command = "ship",
				Description = "Put items away in the ship's inventory and take them out."
			},
			new MenuEntry
			{
				Label = "MERGE SCRAP",
				Command = "merge",
				Description = "Merge scrap of the same kind into one item."
			},
			new MenuEntry
			{
				Label = "REVIVE",
				Command = "revive",
				Description = "Bring dead crew members back to life, for credits."
			},
			new MenuEntry
			{
				Label = "WEATHER PROBE",
				Command = "probe",
				Description = "Change the weather on a moon, for credits."
			}
		};

		private static readonly KeyValuePair<string, MenuEntry>[] PluginSingles = new KeyValuePair<string, MenuEntry>[3]
		{
			new KeyValuePair<string, MenuEntry>("ExtraDaysToDeadline", new MenuEntry
			{
				Label = "BUY EXTRA DAY",
				Command = "buyday",
				Description = "Ask the Company for one more day to reach the quota, for credits.",
				Click = delegate
				{
					Answers.Run(TheTerminal(), "BUY EXTRA DAY", "buyday", asks: true);
				}
			}),
			new KeyValuePair<string, MenuEntry>("abu.TooMuchScrap", new MenuEntry
			{
				Label = "TOO MUCH SCRAP",
				Command = "merge",
				Description = "Merge nearby piles of small scrap (bones, body parts) into one, to cut lag. Host only.",
				Click = delegate
				{
					Answers.AskFirst(TheTerminal(), "MERGE SCRAP", "Merge the small scrap lying close together in the ship into fewer pieces?\n\n<alpha=#99>Only the kinds set in the mod's config merge (body parts and bones by default), only the host can, and by default only at the Company.", "MERGE", "merge");
				}
			}),
			new KeyValuePair<string, MenuEntry>("BLUJAY.WeatherReroll", new MenuEntry
			{
				Label = "WEATHER REROLL",
				Command = "reroll",
				Description = "Roll the weather of every moon again, for credits. Each reroll that day costs more.",
				Click = delegate
				{
					Answers.Run(TheTerminal(), "WEATHER REROLL", "reroll");
				}
			})
		};

		private static readonly HashSet<string> SingleAliases = new HashSet<string> { "merge scrap", "mergescrap", "scrapmerge", "weather" };

		private static readonly Group[] Groups = new Group[9]
		{
			new Group
			{
				Plugin = "com.malco.lethalcompany.moreshipupgrades",
				Title = "LATEGAME UPGRADES",
				Description = "Upgrade store, contracts and the crew's other tools.",
				Claims = new string[20]
				{
					"lategame", "lgu", "lategame store", "lgc", "contracts", "contract", "contract info", "trade", "trade player credits", "lgu trade",
					"convert", "pc", "interns", "intern", "atk", "initattack", "cd", "cooldown", "extend deadline", "probe"
				},
				OnlyIfRegistered = new string[6] { "lgu", "contracts", "trade", "convert", "extend deadline", "probe" },
				Entries = new MenuEntry[10]
				{
					new MenuEntry
					{
						Label = "UPGRADE STORE",
						Command = "lgu",
						Description = "Buy upgrades for the ship and the crew."
					},
					new MenuEntry
					{
						Label = "CONTRACTS",
						Command = "contracts",
						Description = "Take on a contract for a reward."
					},
					new MenuEntry
					{
						Label = "EXTEND DEADLINE",
						Command = "extend deadline",
						Description = "Buy more days before the quota is due."
					},
					new MenuEntry
					{
						Label = "WEATHER PROBE",
						Command = "probe",
						Description = "Change the weather on a moon."
					},
					new MenuEntry
					{
						Label = "INTERNS",
						Command = "interns",
						Description = "Replace the dead player on the radar with a new employee."
					},
					new MenuEntry
					{
						Label = "TRADE CREDITS",
						Command = "trade",
						Description = "Give player credits to someone else."
					},
					new MenuEntry
					{
						Label = "CONVERT CREDITS",
						Command = "convert",
						Description = "Change company credits into player credits and back."
					},
					new MenuEntry
					{
						Label = "STUN ATTACK",
						Command = "atk",
						Description = "Stun nearby enemies. Needs the Discombobulator."
					},
					new MenuEntry
					{
						Label = "STUN COOLDOWN",
						Command = "cd",
						Description = "How long until the stun can be used again."
					},
					new MenuEntry
					{
						Label = "ABOUT",
						Command = "lategame",
						Description = "What the mod has to say about itself."
					}
				}
			},
			new Group
			{
				Plugin = "com.github.zehsteam.SellMyScrap",
				Title = "SELL MY SCRAP",
				Description = "Sell scrap from the ship without carrying it to the counter.",
				Claims = new string[17]
				{
					"sell", "sell amount", "sellamount", "sell quota", "sellquota", "sell all", "sellall", "sell item", "sellitem", "sell list",
					"selllist", "view overtime", "viewovertime", "view scrap", "viewscrap", "view all scrap", "viewallscrap"
				},
				Page = SellMyScrapPages.Menu
			},
			new Group
			{
				Plugin = "GJ.ScrapBank",
				Title = "SCRAP BANK",
				Description = "Put scrap in the bank as money, and sell from it.",
				Claims = new string[0],
				Owns = ScrapBankPages.Owns,
				Page = ScrapBankPages.Menu
			},
			new Group
			{
				Plugin = "NoteBoxz.SelectableInteriors",
				Title = "INTERIORS",
				Description = "Pick the interior the next moon will have.",
				Claims = new string[0],
				Owns = SelectableInteriorsPage.Owns,
				Page = () => SelectableInteriorsPage.Build(Object.FindObjectOfType<Terminal>())
			},
			new Group
			{
				Plugin = "cucumis.lethalrpg",
				Title = "LETHAL RPG",
				Description = "Spend XP on upgrades for your body and your tools.",
				Claims = new string[1] { "rpg" },
				Page = LethalRpgPages.Menu
			},
			new Group
			{
				Plugin = "mattymatty.LobbyControl",
				Title = "LOBBY",
				Description = "Open or close the lobby to new players, and save or load the game. Host only.",
				Claims = new string[1] { "lobby" },
				Entries = new MenuEntry[13]
				{
					new MenuEntry
					{
						Label = "STATUS",
						Command = "lobby status",
						Description = "Whether the lobby is open, who can see it, and the save file."
					},
					new MenuEntry
					{
						Label = "OPEN",
						Command = "lobby open",
						Description = "Let new players join. Only in orbit."
					},
					new MenuEntry
					{
						Label = "CLOSE",
						Command = "lobby close",
						Description = "Keep new players out. Only in orbit."
					},
					new MenuEntry
					{
						Label = "PUBLIC",
						Command = "lobby public",
						Description = "Anyone can find the lobby."
					},
					new MenuEntry
					{
						Label = "FRIENDS ONLY",
						Command = "lobby friend",
						Description = "Only Steam friends can find the lobby."
					},
					new MenuEntry
					{
						Label = "INVITE ONLY",
						Command = "lobby private",
						Description = "Only invited players can join."
					},
					new MenuEntry
					{
						Label = "RENAME",
						Description = "Give the lobby a new name.",
						Click = delegate
						{
							Answers.Typed(TheTerminal(), "RENAME LOBBY", "Type the lobby's new name, then Enter.", "lobby rename ", "RENAME", 40);
						}
					},
					new MenuEntry
					{
						Label = "AUTOSAVE",
						Command = "lobby autosave",
						Description = "Switch saving the game on its own on or off."
					},
					new MenuEntry
					{
						Label = "SAVE",
						Description = "Save the game under a name.",
						Click = delegate
						{
							Answers.Typed(TheTerminal(), "SAVE GAME", "Type a name for the save, then Enter.", "lobby save ", "SAVE", 30);
						}
					},
					new MenuEntry
					{
						Label = "LOAD",
						Description = "Load a saved game into this lobby.",
						Click = delegate
						{
							Answers.Typed(TheTerminal(), "LOAD GAME", "Type the name of the save to load, then Enter. What is not saved is lost.", "lobby load ", "LOAD", 30);
						}
					},
					new MenuEntry
					{
						Label = "SWITCH SAVE",
						Description = "Change which save file the game writes to, without loading it.",
						Click = delegate
						{
							Answers.Typed(TheTerminal(), "SWITCH SAVE", "Type the name of the save file to use from now on, then Enter.", "lobby switch ", "SWITCH", 30);
						}
					},
					new MenuEntry
					{
						Label = "DROP ALL",
						Description = "Everyone drops everything they hold.",
						Click = delegate
						{
							Answers.AskFirst(TheTerminal(), "DROP ALL", "Make every player drop everything they hold?", "DROP ALL", "lobby dropall");
						}
					},
					new MenuEntry
					{
						Label = "CLEAR",
						Description = "Reset this lobby to an empty game.",
						Click = delegate
						{
							Answers.AskFirst(TheTerminal(), "CLEAR LOBBY", "Reset this lobby to an empty game? Everything not saved is lost.", "CLEAR", "lobby clear");
						}
					}
				}
			},
			new Group
			{
				Plugin = "com.seeya.operator",
				Title = "CASSETTES",
				Description = "Play, save and delete what the camcorder recorded.",
				Claims = new string[14]
				{
					"ohelp", "cassettes", "play", "stop", "save", "delete", "slow", "normal", "fast", "turbo",
					"pause", "resume", "volume", "transfer"
				},
				Entries = new MenuEntry[10]
				{
					new MenuEntry
					{
						Label = "SAVED CASSETTES",
						Command = "cassettes",
						Description = "Every saved cassette, with its ID, value, date and length."
					},
					new MenuEntry
					{
						Label = "PLAY",
						Description = "Play a cassette on the terminal, by its ID.",
						Click = delegate
						{
							Answers.Typed(TheTerminal(), "PLAY", "Type the cassette's ID, then Enter.", "play ", "PLAY", 20);
						}
					},
					new MenuEntry
					{
						Label = "STOP",
						Command = "stop",
						Description = "Stop playing."
					},
					new MenuEntry
					{
						Label = "PAUSE",
						Command = "pause",
						Description = "Pause playing."
					},
					new MenuEntry
					{
						Label = "RESUME",
						Command = "resume",
						Description = "Go on playing."
					},
					new MenuEntry
					{
						Label = "SPEED",
						Description = "Play slower or faster.",
						Click = delegate
						{
							Terminal terminal = TheTerminal();
							Window window = new Window
							{
								Title = "SPEED",
								Text = "How fast the cassette plays."
							};
							string[] array = new string[4] { "slow", "normal", "fast", "turbo" };
							foreach (string speed in array)
							{
								window.Buttons.Add(new ToolButton
								{
									Label = speed.ToUpperInvariant(),
									Click = delegate
									{
										Answers.Run(terminal, "SPEED", speed);
									}
								});
							}
							Window.Show(window);
						}
					},
					new MenuEntry
					{
						Label = "VOLUME",
						Description = "How loud the cassette plays, 0 to 100.",
						Click = delegate
						{
							Answers.Typed(TheTerminal(), "VOLUME", "Type the volume, 0 to 100, then Enter.", "volume ", "SET", 3);
						}
					},
					new MenuEntry
					{
						Label = "SAVE",
						Description = "Keep a cassette in the library, by its ID.",
						Click = delegate
						{
							Answers.Typed(TheTerminal(), "SAVE CASSETTE", "Type the cassette's ID, then Enter.", "save ", "SAVE", 20);
						}
					},
					new MenuEntry
					{
						Label = "DELETE",
						Description = "Take a cassette out of the library, by its ID.",
						Click = delegate
						{
							Answers.Typed(TheTerminal(), "DELETE CASSETTE", "Type the ID of the cassette to delete, then Enter.", "delete ", "DELETE", 20);
						}
					},
					new MenuEntry
					{
						Label = "TRANSFER",
						Command = "transfer",
						Description = "Move what is playing onto the ship's monitor, where that is possible."
					}
				}
			},
			new Group
			{
				Plugin = "gavin.shipcomputerassistant",
				Title = "CREW MONITOR",
				Description = "Health, place, weight and held item of every crew member.",
				Claims = new string[5] { "crew", "crew upgrade", "crew monitoring", "buy crew monitor", "status" },
				Entries = new MenuEntry[2]
				{
					new MenuEntry
					{
						Label = "CREW STATUS",
						Command = "crew",
						Description = "How every crew member is doing. Needs the software installed."
					},
					new MenuEntry
					{
						Label = "BUY THE SOFTWARE",
						Command = "crew upgrade",
						Description = "Install Crew Monitoring Software on the ship, for credits."
					}
				}
			},
			new Group
			{
				Plugin = "thej01.lc.TerminalSeedPicker",
				Title = "SEED PICKER",
				Description = "See or set the seed the next moon is built from.",
				Claims = new string[4] { "seed", "get seed", "set seed", "random seed" },
				Entries = new MenuEntry[3]
				{
					new MenuEntry
					{
						Label = "GET SEED",
						Command = "get seed",
						Description = "Show the seed of the moon you are on. Only after landing."
					},
					new MenuEntry
					{
						Label = "RANDOM SEED",
						Command = "random seed",
						Description = "Set a completely random seed for the next moon. Only in orbit."
					},
					new MenuEntry
					{
						Label = "SET SEED",
						Description = "Type a seed for the next moon, text or numbers. Only in orbit.",
						Click = delegate
						{
							Answers.Typed(Object.FindObjectOfType<Terminal>(), "SET SEED", "Type the seed for the next moon, text or numbers, then Enter.", "set seed ", "SET", 40);
						}
					}
				}
			}
		};

		private static readonly HashSet<string> VanillaMain = new HashSet<string> { "moons", "store", "bestiary", "storage", "other" };

		private static readonly HashSet<string> VanillaOther = new HashSet<string> { "view monitor", "switch", "ping", "flash", "transmit", "scan" };

		private static readonly string[] None = new string[0];

		private static Terminal TheTerminal()
		{
			return Object.FindObjectOfType<Terminal>();
		}

		internal static IEnumerable<MenuEntry> SinglesAmong(IEnumerable<string> registered)
		{
			KeyValuePair<string, MenuEntry>[] pluginSingles = PluginSingles;
			for (int i = 0; i < pluginSingles.Length; i++)
			{
				KeyValuePair<string, MenuEntry> keyValuePair = pluginSingles[i];
				if (Chainloader.PluginInfos.ContainsKey(keyValuePair.Key))
				{
					yield return keyValuePair.Value;
				}
			}
			MenuEntry[] singles = Singles;
			foreach (MenuEntry entry in singles)
			{
				string key = Commands.Normalize(entry.Command);
				if (!Present().Any((Group group) => group.Claims.Contains(key)))
				{
					string text = registered.FirstOrDefault((string a) => string.Equals(a, entry.Command, StringComparison.OrdinalIgnoreCase));
					if (text != null)
					{
						yield return new MenuEntry
						{
							Label = entry.Label,
							Description = entry.Description,
							Command = text
						};
					}
				}
			}
		}

		internal static bool IsVanillaMain(MenuEntry entry)
		{
			return VanillaMain.Contains(Commands.Normalize(entry.Command ?? entry.Prefill ?? ""));
		}

		internal static bool IsVanillaOther(MenuEntry entry)
		{
			return VanillaOther.Contains(Commands.Normalize(entry.Command ?? entry.Prefill ?? ""));
		}

		internal static IEnumerable<string> VanillaCommands()
		{
			return VanillaMain.Concat(VanillaOther).Concat(Unexplained.Select((MenuEntry entry) => entry.Command)).Concat(new string[1] { "help" });
		}

		internal static bool IsHidden(string command)
		{
			string key = Commands.Normalize(command ?? "");
			if (!Hidden.Contains(key) && !SingleAliases.Contains(key))
			{
				return Present().Any((Group group) => group.Claims.Contains(key) || (group.Owns != null && group.Owns(key)));
			}
			return true;
		}

		internal static IEnumerable<MenuEntry> UnexplainedFor(Terminal terminal)
		{
			TerminalKeyword[] keywords = (((Object)(object)terminal.terminalNodes != (Object)null) ? terminal.terminalNodes.allKeywords : null);
			if (keywords == null)
			{
				yield break;
			}
			MenuEntry[] unexplained = Unexplained;
			foreach (MenuEntry entry in unexplained)
			{
				if (keywords.Any((TerminalKeyword keyword) => (Object)(object)keyword != (Object)null && keyword.word == entry.Command))
				{
					yield return entry;
				}
			}
		}

		internal static IEnumerable<MenuEntry> GroupEntries()
		{
			foreach (Group item in Present())
			{
				Group shown = item;
				yield return new MenuEntry
				{
					Label = item.Title,
					Description = item.Description,
					Open = () => PageOf(shown)
				};
			}
		}

		private static MenuPage PageOf(Group group)
		{
			if (group.Page != null)
			{
				return group.Page();
			}
			MenuPage menuPage = Navigator.MenuLook(Object.FindObjectOfType<Terminal>(), new MenuPage
			{
				Title = group.Title
			});
			MenuEntry[] entries = group.Entries;
			foreach (MenuEntry menuEntry in entries)
			{
				if (!(group.OnlyIfRegistered ?? None).Contains(menuEntry.Command))
				{
					menuPage.Entries.Add(menuEntry);
					continue;
				}
				string text = InteractiveTerminal.Registered(menuEntry.Command);
				if (text != null)
				{
					menuPage.Entries.Add(new MenuEntry
					{
						Label = menuEntry.Label,
						Description = menuEntry.Description,
						Command = text
					});
				}
			}
			return Answers.InWindows(Object.FindObjectOfType<Terminal>(), menuPage);
		}

		private static IEnumerable<Group> Present()
		{
			return Groups.Where((Group group) => Chainloader.PluginInfos.ContainsKey(group.Plugin));
		}
	}
	internal static class LethalRpgPages
	{
		internal const string Guid = "cucumis.lethalrpg";

		private static readonly Regex Upgrade = new Regex("^\\*\\s+(.+?)\\s{2,}Lv\\s*(\\d+)\\s*/\\s*(\\d+)", RegexOptions.Compiled);

		private static readonly Regex Tags = new Regex("<[^>]+>", RegexOptions.Compiled);

		private static readonly string[] Footer = new string[4] { "Pages:", "Buy:", "More:", "Skill menu:" };

		private static readonly (string Label, string Command)[] Lists = new(string, string)[4]
		{
			("BODY", "rpg"),
			("TOOLS", "rpg tools"),
			("UNIQUE", "rpg unique"),
			("ULTIMATE", "rpg ult")
		};

		internal static MenuPage Menu()
		{
			Terminal terminal = Object.FindObjectOfType<Terminal>();
			MenuPage menuPage = Navigator.MenuLook(terminal, new MenuPage
			{
				Title = "LETHAL RPG"
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "BODY UPGRADES",
				Description = "Stamina, strength, jump and the rest of the body.",
				Click = delegate
				{
					List(terminal, "rpg");
				}
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "TOOL UPGRADES",
				Description = "Upgrades for the tools you carry.",
				Click = delegate
				{
					List(terminal, "rpg tools");
				}
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "UNIQUE UPGRADES",
				Description = "One-of-a-kind upgrades.",
				Click = delegate
				{
					List(terminal, "rpg unique");
				}
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "ULTIMATES",
				Description = "The big ones, for the most XP.",
				Click = delegate
				{
					List(terminal, "rpg ult");
				}
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "MY STATS",
				Command = "rpg stats",
				Description = "Your level, XP and what you have bought."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "CREW",
				Command = "rpg crew",
				Description = "How the rest of the crew is built."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "BUILD",
				Command = "rpg build",
				Description = "Your build, to save and buy back later."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "INFO",
				Command = "rpg info",
				Description = "How the mod works."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "REFUND",
				Description = "Take back every upgrade and get the points to spend again.",
				Click = delegate
				{
					Refund(terminal);
				}
			});
			return Answers.InWindows(terminal, menuPage);
		}

		private static void List(Terminal terminal, string command, string note = null)
		{
			string input = Commands.Ask(terminal, command);
			string[] array = Tags.Replace(input, "").Replace("\r", "").Split('\n');
			string text = string.Join("\n", array.Where((string line) => !Footer.Any((string start) => line.TrimStart().StartsWith(start))));
			Window window = new Window
			{
				Title = "LETHAL RPG",
				Text = ((note != null) ? ("<alpha=#99>" + note + "<alpha=#FF>\n\n") : "") + Window.Tidy(text, "")
			};
			List<ToolButton> list = window.Group("LISTS");
			(string, string)[] lists = Lists;
			for (int num = 0; num < lists.Length; num++)
			{
				(string, string) tuple = lists[num];
				string target = tuple.Item2;
				list.Add(new ToolButton
				{
					Label = tuple.Item1,
					Active = (target == command),
					Click = delegate
					{
						List(terminal, target);
					}
				});
			}
			List<ToolButton> buy = window.Group("BUY A LEVEL");
			string[] array2 = array;
			foreach (string text2 in array2)
			{
				Match match = Upgrade.Match(text2.Trim());
				if (match.Success)
				{
					string name = match.Groups[1].Value.Trim();
					bool off = match.Groups[2].Value == match.Groups[3].Value;
					buy.Add(new ToolButton
					{
						Label = name,
						Off = off,
						Click = delegate
						{
							string text3 = Window.Tidy(Commands.Ask(terminal, "rpg buy " + name.ToLowerInvariant()), "");
							List(terminal, command, (text3.Length > 0) ? text3 : null);
							((MonoBehaviour)terminal).StartCoroutine(ReadAgain(terminal, command));
						}
					});
				}
			}
			if (buy.Count == 0)
			{
				window.Groups.RemoveAll((KeyValuePair<string, List<ToolButton>> group) => group.Value == buy);
			}
			Window.Show(window);
		}

		private static IEnumerator ReadAgain(Terminal terminal, string command)
		{
			yield return (object)new WaitForSecondsRealtime(0.8f);
			if (Window.Open != null && Window.Open.Title == "LETHAL RPG")
			{
				List(terminal, command);
			}
		}

		private static void Refund(Terminal terminal)
		{
			Window window = new Window();
			window.Title = "REFUND";
			window.Text = Window.Tidy(Commands.Ask(terminal, "rpg refund"), "");
			window.Buttons.Add(new ToolButton
			{
				Label = "REFUND",
				Click = delegate
				{
					Answers.Run(terminal, "REFUND", "rpg refund confirm");
				}
			});
			window.Buttons.Add(new ToolButton
			{
				Label = "CANCEL",
				Click = Window.Close
			});
			Window.Show(window);
		}
	}
	internal class MoonData
	{
		internal readonly Dictionary<SelectableLevel, HashSet<string>> Tags = new Dictionary<SelectableLevel, HashSet<string>>();

		internal readonly HashSet<SelectableLevel> Hidden = new HashSet<SelectableLevel>();

		internal readonly HashSet<SelectableLevel> Locked = new HashSet<SelectableLevel>();

		internal const string Vanilla = "VANILLA";

		internal readonly Dictionary<SelectableLevel, string> Authors = new Dictionary<SelectableLevel, string>();

		internal bool LevelLoader;

		internal const string Rotation = "ROTATION";

		internal bool Rotating;

		internal readonly HashSet<SelectableLevel> Drawn = new HashSet<SelectableLevel>();

		private static MoonData last;

		private static float lastAt = -100f;

		internal static MoonData Read()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (last != null && realtimeSinceStartup - lastAt < 3f)
			{
				return last;
			}
			MoonData moonData = new MoonData();
			Try(moonData.ReadLevelLoader, "LethalLevelLoader");
			Try(moonData.ReadDawnLib, "DawnLib");
			Try(moonData.ReadRotation, "TooManyMoons");
			last = moonData;
			lastAt = realtimeSinceStartup;
			return moonData;
		}

		private void ReadLevelLoader()
		{
			Type type = Reflect.Find("LethalLevelLoader.PatchedContent");
			if (type == null || !(AccessTools.Property(type, "ExtendedLevels")?.GetValue(null) is IEnumerable enumerable))
			{
				return;
			}
			LevelLoader = true;
			Dictionary<object, string> teams = Origins.ModTeams();
			bool flag = default(bool);
			bool flag2 = default(bool);
			foreach (object item in enumerable)
			{
				if (item == null)
				{
					continue;
				}
				object value = Traverse.Create(item).Property("SelectableLevel", (object[])null).GetValue();
				SelectableLevel val = (SelectableLevel)((value is SelectableLevel) ? value : null);
				if (val == null || (Object)(object)val == (Object)null)
				{
					continue;
				}
				object value2 = Traverse.Create(item).Property("IsRouteHidden", (object[])null).GetValue();
				int num;
				if (value2 is bool)
				{
					flag = (bool)value2;
					num = 1;
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					Hidden.Add(val);
				}
				value2 = Traverse.Create(item).Property("IsRouteLocked", (object[])null).GetValue();
				int num2;
				if (value2 is bool)
				{
					flag2 = (bool)value2;
					num2 = 1;
				}
				else
				{
					num2 = 0;
				}
				if (((uint)num2 & (flag2 ? 1u : 0u)) != 0)
				{
					Locked.Add(val);
				}
				bool flag3 = Traverse.Create(item).Property("ContentType", (object[])null).GetValue()?.ToString() != "Vanilla";
				Authors[val] = (flag3 ? (Origins.OfExtended(item, teams) ?? "VANILLA") : "VANILLA");
				if (!(Traverse.Create(item).Property("ContentTags", (object[])null).GetValue() is IEnumerable enumerable2))
				{
					continue;
				}
				foreach (object item2 in enumerable2)
				{
					if (item2 != null)
					{
						Add(val, Traverse.Create(item2).Field("contentTagName").GetValue() as string);
					}
				}
			}
		}

		private void ReadDawnLib()
		{
			Type type = Reflect.Find("Dawn.LethalContent");
			object obj = ((!(type != null)) ? null : AccessTools.Field(type, "Moons")?.GetValue(null));
			if (obj == null || !(Traverse.Create(obj).Property("Values", (object[])null).GetValue() is IEnumerable enumerable))
			{
				return;
			}
			foreach (object item in enumerable)
			{
				if (item == null)
				{
					continue;
				}
				object value = Traverse.Create(item).Property("Level", (object[])null).GetValue();
				SelectableLevel val = (SelectableLevel)((value is SelectableLevel) ? value : null);
				if (val == null || (Object)(object)val == (Object)null)
				{
					continue;
				}
				if (!Authors.ContainsKey(val))
				{
					string text = Traverse.Create(item).Property("Key", (object[])null).Property("Namespace", (object[])null)
						.GetValue() as string;
					Authors[val] = ((text == "lethal_company") ? "VANILLA" : (Origins.Clean(text) ?? "VANILLA"));
				}
				if (!(Traverse.Create(item).Method("AllTags", Array.Empty<object>()).GetValue() is IEnumerable enumerable2))
				{
					continue;
				}
				foreach (object item2 in enumerable2)
				{
					if (item2 != null)
					{
						Add(val, Traverse.Create(item2).Property("Key", (object[])null).GetValue() as string);
					}
				}
			}
		}

		private void ReadRotation()
		{
			Type type = Reflect.Find("TooManyMoons.RotationApi");
			SelectableLevel[] array = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.levels : null);
			if (type == null || array == null)
			{
				return;
			}
			object obj = AccessTools.Property(type, "Active")?.GetValue(null);
			if (!(obj is bool) || !(bool)obj)
			{
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "IsDrawn", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				return;
			}
			Rotating = true;
			SelectableLevel[] array2 = array;
			bool flag = default(bool);
			foreach (SelectableLevel val in array2)
			{
				int num;
				if ((Object)(object)val != (Object)null)
				{
					obj = methodInfo.Invoke(null, new object[1] { val });
					if (obj is bool)
					{
						flag = (bool)obj;
						num = 1;
					}
					else
					{
						num = 0;
					}
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					Drawn.Add(val);
				}
			}
		}

		private void Add(SelectableLevel level, string tag)
		{
			if (!string.IsNullOrWhiteSpace(tag))
			{
				if (!Tags.TryGetValue(level, out var value))
				{
					value = (Tags[level] = new HashSet<string>());
				}
				value.Add(tag.Trim().ToLowerInvariant().Replace('_', ' '));
			}
		}

		private static void Try(Action read, string mod)
		{
			try
			{
				read();
			}
			catch (Exception ex)
			{
				Plugin.WarnOnce("Could not read the moon data of " + mod + ": " + ex.Message);
			}
		}
	}
	internal static class Origins
	{
		internal const string Vanilla = "VANILLA";

		internal const string Unknown = "OTHER";

		private static Dictionary<string, string> spellings;

		internal static string TeamOfPath(string file)
		{
			if (string.IsNullOrEmpty(file))
			{
				return null;
			}
			try
			{
				string text = Path.GetFullPath(Paths.PluginPath).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
				string fullPath = Path.GetFullPath(file);
				if (!fullPath.StartsWith(text, StringComparison.OrdinalIgnoreCase))
				{
					return null;
				}
				string text2 = fullPath.Substring(text.Length).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)[0];
				int num = text2.IndexOf('-');
				return (num > 0) ? Spelled(text2.Substring(0, num)) : null;
			}
			catch (Exception)
			{
				return null;
			}
		}

		internal static string Spelled(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return null;
			}
			if (spellings == null)
			{
				spellings = new Dictionary<string, string>();
				try
				{
					string[] directories = Directory.GetDirectories(Paths.PluginPath);
					for (int i = 0; i < directories.Length; i++)
					{
						string fileName = Path.GetFileName(directories[i]);
						int num = fileName.IndexOf('-');
						if (num > 0)
						{
							string text = fileName.Substring(0, num).Replace('_', ' ').Trim()
								.ToUpperInvariant();
							spellings[Squash(text)] = text;
						}
					}
				}
				catch (Exception)
				{
				}
			}
			string text2 = name.Replace('_', ' ').Trim().ToUpperInvariant();
			string key = Squash(text2);
			if (!spellings.TryGetValue(key, out var value))
			{
				value = (spellings[key] = text2);
			}
			return value;
		}

		private static string Squash(string name)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (char c in name)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}

		internal static Dictionary<object, string> ModTeams()
		{
			Dictionary<object, string> dictionary = new Dictionary<object, string>();
			try
			{
				Type type = Reflect.Find("LethalLevelLoader.LethalBundleManager");
				if (type == null || !(AccessTools.Field(type, "obtainedExtendedModsDict")?.GetValue(null) is IDictionary dictionary2))
				{
					return dictionary;
				}
				foreach (DictionaryEntry item in dictionary2)
				{
					if (!(Traverse.Create(item.Key).Field("assetBundleInfos").GetValue() is IEnumerable enumerable) || !(item.Value is IEnumerable enumerable2))
					{
						continue;
					}
					string text = null;
					foreach (object item2 in enumerable)
					{
						text = ((item2 != null) ? TeamOfPath(Traverse.Create(item2).Property("AssetBundleFilePath", (object[])null).GetValue() as string) : null);
						if (text != null)
						{
							break;
						}
					}
					if (text == null)
					{
						continue;
					}
					foreach (object item3 in enumerable2)
					{
						if (item3 != null && !dictionary.ContainsKey(item3))
						{
							dictionary[item3] = text;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.WarnOnce("Could not read which package each mod came from: " + ex.Message);
			}
			return dictionary;
		}

		internal static string OfExtended(object extended, Dictionary<object, string> teams)
		{
			object value = Traverse.Create(extended).Property("ExtendedMod", (object[])null).GetValue();
			if (value != null && teams.TryGetValue(value, out var value2))
			{
				return value2;
			}
			return Clean(Traverse.Create(extended).Property("AuthorName", (object[])null).GetValue() as string) ?? Clean(Traverse.Create(extended).Property("ModName", (object[])null).GetValue() as string);
		}

		internal static Dictionary<EnemyType, string> EnemyCredits()
		{
			Dictionary<EnemyType, string> dictionary = new Dictionary<EnemyType, string>();
			try
			{
				Type type = Reflect.Find("LethalLevelLoader.PatchedContent");
				if (type != null && AccessTools.Property(type, "ExtendedEnemyTypes")?.GetValue(null) is IEnumerable enumerable)
				{
					Dictionary<object, string> teams = ModTeams();
					foreach (object item in enumerable)
					{
						if (item != null)
						{
							object value = Traverse.Create(item).Property("EnemyType", (object[])null).GetValue();
							EnemyType val = (EnemyType)((value is EnemyType) ? value : null);
							if (val != null && !((Object)(object)val == (Object)null))
							{
								bool flag = Traverse.Create(item).Property("ContentType", (object[])null).GetValue()?.ToString() != "Vanilla";
								dictionary[val] = (flag ? OfExtended(item, teams) : "VANILLA");
							}
						}
					}
				}
				Type type2 = Reflect.Find("Dawn.LethalContent");
				object obj = ((!(type2 != null)) ? null : AccessTools.Field(type2, "Enemies")?.GetValue(null));
				if (obj != null && Traverse.Create(obj).Property("Values", (object[])null).GetValue() is IEnumerable enumerable2)
				{
					foreach (object item2 in enumerable2)
					{
						if (item2 != null)
						{
							object value2 = Traverse.Create(item2).Property("EnemyType", (object[])null).GetValue();
							EnemyType val2 = (EnemyType)((value2 is EnemyType) ? value2 : null);
							if (val2 != null && !((Object)(object)val2 == (Object)null) && (!dictionary.TryGetValue(val2, out var value3) || value3 == null))
							{
								string text = Traverse.Create(item2).Property("Key", (object[])null).Property("Namespace", (object[])null)
									.GetValue() as string;
								dictionary[val2] = ((text == "lethal_company") ? "VANILLA" : Clean(text));
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.WarnOnce("Could not read who made the enemies: " + ex.Message);
			}
			return dictionary;
		}

		internal static string OfEnemy(EnemyType enemy, Dictionary<EnemyType, string> credits)
		{
			if ((Object)(object)enemy == (Object)null)
			{
				return "OTHER";
			}
			try
			{
				EnemyAI val = (((Object)(object)enemy.enemyPrefab != (Object)null) ? enemy.enemyPrefab.GetComponent<EnemyAI>() : null);
				Assembly assembly = (((Object)(object)val != (Object)null) ? ((object)val).GetType().Assembly : null);
				if (assembly != null && assembly != typeof(EnemyAI).Assembly)
				{
					string text = TeamOfPath(assembly.Location);
					if (text != null)
					{
						return text;
					}
				}
			}
			catch (Exception)
			{
			}
			if (!credits.TryGetValue(enemy, out var value) || value == null)
			{
				return "VANILLA";
			}
			return value;
		}

		internal static string Clean(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return null;
			}
			string text = name.Replace('_', ' ').Trim().ToUpperInvariant();
			if (!text.StartsWith("UNKNOWN") && !(text == "UNSPECIFIED"))
			{
				return Spelled(text);
			}
			return null;
		}
	}
	internal static class Reflect
	{
		private static readonly Dictionary<string, Type> Known = new Dictionary<string, Type>();

		internal static Type Find(string name)
		{
			if (Known.TryGetValue(name, out var value))
			{
				return value;
			}
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					value = (assembly.IsDynamic ? null : assembly.GetType(name, throwOnError: false));
				}
				catch (Exception)
				{
					value = null;
				}
				if (value != null)
				{
					break;
				}
			}
			Known[name] = value;
			return value;
		}
	}
	internal static class ScrapBankPages
	{
		internal const string Guid = "GJ.ScrapBank";

		private static readonly Regex Tags = new Regex("<[^>]+>", RegexOptions.Compiled);

		private static readonly Regex Heading = new Regex("^=+\\s*(.*?)\\s*=+$", RegexOptions.Compiled);

		private static readonly Regex Paged = new Regex("(\\d+)\\s*/\\s*(\\d+)\\s*$", RegexOptions.Compiled);

		private static readonly Regex Numbered = new Regex("^(\\d+)\\.\\s+(.+?)\\s+(\\$[\\d,.]+)$", RegexOptions.Compiled);

		private static readonly Regex Choice = new Regex("^(\\d+)\\s*/\\s*([A-Z]+)", RegexOptions.Compiled);

		private static readonly Regex Leftover = new Regex("(\\s*/\\s*)+(?=\\s*[:=]|\\s*$)", RegexOptions.Compiled);

		private static readonly Regex Spaces = new Regex("\\s{2,}", RegexOptions.Compiled);

		private static readonly HashSet<string> Words = new HashSet<string> { "bank", "sb", "scrapbank", "scrap", "ship", "sell", "scrapstore", "scrapsell", "scrapmoney", "banksell" };

		private const string ItemTitle = "STORE BY ITEM";

		private static readonly PageState ItemState = new PageState();

		private static string report;

		private static bool storing;

		private static Terminal pending;

		internal static bool Owns(string key)
		{
			string text = key.Split(' ')[0];
			if (text != "ship" && text != "sell")
			{
				return Words.Contains(text);
			}
			return false;
		}

		internal static MenuPage Menu()
		{
			Terminal terminal = Object.FindObjectOfType<Terminal>();
			MenuPage menuPage = Navigator.MenuLook(terminal, new MenuPage
			{
				Title = "SCRAP BANK"
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "BALANCE",
				Command = "bank status",
				Description = "The scrap money in the bank."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "STORE ALL",
				Command = "scrap all",
				Description = "Turn all the scrap in the ship into scrap money."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "STORE AMOUNT",
				Description = "Store scrap worth up to an amount you set.",
				Click = delegate
				{
					Answers.Typed(terminal, "STORE AMOUNT", "Type the value of scrap to store at most, then Enter.", "scrap ", "STORE", 6);
				}
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "STORE BY ITEM",
				Description = "Tick single items in the ship and store those.",
				Open = () => ItemPage(terminal)
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "SELL QUOTA",
				Command = "sell quota",
				Description = "Sell scrap money to meet the quota. Only at the Company."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "SELL ALL",
				Command = "sell all",
				Description = "Sell all the scrap money. Only at the Company."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "SELL AMOUNT",
				Description = "Sell an amount of scrap money you set. Only at the Company.",
				Click = delegate
				{
					TimeOfDay instance = TimeOfDay.Instance;
					int num = (((Object)(object)instance != (Object)null) ? Math.Max(0, instance.profitQuota - instance.quotaFulfilled) : 0);
					Answers.Typed(terminal, "SELL AMOUNT", "Type the scrap money to sell, then Enter.", "sell ", "SELL", 6, Answers.Fill(terminal, $"QUOTA NEEDS ${num}", num.ToString()));
				}
			});
			return Answers.InWindows(terminal, menuPage);
		}

		private static MenuPage ItemPage(Terminal terminal)
		{
			MenuPage menuPage = new MenuPage
			{
				Title = "STORE BY ITEM",
				SortBy = delegate(int column)
				{
					ItemState.SortBy(column);
					Navigator.Rebuild(terminal);
				}
			};
			menuPage.Columns.Add(new Column
			{
				Header = ItemState.HeaderFor("NAME", 0)
			});
			menuPage.Columns.Add(new Column
			{
				Header = ItemState.HeaderFor("VALUE", 1),
				Chars = 9,
				AlignRight = true
			});
			List<MenuEntry> list = new List<MenuEntry>();
			foreach (GrabbableObject item in from item in Storable()
				orderby item.itemProperties.itemName, item.scrapValue descending
				select item)
			{
				GrabbableObject picked = item;
				string itemName = item.itemProperties.itemName;
				int scrapValue = item.scrapValue;
				list.Add(new MenuEntry
				{
					Label = itemName,
					Cells = new string[1] { "$" + scrapValue },
					Keys = new IComparable[2] { itemName, scrapValue },
					Dim = storing,
					Click = delegate
					{
						Store(terminal, picked);
					}
				});
			}
			if (report != null && !storing)
			{
				Window.Show(new Window
				{
					Title = "STORE BY ITEM",
					Text = report
				});
				report = null;
			}
			menuPage.Summary = (storing ? "Storing..." : ((list.Count == 0) ? "No scrap in the ship that can be stored." : "Click an item to store it."));
			menuPage.Entries.AddRange(ItemState.Sorted(list));
			return menuPage;
		}

		private static Type Find(string name)
		{
			return ((object)Chainloader.PluginInfos["GJ.ScrapBank"].Instance).GetType().Assembly.GetTypes().FirstOrDefault((Type type) => type.Name == name);
		}

		private static List<GrabbableObject> Storable()
		{
			MethodInfo eligible = null;
			try
			{
				eligible = Find("ScrapBankManager")?.GetMethod("IsEligibleShipScrap", AccessTools.all);
			}
			catch (Exception)
			{
			}
			return Object.FindObjectsOfType<GrabbableObject>().Where(delegate(GrabbableObject item)
			{
				if ((Object)(object)item == (Object)null || (Object)(object)item.itemProperties == (Object)null || !item.itemProperties.isScrap || !item.isInShipRoom || (Object)(object)((NetworkBehaviour)item).NetworkObject == (Object)null || !((NetworkBehaviour)item).NetworkObject.IsSpawned)
				{
					return false;
				}
				try
				{
					return (eligible != null) ? ((bool)eligible.Invoke(null, new object[1] { item })) : (!item.isHeld && !item.isPocketed && item.scrapValue > 0);
				}
				catch (Exception)
				{
					return false;
				}
			}).ToList();
		}

		private static void Store(Terminal terminal, GrabbableObject item)
		{
			if (storing || (Object)(object)item == (Object)null || (Object)(object)((NetworkBehaviour)item).NetworkObject == (Object)null || !((NetworkBehaviour)item).NetworkObject.IsSpawned)
			{
				return;
			}
			try
			{
				pending = terminal;
				if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsServer)
				{
					Type type = Find("ScrapBankManager");
					Type nestedType = type.GetNestedType("StoreResult", AccessTools.all);
					ParameterExpression parameterExpression = Expression.Parameter(nestedType);
					Delegate obj = Expression.Lambda(typeof(Action<>).MakeGenericType(nestedType), Expression.Call(typeof(ScrapBankPages).GetMethod("Stored", AccessTools.all), Expression.Convert(parameterExpression, typeof(object))), parameterExpression).Compile();
					object[] array = new object[3]
					{
						((NetworkBehaviour)item).NetworkObjectId,
						obj,
						null
					};
					storing = true;
					if (!(bool)type.GetMethod("BeginStoreItem", AccessTools.all).Invoke(null, array))
					{
						storing = false;
						report = Clean((((string)array[2]) ?? "Could not store it.").Replace('\n', ' '));
					}
				}
				else
				{
					Find("NetworkBridge").GetMethod("RequestStoreItem", AccessTools.all).Invoke(null, new object[1] { ((NetworkBehaviour)item).NetworkObjectId });
					storing = true;
					((MonoBehaviour)terminal).StartCoroutine(WaitForHost(terminal, item));
				}
			}
			catch (Exception ex)
			{
				storing = false;
				report = "Could not store: " + (ex.InnerException ?? ex).Message;
				Plugin.mlg.LogWarning((object)("ScrapBank: " + report));
			}
			Redraw();
		}

		private static void Stored(object result)
		{
			storing = false;
			try
			{
				report = Clean((((result.GetType().GetProperty("Message", AccessTools.all)?.GetValue(result) ?? result.GetType().GetField("Message", AccessTools.all)?.GetValue(result)) as string) ?? "Stored.").Replace('\n', ' '));
			}
			catch (Exception)
			{
				report = "Stored.";
			}
			Redraw();
		}

		private static IEnumerator WaitForHost(Terminal terminal, GrabbableObject item)
		{
			string name = item.itemProperties.itemName;
			float until = Time.unscaledTime + 6f;
			while ((Object)(object)item != (Object)null && (Object)(object)((NetworkBehaviour)item).NetworkObject != (Object)null && ((NetworkBehaviour)item).NetworkObject.IsSpawned && Time.unscaledTime < until)
			{
				yield return null;
			}
			storing = false;
			report = (((Object)(object)item == (Object)null || (Object)(object)((NetworkBehaviour)item).NetworkObject == (Object)null || !((NetworkBehaviour)item).NetworkObject.IsSpawned) ? ("Stored " + name + ".") : ("The host has not stored " + name + "."));
			Redraw();
		}

		private static void Redraw()
		{
			View current = Navigator.Current;
			if ((Object)(object)pending != (Object)null && current != null && current.Page != null && current.Page.Title == "STORE BY ITEM")
			{
				Navigator.Rebuild(pending);
			}
		}

		internal static bool Claim(Terminal terminal, View view, string printed, string submitted, string back)
		{
			if (!Chainloader.PluginInfos.ContainsKey("GJ.ScrapBank"))
			{
				return false;
			}
			List<string> lines = new List<string>();
			string[] array = printed.Split('\n');
			int i;
			for (i = 0; i < array.Length; i++)
			{
				string input = array[i];
				lines.Add(Clean(Tags.Replace(input, "")));
			}
			string text = lines.Find((string line) => line.Length > 0) ?? "";
			string text2 = (submitted ?? "").Trim().ToLowerInvariant().Split(' ')[0].TrimStart('/');
			if ((!text.StartsWith("===") && !Words.Contains(text2) && (text2.Length <= 0 || !int.TryParse(text2, out i) || !text.StartsWith("STORE"))) || text.StartsWith("Found "))
			{
				return false;
			}
			if (lines.Exists((string line) => Choice.IsMatch(line) && line.Contains("CANCEL")))
			{
				view.Question = true;
				view.Leave = "cancel";
			}
			view.Builder = () => Page(terminal, lines, back);
			return true;
		}

		private static MenuPage Page(Terminal terminal, List<string> lines, string back)
		{
			MenuPage menuPage = new MenuPage
			{
				Title = "SCRAP BANK",
				HideHeads = true
			};
			menuPage.Columns.Add(new Column());
			menuPage.Columns.Add(new Column
			{
				Chars = 9,
				AlignRight = true
			});
			bool flag = false;
			bool flag2 = true;
			foreach (string line in lines)
			{
				Match match = Heading.Match(line);
				Match match2 = Numbered.Match(line);
				Match match3 = Choice.Match(line);
				if (line.Length == 0)
				{
					if (!flag2)
					{
						menuPage.Entries.Add(new MenuEntry
						{
							Label = "",
							IsHeader = true
						});
					}
					flag2 = true;
				}
				else
				{
					if (line.Contains("ENTER") || line.StartsWith("Page:"))
					{
						continue;
					}
					flag2 = false;
					if (match.Success)
					{
						string text = match.Groups[1].Value.Trim(' ', '/');
						Match match4 = Paged.Match(text);
						if (match4.Success && int.TryParse(match4.Groups[1].Value, out var current2) && int.TryParse(match4.Groups[2].Value, out var last) && last > 1)
						{
							menuPage.Tools.Add(new ToolButton
							{
								Label = "< PREV",
								Off = (current2 <= 1),
								Click = delegate
								{
									Commands.Submit(terminal, "scrap list " + Math.Max(1, current2 - 1));
								}
							});
							menuPage.Tools.Add(new ToolButton
							{
								Label = "NEXT >",
								Off = (current2 >= last),
								Click = delegate
								{
									Commands.Submit(terminal, "scrap list " + Math.Min(last, current2 + 1));
								}
							});
						}
						menuPage.Entries.Add(new MenuEntry
						{
							Label = text,
							IsHeader = true
						});
					}
					else if (match2.Success)
					{
						string number = match2.Groups[1].Value;
						menuPage.Entries.Add(new MenuEntry
						{
							Label = match2.Groups[2].Value.Trim(),
							Cells = new string[1] { match2.Groups[3].Value },
							Click = delegate
							{
								Commands.Submit(terminal, number);
							}
						});
					}
					else if (match3.Success)
					{
						string number2 = match3.Groups[1].Value;
						flag = true;
						menuPage.Entries.Add(new MenuEntry
						{
							Label = "[ " + match3.Groups[2].Value + " ]",
							Click = delegate
							{
								Commands.Submit(terminal, number2);
							}
						});
					}
					else
					{
						menuPage.Entries.Add(new MenuEntry
						{
							Label = line
						});
					}
				}
			}
			if (!flag)
			{
				ResultPages.AddBack(menuPage, terminal, back);
			}
			return menuPage;
		}

		private static string Clean(string line)
		{
			StringBuilder stringBuilder = new StringBuilder(line.Length);
			foreach (char c in line)
			{
				stringBuilder.Append((c < '\u0080') ? c : ' ');
			}
			string input = Leftover.Replace(stringBuilder.ToString(), "");
			return Spaces.Replace(input, (Match m) => (m.Value.Length <= 3) ? " " : m.Value).Trim();
		}
	}
	internal static class ScrollPatchFix
	{
		private static bool tried;

		private static Terminal terminal;

		internal static void Apply(Harmony harmony, Terminal current)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			terminal = current;
			if (tried)
			{
				return;
			}
			tried = true;
			try
			{
				Type type = Reflect.Find("TerminalRelativeScroll.TerminalScrollPatch");
				MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "GetTerminal", (Type[])null, (Type[])null) : null);
				if (methodInfo != null)
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ScrollPatchFix), "HandOver", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Plugin.mlg.LogInfo((object)"Terminal Scroll Patch found: its terminal lookup now uses the one we already have.");
				}
			}
			catch (Exception ex)
			{
				Plugin.mlg.LogWarning((object)("Could not speed up Terminal Scroll Patch: " + ex.Message));
			}
		}

		private static bool HandOver(ref Terminal __result)
		{
			if ((Object)(object)terminal == (Object)null)
			{
				return true;
			}
			__result = terminal;
			return false;
		}
	}
	internal static class SelectableInteriorsPage
	{
		internal const string Guid = "NoteBoxz.SelectableInteriors";

		internal const string ListCommand = "listinside";

		private const string SetCommand = "setinside ";

		private static readonly Regex Answer = new Regex("^Forcing Interior (.+) \\((\\d+)\\) to be loaded", RegexOptions.Compiled);

		private static string clicked;

		private static Type plugin;

		private static bool complained;

		internal static bool Owns(string key)
		{
			if (!(key == "listinside"))
			{
				return key.StartsWith("setinside");
			}
			return true;
		}

		internal static MenuPage Build(Terminal terminal)
		{
			TerminalKeyword[] array = (((Object)(object)terminal != (Object)null && (Object)(object)terminal.terminalNodes != (Object)null) ? terminal.terminalNodes.allKeywords : null);
			if (array == null)
			{
				return null;
			}
			MenuPage menuPage = Navigator.MenuLook(terminal, new MenuPage
			{
				Title = "INTERIORS"
			});
			SortedDictionary<int, MenuEntry> sortedDictionary = new SortedDictionary<int, MenuEntry>();
			List<MenuEntry> list = new List<MenuEntry>();
			string text = "none";
			TerminalKeyword[] array2 = array;
			foreach (TerminalKeyword val in array2)
			{
				if ((Object)(object)val == (Object)null || val.word == null || !val.word.StartsWith("setinside ") || (Object)(object)val.specialKeywordResult == (Object)null)
				{
					continue;
				}
				Match match = Answer.Match(val.specialKeywordResult.displayText ?? "");
				if (match.Success && int.TryParse(match.Groups[2].Value, out var id))
				{
					string word = val.word;
					Func<bool> func = delegate
					{
						if (!Readable())
						{
							return clicked == word;
						}
						return !FullRandom() && Forced() == id;
					};
					sortedDictionary[id] = Row(terminal, match.Groups[1].Value, word, func);
					text = (func() ? match.Groups[1].Value : text);
				}
				else if (val.word == "setinside none")
				{
					string word2 = val.word;
					list.Insert(0, Row(terminal, "No forced interior", word2, delegate
					{
						if (!Readable())
						{
							return clicked == word2;
						}
						return !FullRandom() && Forced() < 0;
					}));
				}
				else if (val.word == "setinside fullrandom")
				{
					string word3 = val.word;
					Func<bool> func2 = () => (!Readable()) ? (clicked == word3) : FullRandom();
					list.Add(Row(terminal, "Fully random", word3, func2));
					text = (func2() ? "fully random" : text);
				}
			}
			menuPage.Summary = "Interior of the next moon: " + text;
			if (sortedDictionary.Count == 0 && list.Count == 0)
			{
				menuPage.Entries.Add(new MenuEntry
				{
					Label = "",
					IsHeader = true
				});
				menuPage.Entries.Add(new MenuEntry
				{
					Label = "No interiors to pick from yet."
				});
				return menuPage;
			}
			menuPage.Entries.AddRange(list);
			if (list.Count > 0 && sortedDictionary.Count > 0)
			{
				menuPage.Entries.Add(new MenuEntry
				{
					Label = "",
					IsHeader = true
				});
			}
			menuPage.Entries.AddRange(sortedDictionary.Values);
			return menuPage;
		}

		private static MenuEntry Row(Terminal terminal, string name, string command, Func<bool> picked)
		{
			return new MenuEntry
			{
				Label = name,
				LiveLabel = () => (!picked()) ? name : ("> " + name + "  (selected)"),
				Click = delegate
				{
					View current = Navigator.Current;
					clicked = command;
					Commands.Submit(terminal, command);
					if (Navigator.Current != current)
					{
						Navigator.Back(terminal);
					}
				}
			};
		}

		private static bool Readable()
		{
			return Read("LVidForceing") is int;
		}

		private static int Forced()
		{
			object obj = Read("LVidForceing");
			if (obj is int)
			{
				return (int)obj;
			}
			return -1;
		}

		private static bool FullRandom()
		{
			object obj = Read("FullRandom");
			bool flag = default(bool);
			int num;
			if (obj is bool)
			{
				flag = (bool)obj;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static object Read(string field)
		{
			try
			{
				if (plugin == null)
				{
					plugin = ((Chainloader.PluginInfos.TryGetValue("NoteBoxz.SelectableInteriors", out var value) && (Object)(object)value.Instance != (Object)null) ? ((object)value.Instance).GetType() : Reflect.Find("SelectableInteriors.SelectableInteriors"));
				}
				return plugin?.GetField(field, AccessTools.all)?.GetValue(null);
			}
			catch (Exception arg)
			{
				if (!complained)
				{
					complained = true;
					Plugin.mlg.LogWarning((object)$"Interiors: cannot read the mod's '{field}': {arg}");
				}
				return null;
			}
		}
	}
	internal static class SellMyScrapPages
	{
		private const string Guid = "com.github.zehsteam.SellMyScrap";

		private static readonly Regex Tags = new Regex("<[^>]+>", RegexOptions.Compiled);

		private static readonly Regex Found = new Regex("^Found (\\d+) items? with a total value of \\$(-?\\d+)", RegexOptions.Compiled);

		private static readonly Regex Catalogue = new Regex("^Found (\\d+) total items from all moons\\. \\(Page (\\d+) / (\\d+)\\)", RegexOptions.Compiled);

		private static readonly Regex Quota = new Regex("^Quota fulfilled: (\\$-?\\d+ / \\$-?\\d+)(?: \\(Need: (\\$-?\\d+)\\))?", RegexOptions.Compiled);

		private static readonly Regex Overtime = new Regex("^Overtime bonus: (\\$-?\\d+)", RegexOptions.Compiled);

		private static readonly Regex Rate = new Regex("buying at %(\\d+)", RegexOptions.Compiled);

		private static readonly Regex Asks = new Regex("\\bCONFIRM or DENY\\b", RegexOptions.Compiled);

		private static readonly Regex Item = new Regex("^(.+?)\\s*\\(x(\\d+)\\)\\s*:?\\s*(\\$-?\\d+)\\s*(\\(\\w+\\))?$", RegexOptions.Compiled);

		private static readonly Regex Gap = new Regex("\\s{2,}", RegexOptions.Compiled);

		private static readonly PageState State = new PageState();

		private static readonly PageState ItemState = new PageState();

		private const string ItemTitle = "SELL ITEM";

		private static string report;

		private static string Spelled(string command, string dashed)
		{
			if (!Chainloader.PluginInfos.ContainsKey("GJ.ScrapBank"))
			{
				return command;
			}
			return dashed;
		}

		internal static MenuPage Menu()
		{
			Terminal terminal = Object.FindObjectOfType<Terminal>();
			MenuPage menuPage = Navigator.MenuLook(terminal, new MenuPage
			{
				Title = "SELL MY SCRAP"
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "SELL QUOTA",
				Command = Spelled("sell quota", "sell-quota"),
				Description = "Sell just enough to meet the quota."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "SELL ALL",
				Command = Spelled("sell all", "sell-all"),
				Description = "Sell all the scrap on the ship."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "SELL AMOUNT",
				Description = "Sell scrap worth an amount you set.",
				Click = delegate
				{
					AmountWindow(terminal);
				}
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "SELL ITEM",
				Description = "Pick a kind of scrap and sell every one of it.",
				Open = () => ItemPage(terminal)
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "SELL LIST",
				Command = Spelled("sell list", "sell-list"),
				Description = "Sell what is on the mod's sell list."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "VIEW SCRAP",
				Command = "view scrap",
				Description = "The scrap on the ship and what it is worth."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "VIEW ALL SCRAP",
				Command = "view all scrap",
				Description = "Every kind of scrap there is in the game."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "VIEW OVERTIME",
				Command = "view overtime",
				Description = "The overtime bonus as it stands."
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "ABOUT",
				Command = Spelled("sell", "sell-help"),
				Description = "The mod's own list of its commands."
			});
			Answers.InWindows(terminal, menuPage);
			foreach (MenuEntry entry in menuPage.Entries)
			{
				Action click = entry.Click;
				if (click != null)
				{
					entry.Click = delegate
					{
						LetGo();
						click();
					};
				}
			}
			menuPage.Entries.First((MenuEntry entry) => entry.Label == "VIEW ALL SCRAP").Click = delegate
			{
				LetGo();
				AllScrap(terminal, "view all scrap");
			};
			return menuPage;
		}

		private static void LetGo()
		{
			try
			{
				Reflect.Find("com.github.zehsteam.SellMyScrap.Commands.CommandManager")?.GetField("AwaitingConfirmationCommand", AccessTools.all)?.SetValue(null, null);
			}
			catch (Exception ex)
			{
				Plugin.WarnOnce("SellMyScrap: could not let go of its waiting command: " + ex.Message);
			}
		}

		private static void AllScrap(Terminal terminal, string command)
		{
			string text = Commands.Ask(terminal, command);
			int num = text.IndexOf("The following commands are available", StringComparison.OrdinalIgnoreCase);
			Window.Show(new Window
			{
				Title = "ALL SCRAP",
				Text = Window.Tidy((num > 0) ? text.Substring(0, num) : text, ""),
				Closing = LetGo,
				Buttons = 
				{
					new ToolButton
					{
						Label = "PREV",
						Click = delegate
						{
							AllScrap(terminal, "prev");
						}
					},
					new ToolButton
					{
						Label = "NEXT",
						Click = delegate
						{
							AllScrap(terminal, "next");
						}
					},
					new ToolButton
					{
						Label = "CLOSE",
						Click = Window.Close
					}
				}
			});
		}

		private static void AmountWindow(Terminal terminal)
		{
			TimeOfDay instance = TimeOfDay.Instance;
			int num = (((Object)(object)instance != (Object)null) ? Math.Max(0, instance.profitQuota - instance.quotaFulfilled) : 0);
			Answers.Typed(terminal, "SELL AMOUNT", "Type the value of scrap to sell, then Enter.", Spelled("sell ", "sell-amount "), "SELL", 6, Answers.Fill(terminal, $"QUOTA NEEDS ${num}", num.ToString()));
		}

		private static MenuPage ItemPage(Terminal terminal)
		{
			MenuPage menuPage = new MenuPage
			{
				Title = "SELL ITEM",
				SortBy = delegate(int column)
				{
					ItemState.SortBy(column);
					Navigator.Rebuild(terminal);
				}
			};
			menuPage.Columns.Add(new Column
			{
				Header = ItemState.HeaderFor("NAME", 0)
			});
			menuPage.Columns.Add(new Column
			{
				Header = ItemState.HeaderFor("COUNT", 1),
				Chars = 5,
				AlignRight = true
			});
			menuPage.Columns.Add(new Column
			{
				Header = ItemState.HeaderFor("VALUE", 2),
				Chars = 9,
				AlignRight = true
			});
			IOrderedEnumerable<IGrouping<string, GrabbableObject>> orderedEnumerable = from item in Object.FindObjectsOfType<GrabbableObject>()
				where (Object)(object)item != (Object)null && (Object)(object)item.itemProperties != (Object)null && item.itemProperties.isScrap && item.isInShipRoom && !item.isHeld && !item.isPocketed
				group item by item.itemProperties.itemName into kind
				orderby kind.Key
				select kind;
			List<MenuEntry> list = new List<MenuEntry>();
			foreach (IGrouping<string, GrabbableObject> item in orderedEnumerable)
			{
				string name = item.Key;
				int count = item.Count();
				int value = item.Sum((GrabbableObject item) => item.scrapValue);
				list.Add(new MenuEntry
				{
					Label = name,
					Cells = new string[2]
					{
						"x" + count,
						"$" + value
					},
					Keys = new IComparable[3] { name, count, value },
					Click = delegate
					{
						SellNow(terminal, name, count, value);
					}
				});
			}
			menuPage.Summary = report ?? ((list.Count == 0) ? "No scrap in the ship." : "Click a kind to sell all of it.");
			menuPage.Entries.AddRange(ItemState.Sorted(list));
			return menuPage;
		}

		private static void SellNow(Terminal terminal, string name, int count, int value)
		{
			if (Sell(terminal, name, out var refused))
			{
				report = refused ?? $"Selling {name} x{count}  (${value})";
				Navigator.Rebuild(terminal);
				((MonoBehaviour)terminal).StartCoroutine(Refresh(terminal));
			}
			else
			{
				report = null;
				Navigator.DropCurrent();
				Commands.Submit(terminal, Spelled("sell item ", "sell-item ") + name);
			}
		}

		private static IEnumerator Refresh(Terminal terminal)
		{
			for (int i = 0; i < 16; i++)
			{
				yield return (object)new WaitForSecondsRealtime(0.5f);
				View current = Navigator.Current;
				if (current == null || current.Page == null || current.Page.Title != "SELL ITEM")
				{
					break;
				}
				Navigator.Rebuild(terminal);
			}
		}

		private static bool Sell(Terminal terminal, string name, out string refused)
		{
			refused = null;
			try
			{
				Assembly assembly = ((object)Chainloader.PluginInfos["com.github.zehsteam.SellMyScrap"].Instance).GetType().Assembly;
				BindingFlags all = AccessTools.all;
				Type type = assembly.GetType("com.github.zehsteam.SellMyScrap.Helpers.ScrapHelper", throwOnError: true);
				Type type2 = assembly.GetType("com.github.zehsteam.SellMyScrap.Managers.SellManager", throwOnError: true);
				Type type3 = assembly.GetType("com.github.zehsteam.SellMyScrap.Commands.SellCommand", throwOnError: true);
				Type type4 = assembly.GetType("com.github.zehsteam.SellMyScrap.Objects.ItemData", throwOnError: true);
				Type type5 = assembly.GetType("com.github.zehsteam.SellMyScrap.Data.SellType", throwOnError: true);
				Type type6 = assembly.GetType("com.github.zehsteam.SellMyScrap.Data.ConfirmationStatus", throwOnError: true);
				object[] array = new object[1];
				if (!(bool)type3.GetMethod("CanUseCommand", all).Invoke(null, array))
				{
					object obj = array[0];
					TerminalNode val = (TerminalNode)((obj is TerminalNode) ? obj : null);
					refused = (((Object)(object)val != (Object)null) ? (val.displayText ?? "").Trim() : "Cannot sell here.");
					return true;
				}
				IList obj2 = (IList)type.GetMethod("GetAllScrapByItemNames", all).Invoke(null, new object[4]
				{
					new string[1] { name },
					false,
					false,
					false
				});
				IList list = (IList)Activator.CreateInstance(obj2.GetType());
				PropertyInfo property = type4.GetProperty("ItemName", all);
				foreach (object item in obj2)
				{
					if ((string)property.GetValue(item) == name)
					{
						list.Add(item);
					}
				}
				if (list.Count == 0)
				{
					refused = "Nothing of that kind left to sell.";
					return true;
				}
				object obj3 = type2.GetMethod("SetScrapToSell", all).Invoke(null, new object[1] { list });
				int num = (int)obj3.GetType().GetProperty("TotalScrapValue", all).GetValue(obj3);
				type2.GetMethod("CreateSellRequest", all).Invoke(null, new object[6]
				{
					Enum.Parse(type5, "Item"),
					num,
					num,
					Enum.Parse(type6, "AwaitingConfirmation"),
					-2,
					-1
				});
				type2.GetMethod("ConfirmSellRequest", all).Invoke(null, null);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.mlg.LogWarning((object)("SellMyScrap: could not sell from the list, using the mod's command instead: " + (ex.InnerException ?? ex).Message));
				return false;
			}
		}

		internal static bool Claim(Terminal terminal, View view, string printed, string back)
		{
			if (!Chainloader.PluginInfos.ContainsKey("com.github.zehsteam.SellMyScrap"))
			{
				return false;
			}
			List<string> lines = new List<string>();
			string[] array = printed.Split('\n');
			foreach (string input in array)
			{
				lines.Add(Tags.Replace(input, "").Trim());
			}
			string input2 = lines.Find((string line) => line.Length > 0) ?? "";
			if (Catalogue.IsMatch(input2))
			{
				view.Question = true;
				view.Leave = "exit";
				view.Builder = () => AllScrap(terminal, lines);
				return true;
			}
			if (!Found.IsMatch(input2))
			{
				return false;
			}
			if (Asks.IsMatch(printed))
			{
				view.Question = true;
				view.Leave = "deny";
				view.Builder = () => Sale(terminal, lines);
			}
			else
			{
				view.Builder = () => Scrap(terminal, lines, back);
			}
			return true;
		}

		private static MenuPage Sale(Terminal terminal, List<string> lines)
		{
			MenuPage menuPage = new MenuPage
			{
				Title = "SELL",
				HideHeads = true
			};
			menuPage.Columns.Add(new Column());
			menuPage.Columns.Add(new Column
			{
				Chars = 4,
				AlignRight = true
			});
			menuPage.Columns.Add(new Column
			{
				Chars = 9,
				AlignRight = true
			});
			List<MenuEntry> list = new List<MenuEntry>();
			Space(menuPage);
			foreach (string line in lines)
			{
				Match match = Found.Match(line);
				Match match2 = Quota.Match(line);
				Match match3 = Overtime.Match(line);
				Match match4 = Rate.Match(line);
				Match match5 = Item.Match(line);
				if (match.Success)
				{
					menuPage.Entries.Add(new MenuEntry
					{
						Label = ((match.Groups[1].Value == "1") ? "Sell 1 item" : ("Sell " + match.Groups[1].Value + " items")),
						Big = true
					});
					Fact(menuPage, "You receive", "$" + match.Groups[2].Value);
				}
				else if (match2.Success)
				{
					Fact(menuPage, "Quota", match2.Groups[1].Value);
					if (match2.Groups[2].Success)
					{
						Fact(menuPage, "Still needed", match2.Groups[2].Value);
					}
				}
				else if (match3.Success)
				{
					Fact(menuPage, "Overtime bonus", match3.Groups[1].Value);
				}
				else if (match4.Success)
				{
					Fact(menuPage, "The Company is buying at", match4.Groups[1].Value + "%");
				}
				else if (match5.Success)
				{
					list.Add(Row(match5));
				}
			}
			if (list.Count > 0)
			{
				Space(menuPage);
				menuPage.Entries.Add(new MenuEntry
				{
					Label = "ITEMS",
					IsHeader = true
				});
				menuPage.Entries.AddRange(list);
			}
			Space(menuPage);
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "[ CONFIRM ]",
				Command = "confirm"
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "[ DENY ]",
				Click = delegate
				{
					Navigator.Back(terminal);
				}
			});
			return menuPage;
		}

		private static MenuPage Scrap(Terminal terminal, List<string> lines, string back)
		{
			MenuPage menuPage = new MenuPage
			{
				Title = "SCRAP ON THE SHIP",
				SortBy = delegate(int column)
				{
					State.SortBy(column);
					Navigator.Rebuild(terminal);
				}
			};
			menuPage.Columns.Add(new Column
			{
				Header = State.HeaderFor("NAME", 0)
			});
			menuPage.Columns.Add(new Column
			{
				Header = State.HeaderFor("COUNT", 1),
				Chars = 5,
				AlignRight = true
			});
			menuPage.Columns.Add(new Column
			{
				Header = State.HeaderFor("VALUE", 2),
				Chars = 9,
				AlignRight = true
			});
			List<MenuEntry> list = new List<MenuEntry>();
			foreach (string line in lines)
			{
				Match match = Found.Match(line);
				Match match2 = Item.Match(line);
				if (match.Success)
				{
					menuPage.Summary = match.Groups[1].Value + " items worth $" + match.Groups[2].Value;
				}
				else if (match2.Success)
				{
					list.Add(Row(match2));
				}
			}
			menuPage.Entries.AddRange(State.Sorted(list));
			ResultPages.AddBack(menuPage, terminal, back);
			return menuPage;
		}

		private static MenuPage AllScrap(Terminal terminal, List<string> lines)
		{
			MenuPage menuPage = new MenuPage
			{
				Title = "ALL SCRAP",
				HideHeads = true
			};
			menuPage.Columns.Add(new Column());
			menuPage.Columns.Add(new Column
			{
				Chars = 24
			});
			int result = 1;
			int result2 = 1;
			bool flag = false;
			foreach (string line in lines)
			{
				Match match = Catalogue.Match(line);
				if (match.Success)
				{
					int.TryParse(match.Groups[2].Value, out result);
					int.TryParse(match.Groups[3].Value, out result2);
					menuPage.Summary = $"{match.Groups[1].Value} kinds of scrap in the game  -  page {result} / {result2}";
					flag = true;
				}
				else if (line.StartsWith("The following commands"))
				{
					flag = false;
				}
				else if (line.StartsWith("Error:"))
				{
					menuPage.Summary = line;
				}
				else if (flag && line.Length > 0)
				{
					string[] array = Gap.Split(line);
					menuPage.Entries.Add(new MenuEntry
					{
						Label = array[0],
						Cells = new string[1] { (array.Length > 1) ? array[1] : "" }
					});
				}
			}
			menuPage.Tools.Add(new ToolButton
			{
				Label = "< PREV",
				Off = (result <= 1),
				Click = delegate
				{
					Commands.Submit(terminal, "prev");
				}
			});
			menuPage.Tools.Add(new ToolButton
			{
				Label = "NEXT >",
				Off = (result >= result2),
				Click = delegate
				{
					Commands.Submit(terminal, "next");
				}
			});
			menuPage.Tools.Add(new ToolButton
			{
				Label = "EXIT",
				Click = delegate
				{
					Navigator.Back(terminal);
				}
			});
			Space(menuPage);
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "[ GO TO PAGE... ]",
				Prefill = "page "
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "[ EXIT ]",
				Click = delegate
				{
					Navigator.Back(terminal);
				}
			});
			return menuPage;
		}

		private static MenuEntry Row(Match item)
		{
			string text = item.Groups[1].Value.Trim();
			int num = int.Parse(item.Groups[2].Value);
			int.TryParse(item.Groups[3].Value.TrimStart('$'), out var result);
			if (item.Groups[4].Success)
			{
				text = text + " " + item.Groups[4].Value;
			}
			MenuEntry menuEntry = new MenuEntry();
			menuEntry.Label = text;
			menuEntry.Cells = new string[2]
			{
				"x" + num,
				item.Groups[3].Value
			};
			menuEntry.Keys = new IComparable[3] { text, num, result };
			return menuEntry;
		}

		private static void Fact(MenuPage page, string label, string value)
		{
			page.Entries.Add(new MenuEntry
			{
				Label = label,
				Cells = new string[2] { "", value }
			});
		}

		private static void Space(MenuPage page)
		{
			page.Entries.Add(new MenuEntry
			{
				Label = "",
				IsHeader = true
			});
		}
	}
	internal static class StoreRotationCompat
	{
		internal const string Guid = "pacoito.StoreRotationConfig";

		private static PropertyInfo instance;

		private static PropertyInfo rotation;

		private static bool looked;

		internal static bool Installed => Chainloader.PluginInfos.ContainsKey("pacoito.StoreRotationConfig");

		internal static int Discount(Terminal terminal, TerminalNode node)
		{
			if ((Object)(object)node == (Object)null || terminal.ShipDecorSelection == null || !Installed)
			{
				return 0;
			}
			try
			{
				Find();
				object obj = instance?.GetValue(null);
				IEnumerable enumerable = ((obj != null) ? (rotation?.GetValue(obj) as IEnumerable) : null);
				if (enumerable == null)
				{
					return 0;
				}
				int num = (node.buyUnlockable ? terminal.ShipDecorSelection.FindIndex((TerminalNode other) => (Object)(object)other != (Object)null && other.shipUnlockableID == node.shipUnlockableID) : terminal.ShipDecorSelection.IndexOf(node));
				int num2 = 0;
				foreach (object item in enumerable)
				{
					if (num2++ == num)
					{
						return (int)item.GetType().GetProperty("UnlockableDiscount", AccessTools.all).GetValue(item);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.mlg.LogWarning((object)("StoreRotationConfig: could not read its discounts: " + ex.Message));
				looked = true;
				instance = null;
			}
			return 0;
		}

		internal static int PriceAfter(int price, int discount)
		{
			return price - (int)((float)price * ((float)discount / 100f));
		}

		private static void Find()
		{
			if (!looked)
			{
				looked = true;
				Type type = Reflect.Find("StoreRotationConfig.Networking.StoreRotationNetworker");
				instance = type?.GetProperty("Instance", AccessTools.all);
				rotation = type?.GetProperty("StoreRotation", AccessTools.all);
			}
		}
	}
	internal static class TooManyEmotesStore
	{
		private const string Header = "[TooManyEmotes]";

		private static readonly Regex Tags = new Regex("<[^>]+>", RegexOptions.Compiled);

		private static readonly Regex Entry = new Regex("^\\s*\\*\\s*(.+?)\\s*//\\s*(.+?)\\s*$", RegexOptions.Compiled);

		private static readonly Regex Balance = new Regex("^Remaining (.+?) balance:\\s*(.+?)\\.?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		internal static bool IsStore(string printed)
		{
			if (printed.Contains("[TooManyEmotes]"))
			{
				return printed.Contains("//");
			}
			return false;
		}

		internal static MenuPage Build(Terminal terminal, string printed, string back)
		{
			MenuPage menuPage = new MenuPage
			{
				Title = "EMOTES",
				Dense = true
			};
			menuPage.Columns.Add(new Column
			{
				Header = "NAME"
			});
			menuPage.Columns.Add(new Column
			{
				Header = "PRICE",
				Chars = 13,
				AlignRight = true
			});
			string[] array = printed.Split('\n');
			foreach (string text in array)
			{
				string text2 = Tags.Replace(text, "").Trim();
				Match match = Entry.Match(text2);
				Match match2 = Balance.Match(text2);
				if (match.Success)
				{
					string value = match.Groups[1].Value;
					string value2 = match.Groups[2].Value;
					bool flag = value2.IndexOf("purchased", StringComparison.OrdinalIgnoreCase) >= 0;
					Match match3 = Entry.Match(text.Trim());
					menuPage.Entries.Add(new MenuEntry
					{
						Label = ((match3.Success && !flag) ? match3.Groups[1].Value : value),
						Command = (flag ? null : ("emote " + value.ToLowerInvariant())),
						Dim = flag,
						Cells = new string[1] { flag ? "owned" : value2 }
					});
				}
				else if (match2.Success)
				{
					string text3 = Capital(match2.Groups[1].Value) + ": " + match2.Groups[2].Value;
					menuPage.Summary = ((menuPage.Summary == null) ? text3 : (menuPage.Summary + "   " + text3));
				}
				else if (text2.Length > 0 && !text2.StartsWith("[") && !text2.StartsWith("-") && text2 != "Store" && menuPage.Entries.Count == 0 && menuPage.Summary == null)
				{
					menuPage.Summary = text2;
				}
			}
			if (menuPage.Entries.Count == 0 && menuPage.Summary == null)
			{
				return null;
			}
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "",
				IsHeader = true
			});
			menuPage.Entries.Add(new MenuEntry
			{
				Label = ((back != null) ? ("[ BACK TO " + back + " ]") : "[ BACK ]"),
				Click = delegate
				{
					Navigator.Back(terminal);
				}
			});
			return menuPage;
		}

		private static string Capital(string text)
		{
			if (text.Length <= 0)
			{
				return text;
			}
			return char.ToUpperInvariant(text[0]) + text.Substring(1);
		}
	}
	internal static class WeatherForecast
	{
		private class Chance
		{
			internal string Name;

			internal float Percent;

			internal bool Possible;
		}

		internal const string Guid = "mrov.WeatherRegistry";

		private static readonly Regex Printed = new Regex("^Forecasting weather for (.+)$", RegexOptions.Compiled);

		private static readonly Regex Tags = new Regex("<[^>]+>", RegexOptions.Compiled);

		private static readonly Regex Alphanumeric = new Regex("^[0-9]+|[-_/\\\\ ]", RegexOptions.Compiled);

		internal static bool Available => Chainloader.PluginInfos.ContainsKey("mrov.WeatherRegistry");

		internal static bool Claim(Terminal terminal, View view, string printed)
		{
			if (!Available)
			{
				return false;
			}
			string input = (from line in printed.Split('\n')
				select Tags.Replace(line, "").Trim()).FirstOrDefault((string line) => line.Length > 0) ?? "";
			Match match = Printed.Match(input);
			if (!match.Success)
			{
				return false;
			}
			string name = match.Groups[1].Value.Trim();
			SelectableLevel level = (((Object)(object)StartOfRound.Instance != (Object)null) ? ((IEnumerable<SelectableLevel>)StartOfRound.Instance.levels).FirstOrDefault((Func<SelectableLevel, bool>)((SelectableLevel each) => (Object)(object)each != (Object)null && Alphanumeric.Replace(each.PlanetName, "") == name)) : null);
			if ((Object)(object)level == (Object)null)
			{
				return false;
			}
			view.Builder = () => Page(terminal, level);
			return true;
		}

		internal static MenuPage Page(Terminal terminal, SelectableLevel level)
		{
			MenuPage menuPage = new MenuPage
			{
				Title = "FORECAST",
				HideHeads = true,
				Plain = true
			};
			menuPage.Columns.Add(new Column());
			menuPage.Columns.Add(new Column
			{
				Chars = 8,
				AlignRight = true
			});
			Space(menuPage);
			menuPage.Entries.Add(new MenuEntry
			{
				Label = MoonsPage.NameOf(level),
				Big = true
			});
			if (!Read(level, out var now, out var chances))
			{
				menuPage.Entries.Add(new MenuEntry
				{
					Label = "WeatherRegistry did not give a forecast."
				});
				ResultPages.AddBack(menuPage, terminal, null);
				return menuPage;
			}
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "Weather now",
				Cells = new string[1] { now }
			});
			Space(menuPage);
			menuPage.Entries.Add(new MenuEntry
			{
				Label = "TOMORROW",
				IsHeader = true
			});
			foreach (Chance item in chances.Where((Chance each) => each.Possible && each.Percent > 0f))
			{
				menuPage.Entries.Add(new MenuEntry
				{
					Label = item.Name,
					Cells = new string[1] { item.Percent.ToString("0.0") + "%" }
				});
			}
			List<string> list = (from each in chances
				where !each.Possible
				select each.Name).ToList();
			if (list.Count > 0)
			{
				Space(menuPage);
				menuPage.Entries.Add(new MenuEntry
				{
					Label = "Never here: " + string.Join(", ", list),
					Dim = true
				});
			}
			ResultPages.AddBack(menuPage, terminal, null);
			return menuPage;
		}

		internal static void ShowWindow(SelectableLevel level)
		{
			Window window = new Window
			{
				Title = "FORECAST: " + MoonsPage.NameOf(level).ToUpperInvariant()
			};
			if (!Read(level, out var now, out var chances))
			{
				window.Text = "WeatherRegistry did not give a forecast.";
				Window.Show(window);
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<alpha=#99>WEATHER NOW<alpha=#FF><pos=55%>").Append(Named(now)).Append("\n\n");
			stringBuilder.Append("<alpha=#99>TOMORROW<alpha=#FF>\n");
			foreach (Chance item in chances.Where((Chance each) => each.Possible && each.Percent > 0f))
			{
				stringBuilder.Append(Named(item.Name)).Append("<pos=55%><mspace=0.6em>").Append(item.Percent.ToString("0.0").PadLeft(5))
					.Append("</mspace>%\n");
			}
			List<string> list = (from each in chances
				where !each.Possible
				select Named(each.Name)).ToList();
			if (list.Count > 0)
			{
				stringBuilder.Append("\n<alpha=#99>NEVER HERE<alpha=#BB>\n").Append(string.Join(", ", list));
			}
			window.Text = stringBuilder.ToString();
			Window.Show(window);
			static string Named(string weather)
			{
				if (!(weather == "None"))
				{
					return weather;
				}
				return "Clear";
			}
		}

		private static bool Read(SelectableLevel level, out string now, out List<Chance> chances)
		{
			now = null;
			chances = new List<Chance>();
			try
			{
				BindingFlags all = AccessTools.all;
				Type type = Reflect.Find("WeatherRegistry.WeatherManager");
				Type type2 = Reflect.Find("WeatherRegistry.Managers.WeightsManager");
				Type type3 = Reflect.Find("WeatherRegistry.Enums.WeatherWeightType");
				if (type == null || type2 == null || type3 == null)
				{
					return false;
				}
				object obj = type.GetMethod("GetCurrentWeather", all, null, new Type[1] { typeof(SelectableLevel) }, null)?.Invoke(null, new object[1] { level });
				now = (obj?.GetType().GetProperty("Name", all)?.GetValue(obj) as string) ?? ((object)Unsafe.As<LevelWeatherType, LevelWeatherType>(ref level.currentWeather)/*cast due to .constrained prefix*/).ToString();
				IList list = type2.GetMethod("GetPlanetPossibleWeathers", all)?.Invoke(null, new object[1] { level }) as IList;
				MethodInfo method = type2.GetMethod("GetWeatherWeightsAllTypes", all);
				IEnumerable enumerable = type.GetMethod("GetWeathers", all)?.Invoke(null, null) as IEnumerable;
				if (method == null || enumerable == null)
				{
					return false;
				}
				object key = Enum.ToObject(type3, 0);
				object key2 = Enum.ToObject(type3, 1);
				object key3 = Enum.ToObject(type3, 2);
				List<(object, int)> list2 = new List<(object, int)>();
				int num = 0;
				foreach (object item3 in enumerable)
				{
					if (method.Invoke(null, new object[2] { level, item3 }) is IDictionary dictionary)
					{
						int num2 = (dictionary.Contains(key) ? ((int)dictionary[key]) : 0);
						int num3 = (dictionary.Contains(key2) ? ((int)dictionary[key2]) : 0);
						int num4 = (dictionary.Contains(key3) ? ((int)dictionary[key3]) : 0);
						num += num2 + num3 + num4;
						list2.Add((item3, (num4 != 0) ? num4 : ((num3 != 0) ? num3 : num2)));
					}
				}
				if (num <= 0)
				{
					return false;
				}
				foreach (var item4 in list2.OrderByDescending<(object, int), int>(((object Weather, int Weight) each) => each.Weight))
				{
					object item = item4.Item1;
					int item2 = item4.Item2;
					Type type4 = item.GetType();
					object obj2 = type4.GetProperty("VanillaWeatherType", all)?.GetValue(item);
					chances.Add(new Chance
					{
						Name = ((type4.GetProperty("Name", all)?.GetValue(item) as string) ?? "?"),
						Percent = (float)item2 * 100f / (float)num,
						Possible = (list == null || obj2 == null || list.Contains(obj2))
					});
				}
				return true;
			}
			catch (Exception ex)
			{
				Plugin.WarnOnce("WeatherRegistry forecast could not be read: " + (ex.InnerException ?? ex).Message);
				return false;
			}
		}

		private static void Space(MenuPage page)
		{
			page.Entries.Add(new MenuEntry
			{
				Label = "",
				IsHeader = true
			});
		}
	}
	internal enum MonsterDetail
	{
		Off,
		Count,
		Kinds,
		Places
	}
	internal enum ExtrasPlace
	{
		Off,
		OwnPage,
		OtherPage,
		StatusPanel
	}
	internal enum ShowHidden
	{
		Never,
		AfterOrbit,
		Mystery,
		Always
	}
	internal static class Settings
	{
		internal static ConfigEntry<bool> OwnPages;

		internal static ConfigEntry<bool> AppsInWindow;

		internal static ConfigEntry<bool> SkipWelcome;

		internal static ConfigEntry<bool> SharpTerminal;

		internal static ConfigEntry<bool> ZoomToScreen;

		internal static ConfigEntry<float> CursorSpeed;

		internal static ConfigEntry<bool> LineScroll;

		internal static ConfigEntry<int> ScrollLines;

		internal static ConfigEntry<ExtrasPlace> Extras;

		internal static ConfigEntry<MonsterDetail> Monsters;

		internal static ConfigEntry<bool> Re