Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RandomStages v1.3.1
RandomStages.dll
Decompiled 18 hours agousing 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); } } }