Decompiled source of Random Interiors v1.0.1

RandomInteriors.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using DunGen.Graph;
using HarmonyLib;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("RandomInteriors")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RandomInteriors")]
[assembly: AssemblyTitle("RandomInteriors")]
[assembly: AssemblyVersion("1.0.6.0")]
namespace Hibyhii.RandomInteriors;

internal static class PatchHelpers
{
	internal static bool IsServer()
	{
		NetworkManager singleton = NetworkManager.Singleton;
		if (singleton != null)
		{
			return singleton.IsServer || singleton.IsHost;
		}
		return true;
	}
}
[HarmonyPatch(typeof(RoundManager), "Start")]
internal static class RoundManagerStartPatch
{
	[HarmonyPostfix]
	[HarmonyPriority(-800)]
	private static void Postfix()
	{
		RuntimeRegistry.Scan(forceLog: true);
	}
}
[HarmonyPatch(typeof(StartOfRound), "Start")]
internal static class StartOfRoundStartPatch
{
	[HarmonyPostfix]
	[HarmonyPriority(-800)]
	private static void Postfix()
	{
		RuntimeRegistry.Scan(forceLog: true);
	}
}
[HarmonyPatch(typeof(StartOfRound), "ChooseNewRandomMapSeed")]
internal static class ChooseNewRandomMapSeedPatch
{
	[HarmonyPostfix]
	[HarmonyPriority(-800)]
	private static void Postfix(StartOfRound __instance)
	{
		try
		{
			if (!PatchHelpers.IsServer() || __instance == null || __instance.currentLevel == null)
			{
				return;
			}
			RuntimeRegistry.Scan(forceLog: false);
			int randomMapSeed = __instance.randomMapSeed;
			if (SelectionManager.IsEncodedSeed(randomMapSeed))
			{
				return;
			}
			RoundManager instance = RoundManager.Instance;
			if (instance != null && instance.currentLevel != null && SelectionManager.TryComputeAndStoreHostSelection(instance, randomMapSeed, out var encodedSeed, out var globalInteriorId))
			{
				__instance.randomMapSeed = encodedSeed;
				if (RuntimeRegistry.TryGetInterior(globalInteriorId, out var interior))
				{
					Plugin.Log.LogDebug((object)("[Random Interiors] Host prepared interior '" + interior.Name + "' for the current landing and synchronized it through the level seed."));
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[Random Interiors] ChooseNewRandomMapSeed patch failed safely: " + ex));
		}
	}
}
[HarmonyPatch(typeof(RoundManager), "GenerateNewLevelClientRpc")]
internal static class GenerateNewLevelClientRpcPatch
{
	[HarmonyPrefix]
	[HarmonyPriority(-800)]
	private static void Prefix(RoundManager __instance, ref int randomSeed, int levelID)
	{
		try
		{
			RuntimeRegistry.Scan(forceLog: false);
			if (!SelectionManager.IsEncodedSeed(randomSeed))
			{
				if (PatchHelpers.IsServer() && SelectionManager.TryComputeAndStoreHostSelection(__instance, randomSeed, out var encodedSeed, out var _))
				{
					randomSeed = encodedSeed;
					if ((Object)(object)StartOfRound.Instance != (Object)null)
					{
						StartOfRound.Instance.randomMapSeed = encodedSeed;
					}
				}
				return;
			}
			int globalId = SelectionManager.DecodeInteriorId(randomSeed);
			if (!RuntimeRegistry.TryGetInterior(globalId, out var interior))
			{
				Plugin.Log.LogError((object)("[Random Interiors] Client received synchronized interior ID " + globalId + " but it is not present in the local global DungeonFlow pool. This usually means the clients do not have identical interior registrations."));
				return;
			}
			if (StartOfRound.Instance != null)
			{
				StartOfRound.Instance.randomMapSeed = randomSeed;
			}
			if (!PatchHelpers.IsServer())
			{
				Plugin.Log.LogInfo((object)("[Random Interiors] Received host interior selection: '" + interior.Name + "' (global DungeonFlow ID " + globalId + ")."));
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[Random Interiors] GenerateNewLevelClientRpc synchronization patch failed safely: " + ex));
		}
	}
}
[HarmonyPatch(typeof(RoundManager), "GenerateNewFloor")]
internal static class GenerateNewFloorPatch
{
	[HarmonyPrefix]
	[HarmonyPriority(-800)]
	private static void Prefix(RoundManager __instance, out SelectionManager.GenerationState __state)
	{
		__state = null;
		try
		{
			RuntimeRegistry.Scan(forceLog: false);
			bool isServer = PatchHelpers.IsServer();
			int explicitEncodedSeed = ((StartOfRound.Instance != null) ? StartOfRound.Instance.randomMapSeed : int.MinValue);
			SelectionManager.TryPrepareGeneration(__instance, out __state, out var _, isServer, explicitEncodedSeed);
		}
		catch (Exception ex)
		{
			__state = null;
			Plugin.Log.LogError((object)("[Random Interiors] GenerateNewFloor preparation failed safely: " + ex));
		}
	}

	[HarmonyFinalizer]
	private static Exception Finalizer(RoundManager __instance, SelectionManager.GenerationState __state, Exception __exception)
	{
		try
		{
			SelectionManager.RestoreGeneration(__state, __exception == null);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[Random Interiors] Failed to restore the moon's original DungeonFlow list: " + ex));
		}
		if (__exception != null)
		{
			Plugin.Log.LogError((object)("[Random Interiors] The game's GenerateNewFloor threw an exception after Random Interiors preparation: " + __exception));
		}
		return __exception;
	}
}
[HarmonyPatch(typeof(StartOfRound), "EndOfGame")]
internal static class EndOfGamePatch
{
	[HarmonyPrefix]
	[HarmonyPriority(-800)]
	private static void Prefix(StartOfRound __instance)
	{
		try
		{
			if (PatchHelpers.IsServer() && !((Object)(object)TimeOfDay.Instance == (Object)null) && TimeOfDay.Instance.daysUntilDeadline <= 0 && TimeOfDay.Instance.quotaFulfilled < TimeOfDay.Instance.profitQuota)
			{
				if (Plugin.DontRepeatInteriors.Value && Plugin.DontRepeatEvenWhenFired.Value)
				{
					Plugin.Log.LogInfo((object)"[Random Interiors] Crew was fired, but persistent no-repeat history is enabled; keeping the current interior cycle.");
				}
				else
				{
					SelectionManager.ResetHistory("the quota deadline was missed and the crew was fired");
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[Random Interiors] EndOfGame reset check failed safely: " + ex));
		}
	}
}
[HarmonyPatch(typeof(GameNetworkManager), "Disconnect")]
internal static class DisconnectPatch
{
	[HarmonyPostfix]
	[HarmonyPriority(-800)]
	private static void Postfix()
	{
		try
		{
			if (Plugin.DontRepeatInteriors.Value && Plugin.DontRepeatEvenWhenFired.Value)
			{
				Plugin.Log.LogInfo((object)"[Random Interiors] Multiplayer connection disconnected; persistent no-repeat history will be reloaded from the active save when needed.");
			}
			else
			{
				SelectionManager.ResetHistory("the multiplayer connection was disconnected");
			}
			PersistentHistoryStore.ForgetLoadedSave();
			RuntimeRegistry.MarkNotReady();
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[Random Interiors] Disconnect reset failed safely: " + ex));
		}
	}
}
internal static class PersistentHistoryStore
{
	private const string SaveKey = "Hibyhii.RandomInteriors.NoRepeatHistory.v1";

	private static readonly object SyncRoot = new object();

	private static string _loadedSaveFileName;

	private static bool _loadAttempted;

	private static Type _es3Type;

	private static MethodInfo _saveMethod;

	private static MethodInfo _loadMethod;

	private static bool _methodsResolved;

	internal static bool IsAvailable
	{
		get
		{
			ResolveMethods();
			return _saveMethod != null && _loadMethod != null;
		}
	}

	internal static void Load(HashSet<string> destination)
	{
		if (destination == null)
		{
			return;
		}
		lock (SyncRoot)
		{
			string currentSaveFileName = GetCurrentSaveFileName();
			if (string.IsNullOrWhiteSpace(currentSaveFileName))
			{
				_loadedSaveFileName = null;
				_loadAttempted = false;
			}
			else
			{
				if (_loadAttempted && string.Equals(_loadedSaveFileName, currentSaveFileName, StringComparison.Ordinal))
				{
					return;
				}
				_loadedSaveFileName = currentSaveFileName;
				_loadAttempted = true;
				destination.Clear();
				try
				{
					ResolveMethods();
					if (_loadMethod == null)
					{
						Plugin.Log.LogWarning((object)"[Random Interiors] Could not locate Easy Save's load method. Persistent no-repeat history will remain in memory for this session only.");
						return;
					}
					object obj = InvokeLoad(currentSaveFileName);
					string text = obj as string;
					if (string.IsNullOrEmpty(text))
					{
						Plugin.Log.LogInfo((object)("[Random Interiors] No persistent no-repeat history exists for save '" + currentSaveFileName + "'. Starting with a fresh history."));
						return;
					}
					string[] array = text.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
					for (int i = 0; i < array.Length; i++)
					{
						try
						{
							string text2 = Encoding.UTF8.GetString(Convert.FromBase64String(array[i]));
							if (!string.IsNullOrEmpty(text2))
							{
								destination.Add(text2);
							}
						}
						catch
						{
						}
					}
					Plugin.Log.LogInfo((object)("[Random Interiors] Loaded " + destination.Count + " persistent no-repeat interior(s) from save '" + currentSaveFileName + "'."));
					return;
				}
				catch (Exception ex)
				{
					destination.Clear();
					Plugin.Log.LogWarning((object)("[Random Interiors] Failed to load persistent no-repeat history safely: " + ex.Message));
					return;
				}
			}
		}
	}

	internal static void Save(IEnumerable<string> history)
	{
		if (history == null)
		{
			return;
		}
		lock (SyncRoot)
		{
			string currentSaveFileName = GetCurrentSaveFileName();
			if (string.IsNullOrWhiteSpace(currentSaveFileName))
			{
				return;
			}
			try
			{
				ResolveMethods();
				if (_saveMethod == null)
				{
					Plugin.Log.LogWarning((object)"[Random Interiors] Could not locate Easy Save's save method. Persistent no-repeat history could not be written.");
					return;
				}
				List<string> list = (from x in history.Where((string x) => !string.IsNullOrEmpty(x)).Distinct<string>(StringComparer.OrdinalIgnoreCase)
					select Convert.ToBase64String(Encoding.UTF8.GetBytes(x))).ToList();
				string serialized = string.Join("\n", list.ToArray());
				InvokeSave(currentSaveFileName, serialized);
				_loadedSaveFileName = currentSaveFileName;
				_loadAttempted = true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Random Interiors] Failed to save persistent no-repeat history safely: " + ex.Message));
			}
		}
	}

	internal static void ForgetLoadedSave()
	{
		lock (SyncRoot)
		{
			_loadedSaveFileName = null;
			_loadAttempted = false;
		}
	}

	private static string GetCurrentSaveFileName()
	{
		try
		{
			if ((Object)(object)GameNetworkManager.Instance == (Object)null)
			{
				return null;
			}
			return GameNetworkManager.Instance.currentSaveFileName;
		}
		catch
		{
			return null;
		}
	}

	private static void ResolveMethods()
	{
		if (_methodsResolved)
		{
			return;
		}
		lock (SyncRoot)
		{
			if (_methodsResolved)
			{
				return;
			}
			try
			{
				_es3Type = AppDomain.CurrentDomain.GetAssemblies().Select(delegate(Assembly a)
				{
					try
					{
						return a.GetType("ES3");
					}
					catch
					{
						return (Type)null;
					}
				}).FirstOrDefault((Type t) => t != null);
				if (_es3Type == null)
				{
					_methodsResolved = true;
					return;
				}
				MethodInfo[] methods = _es3Type.GetMethods(BindingFlags.Static | BindingFlags.Public);
				foreach (MethodInfo methodInfo in methods)
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (_saveMethod == null && string.Equals(methodInfo.Name, "Save", StringComparison.Ordinal) && methodInfo.IsGenericMethodDefinition && parameters.Length == 3 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType.IsGenericParameter && parameters[2].ParameterType == typeof(string))
					{
						_saveMethod = methodInfo;
					}
					if (_loadMethod == null && string.Equals(methodInfo.Name, "Load", StringComparison.Ordinal) && methodInfo.IsGenericMethodDefinition && parameters.Length == 3 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType.IsGenericParameter && parameters[2].ParameterType == typeof(string))
					{
						_loadMethod = methodInfo;
					}
				}
			}
			catch
			{
				_saveMethod = null;
				_loadMethod = null;
			}
			_methodsResolved = true;
		}
	}

	private static object InvokeLoad(string saveFileName)
	{
		MethodInfo methodInfo = _loadMethod.MakeGenericMethod(typeof(string));
		return methodInfo.Invoke(null, new object[3]
		{
			"Hibyhii.RandomInteriors.NoRepeatHistory.v1",
			string.Empty,
			saveFileName
		});
	}

	private static void InvokeSave(string saveFileName, string serialized)
	{
		MethodInfo methodInfo = _saveMethod.MakeGenericMethod(typeof(string));
		methodInfo.Invoke(null, new object[3] { "Hibyhii.RandomInteriors.NoRepeatHistory.v1", serialized, saveFileName });
	}
}
[BepInPlugin("Hibyhii.RandomInteriors", "Random Interiors", "1.0.6")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "Hibyhii.RandomInteriors";

	public const string PluginName = "Random Interiors";

	public const string PluginVersion = "1.0.6";

	private Harmony _harmony;

	internal static Plugin Instance { get; private set; }

	internal static ManualLogSource Log { get; private set; }

	internal static ConfigEntry<bool> DontRepeatInteriors { get; private set; }

	internal static ConfigEntry<bool> DontRepeatEvenWhenFired { get; private set; }

	private void Awake()
	{
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Expected O, but got Unknown
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Expected O, but got Unknown
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		DontRepeatInteriors = ((BaseUnityPlugin)this).Config.Bind<bool>("Global", "Do Not Repeat Interiors", true, new ConfigDescription("When enabled, a randomly selected interior is not eligible to be selected again until every other currently included interior has been seen once. By default, the no-repeat history resets when the crew is fired or the lobby is disconnected.", (AcceptableValueBase)null, new object[1]
		{
			new ConfigurationManagerAttributes
			{
				Order = 1000
			}
		}));
		DontRepeatEvenWhenFired = ((BaseUnityPlugin)this).Config.Bind<bool>("Global", "Do not repeat even when fired", false, new ConfigDescription("Only applies when Do Not Repeat Interiors is enabled. When true, the no-repeat history is saved with the current save and is preserved when the crew is fired, the game is restarted, or the lobby is disconnected. A newly created save starts with a fresh history. The history also resets naturally after every currently included interior has been selected once.", (AcceptableValueBase)null, new object[1]
		{
			new ConfigurationManagerAttributes
			{
				Order = 999
			}
		}));
		_harmony = new Harmony("Hibyhii.RandomInteriors");
		_harmony.PatchAll();
		Log.LogInfo((object)"[Random Interiors] Hibyhii's Random Interiors 1.0.6 loaded.");
		Log.LogInfo((object)"[Random Interiors] No LethalLevelLoader, LethalLib, or other mod dependency is required.");
		((MonoBehaviour)this).StartCoroutine(InitialScanRoutine());
	}

	private IEnumerator InitialScanRoutine()
	{
		yield return null;
		RuntimeRegistry.Scan(forceLog: false);
		for (int i = 0; i < 6; i++)
		{
			yield return null;
			RuntimeRegistry.Scan(forceLog: false);
		}
		RuntimeRegistry.Scan(forceLog: true);
	}
}
internal sealed class ConfigurationManagerAttributes
{
	public int? Order;
}
internal static class RuntimeRegistry
{
	internal sealed class InteriorInfo
	{
		internal int GlobalId;

		internal DungeonFlow Flow;

		internal string Name;

		internal string ConfigKey;

		internal ConfigEntry<string> Config;
	}

	internal sealed class MoonInfo
	{
		internal SelectableLevel Level;

		internal string Name;

		internal string ConfigKey;

		internal ConfigEntry<string> Config;

		internal HashSet<int> NativeInteriorIds = new HashSet<int>();
	}

	internal const string IncludeInterior = "Include Interior In Randomization";

	internal const string ExcludeInterior = "Exclude Interior From Randomization";

	internal const string RandomizeMoon = "Randomize";

	internal const string DontRandomizeMoon = "Don't Randomize on This Moon";

	internal const string NativeOnlyMoon = "Only Randomize Native Interiors";

	private static readonly char[] InvalidConfigCharacters = new char[6] { '=', '\\', '"', '\'', '[', ']' };

	private static readonly object SyncRoot = new object();

	private static readonly Dictionary<int, InteriorInfo> InteriorsById = new Dictionary<int, InteriorInfo>();

	private static readonly Dictionary<string, MoonInfo> MoonsByKey = new Dictionary<string, MoonInfo>(StringComparer.OrdinalIgnoreCase);

	internal static List<InteriorInfo> Interiors { get; private set; } = new List<InteriorInfo>();

	internal static List<MoonInfo> Moons { get; private set; } = new List<MoonInfo>();

	internal static bool IsReady { get; private set; }

	internal static int ScanGeneration { get; private set; }

	internal static void MarkNotReady()
	{
		IsReady = false;
	}

	internal static void Scan(bool forceLog)
	{
		lock (SyncRoot)
		{
			try
			{
				RoundManager instance = RoundManager.Instance;
				StartOfRound instance2 = StartOfRound.Instance;
				if (instance == null || instance2 == null)
				{
					if (forceLog)
					{
						Plugin.Log.LogWarning((object)"[Random Interiors] Scan skipped because RoundManager or StartOfRound is not ready yet.");
					}
					return;
				}
				IndoorMapType[] dungeonFlowTypes = instance.dungeonFlowTypes;
				SelectableLevel[] levels = instance2.levels;
				if (dungeonFlowTypes == null || dungeonFlowTypes.Length == 0)
				{
					if (forceLog)
					{
						Plugin.Log.LogWarning((object)"[Random Interiors] No global DungeonFlow entries are available yet. The mod will keep trying during startup.");
					}
					return;
				}
				List<InteriorInfo> list = new List<InteriorInfo>();
				Dictionary<int, InteriorInfo> dictionary = new Dictionary<int, InteriorInfo>();
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				for (int i = 0; i < dungeonFlowTypes.Length; i++)
				{
					IndoorMapType val = dungeonFlowTypes[i];
					if (val == null)
					{
						continue;
					}
					DungeonFlow dungeonFlow = val.dungeonFlow;
					if (dungeonFlow != null)
					{
						string text = (string.IsNullOrWhiteSpace(((Object)dungeonFlow).name) ? ("Unnamed DungeonFlow " + i) : ((Object)dungeonFlow).name);
						string proposedKey = MakeInteriorConfigKey(text, i);
						proposedKey = MakeUniqueConfigKey(proposedKey, hashSet);
						if (!TryBindInteriorConfig(proposedKey, out var config))
						{
							Plugin.Log.LogError((object)("[Random Interiors] Could not create the config entry for DungeonFlow ID " + i + " (" + text + "). That interior will be skipped for randomization, but discovery will continue."));
						}
						else
						{
							InteriorInfo interiorInfo = new InteriorInfo
							{
								GlobalId = i,
								Flow = dungeonFlow,
								Name = text,
								ConfigKey = proposedKey,
								Config = config
							};
							hashSet.Add(proposedKey);
							list.Add(interiorInfo);
							dictionary[i] = interiorInfo;
						}
					}
				}
				List<MoonInfo> list2 = new List<MoonInfo>();
				Dictionary<string, MoonInfo> dictionary2 = new Dictionary<string, MoonInfo>(StringComparer.OrdinalIgnoreCase);
				HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				if (levels != null)
				{
					for (int j = 0; j < levels.Length; j++)
					{
						SelectableLevel val2 = levels[j];
						if (val2 == null)
						{
							continue;
						}
						string text2 = (string.IsNullOrWhiteSpace(val2.PlanetName) ? ("Unnamed Moon " + j) : val2.PlanetName);
						string sceneName = (string.IsNullOrWhiteSpace(val2.sceneName) ? "no scene name" : val2.sceneName);
						string proposedKey2 = MakeMoonConfigKey(text2, sceneName, j);
						proposedKey2 = MakeUniqueConfigKey(proposedKey2, hashSet2);
						if (!TryBindMoonConfig(proposedKey2, out var config2))
						{
							Plugin.Log.LogError((object)("[Random Interiors] Could not create the config entry for moon " + text2 + " (index " + j + "). That moon will be skipped, but discovery will continue."));
							continue;
						}
						MoonInfo moonInfo = new MoonInfo
						{
							Level = val2,
							Name = text2,
							ConfigKey = proposedKey2,
							Config = config2
						};
						hashSet2.Add(proposedKey2);
						if (val2.dungeonFlowTypes != null)
						{
							for (int k = 0; k < val2.dungeonFlowTypes.Length; k++)
							{
								IntWithRarity val3 = val2.dungeonFlowTypes[k];
								if (dictionary.ContainsKey(val3.id))
								{
									moonInfo.NativeInteriorIds.Add(val3.id);
								}
								else if (val3.id >= 0)
								{
									Plugin.Log.LogDebug((object)("[Random Interiors] Moon '" + text2 + "' references global DungeonFlow ID " + val3.id + ", but that global entry is not currently valid."));
								}
							}
						}
						list2.Add(moonInfo);
						dictionary2[proposedKey2] = moonInfo;
					}
				}
				Interiors = list;
				Moons = list2;
				InteriorsById.Clear();
				foreach (KeyValuePair<int, InteriorInfo> item in dictionary)
				{
					InteriorsById[item.Key] = item.Value;
				}
				MoonsByKey.Clear();
				foreach (KeyValuePair<string, MoonInfo> item2 in dictionary2)
				{
					MoonsByKey[item2.Key] = item2.Value;
				}
				IsReady = list.Count > 0;
				ScanGeneration++;
				try
				{
					((BaseUnityPlugin)Plugin.Instance).Config.Save();
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[Random Interiors] Config save failed during scan: " + ex.Message));
				}
				if (forceLog || !IsReady)
				{
					Plugin.Log.LogInfo((object)("[Random Interiors] Registry scan complete: " + list.Count + " usable global interiors and " + list2.Count + " moons discovered. Scan #" + ScanGeneration + "."));
				}
				if (forceLog)
				{
					for (int l = 0; l < list.Count; l++)
					{
						InteriorInfo interiorInfo2 = list[l];
						Plugin.Log.LogInfo((object)("[Random Interiors] Interior ID " + interiorInfo2.GlobalId + ": " + interiorInfo2.Name + " -> " + ((interiorInfo2.Config == null) ? "(no config)" : interiorInfo2.Config.Value) + " | Key: " + interiorInfo2.ConfigKey));
					}
					Plugin.Log.LogInfo((object)("[Random Interiors] Moon configuration entries registered: " + list2.Count + "."));
				}
			}
			catch (Exception ex2)
			{
				IsReady = false;
				Plugin.Log.LogError((object)("[Random Interiors] Registry scan failed safely: " + ex2));
			}
		}
	}

	private static bool TryBindInteriorConfig(string configKey, out ConfigEntry<string> config)
	{
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Expected O, but got Unknown
		config = null;
		try
		{
			config = ((BaseUnityPlugin)Plugin.Instance).Config.Bind<string>("Interiors", configKey, "Include Interior In Randomization", new ConfigDescription("Controls whether this registered DungeonFlow is eligible for Random Interiors. The entry is discovered automatically from RoundManager's live global DungeonFlow pool. Include = eligible for global randomization; Exclude = never selected by this mod.", (AcceptableValueBase)null, new object[1]
			{
				new AcceptableValueList<string>(new string[2] { "Include Interior In Randomization", "Exclude Interior From Randomization" })
			}));
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[Random Interiors] Interior config bind failed for key '" + configKey + "': " + ex.Message));
			return false;
		}
	}

	private static bool TryBindMoonConfig(string configKey, out ConfigEntry<string> config)
	{
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Expected O, but got Unknown
		config = null;
		try
		{
			config = ((BaseUnityPlugin)Plugin.Instance).Config.Bind<string>("Moons", configKey, "Randomize", new ConfigDescription("Controls dungeon randomization for this moon. Randomize = choose from every included interior. Don't Randomize on This Moon = leave the moon's native dungeon generation untouched. Only Randomize Native Interiors = choose only among DungeonFlows already listed by this moon. The host's selection is synchronized to clients through the existing level-generation seed.", (AcceptableValueBase)null, new object[1]
			{
				new AcceptableValueList<string>(new string[3] { "Randomize", "Don't Randomize on This Moon", "Only Randomize Native Interiors" })
			}));
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[Random Interiors] Moon config bind failed for key '" + configKey + "': " + ex.Message));
			return false;
		}
	}

	internal static bool TryGetInterior(int globalId, out InteriorInfo interior)
	{
		return InteriorsById.TryGetValue(globalId, out interior);
	}

	internal static MoonInfo FindMoon(SelectableLevel level)
	{
		if (level == null)
		{
			return null;
		}
		for (int i = 0; i < Moons.Count; i++)
		{
			if (Moons[i].Level == level)
			{
				return Moons[i];
			}
		}
		return null;
	}

	internal static bool IsInteriorIncluded(int globalId)
	{
		if (!InteriorsById.TryGetValue(globalId, out var value) || value.Config == null)
		{
			return false;
		}
		return string.Equals(value.Config.Value, "Include Interior In Randomization", StringComparison.OrdinalIgnoreCase);
	}

	private static string MakeInteriorConfigKey(string name, int index)
	{
		return SanitizeConfigKey(name) + " - Global ID " + index;
	}

	private static string MakeMoonConfigKey(string name, string sceneName, int index)
	{
		return SanitizeConfigKey(name) + " - " + SanitizeConfigKey(sceneName) + " - Moon Index " + index;
	}

	private static string MakeUniqueConfigKey(string proposedKey, HashSet<string> usedKeys)
	{
		string text = SanitizeConfigKey(proposedKey);
		if (text.Length == 0)
		{
			text = "Unnamed";
		}
		string text2 = text;
		int num = 2;
		while (usedKeys.Contains(text2))
		{
			text2 = text + " - " + num;
			num++;
		}
		return text2;
	}

	private static string SanitizeConfigKey(string value)
	{
		if (string.IsNullOrWhiteSpace(value))
		{
			return "Unnamed";
		}
		string text = value.Trim();
		for (int i = 0; i < InvalidConfigCharacters.Length; i++)
		{
			text = text.Replace(InvalidConfigCharacters[i].ToString(), " ");
		}
		text = text.Replace("\r", " ").Replace("\n", " ").Replace("\t", " ");
		StringBuilder stringBuilder = new StringBuilder(text.Length);
		foreach (char c in text)
		{
			if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == ' ' || c == '-' || c == '_' || c == '.')
			{
				stringBuilder.Append(c);
			}
			else
			{
				stringBuilder.Append('_');
			}
		}
		string text2 = stringBuilder.ToString();
		while (text2.Contains("  "))
		{
			text2 = text2.Replace("  ", " ");
		}
		text2 = text2.Trim(' ', '.', '_', '-');
		if (text2.Length == 0)
		{
			return "Unnamed";
		}
		return (text2.Length > 180) ? text2.Substring(0, 180).TrimEnd(' ', '.', '_', '-') : text2;
	}
}
internal static class SelectionManager
{
	internal sealed class PendingHostSelection
	{
		internal int SourceSeed;

		internal int GlobalInteriorId;

		internal int MoonInstanceId;
	}

	internal sealed class GenerationState
	{
		internal SelectableLevel Level;

		internal IntWithRarity[] OriginalChoices;

		internal bool ReplacedChoices;

		internal bool IsHostGeneration;

		internal string SelectedInteriorKey;
	}

	private const int MarkerBit = 31;

	private const int SeedBits = 19;

	private const int IndexBits = 12;

	private const int SeedMask = 524287;

	private const int IndexMask = 4095;

	private const int MaxInteriorId = 4095;

	private static readonly HashSet<string> SeenThisCycle = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private static PendingHostSelection _pendingHostSelection;

	private static int _lastProcessedSeed = int.MinValue;

	internal static void ResetHistory(string reason)
	{
		SeenThisCycle.Clear();
		_pendingHostSelection = null;
		_lastProcessedSeed = int.MinValue;
		if (ShouldPersistHistory())
		{
			PersistentHistoryStore.Save(SeenThisCycle);
		}
		Plugin.Log.LogInfo((object)("[Random Interiors] Interior no-repeat history reset: " + reason));
	}

	internal static bool IsEncodedSeed(int seed)
	{
		return (seed & int.MinValue) != 0;
	}

	internal static int EncodeSeed(int sourceSeed, int globalInteriorId)
	{
		return int.MinValue | ((globalInteriorId & 0xFFF) << 19) | (sourceSeed & 0x7FFFF);
	}

	internal static int DecodeInteriorId(int encodedSeed)
	{
		return (encodedSeed >>> 19) & 0xFFF;
	}

	internal static bool TryGetPendingHostSelection(int sourceSeed, int moonInstanceId, out int globalInteriorId)
	{
		globalInteriorId = -1;
		if (_pendingHostSelection == null)
		{
			return false;
		}
		if (_pendingHostSelection.SourceSeed != sourceSeed || _pendingHostSelection.MoonInstanceId != moonInstanceId)
		{
			return false;
		}
		globalInteriorId = _pendingHostSelection.GlobalInteriorId;
		return true;
	}

	internal static bool TryComputeAndStoreHostSelection(RoundManager roundManager, int sourceSeed, out int encodedSeed, out int globalInteriorId)
	{
		encodedSeed = sourceSeed;
		globalInteriorId = -1;
		if (roundManager == null || roundManager.currentLevel == null)
		{
			return false;
		}
		EnsurePersistenceLoaded();
		RuntimeRegistry.Scan(forceLog: false);
		RuntimeRegistry.MoonInfo moonInfo = RuntimeRegistry.FindMoon(roundManager.currentLevel);
		if (moonInfo == null)
		{
			Plugin.Log.LogWarning((object)"[Random Interiors] Current moon was not present in the scanned moon registry. Native generation will be preserved.");
			return false;
		}
		string text = ((moonInfo.Config == null) ? "Randomize" : moonInfo.Config.Value);
		if (string.Equals(text, "Don't Randomize on This Moon", StringComparison.OrdinalIgnoreCase))
		{
			Plugin.Log.LogInfo((object)("[Random Interiors] Moon '" + moonInfo.Name + "' is configured as 'Don't Randomize on This Moon'; native interior generation will be used."));
			return false;
		}
		List<RuntimeRegistry.InteriorInfo> list = BuildCandidates(text, moonInfo);
		if (list.Count == 0)
		{
			if (string.Equals(text, "Only Randomize Native Interiors", StringComparison.OrdinalIgnoreCase))
			{
				Plugin.Log.LogWarning((object)("[Random Interiors] Moon '" + moonInfo.Name + "' is set to native-only, but none of its native interiors are currently included. Native generation will be preserved for safety."));
			}
			else
			{
				Plugin.Log.LogWarning((object)"[Random Interiors] No interiors are currently included in global randomization. Native generation will be preserved for safety.");
			}
			return false;
		}
		if (list.Count > 4096)
		{
			Plugin.Log.LogError((object)("[Random Interiors] The global interior registry contains more than " + 4096 + " entries. The seed synchronization format cannot safely encode that many entries; native generation will be preserved."));
			return false;
		}
		List<RuntimeRegistry.InteriorInfo> list2 = list;
		if (Plugin.DontRepeatInteriors.Value)
		{
			PruneSeen(list);
			List<RuntimeRegistry.InteriorInfo> list3 = list.Where((RuntimeRegistry.InteriorInfo info) => !SeenThisCycle.Contains(GetInteriorKey(info))).ToList();
			if (list3.Count == 0)
			{
				SeenThisCycle.Clear();
				list3 = list;
				Plugin.Log.LogInfo((object)"[Random Interiors] Every currently included interior has been seen. Starting a new no-repeat cycle.");
			}
			list2 = list3;
		}
		Random random = new Random(sourceSeed ^ StableStringHash(moonInfo.Name));
		RuntimeRegistry.InteriorInfo interiorInfo = list2[random.Next(list2.Count)];
		Plugin.Log.LogInfo((object)("[Random Interiors] Moon '" + moonInfo.Name + "' has " + list.Count + " eligible interiors (" + list2.Count + " currently selectable after no-repeat filtering). Chosen: '" + interiorInfo.Name + "' [ID " + interiorInfo.GlobalId + "]. Source seed: " + sourceSeed + "."));
		globalInteriorId = interiorInfo.GlobalId;
		encodedSeed = EncodeSeed(sourceSeed, globalInteriorId);
		_pendingHostSelection = new PendingHostSelection
		{
			SourceSeed = sourceSeed,
			GlobalInteriorId = globalInteriorId,
			MoonInstanceId = ((Object)roundManager.currentLevel).GetInstanceID()
		};
		return true;
	}

	internal static bool TryPrepareGeneration(RoundManager roundManager, out GenerationState state, out int globalInteriorId, bool isServer, int explicitEncodedSeed = int.MinValue)
	{
		//IL_0293: Unknown result type (might be due to invalid IL or missing references)
		//IL_0299: Expected O, but got Unknown
		state = null;
		globalInteriorId = -1;
		if (roundManager == null || roundManager.currentLevel == null)
		{
			return false;
		}
		EnsurePersistenceLoaded();
		int instanceID = ((Object)roundManager.currentLevel).GetInstanceID();
		int num = ((explicitEncodedSeed == int.MinValue) ? (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.randomMapSeed : 0) : explicitEncodedSeed);
		bool flag = IsEncodedSeed(num);
		int globalInteriorId2 = -1;
		if (flag)
		{
			globalInteriorId2 = DecodeInteriorId(num);
		}
		else if (isServer && TryGetPendingHostSelection(num, instanceID, out globalInteriorId2))
		{
			flag = true;
			int num2 = EncodeSeed(num, globalInteriorId2);
			if ((Object)(object)StartOfRound.Instance != (Object)null)
			{
				StartOfRound.Instance.randomMapSeed = num2;
			}
			num = num2;
		}
		else
		{
			if (!isServer)
			{
				Plugin.Log.LogError((object)"[Random Interiors] Client reached dungeon generation without a synchronized host interior selection. Native generation is being preserved to avoid guessing and making the desync worse.");
				return false;
			}
			if (!TryComputeAndStoreHostSelection(roundManager, num, out var encodedSeed, out globalInteriorId2))
			{
				return false;
			}
			flag = true;
			num = encodedSeed;
			if ((Object)(object)StartOfRound.Instance != (Object)null)
			{
				StartOfRound.Instance.randomMapSeed = encodedSeed;
			}
		}
		if (!RuntimeRegistry.TryGetInterior(globalInteriorId2, out var interior) || interior.Flow == null)
		{
			Plugin.Log.LogError((object)("[Random Interiors] Synchronized interior ID " + globalInteriorId2 + " could not be resolved in this client's global DungeonFlow pool. Native generation will be preserved."));
			return false;
		}
		RuntimeRegistry.MoonInfo moonInfo = RuntimeRegistry.FindMoon(roundManager.currentLevel);
		if (moonInfo == null)
		{
			Plugin.Log.LogWarning((object)"[Random Interiors] Current moon was not found during floor preparation. Native generation will be preserved.");
			return false;
		}
		if (moonInfo.Config == null || string.Equals(moonInfo.Config.Value, "Don't Randomize on This Moon", StringComparison.OrdinalIgnoreCase))
		{
			Plugin.Log.LogInfo((object)("[Random Interiors] Moon '" + moonInfo.Name + "' is not randomized; its native interior selection will be used."));
			return false;
		}
		IntWithRarity[] dungeonFlowTypes = roundManager.currentLevel.dungeonFlowTypes;
		if (dungeonFlowTypes == null || dungeonFlowTypes.Length == 0)
		{
			Plugin.Log.LogWarning((object)("[Random Interiors] Current moon '" + moonInfo.Name + "' has no DungeonFlow choices for the base generator to consume. Native generation will be preserved."));
			return false;
		}
		state = new GenerationState
		{
			Level = roundManager.currentLevel,
			OriginalChoices = dungeonFlowTypes,
			IsHostGeneration = isServer,
			ReplacedChoices = true,
			SelectedInteriorKey = GetInteriorKey(interior)
		};
		roundManager.currentLevel.dungeonFlowTypes = (IntWithRarity[])(object)new IntWithRarity[1]
		{
			new IntWithRarity(globalInteriorId2, 100, (LevelAmbienceLibrary)null)
		};
		if (_lastProcessedSeed != num || isServer)
		{
			Plugin.Log.LogInfo((object)("[Random Interiors] Landing on '" + moonInfo.Name + "': forcing interior '" + interior.Name + "' (global DungeonFlow ID " + globalInteriorId2 + ")."));
			_lastProcessedSeed = num;
		}
		return true;
	}

	internal static void RestoreGeneration(GenerationState state, bool generationSucceeded)
	{
		if (state == null || !state.ReplacedChoices || (Object)(object)state.Level == (Object)null)
		{
			return;
		}
		state.Level.dungeonFlowTypes = state.OriginalChoices;
		if (generationSucceeded && state.IsHostGeneration && Plugin.DontRepeatInteriors.Value && !string.IsNullOrEmpty(state.SelectedInteriorKey))
		{
			SeenThisCycle.Add(state.SelectedInteriorKey);
			if (Plugin.DontRepeatEvenWhenFired.Value)
			{
				PersistentHistoryStore.Save(SeenThisCycle);
			}
		}
	}

	private static void EnsurePersistenceLoaded()
	{
		if (Plugin.DontRepeatInteriors.Value && Plugin.DontRepeatEvenWhenFired.Value)
		{
			PersistentHistoryStore.Load(SeenThisCycle);
		}
	}

	private static bool ShouldPersistHistory()
	{
		return Plugin.DontRepeatInteriors.Value && Plugin.DontRepeatEvenWhenFired.Value;
	}

	private static List<RuntimeRegistry.InteriorInfo> BuildCandidates(string moonMode, RuntimeRegistry.MoonInfo moon)
	{
		List<RuntimeRegistry.InteriorInfo> list = new List<RuntimeRegistry.InteriorInfo>();
		for (int i = 0; i < RuntimeRegistry.Interiors.Count; i++)
		{
			RuntimeRegistry.InteriorInfo interiorInfo = RuntimeRegistry.Interiors[i];
			if (RuntimeRegistry.IsInteriorIncluded(interiorInfo.GlobalId) && (!string.Equals(moonMode, "Only Randomize Native Interiors", StringComparison.OrdinalIgnoreCase) || moon.NativeInteriorIds.Contains(interiorInfo.GlobalId)))
			{
				list.Add(interiorInfo);
			}
		}
		return list;
	}

	private static void PruneSeen(List<RuntimeRegistry.InteriorInfo> activeCandidates)
	{
		HashSet<string> activeKeys = new HashSet<string>(activeCandidates.Select(GetInteriorKey), StringComparer.OrdinalIgnoreCase);
		SeenThisCycle.RemoveWhere((string key) => !activeKeys.Contains(key));
	}

	private static string GetInteriorKey(RuntimeRegistry.InteriorInfo interior)
	{
		return interior.GlobalId + "|" + interior.Name;
	}

	private static int StableStringHash(string text)
	{
		int num = 23;
		if (text == null)
		{
			return num;
		}
		for (int i = 0; i < text.Length; i++)
		{
			num = num * 31 + text[i];
		}
		return num;
	}
}