Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ExtendedRandomMoons v1.0.0
ExtendedRandomMoons.dll
Decompiled 3 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("ExtendedRandomMoons")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ExtendedRandomMoons")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("832d2f86-4282-4d2b-8a6a-b706dcac09b0")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.0.0")] namespace HoppinHauler.ExtendedRandomMoons; internal sealed class ConfigExt { private readonly ConfigFile _cfg; public ConfigEntry<bool> DeductCredits; public ConfigEntry<bool> SkipConfirmation; public ConfigEntry<bool> DifferentPlanetEachTime; public ConfigEntry<int> AvoidRepeatCount; public ConfigEntry<string> Blacklist; public ConfigEntry<bool> ExcludeCompanyMoons; public ConfigEntry<string> DisallowedWeathers; public ConfigEntry<bool> DebugLogging; public readonly HashSet<string> BlacklistSet = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); public readonly HashSet<string> DisallowedWeatherSet = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); public ConfigExt(ConfigFile cfg) { _cfg = cfg; _cfg.SaveOnConfigSet = false; DeductCredits = _cfg.Bind<bool>("General", "DeductCredits", true, "If false, routing via 'route random' will not deduct credits (cost forced to 0)."); SkipConfirmation = _cfg.Bind<bool>("General", "SkipConfirmation", false, "If true, 'route random' will skip the confirmation node and route immediately."); DifferentPlanetEachTime = _cfg.Bind<bool>("General", "DifferentPlanetEachTime", true, "If true, excludes the currently orbited moon."); AvoidRepeatCount = _cfg.Bind<int>("General", "AvoidRepeatCount", 3, "Avoids choosing any of the last N selected moons (best-effort)."); Blacklist = _cfg.Bind<string>("Moons", "Blacklist", "Gordion,Liquidation", "Comma-separated list of moons that will never be selected (e.g. Gordion,Liquidation)."); ExcludeCompanyMoons = _cfg.Bind<bool>("Moons", "ExcludeCompanyMoons", true, "If true, excludes company moons (best-effort heuristic)."); DisallowedWeathers = _cfg.Bind<string>("Weather", "DisallowedWeathers", "Eclipsed", "Comma-separated list of weather keys/names to exclude. Vanilla examples: Mild,DustClouds,Rainy,Stormy,Foggy,Flooded,Eclipsed"); DebugLogging = _cfg.Bind<bool>("Debug", "DebugLogging", false, "If true, enables verbose [ERM] debug logs."); _cfg.Save(); _cfg.SaveOnConfigSet = true; } public void ReloadDerived() { Util.ParseCsvToNormalizedSet(Blacklist.Value, BlacklistSet); Util.ParseCsvToNormalizedSet(DisallowedWeathers.Value, DisallowedWeatherSet); Util.ExpandWeatherAliases(DisallowedWeatherSet); } } internal static class ERMLog { public static void Debug(string message) { if (Plugin.Log != null && Plugin.Cfg != null && Plugin.Cfg.DebugLogging != null && Plugin.Cfg.DebugLogging.Value) { Plugin.Log.LogInfo((object)message); } } public static void Info(string message) { if (Plugin.Log != null) { Plugin.Log.LogInfo((object)message); } } public static void Warn(string message) { if (Plugin.Log != null) { Plugin.Log.LogWarning((object)message); } } public static void Error(string message) { if (Plugin.Log != null) { Plugin.Log.LogError((object)message); } } } internal static class MoonSelector { private static readonly Random Rand = new Random(); private static readonly Queue<string> LastKeys = new Queue<string>(); public static bool TryPickMoon(object terminalInstance, ConfigExt cfg, out MoonCandidate chosen, out string failureReason) { chosen = default(MoonCandidate); failureReason = null; object startOfRoundInstance = ReflectionCache.GetStartOfRoundInstance(); if (startOfRoundInstance == null) { failureReason = "StartOfRound.Instance not available."; return false; } object[] array = Util.TryGetLevelsArray(startOfRoundInstance); if (array == null || array.Length == 0) { failureReason = "No levels found on StartOfRound."; return false; } object obj = Util.TryGetCurrentLevel(startOfRoundInstance); int num = Util.TryGetGroupCredits(terminalInstance, int.MaxValue); string text = null; int num2 = int.MinValue; if (obj != null) { string text2 = Util.TryGetPlanetName(obj); text = (string.IsNullOrEmpty(text2) ? null : Util.NormalizeMoonName(text2)); num2 = Util.TryGetLevelId(obj, int.MinValue); } ERMLog.Debug("[ERM] TryPickMoon: levels=" + array.Length + ", current='" + (text ?? "(null)") + "', credits=" + ((num == int.MaxValue) ? (-1) : num) + ", deduct=" + cfg.DeductCredits.Value + ", diffEachTime=" + cfg.DifferentPlanetEachTime.Value + ", excludeCompany=" + cfg.ExcludeCompanyMoons.Value + ", avoidN=" + cfg.AvoidRepeatCount.Value); List<MoonCandidate> list = new List<MoonCandidate>(array.Length); for (int i = 0; i < array.Length; i++) { object obj2 = array[i]; if (obj2 == null) { continue; } string text3 = Util.TryGetPlanetName(obj2); if (string.IsNullOrEmpty(text3)) { continue; } string text4 = Util.NormalizeMoonName(text3); int num3 = Util.TryGetLevelId(obj2, i); if (cfg.DifferentPlanetEachTime.Value) { bool flag = num2 != int.MinValue && num3 == num2; bool flag2 = !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text4) && string.Equals(text4, text, StringComparison.InvariantCultureIgnoreCase); if (flag || flag2) { ERMLog.Debug("[ERM] Filtered (current moon): " + text3); continue; } } if (cfg.BlacklistSet.Contains(text4)) { ERMLog.Debug("[ERM] Filtered (blacklist): " + text3); continue; } if (cfg.ExcludeCompanyMoons.Value && Util.IsCompanyMoonHeuristic(obj2)) { ERMLog.Debug("[ERM] Filtered (company heuristic): " + text3); continue; } string weatherKey = WeatherResolver.GetWeatherKey(obj2); if (!string.IsNullOrEmpty(weatherKey) && cfg.DisallowedWeatherSet.Contains(weatherKey)) { ERMLog.Debug("[ERM] Filtered (weather '" + weatherKey + "'): " + text3); continue; } int cost = -1; if (TerminalIntegration.TryGetCachedRouteCost(num3, out var cost2)) { cost = cost2; } MoonCandidate moonCandidate = default(MoonCandidate); moonCandidate.PlanetName = text3; moonCandidate.NormalizedName = text4; moonCandidate.LevelId = num3; moonCandidate.Cost = cost; moonCandidate.WeatherKey = weatherKey; MoonCandidate item = moonCandidate; ERMLog.Debug("[ERM] Candidate: planet='" + item.PlanetName + "', levelId=" + item.LevelId + ", cost=" + ((item.Cost == 0) ? "FREE" : item.Cost.ToString()) + ", weather=" + (item.WeatherKey ?? "(null)")); if (cfg.DeductCredits.Value && item.Cost > 0 && num != int.MaxValue && num < item.Cost) { ERMLog.Debug("[ERM] Filtered (cannot afford): " + text3 + " cost=" + item.Cost + " credits=" + num); } else { list.Add(item); } } ERMLog.Debug("[ERM] Candidates after filtering: " + list.Count); if (list.Count == 0) { failureReason = "All moons were filtered out."; return false; } int num4 = cfg.AvoidRepeatCount.Value; if (num4 > 0) { if (num4 > 50) { num4 = 50; } List<MoonCandidate> list2 = new List<MoonCandidate>(list.Count); for (int j = 0; j < list.Count; j++) { if (!LastKeys.Contains(list[j].RepeatKey)) { list2.Add(list[j]); } } if (list2.Count > 0) { list = list2; } } chosen = list[Rand.Next(list.Count)]; ERMLog.Debug("[ERM] Chosen: '" + chosen.PlanetName + "' levelId=" + chosen.LevelId + " cost=" + ((chosen.Cost == 0) ? "FREE" : chosen.Cost.ToString()) + " weather=" + (string.IsNullOrEmpty(chosen.WeatherKey) ? "(null)" : chosen.WeatherKey)); if (num4 > 0) { LastKeys.Enqueue(chosen.RepeatKey); while (LastKeys.Count > num4) { LastKeys.Dequeue(); } } return true; } } internal struct MoonCandidate { public string PlanetName; public string NormalizedName; public int Cost; public int LevelId; public string WeatherKey; public string RepeatKey => string.IsNullOrEmpty(NormalizedName) ? ("id:" + LevelId) : (NormalizedName + "|id:" + LevelId); } [BepInPlugin("HoppinHauler.extendedrandommoons", "ExtendedRandomMoons", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "HoppinHauler.extendedrandommoons"; public const string PluginName = "ExtendedRandomMoons"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; internal static ConfigExt Cfg; private Harmony _harmony; private void Awake() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { Cfg = new ConfigExt(((BaseUnityPlugin)this).Config); Cfg.ReloadDerived(); ReflectionCache.Warmup(Log); _harmony = new Harmony("HoppinHauler.extendedrandommoons"); TerminalIntegration.ApplyPatches(_harmony, Log); Log.LogInfo((object)"ExtendedRandomMoons loaded."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)"Failed to initialize ExtendedRandomMoons"); ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } } internal static class ReflectionCache { internal static Type TerminalType; internal static Type TerminalNodeType; internal static Type TerminalKeywordType; internal static Type CompatibleNounType; internal static Type StartOfRoundType; internal static Type SelectableLevelType; internal static void Warmup(ManualLogSource log) { TerminalType = AccessTools.TypeByName("Terminal"); TerminalNodeType = AccessTools.TypeByName("TerminalNode"); TerminalKeywordType = AccessTools.TypeByName("TerminalKeyword"); CompatibleNounType = AccessTools.TypeByName("CompatibleNoun"); StartOfRoundType = AccessTools.TypeByName("StartOfRound"); SelectableLevelType = AccessTools.TypeByName("SelectableLevel"); if (TerminalType == null || TerminalNodeType == null || TerminalKeywordType == null || CompatibleNounType == null) { log.LogWarning((object)"Some terminal types could not be resolved by name. Terminal integration may fail."); } if (StartOfRoundType == null || SelectableLevelType == null) { log.LogWarning((object)"Some gameplay types could not be resolved by name. Moon selection may fail."); } } internal static object GetStartOfRoundInstance() { if (StartOfRoundType == null) { return null; } PropertyInfo property = StartOfRoundType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(null, null); } FieldInfo field = StartOfRoundType.GetField("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(null); } return null; } } internal static class TerminalIntegration { private const string NodeNameRouteRandom = "erm_routeRandom"; private const string NodeNameNoSuitable = "erm_noSuitable"; private static ManualLogSource _log; private static object _routeKeyword; private static object _sampleDenyCompatibleNoun; private static object _sampleConfirmKeyword; private static readonly Dictionary<int, int> _routeCostByLevelId = new Dictionary<int, int>(); public static bool TryGetCachedRouteCost(int levelId, out int cost) { return _routeCostByLevelId.TryGetValue(levelId, out cost); } public static void ApplyPatches(Harmony harmony, ManualLogSource log) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown _log = log; MethodInfo methodInfo = AccessTools.Method(ReflectionCache.TerminalType, "Awake", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(ReflectionCache.TerminalType, "ParsePlayerSentence", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(TerminalIntegration), "TerminalAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(TerminalIntegration), "ParsePlayerSentencePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } public static void TerminalAwakePostfix(object __instance) { try { if (__instance == null) { return; } Plugin.Cfg.ReloadDerived(); _routeKeyword = TerminalNodeFactory.GetKeywordByName(__instance, "Route"); if (_routeKeyword == null) { _log.LogWarning((object)"Failed to find Route keyword."); return; } BuildRouteCostCacheFromRouteKeyword(_routeKeyword); if (TerminalNodeFactory.HasRouteRandomAlready(__instance)) { ERMLog.Debug("[ERM] route random already registered, skipping."); TerminalNodeFactory.TryCacheConfirmDenySamples(__instance, _routeKeyword, out _sampleConfirmKeyword, out _sampleDenyCompatibleNoun); return; } object templateKeyword = null; object templateCompatibleNoun = null; if (Util.TryGetMemberValue(_routeKeyword, "compatibleNouns") is Array array) { for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); if (value != null) { object obj = Util.TryGetMemberValue(value, "noun"); if (obj != null) { templateCompatibleNoun = value; templateKeyword = obj; break; } } } } object obj2 = TerminalNodeFactory.CreateTerminalKeywordFromTemplate(templateKeyword, "random", "ExtendedRandom", _routeKeyword); object resultNode = TerminalNodeFactory.CreateEmptyResultNode("erm_routeRandom"); object newCompatibleNoun = TerminalNodeFactory.CreateCompatibleNounFromTemplate(templateCompatibleNoun, obj2, resultNode); TerminalNodeFactory.AddKeyword(__instance, obj2); TerminalNodeFactory.AddCompatibleNounToKeyword(__instance, "Route", newCompatibleNoun); TerminalNodeFactory.AppendMoonsHelp(__instance, "* Random // Routes you to a random moon (uses config filters)\n"); TerminalNodeFactory.TryCacheConfirmDenySamples(__instance, _routeKeyword, out _sampleConfirmKeyword, out _sampleDenyCompatibleNoun); _log.LogInfo((object)"Registered terminal command: route random"); } catch (Exception ex) { _log.LogError((object)"Failed to install terminal keywords."); _log.LogError((object)ex); } } public static void ParsePlayerSentencePostfix(ref object __result, object __instance) { try { if (__result == null || __instance == null) { return; } string a = TerminalNodeFactory.TryGetNodeName(__result); if (string.Equals(a, "erm_routeRandom", StringComparison.InvariantCultureIgnoreCase)) { Plugin.Cfg.ReloadDerived(); if (!MoonSelector.TryPickMoon(__instance, Plugin.Cfg, out var chosen, out var failureReason)) { __result = TerminalNodeFactory.CreateSimpleNode("erm_noSuitable", "\nNo suitable moons found.\nCheck your ExtendedRandomMoons config (blacklist/weather/cost).\n" + (string.IsNullOrEmpty(failureReason) ? "\n\n\n" : ("\n" + failureReason + "\n\n\n")), clearPreviousText: true); return; } bool free = !Plugin.Cfg.DeductCredits.Value; object obj = TerminalNodeFactory.CreateRouteNode(__instance, chosen, free, Plugin.Cfg.SkipConfirmation.Value, _sampleConfirmKeyword, _sampleDenyCompatibleNoun); __result = obj; } } catch (Exception ex) { _log.LogError((object)"Failed handling route random."); _log.LogError((object)ex); } } private static void BuildRouteCostCacheFromRouteKeyword(object routeKeyword) { _routeCostByLevelId.Clear(); object startOfRoundInstance = ReflectionCache.GetStartOfRoundInstance(); object[] levels = ((startOfRoundInstance != null) ? Util.TryGetLevelsArray(startOfRoundInstance) : null); if (!(Util.TryGetMemberValue(routeKeyword, "compatibleNouns") is Array array) || array.Length == 0) { ERMLog.Debug("[ERM] Route cost cache: route keyword has no compatibleNouns."); return; } for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); if (value == null) { continue; } object obj = Util.TryGetMemberValue(value, "noun"); object obj2 = Util.TryGetMemberValue(value, "result"); if (obj == null || obj2 == null) { continue; } int num = Util.TryGetIntMember(obj2, "itemCost", int.MinValue); if (num == int.MinValue) { num = Util.TryGetIntMember(obj2, "ItemCost", int.MinValue); } if (num == int.MinValue) { continue; } string text = Util.TryGetStringMember(obj, "name") ?? Util.TryGetStringMember(obj, "word"); if (string.IsNullOrEmpty(text)) { continue; } int num2 = FindLevelIdByName(levels, text); if (num2 < 0) { int num3 = Util.TryGetIntMember(obj2, "buyRerouteToMoon", int.MinValue); if (num3 != int.MinValue && num3 >= 0) { num2 = num3; } } if (num2 >= 0) { _routeCostByLevelId[num2] = num; } } ERMLog.Debug("[ERM] Route cost cache built: entries=" + _routeCostByLevelId.Count); } private static int FindLevelIdByName(object[] levels, string nounName) { if (levels == null || levels.Length == 0) { return -1; } string text = Util.NormalizeMoonName(nounName); for (int i = 0; i < levels.Length; i++) { object obj = levels[i]; if (obj == null) { continue; } string text2 = Util.TryGetPlanetName(obj); if (!string.IsNullOrEmpty(text2)) { string text3 = Util.NormalizeMoonName(text2); if (string.Equals(text3, text, StringComparison.InvariantCultureIgnoreCase) || text3.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) >= 0 || text.IndexOf(text3, StringComparison.InvariantCultureIgnoreCase) >= 0) { return Util.TryGetLevelId(obj, i); } } } return -1; } } internal static class TerminalNodeFactory { private static object SmartCreateInstance(Type t) { if (t == null) { return null; } if (typeof(ScriptableObject).IsAssignableFrom(t)) { return ScriptableObject.CreateInstance(t); } return Activator.CreateInstance(t); } private static object CloneScriptableObject(object template) { if (template == null) { return null; } ScriptableObject val = (ScriptableObject)((template is ScriptableObject) ? template : null); if ((Object)(object)val == (Object)null) { return null; } return Object.Instantiate<ScriptableObject>(val); } public static object GetKeywordByName(object terminalInstance, string keywordName) { if (terminalInstance == null) { return null; } object obj = Util.TryGetMemberValue(terminalInstance, "terminalNodes"); if (obj == null) { return null; } if (!(Util.TryGetMemberValue(obj, "allKeywords") is Array array)) { return null; } for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); if (value != null) { string a = Util.TryGetStringMember(value, "name"); if (string.Equals(a, keywordName, StringComparison.InvariantCultureIgnoreCase)) { return value; } } } return null; } public static object GetKeywordByWord(object terminalInstance, string word) { if (terminalInstance == null) { return null; } object obj = Util.TryGetMemberValue(terminalInstance, "terminalNodes"); if (obj == null) { return null; } if (!(Util.TryGetMemberValue(obj, "allKeywords") is Array array)) { return null; } for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); if (value != null) { string a = Util.TryGetStringMember(value, "word"); if (string.Equals(a, word, StringComparison.InvariantCultureIgnoreCase)) { return value; } } } return null; } public static bool HasRouteRandomAlready(object terminalInstance) { object keywordByWord = GetKeywordByWord(terminalInstance, "random"); if (keywordByWord == null) { return false; } object keywordByName = GetKeywordByName(terminalInstance, "Route"); if (keywordByName == null) { return false; } if (!(Util.TryGetMemberValue(keywordByName, "compatibleNouns") is Array array)) { return false; } for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); if (value == null) { continue; } object obj = Util.TryGetMemberValue(value, "noun"); if (obj != null) { if (obj == keywordByWord) { return true; } string a = Util.TryGetStringMember(obj, "word"); if (string.Equals(a, "random", StringComparison.InvariantCultureIgnoreCase)) { return true; } } } return false; } public static void AddKeyword(object terminalInstance, object newKeyword) { object obj = Util.TryGetMemberValue(terminalInstance, "terminalNodes"); if (obj != null && Util.TryGetMemberValue(obj, "allKeywords") is Array array) { Array value = Util.AppendToArray(array, newKeyword); Util.TrySetMemberValue(obj, "allKeywords", value); } } public static void AddCompatibleNounToKeyword(object terminalInstance, string keywordName, object newCompatibleNoun) { object keywordByName = GetKeywordByName(terminalInstance, keywordName); if (keywordByName != null) { Array array = Util.TryGetMemberValue(keywordByName, "compatibleNouns") as Array; if (array == null) { array = Array.CreateInstance(ReflectionCache.CompatibleNounType, 0); } Array value = Util.AppendToArray(array, newCompatibleNoun); Util.TrySetMemberValue(keywordByName, "compatibleNouns", value); } } public static void AppendMoonsHelp(object terminalInstance, string extraText) { object keywordByName = GetKeywordByName(terminalInstance, "Moons"); if (keywordByName == null) { return; } object obj = Util.TryGetMemberValue(keywordByName, "specialKeywordResult"); if (obj != null) { string text = Util.TryGetStringMember(obj, "displayText") ?? ""; if (text.IndexOf(extraText, StringComparison.InvariantCultureIgnoreCase) < 0 && (extraText.IndexOf("* Random", StringComparison.InvariantCultureIgnoreCase) < 0 || text.IndexOf("* Random", StringComparison.InvariantCultureIgnoreCase) < 0)) { text += extraText; Util.TrySetMemberValue(obj, "displayText", text); } } } public static object CreateTerminalKeywordFromTemplate(object templateKeyword, string word, string name, object defaultVerbKeyword) { object obj = CloneScriptableObject(templateKeyword) ?? SmartCreateInstance(ReflectionCache.TerminalKeywordType); Util.TrySetMemberValue(obj, "word", word); Util.TrySetMemberValue(obj, "name", name); Util.TrySetMemberValue(obj, "defaultVerb", defaultVerbKeyword); Util.TrySetMemberValue(obj, "compatibleNouns", Array.CreateInstance(ReflectionCache.CompatibleNounType, 0)); Util.TrySetMemberValue(obj, "isVerb", false); Util.TrySetMemberValue(obj, "IsVerb", false); return obj; } public static object CreateCompatibleNounFromTemplate(object templateCompatibleNoun, object nounKeyword, object resultNode) { object obj = CloneScriptableObject(templateCompatibleNoun) ?? SmartCreateInstance(ReflectionCache.CompatibleNounType); Util.TrySetMemberValue(obj, "noun", nounKeyword); Util.TrySetMemberValue(obj, "result", resultNode); return obj; } public static object CreateEmptyResultNode(string name) { object obj = SmartCreateInstance(ReflectionCache.TerminalNodeType); Util.TrySetMemberValue(obj, "name", name); Util.TrySetMemberValue(obj, "buyRerouteToMoon", -1); Util.TrySetMemberValue(obj, "terminalOptions", Array.CreateInstance(ReflectionCache.CompatibleNounType, 0)); return obj; } public static object CreateSimpleNode(string name, string displayText, bool clearPreviousText) { object obj = SmartCreateInstance(ReflectionCache.TerminalNodeType); Util.TrySetMemberValue(obj, "name", name); Util.TrySetMemberValue(obj, "displayText", displayText); Util.TrySetMemberValue(obj, "clearPreviousText", clearPreviousText); return obj; } public static string TryGetNodeName(object terminalNode) { return Util.TryGetStringMember(terminalNode, "name"); } public static void TryCacheConfirmDenySamples(object terminalInstance, object routeKeyword, out object confirmKeyword, out object denyCompatibleNoun) { confirmKeyword = GetKeywordByName(terminalInstance, "Confirm"); denyCompatibleNoun = null; if (!(Util.TryGetMemberValue(routeKeyword, "compatibleNouns") is Array array)) { return; } for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); if (value == null) { continue; } object obj = Util.TryGetMemberValue(value, "result"); if (obj == null) { continue; } int num = Util.TryGetIntMember(obj, "buyRerouteToMoon", int.MinValue); if (num != -2 || !(Util.TryGetMemberValue(obj, "terminalOptions") is Array array2)) { continue; } for (int j = 0; j < array2.Length; j++) { object value2 = array2.GetValue(j); if (value2 == null) { continue; } object obj2 = Util.TryGetMemberValue(value2, "noun"); if (obj2 != null) { string a = Util.TryGetStringMember(obj2, "name"); if (string.Equals(a, "Deny", StringComparison.InvariantCultureIgnoreCase)) { denyCompatibleNoun = value2; return; } } } } } public static object CreateRouteNode(object terminalInstance, MoonCandidate moon, bool free, bool skipConfirmation, object sampleConfirmKeyword, object sampleDenyCompatibleNoun) { object obj = SmartCreateInstance(ReflectionCache.TerminalNodeType); Util.TrySetMemberValue(obj, "name", "erm_confirmRoute_" + Util.SanitizeId(moon.NormalizedName)); Util.TrySetMemberValue(obj, "buyRerouteToMoon", moon.LevelId); Util.TrySetMemberValue(obj, "clearPreviousText", true); int num = ((!free) ? ((moon.Cost >= 0) ? moon.Cost : 0) : 0); string text = moon.PlanetName ?? moon.NormalizedName ?? "UNKNOWN"; string text2 = ((num == 0) ? "FREE" : (num + " credits")); ERMLog.Debug("[ERM] CreateRouteNode: planet='" + text + "' levelId=" + moon.LevelId + " cost=" + ((moon.Cost == 0) ? "FREE" : moon.Cost.ToString()) + " freeFlag=" + free + " itemCost=" + ((num == 0) ? "FREE" : num.ToString()) + " skipConfirm=" + skipConfirmation); Util.TrySetMemberValue(obj, "itemCost", num); string value = "\nRouting autopilot to " + text + " (" + text2 + ").\nYour new balance is [playerCredits].\n\nPlease enjoy your flight.\n\n\n"; Util.TrySetMemberValue(obj, "displayText", value); if (skipConfirmation) { return obj; } object obj2 = SmartCreateInstance(ReflectionCache.TerminalNodeType); Util.TrySetMemberValue(obj2, "name", "erm_routeNode_" + Util.SanitizeId(moon.NormalizedName)); Util.TrySetMemberValue(obj2, "buyRerouteToMoon", -2); Util.TrySetMemberValue(obj2, "clearPreviousText", true); Util.TrySetMemberValue(obj2, "overrideOptions", true); Util.TrySetMemberValue(obj2, "displayPlanetInfo", true); string value2 = "\nRoute autopilot to " + text + " (" + text2 + ")?\nType 'c' to confirm or 'd' to cancel.\n\n"; Util.TrySetMemberValue(obj2, "displayText", value2); object value3 = CreateCompatibleNounFromTemplate(sampleDenyCompatibleNoun, sampleConfirmKeyword, obj); object obj3 = sampleDenyCompatibleNoun; if (obj3 == null) { object resultNode = CreateSimpleNode("erm_deny", "\nCancelled.\n\n\n", clearPreviousText: true); object keywordByName = GetKeywordByName(terminalInstance, "Deny"); obj3 = CreateCompatibleNounFromTemplate(null, keywordByName, resultNode); } Array array = Array.CreateInstance(ReflectionCache.CompatibleNounType, 2); array.SetValue(obj3, 0); array.SetValue(value3, 1); Util.TrySetMemberValue(obj2, "terminalOptions", array); return obj2; } } internal static class Util { private static readonly Regex LeadingDigits = new Regex("^[0-9]+", RegexOptions.Compiled); public static void ParseCsvToNormalizedSet(string csv, HashSet<string> set) { set.Clear(); if (string.IsNullOrWhiteSpace(csv)) { return; } string[] array = csv.Split(','); for (int i = 0; i < array.Length; i++) { string text = ((array[i] == null) ? null : array[i].Trim()); if (!string.IsNullOrWhiteSpace(text)) { set.Add(NormalizeMoonName(text)); } } } public static string NormalizeMoonName(string name) { if (string.IsNullOrWhiteSpace(name)) { return string.Empty; } string text = LeadingDigits.Replace(name.Trim(), string.Empty); text = text.Trim(); if (text.StartsWith("-", StringComparison.InvariantCulture)) { text = text.Substring(1).Trim(); } return text; } public static string NormalizeWeatherToken(string token) { if (string.IsNullOrWhiteSpace(token)) { return null; } string text = token.Trim().ToLowerInvariant(); text = text.Replace(" ", ""); text = text.Replace("_", ""); text = text.Replace("-", ""); if (text == "dustcloud" || text == "dustclouds") { return "dustclouds"; } if (text == "none") { return "mild"; } return text; } public static void ExpandWeatherAliases(HashSet<string> set) { List<string> list = new List<string>(set); set.Clear(); for (int i = 0; i < list.Count; i++) { string text = NormalizeWeatherToken(list[i]); if (!string.IsNullOrEmpty(text)) { set.Add(text); } } if (set.Contains("none")) { set.Remove("none"); set.Add("mild"); } } public static object[] TryGetLevelsArray(object startOfRoundInstance) { object obj = TryGetMemberValue(startOfRoundInstance, "levels"); if (!(obj is Array array)) { return null; } object[] array2 = new object[array.Length]; for (int i = 0; i < array.Length; i++) { array2[i] = array.GetValue(i); } return array2; } public static object TryGetCurrentLevel(object startOfRoundInstance) { return TryGetMemberValue(startOfRoundInstance, "currentLevel"); } public static string TryGetPlanetName(object selectableLevel) { object obj = TryGetMemberValue(selectableLevel, "PlanetName") ?? TryGetMemberValue(selectableLevel, "planetName") ?? TryGetMemberValue(selectableLevel, "name"); return obj as string; } public static int TryGetLevelId(object selectableLevel, int fallbackIndex) { if ((TryGetMemberValue(selectableLevel, "levelID") ?? TryGetMemberValue(selectableLevel, "LevelID")) is int result) { return result; } return fallbackIndex; } public static bool IsCompanyMoonHeuristic(object selectableLevel) { object obj = TryGetMemberValue(selectableLevel, "planetHasTime"); object obj2 = TryGetMemberValue(selectableLevel, "spawnEnemiesAndScrap"); bool? flag = obj as bool?; bool? flag2 = obj2 as bool?; if (flag.HasValue && flag2.HasValue) { return !flag.Value && !flag2.Value; } return false; } public static object TryGetMemberValue(object instance, string name) { if (instance == null || string.IsNullOrEmpty(name)) { return null; } Type type = instance.GetType(); FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(instance); } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(instance, null); } return null; } public static bool TrySetMemberValue(object instance, string name, object value) { if (instance == null || string.IsNullOrEmpty(name)) { return false; } Type type = instance.GetType(); FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { field.SetValue(instance, value); return true; } catch { return false; } } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0 && property.CanWrite) { try { property.SetValue(instance, value, null); return true; } catch { return false; } } return false; } public static string TryGetStringMember(object instance, string name) { object obj = TryGetMemberValue(instance, name); return obj as string; } public static int TryGetIntMember(object instance, string name, int fallback) { return (TryGetMemberValue(instance, name) is int num) ? num : fallback; } public static Array AppendToArray(Array array, object item) { if (array == null) { Array array2 = Array.CreateInstance(item.GetType(), 1); array2.SetValue(item, 0); return array2; } Type elementType = array.GetType().GetElementType(); Array array3 = Array.CreateInstance(elementType, array.Length + 1); Array.Copy(array, array3, array.Length); array3.SetValue(item, array.Length); return array3; } public static string SanitizeId(string s) { if (string.IsNullOrEmpty(s)) { return "unknown"; } s = s.Trim().ToLowerInvariant(); s = Regex.Replace(s, "[^a-z0-9]+", "_"); return s.Trim('_'); } public static int TryGetGroupCredits(object terminalInstance, int fallback) { if (TryGetMemberValue(terminalInstance, "groupCredits") is int result) { return result; } return fallback; } } internal static class WeatherResolver { public static string GetWeatherKey(object selectableLevel) { if (selectableLevel == null) { return null; } object obj = Util.TryGetMemberValue(selectableLevel, "currentWeather"); if (obj != null) { string text = NormalizeWeather(obj); if (!string.IsNullOrEmpty(text)) { return text; } } object obj2 = Util.TryGetMemberValue(selectableLevel, "currentWeatherString") ?? Util.TryGetMemberValue(selectableLevel, "weatherString") ?? Util.TryGetMemberValue(selectableLevel, "Weather") ?? Util.TryGetMemberValue(selectableLevel, "weather"); if (obj2 is string text2 && !string.IsNullOrWhiteSpace(text2)) { return Util.NormalizeWeatherToken(text2); } return null; } private static string NormalizeWeather(object weatherValue) { string text = weatherValue.ToString(); if (string.IsNullOrWhiteSpace(text)) { return null; } if (string.Equals(text, "None", StringComparison.InvariantCultureIgnoreCase)) { return "mild"; } return Util.NormalizeWeatherToken(text); } }