using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using InteractiveTerminalAPI.UI; using InteractiveTerminalAPI.UI.Application; using InteractiveTerminalAPI.UI.Cursor; using InteractiveTerminalAPI.UI.Screen; using LethalConfig; using TMPro; using UnityEngine; using UnityEngine.TextCore; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("BM_MoonCatalogue")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BM_MoonCatalogue")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("b1172a76-4ab4-4be8-9676-d7eb68352d65")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace BM_MoonCatalogue; public enum CatalogueSortMode { Name, PriceAscending, PriceDescending } public enum WeatherDisplayMode { Auto, Full, Compact } internal static class Configuration { internal static ConfigEntry Enabled; internal static ConfigEntry DefaultSort; internal static ConfigEntry WeatherDisplay; internal static ConfigEntry UnknownPriceText; internal static void Initialize(ConfigFile config) { Enabled = config.Bind("General", "Enabled", true, "Enables BM_MoonCatalogue."); DefaultSort = config.Bind("Catalogue", "Default Sort", CatalogueSortMode.Name, "Sorting used by the normal 'moons' command."); WeatherDisplay = config.Bind("Catalogue", "Weather Display", WeatherDisplayMode.Auto, "Full shows complete weather names. Compact shortens weather names. Auto only shortens long combinations."); UnknownPriceText = config.Bind("Catalogue", "Unknown Price Text", "?", "Text shown when the route price cannot be determined reliably."); LethalConfigManager.SetModDescription("Provides a cleaner Interactive Terminal moon catalogue with weather, route prices and sorting."); } } internal sealed class MoonEntry { internal SelectableLevel Level; internal int LevelIndex; internal string Name; internal string Weather; internal bool PriceKnown; internal int Price; internal string PriceSource; } internal static class WeatherIconManager { private const int IconCellSize = 32; private const int IconCount = 8; private const int ExpectedWidth = 256; private const int ExpectedHeight = 32; private const float TargetIconHeight = 21f; private const float TargetBearingY = 16f; private const float HorizontalPadding = 2f; private static readonly string[] IconNames = new string[8] { "storm", "rain", "flood", "fog", "eclipse", "tornado", "meteor", "dust" }; private static Texture2D texture; private static TMP_SpriteAsset spriteAsset; private static Material spriteMaterial; private static bool initialized; private static bool available; internal static bool Available => available; internal static void Initialize(string path) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown if (initialized) { return; } initialized = true; available = false; if (string.IsNullOrEmpty(path)) { Plugin.Log.LogWarning((object)"Weather icon path is empty."); return; } if (!File.Exists(path)) { Plugin.Log.LogWarning((object)("Weather icon sheet not found: " + path)); return; } try { byte[] array = File.ReadAllBytes(path); texture = new Texture2D(2, 2, (TextureFormat)4, false); ((Object)texture).name = "BM_WeatherIcons_Texture"; ((Texture)texture).filterMode = (FilterMode)0; ((Texture)texture).wrapMode = (TextureWrapMode)1; if (!ImageConversion.LoadImage(texture, array, false)) { Plugin.Log.LogWarning((object)"Unity could not decode weather_icons.png."); return; } Plugin.Log.LogInfo((object)("Weather icon texture loaded: " + ((Texture)texture).width + "x" + ((Texture)texture).height)); if (((Texture)texture).width != 256 || ((Texture)texture).height != 32) { Plugin.Log.LogWarning((object)("weather_icons.png has wrong dimensions. Expected " + 256 + "x" + 32 + ", got " + ((Texture)texture).width + "x" + ((Texture)texture).height + ".")); } else { spriteAsset = BuildSpriteAsset(texture); if ((Object)(object)spriteAsset == (Object)null) { Plugin.Log.LogWarning((object)"Failed creating TMP weather sprite asset."); return; } available = true; Plugin.Log.LogWarning((object)"### BM WEATHER ICONS READY ###"); Plugin.Log.LogInfo((object)("Loaded weather icon sheet: " + path)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Failed loading weather icons: " + ex)); } } private static TMP_SpriteAsset BuildSpriteAsset(Texture2D sourceTexture) { //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Expected O, but got Unknown //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Expected O, but got Unknown TMP_SpriteAsset val = ScriptableObject.CreateInstance(); ((Object)val).name = "BM_WeatherIcons"; try { FieldInfo field = typeof(TMP_SpriteAsset).GetField("m_Version", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = typeof(TMP_SpriteAsset).GetField("m_SpriteGlyphTable", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field3 = typeof(TMP_SpriteAsset).GetField("m_SpriteCharacterTable", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { field.SetValue(val, "1.1.0"); } if (field2 == null) { Plugin.Log.LogError((object)"TMP field m_SpriteGlyphTable was not found."); Object.Destroy((Object)(object)val); return null; } if (field3 == null) { Plugin.Log.LogError((object)"TMP field m_SpriteCharacterTable was not found."); Object.Destroy((Object)(object)val); return null; } List list = new List(); List list2 = new List(); field2.SetValue(val, list); field3.SetValue(val, list2); val.spriteSheet = (Texture)(object)sourceTexture; Shader val2 = Shader.Find("TextMeshPro/Sprite"); if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogError((object)"TextMeshPro/Sprite shader was not found."); Object.Destroy((Object)(object)val); return null; } spriteMaterial = new Material(val2); ((Object)spriteMaterial).name = "BM_WeatherIcons_Material"; spriteMaterial.SetTexture(ShaderUtilities.ID_MainTex, (Texture)(object)sourceTexture); ((TMP_Asset)val).material = spriteMaterial; Color32[] pixels = sourceTexture.GetPixels32(); GlyphMetrics val3 = default(GlyphMetrics); for (int i = 0; i < 8; i++) { GlyphRect tightGlyphRect = GetTightGlyphRect(sourceTexture, pixels, i); float num = 1f; if (((GlyphRect)(ref tightGlyphRect)).height > 0) { num = (float)((GlyphRect)(ref tightGlyphRect)).width / (float)((GlyphRect)(ref tightGlyphRect)).height; } float num2 = 21f; float num3 = num2 * num; if (num3 < 9f) { num3 = 9f; } if (num3 > 24f) { num3 = 24f; } float num4 = num3 + 2f; ((GlyphMetrics)(ref val3))..ctor(num3, num2, 0f, 16f, num4); TMP_SpriteGlyph val4 = new TMP_SpriteGlyph((uint)i, val3, tightGlyphRect, 1f, 0); list.Add(val4); uint num5 = (uint)(57344 + i); TMP_SpriteCharacter val5 = new TMP_SpriteCharacter(num5, val, val4); val5.name = IconNames[i]; ((TMP_TextElement)val5).scale = 1f; list2.Add(val5); Plugin.Log.LogDebug((object)("Weather icon " + IconNames[i] + " crop=" + ((GlyphRect)(ref tightGlyphRect)).x + "," + ((GlyphRect)(ref tightGlyphRect)).y + " " + ((GlyphRect)(ref tightGlyphRect)).width + "x" + ((GlyphRect)(ref tightGlyphRect)).height + " display=" + num3 + "x" + num2)); } val.SortGlyphTable(); val.UpdateLookupTables(); Plugin.Log.LogInfo((object)("Created runtime TMP weather sprite asset with " + list2.Count + " icon(s).")); return val; } catch (Exception ex) { Plugin.Log.LogError((object)("Failed building TMP weather sprite asset: " + ex)); Object.Destroy((Object)(object)val); return null; } } private static GlyphRect GetTightGlyphRect(Texture2D sourceTexture, Color32[] pixels, int iconIndex) { //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) int num = iconIndex * 32; int num2 = num + 32 - 1; int num3 = num2; int num4 = num; int num5 = 31; int num6 = 0; bool flag = false; for (int i = 0; i < 32; i++) { for (int j = num; j <= num2; j++) { int num7 = i * ((Texture)sourceTexture).width + j; if (num7 >= 0 && num7 < pixels.Length && pixels[num7].a > 16) { flag = true; if (j < num3) { num3 = j; } if (j > num4) { num4 = j; } if (i < num5) { num5 = i; } if (i > num6) { num6 = i; } } } } if (!flag) { return new GlyphRect(num, 0, 32, 32); } num3 = Mathf.Max(num, num3 - 1); num4 = Mathf.Min(num2, num4 + 1); num5 = Mathf.Max(0, num5 - 1); num6 = Mathf.Min(31, num6 + 1); int num8 = num4 - num3 + 1; int num9 = num6 - num5 + 1; return new GlyphRect(num3, num5, num8, num9); } internal static void ApplyToTerminal(Terminal terminal) { if (!available || (Object)(object)spriteAsset == (Object)null || (Object)(object)terminal == (Object)null || (Object)(object)terminal.screenText == (Object)null || (Object)(object)terminal.screenText.textComponent == (Object)null) { return; } TMP_Text textComponent = terminal.screenText.textComponent; textComponent.richText = true; if ((Object)(object)textComponent.spriteAsset == (Object)null) { textComponent.spriteAsset = spriteAsset; Plugin.Log.LogInfo((object)"BM weather sprite asset assigned directly to terminal TMP."); } else if ((Object)(object)textComponent.spriteAsset != (Object)(object)spriteAsset) { if (textComponent.spriteAsset.fallbackSpriteAssets == null) { textComponent.spriteAsset.fallbackSpriteAssets = new List(); } if (!textComponent.spriteAsset.fallbackSpriteAssets.Contains(spriteAsset)) { textComponent.spriteAsset.fallbackSpriteAssets.Add(spriteAsset); Plugin.Log.LogInfo((object)"BM weather sprite asset added as terminal TMP fallback."); } } ((Graphic)textComponent).SetVerticesDirty(); ((Graphic)textComponent).SetLayoutDirty(); } internal static string GetTag(string weatherName) { if (!available) { return null; } string spriteName = GetSpriteName(weatherName); if (string.IsNullOrEmpty(spriteName)) { return null; } return ""; } private static string GetSpriteName(string weatherName) { if (string.IsNullOrWhiteSpace(weatherName)) { return null; } switch (weatherName.Trim().ToLowerInvariant()) { case "stormy": case "storm": return "storm"; case "rainy": case "rain": return "rain"; case "flooded": case "flood": return "flood"; case "foggy": case "fog": return "fog"; case "eclipsed": case "eclipse": return "eclipse"; case "tornado": case "torn": return "tornado"; case "meteor shower": case "meteorshower": case "meteor": return "meteor"; case "dust clouds": case "dustclouds": case "dust": return "dust"; case "none": return null; default: return null; } } } internal static class MoonCatalogue { private static readonly Regex WeatherParenthesesRegex = new Regex("\\((.*?)\\)", RegexOptions.Compiled); private static MethodInfo textPostProcessMethod; private static bool searchedForTextPostProcess; private static MethodInfo loadNewNodeIfAffordableMethod; internal static List BuildEntries(Terminal terminal, CatalogueSortMode sortMode) { List list = new List(); if ((Object)(object)terminal == (Object)null) { return list; } SelectableLevel[] levels = GetLevels(terminal); if (levels == null) { return list; } string existingCatalogueText = GetExistingCatalogueText(terminal); for (int i = 0; i < levels.Length; i++) { SelectableLevel val = levels[i]; if (!((Object)(object)val == (Object)null)) { MoonEntry moonEntry = new MoonEntry(); moonEntry.Level = val; moonEntry.LevelIndex = i; moonEntry.Name = GetCleanMoonName(val); moonEntry.Weather = ResolveWeather(val, moonEntry.Name, existingCatalogueText); MoonPriceResolver.PriceResult priceResult = MoonPriceResolver.ResolvePrice(terminal, val, i); moonEntry.PriceKnown = priceResult.Known; moonEntry.Price = priceResult.Price; moonEntry.PriceSource = priceResult.Source; list.Add(moonEntry); } } SortEntries(list, sortMode); Plugin.Log.LogInfo((object)("BM catalogue built " + list.Count + " moon entries. Sort=" + sortMode)); return list; } private static SelectableLevel[] GetLevels(Terminal terminal) { if ((Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.levels != null) { return StartOfRound.Instance.levels; } if ((Object)(object)terminal != (Object)null && terminal.moonsCatalogueList != null) { return terminal.moonsCatalogueList; } return null; } internal static string FormatRow(MoonEntry entry) { if (entry == null) { return string.Empty; } string text = Truncate(entry.Name, 16); string priceText = GetPriceText(entry); int visualUnits; string text2 = FormatWeatherForRow(entry.Weather, out visualUnits); string text3 = priceText.PadLeft(5); return "" + text + "" + text2 + "" + text3; } internal static string GetHeader() { return "MOONWEATHERPRICE"; } private static string GetPriceText(MoonEntry entry) { if (entry != null && entry.PriceKnown) { return entry.Price.ToString(); } string text = Configuration.UnknownPriceText.Value; if (string.IsNullOrEmpty(text)) { text = "?"; } return text; } private static string FormatWeatherForRow(string weather, out int visualUnits) { visualUnits = 0; if (string.IsNullOrWhiteSpace(weather)) { return string.Empty; } if (weather.Trim().Equals("None", StringComparison.OrdinalIgnoreCase)) { return string.Empty; } if (WeatherIconManager.Available) { string[] array = weather.Split(new char[1] { '+' }, StringSplitOptions.RemoveEmptyEntries); StringBuilder stringBuilder = new StringBuilder(); int num = 0; string[] array2 = array; foreach (string text in array2) { string text2 = text.Trim(); string tag = WeatherIconManager.GetTag(text2); if (num > 0) { stringBuilder.Append(' '); } if (!string.IsNullOrEmpty(tag)) { stringBuilder.Append(tag); visualUnits++; } else { string unknownWeatherCode = GetUnknownWeatherCode(text2); stringBuilder.Append(unknownWeatherCode); visualUnits += unknownWeatherCode.Length; } num++; } return stringBuilder.ToString(); } string text3 = CompactWeather(weather); visualUnits = text3.Length; return text3; } private static string GetUnknownWeatherCode(string weather) { if (string.IsNullOrWhiteSpace(weather)) { return "?"; } StringBuilder stringBuilder = new StringBuilder(); foreach (char c in weather) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToUpperInvariant(c)); } if (stringBuilder.Length >= 3) { break; } } if (stringBuilder.Length == 0) { return "?"; } return stringBuilder.ToString(); } internal static string GetSortText(CatalogueSortMode sort) { return sort switch { CatalogueSortMode.PriceAscending => "PRICE ^", CatalogueSortMode.PriceDescending => "PRICE v", _ => "NAME", }; } internal static void OpenRouteConfirmation(Terminal terminal, MoonEntry entry) { if ((Object)(object)terminal == (Object)null || entry == null) { return; } TerminalNode val = FindRouteNode(terminal, entry); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)("Could not find route node for " + entry.Name + ".")); return; } try { if (loadNewNodeIfAffordableMethod == null) { loadNewNodeIfAffordableMethod = typeof(Terminal).GetMethod("LoadNewNodeIfAffordable", BindingFlags.Instance | BindingFlags.NonPublic); } if (loadNewNodeIfAffordableMethod == null) { Plugin.Log.LogError((object)"Terminal.LoadNewNodeIfAffordable could not be found."); return; } Plugin.Log.LogInfo((object)("Opening vanilla route confirmation for " + entry.Name + ". LevelIndex=" + entry.LevelIndex + ", Price=" + val.itemCost)); loadNewNodeIfAffordableMethod.Invoke(terminal, new object[1] { val }); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed opening route confirmation for " + entry.Name + ": " + ex)); } } private static TerminalNode FindRouteNode(Terminal terminal, MoonEntry entry) { if ((Object)(object)terminal == (Object)null || entry == null || (Object)(object)terminal.terminalNodes == (Object)null) { return null; } TerminalKeyword val = FindKeyword(terminal, "route"); if ((Object)(object)val != (Object)null && val.compatibleNouns != null) { CompatibleNoun[] compatibleNouns = val.compatibleNouns; foreach (CompatibleNoun val2 in compatibleNouns) { if (val2 != null && !((Object)(object)val2.noun == (Object)null) && !((Object)(object)val2.result == (Object)null) && MoonNamesMatch(entry.Name, val2.noun.word)) { return val2.result; } } } TerminalNode val3 = FindRouteNodeInList(terminal.terminalNodes.terminalNodes, entry.LevelIndex); if ((Object)(object)val3 != (Object)null) { return val3; } return FindRouteNodeInList(terminal.terminalNodes.specialNodes, entry.LevelIndex); } private static TerminalNode FindRouteNodeInList(List nodes, int levelIndex) { if (nodes == null) { return null; } foreach (TerminalNode node in nodes) { if ((Object)(object)node == (Object)null || node.buyRerouteToMoon != levelIndex) { continue; } return node; } return null; } private static TerminalKeyword FindKeyword(Terminal terminal, string word) { if ((Object)(object)terminal == (Object)null || (Object)(object)terminal.terminalNodes == (Object)null || terminal.terminalNodes.allKeywords == null) { return null; } TerminalKeyword[] allKeywords = terminal.terminalNodes.allKeywords; foreach (TerminalKeyword val in allKeywords) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.word) && val.word.Equals(word, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } private static bool MoonNamesMatch(string left, string right) { if (string.IsNullOrEmpty(left) || string.IsNullOrEmpty(right)) { return false; } return NormalizeMoonName(left) == NormalizeMoonName(right); } private static string GetExistingCatalogueText(Terminal terminal) { TerminalNode val = FindOriginalMoonsNode(terminal); if ((Object)(object)val == (Object)null || string.IsNullOrEmpty(val.displayText)) { return null; } try { if (!searchedForTextPostProcess) { searchedForTextPostProcess = true; textPostProcessMethod = typeof(Terminal).GetMethod("TextPostProcess", BindingFlags.Instance | BindingFlags.NonPublic); } if (textPostProcessMethod == null) { return val.displayText; } object obj = textPostProcessMethod.Invoke(terminal, new object[2] { val.displayText, val }); string text = obj as string; if (!string.IsNullOrEmpty(text)) { return text; } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Could not read original processed moons text: " + ex.Message)); } return val.displayText; } private static TerminalNode FindOriginalMoonsNode(Terminal terminal) { if ((Object)(object)terminal == (Object)null || (Object)(object)terminal.terminalNodes == (Object)null || terminal.terminalNodes.allKeywords == null) { return null; } TerminalKeyword[] allKeywords = terminal.terminalNodes.allKeywords; foreach (TerminalKeyword val in allKeywords) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.word) && val.word.Equals("moons", StringComparison.OrdinalIgnoreCase) && (Object)(object)val.specialKeywordResult != (Object)null) { return val.specialKeywordResult; } } return null; } private static string ResolveWeather(SelectableLevel level, string moonName, string originalCatalogue) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 string text = ExtractWeatherFromExistingCatalogue(originalCatalogue, moonName); if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } if ((int)level.currentWeather == -1) { return "None"; } return ((object)Unsafe.As(ref level.currentWeather)/*cast due to .constrained prefix*/).ToString(); } private static string ExtractWeatherFromExistingCatalogue(string catalogue, string moonName) { if (string.IsNullOrEmpty(catalogue) || string.IsNullOrEmpty(moonName)) { return null; } string value = NormalizeMoonName(moonName); string[] array = catalogue.Replace("\r", string.Empty).Split(new char[1] { '\n' }); string[] array2 = array; foreach (string text in array2) { if (string.IsNullOrWhiteSpace(text)) { continue; } string text2 = text.Trim(); string text3 = NormalizeMoonName(text2); if (text3.IndexOf(value, StringComparison.OrdinalIgnoreCase) < 0) { continue; } Match match = WeatherParenthesesRegex.Match(text2); if (match.Success && match.Groups.Count >= 2) { string text4 = match.Groups[1].Value.Trim(); if (!string.IsNullOrEmpty(text4)) { return text4; } } } return null; } private static string CompactWeather(string weather) { if (string.IsNullOrWhiteSpace(weather)) { return string.Empty; } string[] array = weather.Split(new char[1] { '+' }, StringSplitOptions.RemoveEmptyEntries); List list = new List(); string[] array2 = array; foreach (string text in array2) { string text2 = CompactWeatherName(text.Trim()); if (!string.IsNullOrEmpty(text2)) { list.Add(text2); } } return string.Join("+", list.ToArray()); } private static string CompactWeatherName(string weather) { if (string.IsNullOrWhiteSpace(weather)) { return string.Empty; } return weather.Trim().ToLowerInvariant() switch { "eclipsed" => "ECL", "flooded" => "FLD", "rainy" => "RAIN", "foggy" => "FOG", "stormy" => "STM", "tornado" => "TOR", "meteor shower" => "MET", "dust clouds" => "DST", "none" => "", _ => GetUnknownWeatherCode(weather), }; } private static void SortEntries(List entries, CatalogueSortMode mode) { switch (mode) { case CatalogueSortMode.PriceAscending: entries.Sort(delegate(MoonEntry left, MoonEntry right) { int num = CompareKnownPrice(left, right); if (num != 0) { return num; } if (left.PriceKnown && right.PriceKnown) { int num2 = left.Price.CompareTo(right.Price); if (num2 != 0) { return num2; } } return CompareName(left, right); }); break; case CatalogueSortMode.PriceDescending: entries.Sort(delegate(MoonEntry left, MoonEntry right) { int num = CompareKnownPrice(left, right); if (num != 0) { return num; } if (left.PriceKnown && right.PriceKnown) { int num2 = right.Price.CompareTo(left.Price); if (num2 != 0) { return num2; } } return CompareName(left, right); }); break; default: entries.Sort(CompareName); break; } } private static int CompareKnownPrice(MoonEntry left, MoonEntry right) { if (left.PriceKnown && !right.PriceKnown) { return -1; } if (!left.PriceKnown && right.PriceKnown) { return 1; } return 0; } private static int CompareName(MoonEntry left, MoonEntry right) { return string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase); } private static string GetCleanMoonName(SelectableLevel level) { if ((Object)(object)level == (Object)null) { return "Unknown"; } string text = level.PlanetName; if (string.IsNullOrWhiteSpace(text)) { text = ((Object)level).name; } if (string.IsNullOrWhiteSpace(text)) { return "Unknown"; } text = text.Trim(); Match match = Regex.Match(text, "^\\d+\\s+(.+)$"); if (match.Success && match.Groups.Count > 1) { text = match.Groups[1].Value.Trim(); } return text; } private static string NormalizeMoonName(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } private static string Truncate(string value, int maximum) { if (string.IsNullOrEmpty(value)) { return string.Empty; } if (value.Length <= maximum) { return value; } if (maximum <= 3) { return value.Substring(0, maximum); } return value.Substring(0, maximum - 3) + "..."; } } public abstract class BaseMoonCatalogueApplication : InteractiveTerminalApplication { protected abstract CatalogueSortMode SortMode { get; } public override void Initialization() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown if (!Configuration.Enabled.Value) { ShowDisabledScreen(); return; } Terminal currentTerminal = ((TerminalApplication)this).terminal; WeatherIconManager.ApplyToTerminal(currentTerminal); List list = MoonCatalogue.BuildEntries(currentTerminal, SortMode); CursorElement[] array = (CursorElement[])(object)new CursorElement[list.Count]; for (int i = 0; i < list.Count; i++) { MoonEntry entry = list[i]; CursorElement val = new CursorElement(); val.Name = MoonCatalogue.FormatRow(entry); val.Description = string.Empty; val.Action = delegate { ((BaseInteractiveApplication)(object)this).currentCursorMenu = null; ((TerminalApplication)this).currentScreen = null; MoonCatalogue.OpenRouteConfirmation(currentTerminal, entry); }; array[i] = val; } if (array.Length == 0) { ShowEmptyScreen(); return; } CursorMenu val2 = CursorMenu.Create(0, '>', array, (Func[])null); ITextElement[] array2 = (ITextElement[])(object)new ITextElement[8] { (ITextElement)TextElement.Create("SORT: " + MoonCatalogue.GetSortText(SortMode)), (ITextElement)TextElement.Create(" "), (ITextElement)TextElement.Create(MoonCatalogue.GetHeader()), (ITextElement)TextElement.Create("---------------------------------------------"), (ITextElement)val2, (ITextElement)TextElement.Create(" "), (ITextElement)TextElement.Create("ENTER: Route to selected moon"), (ITextElement)TextElement.Create("moons | moons price | moons price desc") }; IScreen currentScreen = (IScreen)(object)BoxedScreen.Create("BM Moon Catalogue", array2); ((BaseInteractiveApplication)(object)this).currentCursorMenu = (BaseCursorMenu)(object)val2; ((TerminalApplication)this).currentScreen = currentScreen; Plugin.Log.LogInfo((object)("Opened BM Moon Catalogue. Entries=" + list.Count + ", sort=" + SortMode.ToString() + ", icons=" + WeatherIconManager.Available)); } private void ShowDisabledScreen() { IScreen currentScreen = (IScreen)(object)BoxedScreen.Create("BM Moon Catalogue", (ITextElement[])(object)new ITextElement[1] { (ITextElement)TextElement.Create("BM_MoonCatalogue is disabled in config.") }); ((TerminalApplication)this).currentScreen = currentScreen; } private void ShowEmptyScreen() { IScreen currentScreen = (IScreen)(object)BoxedScreen.Create("BM Moon Catalogue", (ITextElement[])(object)new ITextElement[1] { (ITextElement)TextElement.Create("No moons were found.") }); ((TerminalApplication)this).currentScreen = currentScreen; } } public class MoonCatalogueApplication : BaseMoonCatalogueApplication { protected override CatalogueSortMode SortMode => Configuration.DefaultSort.Value; } public class MoonCatalogueNameApplication : BaseMoonCatalogueApplication { protected override CatalogueSortMode SortMode => CatalogueSortMode.Name; } public class MoonCataloguePriceApplication : BaseMoonCatalogueApplication { protected override CatalogueSortMode SortMode => CatalogueSortMode.PriceAscending; } public class MoonCataloguePriceDescendingApplication : BaseMoonCatalogueApplication { protected override CatalogueSortMode SortMode => CatalogueSortMode.PriceDescending; } internal static class MoonPriceResolver { internal sealed class PriceResult { public bool Known; public int Price; public string Source; } private const int MaxSearchDepth = 4; internal static void Initialize() { } internal static PriceResult ResolvePrice(Terminal terminal, SelectableLevel level, int levelIndex) { PriceResult priceResult = new PriceResult(); priceResult.Known = false; priceResult.Price = 0; priceResult.Source = "Unknown"; if ((Object)(object)terminal == (Object)null || (Object)(object)level == (Object)null || (Object)(object)terminal.terminalNodes == (Object)null) { return priceResult; } if (TryResolveFromRouteKeyword(terminal, level, levelIndex, out var price)) { priceResult.Known = true; priceResult.Price = price; priceResult.Source = "RouteKeyword"; return priceResult; } if (TryResolveFromAllKeywords(terminal, level, levelIndex, out price)) { priceResult.Known = true; priceResult.Price = price; priceResult.Source = "TerminalKeyword"; return priceResult; } if (TryResolveFromNodeLists(terminal, levelIndex, out price)) { priceResult.Known = true; priceResult.Price = price; priceResult.Source = "TerminalNode"; return priceResult; } return priceResult; } private static bool TryResolveFromRouteKeyword(Terminal terminal, SelectableLevel level, int levelIndex, out int price) { price = 0; TerminalKeyword val = FindKeyword(terminal, "route"); if ((Object)(object)val == (Object)null || val.compatibleNouns == null) { return false; } string left = CleanMoonName(level.PlanetName); CompatibleNoun[] compatibleNouns = val.compatibleNouns; foreach (CompatibleNoun val2 in compatibleNouns) { if (val2 == null || (Object)(object)val2.noun == (Object)null) { continue; } string word = val2.noun.word; if (MoonNamesMatch(left, word)) { HashSet visited = new HashSet(); if (TryResolveNodeChain(val2.result, levelIndex, 0, visited, out price)) { return true; } } } return false; } private static bool TryResolveFromAllKeywords(Terminal terminal, SelectableLevel level, int levelIndex, out int price) { price = 0; if (terminal.terminalNodes.allKeywords == null) { return false; } string left = CleanMoonName(level.PlanetName); TerminalKeyword[] allKeywords = terminal.terminalNodes.allKeywords; foreach (TerminalKeyword val in allKeywords) { if ((Object)(object)val == (Object)null) { continue; } if (val.compatibleNouns != null) { CompatibleNoun[] compatibleNouns = val.compatibleNouns; foreach (CompatibleNoun val2 in compatibleNouns) { if (val2 != null && !((Object)(object)val2.noun == (Object)null) && MoonNamesMatch(left, val2.noun.word)) { HashSet visited = new HashSet(); if (TryResolveNodeChain(val2.result, levelIndex, 0, visited, out price)) { return true; } } } } if ((Object)(object)val.specialKeywordResult != (Object)null) { HashSet visited2 = new HashSet(); if (TryResolveNodeChain(val.specialKeywordResult, levelIndex, 0, visited2, out price)) { return true; } } } return false; } private static bool TryResolveFromNodeLists(Terminal terminal, int levelIndex, out int price) { price = 0; List terminalNodes = terminal.terminalNodes.terminalNodes; if (terminalNodes != null) { foreach (TerminalNode item in terminalNodes) { HashSet visited = new HashSet(); if (TryResolveNodeChain(item, levelIndex, 0, visited, out price)) { return true; } } } List specialNodes = terminal.terminalNodes.specialNodes; if (specialNodes != null) { foreach (TerminalNode item2 in specialNodes) { HashSet visited2 = new HashSet(); if (TryResolveNodeChain(item2, levelIndex, 0, visited2, out price)) { return true; } } } return false; } private static bool TryResolveNodeChain(TerminalNode node, int levelIndex, int depth, HashSet visited, out int price) { price = 0; if ((Object)(object)node == (Object)null) { return false; } if (depth > 4) { return false; } if (visited.Contains(node)) { return false; } visited.Add(node); if (node.buyRerouteToMoon == levelIndex) { price = node.itemCost; return true; } if (node.isConfirmationNode && node.itemCost >= 0 && node.buyRerouteToMoon == levelIndex) { price = node.itemCost; return true; } if (node.terminalOptions == null) { return false; } CompatibleNoun[] terminalOptions = node.terminalOptions; foreach (CompatibleNoun val in terminalOptions) { if (val != null && !((Object)(object)val.result == (Object)null) && TryResolveNodeChain(val.result, levelIndex, depth + 1, visited, out price)) { return true; } } return false; } private static TerminalKeyword FindKeyword(Terminal terminal, string word) { if ((Object)(object)terminal == (Object)null || (Object)(object)terminal.terminalNodes == (Object)null || terminal.terminalNodes.allKeywords == null) { return null; } TerminalKeyword[] allKeywords = terminal.terminalNodes.allKeywords; foreach (TerminalKeyword val in allKeywords) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.word) && val.word.Equals(word, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } private static bool MoonNamesMatch(string left, string right) { if (string.IsNullOrEmpty(left) || string.IsNullOrEmpty(right)) { return false; } return Normalize(left) == Normalize(right); } private static string CleanMoonName(string value) { if (string.IsNullOrEmpty(value)) { return value; } value = value.Trim(); int num = value.IndexOf(' '); if (num > 0 && int.TryParse(value.Substring(0, num), out var _)) { value = value.Substring(num + 1); } return value.Trim(); } private static string Normalize(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } } [BepInPlugin("com.brox.BM_MoonCatalogue", "BM_MoonCatalogue", "0.3.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.brox.BM_MoonCatalogue"; public const string ModName = "BM_MoonCatalogue"; public const string ModVersion = "0.3.0"; internal static ManualLogSource Log; private static bool initialized; internal static string WeatherIconPath => Path.Combine(Paths.PluginPath, "Brox-BM_MoonCatalogue", "assets", "weather_icons.png"); private void Awake() { if (!initialized) { initialized = true; Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"BM_MoonCatalogue v0.3.0 initializing..."); Configuration.Initialize(((BaseUnityPlugin)this).Config); MoonPriceResolver.Initialize(); WeatherIconManager.Initialize(WeatherIconPath); RegisterApplications(); Log.LogWarning((object)"### BM_MOONCATALOGUE ITAPI APPLICATIONS REGISTERED ###"); Log.LogInfo((object)"BM_MoonCatalogue loaded."); } } private static void RegisterApplications() { InteractiveTerminalManager.RegisterApplication("moons", false); InteractiveTerminalManager.RegisterApplication("moons name", false); InteractiveTerminalManager.RegisterApplication("moons price", false); InteractiveTerminalManager.RegisterApplication("moons price desc", false); InteractiveTerminalManager.RegisterApplication("bmoons", false); } }