Decompiled source of RandomStages v1.3.1

RandomStages.dll

Decompiled 18 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HG;
using On.RoR2;
using RoR2;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace RandomStages;

[BepInPlugin("com.randomstages", "RandomStages", "1.3.1")]
public class RandomStages : BaseUnityPlugin
{
	public static ConfigEntry<bool> Enabled;

	public static ConfigEntry<bool> RandomizeFinalBoss;

	public static ConfigEntry<bool> AvoidRepeats;

	public static ConfigEntry<string> ExtraFinalBossScenes;

	internal static ManualLogSource Log;

	private static readonly HashSet<string> SpecialScenes = new HashSet<string>
	{
		"bazaar", "goldshores", "arena", "mysteryspace", "limbo", "moon", "moon2", "voidstage", "voidraid", "artifactworld",
		"artifactworld01", "artifactworld02", "artifactworld03", "meridian", "title", "lobby", "logbook", "splash", "intro", "outro",
		"crystalworld", "eclipseworld", "infinitetowerworld", "loadingbasic"
	};

	private static readonly string[] KnownFinalBossScenes = new string[3] { "moon2", "meridian", "voidraid" };

	private static readonly HashSet<string> Visited = new HashSet<string>();

	private static bool dumpedCatalog;

	private static SceneDef alreadyRandomized;

	public void Awake()
	{
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Expected O, but got Unknown
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Randomize every normal stage (1 to 5) using any vanilla or DLC map.");
		RandomizeFinalBoss = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RandomizeFinalBoss", true, "When you head to a final boss, send you to a random final boss map instead.");
		AvoidRepeats = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AvoidRepeats", true, "Try not to send you to a map you already visited this run.");
		ExtraFinalBossScenes = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ExtraFinalBossScenes", "", "Extra scene names (comma separated) to treat as final boss maps, e.g. for new DLC bosses.");
		StageExtras.Init(((BaseUnityPlugin)this).Config);
		Run.AdvanceStage += new hook_AdvanceStage(Run_AdvanceStage);
		Run.PickNextStageScene += new hook_PickNextStageScene(Run_PickNextStageScene);
	}

	private static void DumpCatalog()
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		if (dumpedCatalog)
		{
			return;
		}
		dumpedCatalog = true;
		foreach (SceneDef item in ((IEnumerable<SceneDef>)(object)SceneCatalog.allSceneDefs).Where((SceneDef d) => Object.op_Implicit((Object)(object)d)))
		{
			Log.LogInfo((object)($"[catalog] {item.cachedName} type={item.sceneType} order={item.stageOrder} " + $"valid={item.validForRandomSelection} final={item.isFinalStage} " + $"lockedBeforeLoop={item.isLockedBeforeLooping} " + "dlc=" + (Object.op_Implicit((Object)(object)item.requiredExpansion) ? ((Object)item.requiredExpansion).name : "none")));
		}
	}

	private static void Run_PickNextStageScene(orig_PickNextStageScene orig, Run self, WeightedSelection<SceneDef> choices)
	{
		orig.Invoke(self, choices);
		try
		{
			DumpCatalog();
			if (self.stageClearCount == 0)
			{
				Visited.Clear();
			}
			SceneDef nextStageScene = self.nextStageScene;
			Log.LogInfo((object)string.Format("PickNextStageScene chose {0} (cleared={1})", Object.op_Implicit((Object)(object)nextStageScene) ? nextStageScene.cachedName : "null", self.stageClearCount));
			if (Enabled.Value && NetworkServer.active && IsNormalStage(nextStageScene))
			{
				SceneDef val = Pick(self, StagePool(self));
				if (Object.op_Implicit((Object)(object)val) && (Object)(object)val != (Object)(object)nextStageScene)
				{
					Log.LogInfo((object)("Randomized: " + nextStageScene.cachedName + " -> " + val.cachedName));
					self.nextStageScene = val;
				}
				alreadyRandomized = self.nextStageScene;
				if (Object.op_Implicit((Object)(object)self.nextStageScene))
				{
					Visited.Add(self.nextStageScene.baseSceneName);
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogError((object)ex);
		}
	}

	private static void Run_AdvanceStage(orig_AdvanceStage orig, Run self, SceneDef nextScene)
	{
		try
		{
			DumpCatalog();
			if (self.stageClearCount == 0)
			{
				Visited.Clear();
			}
			SceneDef mostRecentSceneDef = SceneCatalog.mostRecentSceneDef;
			Log.LogInfo((object)("AdvanceStage from " + (Object.op_Implicit((Object)(object)mostRecentSceneDef) ? mostRecentSceneDef.cachedName : "?") + " to " + (Object.op_Implicit((Object)(object)nextScene) ? nextScene.cachedName : "null") + " (type=" + (Object.op_Implicit((Object)(object)nextScene) ? ((object)Unsafe.As<SceneType, SceneType>(ref nextScene.sceneType)/*cast due to .constrained prefix*/).ToString() : "-") + ", " + $"order={(Object.op_Implicit((Object)(object)nextScene) ? nextScene.stageOrder : (-1))}, cleared={self.stageClearCount})"));
			if (Enabled.Value && NetworkServer.active && Object.op_Implicit((Object)(object)nextScene))
			{
				SceneDef val = null;
				if (IsFinalBoss(self, nextScene))
				{
					if (RandomizeFinalBoss.Value)
					{
						val = Pick(self, FinalBossPool(self));
					}
				}
				else if (IsNormalStage(nextScene) && (Object)(object)nextScene != (Object)(object)alreadyRandomized)
				{
					val = Pick(self, StagePool(self));
				}
				if (Object.op_Implicit((Object)(object)val) && (Object)(object)val != (Object)(object)nextScene)
				{
					Log.LogInfo((object)("Randomized: " + nextScene.cachedName + " -> " + val.cachedName));
					nextScene = val;
				}
			}
			if (Object.op_Implicit((Object)(object)nextScene))
			{
				Visited.Add(nextScene.baseSceneName);
			}
			alreadyRandomized = null;
		}
		catch (Exception ex)
		{
			Log.LogError((object)ex);
		}
		orig.Invoke(self, nextScene);
	}

	internal static bool IsNormalStage(SceneDef def)
	{
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Invalid comparison between Unknown and I4
		if (Object.op_Implicit((Object)(object)def) && (int)def.sceneType == 1 && def.stageOrder >= 1 && def.stageOrder <= 5 && !def.isOfflineScene && !SpecialScenes.Contains(def.baseSceneName))
		{
			return !SpecialScenes.Contains(def.cachedName);
		}
		return false;
	}

	private static HashSet<string> FinalBossNames()
	{
		HashSet<string> hashSet = new HashSet<string>(KnownFinalBossScenes);
		string[] array = ExtraFinalBossScenes.Value.Split(',');
		foreach (string text in array)
		{
			if (!string.IsNullOrWhiteSpace(text))
			{
				hashSet.Add(text.Trim());
			}
		}
		return hashSet;
	}

	private static bool IsFinalBoss(Run run, SceneDef def)
	{
		if (Object.op_Implicit((Object)(object)def))
		{
			if (!def.isFinalStage && !FinalBossNames().Contains(def.cachedName))
			{
				return FinalBossNames().Contains(def.baseSceneName);
			}
			return true;
		}
		return false;
	}

	private static bool Allowed(Run run, SceneDef d)
	{
		if (Object.op_Implicit((Object)(object)d))
		{
			if (Object.op_Implicit((Object)(object)d.requiredExpansion))
			{
				return run.IsExpansionEnabled(d.requiredExpansion);
			}
			return true;
		}
		return false;
	}

	private static List<SceneDef> FinalBossPool(Run run)
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		return (from d in (IEnumerable<SceneDef>)(object)SceneCatalog.allSceneDefs
			where Object.op_Implicit((Object)(object)d) && !d.isOfflineScene && d.cachedName != "moon"
			where IsFinalBoss(run, d)
			where Allowed(run, d)
			select d).Distinct().ToList();
	}

	private static List<SceneDef> StagePool(Run run)
	{
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		bool looped = run.stageClearCount >= 5;
		HashSet<SceneDef> rotation = new HashSet<SceneDef>();
		AddGroup(run.startingSceneGroup);
		foreach (SceneDef item in ((IEnumerable<SceneDef>)(object)SceneCatalog.allSceneDefs).Where((SceneDef d) => Object.op_Implicit((Object)(object)d)))
		{
			AddGroup(item.destinationsGroup);
			AddGroup(item.loopedDestinationsGroup);
			if (item.validForRandomSelection)
			{
				rotation.Add(item);
			}
		}
		HashSet<SceneDef> loopedOnly = new HashSet<SceneDef>(from d in (IEnumerable<SceneDef>)(object)SceneCatalog.allSceneDefs
			where Object.op_Implicit((Object)(object)d) && Object.op_Implicit((Object)(object)d.loopedSceneDef) && (Object)(object)d.loopedSceneDef != (Object)(object)d
			select d.loopedSceneDef);
		return (from d in rotation.Where(IsNormalStage)
			where !loopedOnly.Contains(d)
			where Allowed(run, d)
			where looped || !d.isLockedBeforeLooping
			select (!looped || !Object.op_Implicit((Object)(object)d.loopedSceneDef)) ? d : d.loopedSceneDef).Distinct().ToList();
		void AddGroup(SceneCollection group)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)group))
			{
				return;
			}
			Enumerator<SceneEntry> enumerator2 = group.sceneEntries.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					SceneEntry current2 = enumerator2.Current;
					if (Object.op_Implicit((Object)(object)current2.sceneDef))
					{
						rotation.Add(current2.sceneDef);
					}
				}
			}
			finally
			{
				((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
		}
	}

	private static SceneDef Pick(Run run, List<SceneDef> pool)
	{
		if (AvoidRepeats.Value)
		{
			List<SceneDef> list = pool.Where((SceneDef d) => !Visited.Contains(d.baseSceneName)).ToList();
			if (list.Count > 0)
			{
				pool = list;
			}
		}
		Log.LogInfo((object)string.Format("Pool ({0}): {1}", pool.Count, string.Join(", ", pool.Select((SceneDef d) => d.cachedName))));
		if (pool.Count == 0)
		{
			return null;
		}
		Xoroshiro128Plus val = run.nextStageRng ?? run.runRNG;
		return pool[val.RangeInt(0, pool.Count)];
	}
}
public static class StageExtras
{
	public static ConfigEntry<bool> Stage5BossTeleporter;

	public static ConfigEntry<bool> PrimordialOnlyOnStage5;

	public static ConfigEntry<bool> ScaleSpawns;

	public static ConfigEntry<float> ExtraSpawnsPerStage;

	private const string NormalTeleporterKey = "RoR2/Base/Teleporters/iscTeleporter.asset";

	private const string PrimordialTeleporterKey = "RoR2/Base/Teleporters/iscLunarTeleporter.asset";

	private const string CelestialPortalCardKey = "RoR2/Base/PortalMS/iscMSPortal.asset";

	private static InteractableSpawnCard normalTeleporter;

	private static InteractableSpawnCard primordialTeleporter;

	private static InteractableSpawnCard celestialPortalCard;

	private static bool needBackupPortal;

	private static readonly FieldInfo MonsterCreditField = typeof(SceneDirector).GetField("monsterCredit", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	public static void Init(ConfigFile config)
	{
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Expected O, but got Unknown
		Stage5BossTeleporter = config.Bind<bool>("Stage 5", "BossTeleporterOnStage5", true, "On every stage 5, swap the normal teleporter for the Primordial Teleporter so you can always reach the final boss. If a map has an unusual teleporter, a boss portal opens next to it once charged.");
		PrimordialOnlyOnStage5 = config.Bind<bool>("Stage 5", "PrimordialOnlyOnStage5", true, "When Sky Meadow shows up before stage 5, give it a normal teleporter so you can't skip to the boss early.");
		ScaleSpawns = config.Bind<bool>("Difficulty", "ScaleSpawnsToStage", true, "When a map from an earlier stage shows up later in the run (like a stage 1 map on stage 4), give the enemy spawners a bigger budget so it plays like the stage you are really on.");
		ExtraSpawnsPerStage = config.Bind<float>("Difficulty", "ExtraSpawnsPerStage", 0.25f, "How much bigger the enemy budget gets for each stage the map is behind. 0.25 means a stage 1 map on stage 4 gets 1.75x the enemy budget.");
		SceneDirector.PlaceTeleporter += new hook_PlaceTeleporter(SceneDirector_PlaceTeleporter);
		TeleporterInteraction.onTeleporterChargedGlobal += OnTeleporterCharged;
		SceneDirector.onPrePopulateMonstersSceneServer += OnPrePopulateScene;
		Stage.onServerStageBegin += OnServerStageBegin;
	}

	public static int ActualStageNumber(Run run)
	{
		return run.stageClearCount % 5 + 1;
	}

	private static void LoadCards()
	{
		if (!Object.op_Implicit((Object)(object)normalTeleporter))
		{
			normalTeleporter = Assets.Load<InteractableSpawnCard>("RoR2/Base/Teleporters/iscTeleporter.asset");
		}
		if (!Object.op_Implicit((Object)(object)primordialTeleporter))
		{
			primordialTeleporter = Assets.Load<InteractableSpawnCard>("RoR2/Base/Teleporters/iscLunarTeleporter.asset");
		}
		if (!Object.op_Implicit((Object)(object)celestialPortalCard))
		{
			celestialPortalCard = Assets.Load<InteractableSpawnCard>("RoR2/Base/PortalMS/iscMSPortal.asset");
		}
	}

	private static void SceneDirector_PlaceTeleporter(orig_PlaceTeleporter orig, SceneDirector self)
	{
		needBackupPortal = false;
		try
		{
			Run instance = Run.instance;
			SceneDef val = SceneCatalog.GetSceneDefForCurrentScene() ?? SceneCatalog.mostRecentSceneDef;
			if (RandomStages.Enabled.Value && Object.op_Implicit((Object)(object)instance) && NetworkServer.active && RandomStages.IsNormalStage(val))
			{
				LoadCards();
				SpawnCard teleporterSpawnCard = self.teleporterSpawnCard;
				string text = (Object.op_Implicit((Object)(object)teleporterSpawnCard) ? ((Object)teleporterSpawnCard).name : "none");
				bool flag = text == "iscTeleporter";
				bool flag2 = text.StartsWith("iscLunarTeleporter");
				int num = ActualStageNumber(instance);
				if (num == 5 && Stage5BossTeleporter.Value)
				{
					if (flag && Object.op_Implicit((Object)(object)primordialTeleporter))
					{
						self.teleporterSpawnCard = (SpawnCard)(object)primordialTeleporter;
						RandomStages.Log.LogInfo((object)("Stage 5 on " + val.cachedName + ": using the Primordial Teleporter."));
					}
					else if (!flag2)
					{
						needBackupPortal = true;
						RandomStages.Log.LogInfo((object)("Stage 5 on " + val.cachedName + ": unusual teleporter (" + text + "), a boss portal will open when it is charged."));
					}
				}
				else if (num != 5 && flag2 && PrimordialOnlyOnStage5.Value && Object.op_Implicit((Object)(object)normalTeleporter))
				{
					self.teleporterSpawnCard = (SpawnCard)(object)normalTeleporter;
					RandomStages.Log.LogInfo((object)$"Stage {num} on {val.cachedName}: using a normal teleporter.");
				}
			}
		}
		catch (Exception ex)
		{
			RandomStages.Log.LogError((object)ex);
		}
		orig.Invoke(self);
	}

	private static void OnTeleporterCharged(TeleporterInteraction teleporter)
	{
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Expected O, but got Unknown
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Expected O, but got Unknown
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0103: Expected O, but got Unknown
		if (!needBackupPortal || !NetworkServer.active || !Object.op_Implicit((Object)(object)teleporter))
		{
			return;
		}
		needBackupPortal = false;
		try
		{
			LoadCards();
			SceneDef val = SceneCatalog.FindSceneDef("moon2");
			if (!Object.op_Implicit((Object)(object)celestialPortalCard) || !Object.op_Implicit((Object)(object)val))
			{
				return;
			}
			DirectorPlacementRule val2 = new DirectorPlacementRule
			{
				placementMode = (PlacementMode)1,
				minDistance = 10f,
				maxDistance = 40f,
				spawnOnTarget = ((Component)teleporter).transform
			};
			DirectorSpawnRequest val3 = new DirectorSpawnRequest((SpawnCard)(object)celestialPortalCard, val2, Run.instance.stageRng);
			GameObject val4 = (Object.op_Implicit((Object)(object)DirectorCore.instance) ? DirectorCore.instance.TrySpawnObject(val3) : null);
			if (Object.op_Implicit((Object)(object)val4))
			{
				SceneExitController component = val4.GetComponent<SceneExitController>();
				if (Object.op_Implicit((Object)(object)component))
				{
					component.useRunNextStageScene = false;
					component.destinationScene = val;
				}
				GenericDisplayNameProvider component2 = val4.GetComponent<GenericDisplayNameProvider>();
				if (Object.op_Implicit((Object)(object)component2))
				{
					component2.displayToken = "Final Boss Portal";
				}
				Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage
				{
					baseToken = "<color=#8fd3ff>A portal to the final boss has opened near the teleporter.</color>"
				});
				RandomStages.Log.LogInfo((object)"Spawned a backup final boss portal.");
			}
		}
		catch (Exception ex)
		{
			RandomStages.Log.LogError((object)ex);
		}
	}

	private static float SpawnMultiplier()
	{
		Run instance = Run.instance;
		SceneDef val = SceneCatalog.GetSceneDefForCurrentScene() ?? SceneCatalog.mostRecentSceneDef;
		if (!ScaleSpawns.Value || !RandomStages.Enabled.Value || !Object.op_Implicit((Object)(object)instance) || !RandomStages.IsNormalStage(val))
		{
			return 1f;
		}
		int num = ActualStageNumber(instance) - val.stageOrder;
		if (num <= 0)
		{
			return 1f;
		}
		return 1f + (float)num * Mathf.Max(0f, ExtraSpawnsPerStage.Value);
	}

	private static void OnPrePopulateScene(SceneDirector director)
	{
		float num = SpawnMultiplier();
		if (num <= 1f)
		{
			return;
		}
		try
		{
			if (MonsterCreditField == null)
			{
				return;
			}
			object value = Convert.ChangeType(Convert.ToSingle(MonsterCreditField.GetValue(director)) * num, MonsterCreditField.FieldType);
			MonsterCreditField.SetValue(director, value);
		}
		catch (Exception ex)
		{
			RandomStages.Log.LogError((object)ex);
			return;
		}
		RandomStages.Log.LogInfo((object)$"Starting enemy budget x{num:0.00} for an early map on a later stage.");
	}

	private static void OnServerStageBegin(Stage stage)
	{
		float num = SpawnMultiplier();
		if (!(num <= 1f))
		{
			CombatDirector[] array = Object.FindObjectsOfType<CombatDirector>(true);
			foreach (CombatDirector obj in array)
			{
				obj.creditMultiplier *= num;
			}
			RandomStages.Log.LogInfo((object)$"Enemy spawn budget x{num:0.00} for this stage.");
		}
	}
}
internal static class Assets
{
	public static T Load<T>(string key) where T : Object
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			T val = Addressables.LoadAssetAsync<T>((object)key).WaitForCompletion();
			if (!Object.op_Implicit((Object)(object)val))
			{
				RandomStages.Log.LogWarning((object)("Asset not found: " + key));
			}
			return val;
		}
		catch (Exception ex)
		{
			RandomStages.Log.LogWarning((object)("Asset failed to load: " + key + " (" + ex.GetType().Name + ")"));
			return default(T);
		}
	}
}