Decompiled source of ExtendedRandomMoons v1.0.0

ExtendedRandomMoons.dll

Decompiled 3 hours ago
using 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);
	}
}