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 EndingPortalsAfterStages v1.1.1
plugins/EndingPortalsAfterStages/EndingPortalsAfterStages.dll
Decompiled 5 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HG; using Microsoft.CodeAnalysis; using RiskOfOptions; using RiskOfOptions.OptionConfigs; using RiskOfOptions.Options; using RoR2; using RoR2.ExpansionManagement; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("EndingPortalsAfterStages")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("EndingPortalsAfterStages")] [assembly: AssemblyTitle("EndingPortalsAfterStages")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EndingPortalsAfterStages { public enum GreenPortalStage { Stage3, Stage4, Stage5, Stage8, Stage9, Stage10 } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.fafnir62.EndingPortalsAfterStages", "Ending Portals After Stages", "1.1.1")] public sealed class EndingPortalsAfterStagesPlugin : BaseUnityPlugin { private enum PortalKind { Celestial, Colossus, SolusWing } private sealed class PortalRoute { public readonly PortalKind Kind; public readonly string DisplayName; public readonly string SpawnCardAddress; public readonly string OpenMessageToken; public readonly string ExpansionAddress; public readonly ConfigEntry<bool> Enabled; public readonly ConfigEntry<int> Stage; public readonly ConfigEntry<GreenPortalStage> GreenStage; public bool AttemptedThisRun; public bool AssetLoadAttempted; public bool AssetsLoaded; public InteractableSpawnCard SpawnCard; public ExpansionDef RequiredExpansion; public int StageNumber { get { if (GreenStage == null) { return Stage.Value; } return GreenStage.Value switch { GreenPortalStage.Stage3 => 3, GreenPortalStage.Stage4 => 4, GreenPortalStage.Stage5 => 5, GreenPortalStage.Stage8 => 8, GreenPortalStage.Stage9 => 9, GreenPortalStage.Stage10 => 10, _ => 5, }; } } public PortalRoute(PortalKind kind, string displayName, string spawnCardAddress, string openMessageToken, string expansionAddress, ConfigEntry<bool> enabled, ConfigEntry<int> stage, ConfigEntry<GreenPortalStage> greenStage) { Kind = kind; DisplayName = displayName; SpawnCardAddress = spawnCardAddress; OpenMessageToken = openMessageToken; ExpansionAddress = expansionAddress; Enabled = enabled; Stage = stage; GreenStage = greenStage; } } public const string PluginGuid = "com.fafnir62.EndingPortalsAfterStages"; public const string PluginName = "Ending Portals After Stages"; public const string PluginVersion = "1.1.1"; private const string RiskOfOptionsPluginGuid = "com.rune580.riskofoptions"; private const string CelestialPortalAddress = "RoR2/Base/PortalMS/iscMSPortal.asset"; private const string ColossusPortalAddress = "RoR2/DLC2/iscColossusPortal.asset"; private const string SolusWingPortalAddress = "RoR2/DLC3/iscHardwareProgPortal_Haunt.asset"; private const string Dlc2ExpansionAddress = "RoR2/DLC2/Common/DLC2.asset"; private const string Dlc3ExpansionAddress = "RoR2/DLC3/DLC3.asset"; private static readonly BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly PropertyInfo PortalSpawnCardProperty = typeof(PortalSpawner).GetProperty("PortalSpawnCard", MemberFlags); private static readonly FieldInfo PortalSpawnCardField = typeof(PortalSpawner).GetField("portalSpawnCard", MemberFlags); private static readonly PropertyInfo PortalWillSpawnProperty = typeof(PortalSpawner).GetProperty("WillSpawn", MemberFlags); private static readonly PropertyInfo HiddenRealmPortalProperty = typeof(TeleporterInteraction).GetProperty("shouldAttemptToSpawnHiddenRealmsPortal", MemberFlags); private readonly List<PortalRoute> portalRoutes = new List<PortalRoute>(); private readonly Dictionary<int, int> digitalPortalExitStages = new Dictionary<int, int>(); private ManualLogSource log; private bool digitalJourneyActive; private SceneDef digitalContinuationScene; private void Awake() { log = ((BaseUnityPlugin)this).Logger; BindConfig(); RegisterRiskOfOptions(); Run.onRunStartGlobal += OnRunStart; Run.onRunDestroyGlobal += OnRunDestroy; TeleporterInteraction.onTeleporterChargedGlobal += OnTeleporterCharged; SceneExitController.onBeginExit += OnSceneExitBegin; Stage.onServerStageBegin += OnServerStageBegin; log.LogInfo((object)"Ending Portals After Stages 1.1.1 loaded."); } private void FixedUpdate() { if (NetworkServer.active && Object.op_Implicit((Object)(object)Run.instance) && Object.op_Implicit((Object)(object)TeleporterInteraction.instance)) { PreparePortalPreviews(TeleporterInteraction.instance, Run.instance); } } private void OnDestroy() { Run.onRunStartGlobal -= OnRunStart; Run.onRunDestroyGlobal -= OnRunDestroy; TeleporterInteraction.onTeleporterChargedGlobal -= OnTeleporterCharged; SceneExitController.onBeginExit -= OnSceneExitBegin; Stage.onServerStageBegin -= OnServerStageBegin; } private void BindConfig() { portalRoutes.Add(CreateRoute(PortalKind.Celestial, "1 - Twisted Scavenger (Celestial Portal)", "Celestial Portal", "RoR2/Base/PortalMS/iscMSPortal.asset", "PORTAL_MS_OPEN", null, enabledByDefault: true, 5)); portalRoutes.Add(CreateRoute(PortalKind.Colossus, "2 - False Son (Green Portal)", "Green Portal", "RoR2/DLC2/iscColossusPortal.asset", "PORTAL_STORM_OPEN", "RoR2/DLC2/Common/DLC2.asset", enabledByDefault: true, 5)); portalRoutes.Add(CreateRoute(PortalKind.SolusWing, "3 - Solus Wing (Digital Portal)", "Digital Portal", "RoR2/DLC3/iscHardwareProgPortal_Haunt.asset", "PORTAL_SOLUSSHOP_OPEN", "RoR2/DLC3/DLC3.asset", enabledByDefault: false, 5)); RemoveOldSetting("1 - Twisted Scavenger (Celestial Portal)"); RemoveOldSetting("2 - False Son (Green Portal)"); RemoveOldSetting("3 - Solus Wing (Digital Portal)"); ((BaseUnityPlugin)this).Config.Save(); } private void RemoveOldSetting(string section) { ConfigEntry<string> val = ((BaseUnityPlugin)this).Config.Bind<string>(section, "Spawn Relation", string.Empty, string.Empty); ((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val).Definition); } private PortalRoute CreateRoute(PortalKind kind, string section, string displayName, string spawnCardAddress, string openMessageToken, string expansionAddress, bool enabledByDefault, int defaultStage) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown ConfigEntry<bool> enabled = ((BaseUnityPlugin)this).Config.Bind<bool>(section, "Enabled", enabledByDefault, "Adds one scheduled " + displayName + " per run. Vanilla portal appearances are never disabled or moved."); ConfigEntry<int> stage = null; ConfigEntry<GreenPortalStage> val = null; if (kind == PortalKind.Colossus) { val = ((BaseUnityPlugin)this).Config.Bind<GreenPortalStage>(section, "Stage Number", GreenPortalStage.Stage5, "The portal opens after this numbered stage. Stages 1, 2, 6, and 7 are excluded because the vanilla route sends them through the Path of the Colossus instead of directly to False Son."); if (!Enum.IsDefined(typeof(GreenPortalStage), val.Value)) { val.Value = GreenPortalStage.Stage5; } } else { stage = ((BaseUnityPlugin)this).Config.Bind<int>(section, "Stage Number", defaultStage, new ConfigDescription("The portal opens after this numbered stage's teleporter finishes charging. Hidden realms do not count.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); } return new PortalRoute(kind, displayName, spawnCardAddress, openMessageToken, expansionAddress, enabled, stage, val); } private void RegisterRiskOfOptions() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown if (!Chainloader.PluginInfos.ContainsKey("com.rune580.riskofoptions")) { log.LogInfo((object)"Risk of Options is not installed; settings remain available in the BepInEx config file."); return; } try { ModSettingsManager.SetModDescription("Schedule one extra Celestial, Green, and Digital Portal on an exact numbered stage. The game's normal portal appearances remain unchanged."); foreach (PortalRoute portalRoute in portalRoutes) { ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(portalRoute.Enabled, new CheckBoxConfig { restartRequired = false })); if (portalRoute.GreenStage != null) { ModSettingsManager.AddOption((BaseOption)new ChoiceOption((ConfigEntryBase)(object)portalRoute.GreenStage, new ChoiceConfig { restartRequired = false })); continue; } ModSettingsManager.AddOption((BaseOption)new IntSliderOption(portalRoute.Stage, new IntSliderConfig { min = 1, max = 10, restartRequired = false })); } } catch (Exception arg) { log.LogWarning((object)$"Could not register the Risk of Options menu: {arg}"); } } private void OnRunStart(Run run) { ResetRunState(); } private void OnRunDestroy(Run run) { ResetRunState(); } private void ResetRunState() { foreach (PortalRoute portalRoute in portalRoutes) { portalRoute.AttemptedThisRun = false; } digitalPortalExitStages.Clear(); digitalJourneyActive = false; digitalContinuationScene = null; } private void PreparePortalPreviews(TeleporterInteraction teleporter, Run run) { int completedStageNumber = run.stageClearCount + 1; foreach (PortalRoute portalRoute in portalRoutes) { if (ShouldAttemptRoute(portalRoute, completedStageNumber) && (portalRoute.Kind != PortalKind.SolusWing || !SolusRouteWasAlreadyUsed(run)) && TryLoadRouteAssets(portalRoute) && (!Object.op_Implicit((Object)(object)portalRoute.RequiredExpansion) || run.IsExpansionEnabled(portalRoute.RequiredExpansion))) { QueueVanillaPortal(portalRoute, teleporter); } } } private static void QueueVanillaPortal(PortalRoute route, TeleporterInteraction teleporter) { if (route.Kind == PortalKind.Celestial) { teleporter.shouldAttemptToSpawnMSPortal = true; return; } PortalSpawner[] portalSpawners = teleporter.portalSpawners; if (portalSpawners != null) { PortalSpawner[] array = portalSpawners; foreach (PortalSpawner val in array) { SpawnCard portalSpawnCard = GetPortalSpawnCard(val); if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)portalSpawnCard) && string.Equals(((Object)portalSpawnCard).name, ((Object)route.SpawnCard).name, StringComparison.OrdinalIgnoreCase)) { if (PortalWillSpawnProperty != null) { PortalWillSpawnProperty.SetValue(val, true, null); } return; } } } if (route.Kind == PortalKind.SolusWing) { HiddenRealmPortalProperty?.SetValue(teleporter, true, null); } } private static SpawnCard GetPortalSpawnCard(PortalSpawner spawner) { if (!Object.op_Implicit((Object)(object)spawner)) { return null; } if (PortalSpawnCardProperty != null) { object? value = PortalSpawnCardProperty.GetValue(spawner, null); return (SpawnCard)((value is SpawnCard) ? value : null); } object? obj = PortalSpawnCardField?.GetValue(spawner); return (SpawnCard)((obj is SpawnCard) ? obj : null); } private void OnTeleporterCharged(TeleporterInteraction teleporter) { //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown if (!NetworkServer.active || !Object.op_Implicit((Object)(object)teleporter) || !Object.op_Implicit((Object)(object)Run.instance)) { return; } Run instance = Run.instance; int num = instance.stageClearCount + 1; foreach (PortalRoute portalRoute in portalRoutes) { if (!ShouldAttemptRoute(portalRoute, num)) { continue; } portalRoute.AttemptedThisRun = true; if (portalRoute.Kind == PortalKind.SolusWing && SolusRouteWasAlreadyUsed(instance)) { log.LogInfo((object)("Skipped the scheduled " + portalRoute.DisplayName + ": the Solus route was already entered or completed this run.")); } else { if (!TryLoadRouteAssets(portalRoute)) { continue; } if (Object.op_Implicit((Object)(object)portalRoute.RequiredExpansion) && !instance.IsExpansionEnabled(portalRoute.RequiredExpansion)) { log.LogInfo((object)("Skipped the scheduled " + portalRoute.DisplayName + ": its expansion is not enabled for this run.")); continue; } List<SceneExitController> list = FindExistingPortalExits(portalRoute); if (list.Count > 0) { log.LogInfo((object)$"A {portalRoute.DisplayName} already exists on stage {num}; no duplicate was created."); if (portalRoute.Kind == PortalKind.SolusWing) { RegisterDigitalPortalExits(list, num); } continue; } GameObject val = SpawnPortal(portalRoute, teleporter, instance); if (!Object.op_Implicit((Object)(object)val)) { log.LogWarning((object)$"Could not find a valid placement for the scheduled {portalRoute.DisplayName} on stage {num}."); continue; } if (!string.IsNullOrEmpty(portalRoute.OpenMessageToken)) { Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage { baseToken = portalRoute.OpenMessageToken }); } if (portalRoute.Kind == PortalKind.SolusWing) { RegisterDigitalPortal(val, num); } log.LogInfo((object)$"Opened the scheduled {portalRoute.DisplayName} after numbered stage {num}."); } } } private static bool ShouldAttemptRoute(PortalRoute route, int completedStageNumber) { if (!route.Enabled.Value || route.AttemptedThisRun) { return false; } return completedStageNumber == route.StageNumber; } private bool TryLoadRouteAssets(PortalRoute route) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0057: Unknown result type (might be due to invalid IL or missing references) if (route.AssetsLoaded) { return Object.op_Implicit((Object)(object)route.SpawnCard); } if (route.AssetLoadAttempted) { return false; } route.AssetLoadAttempted = true; try { route.SpawnCard = Addressables.LoadAssetAsync<InteractableSpawnCard>((object)route.SpawnCardAddress).WaitForCompletion(); if (!string.IsNullOrEmpty(route.ExpansionAddress)) { route.RequiredExpansion = Addressables.LoadAssetAsync<ExpansionDef>((object)route.ExpansionAddress).WaitForCompletion(); } route.AssetsLoaded = Object.op_Implicit((Object)(object)route.SpawnCard) && (string.IsNullOrEmpty(route.ExpansionAddress) || Object.op_Implicit((Object)(object)route.RequiredExpansion)); if (!route.AssetsLoaded) { log.LogError((object)("Failed to load the vanilla assets for " + route.DisplayName + ".")); } } catch (Exception arg) { log.LogError((object)$"Failed to load the vanilla assets for {route.DisplayName}: {arg}"); } return route.AssetsLoaded; } private static bool SolusRouteWasAlreadyUsed(Run run) { if (!run.GetEventFlag("HardwarePortalTaken") && !run.GetEventFlag("SolusWingBeaten")) { return run.GetEventFlag("SolusHeartBeaten"); } return true; } private static GameObject SpawnPortal(PortalRoute route, TeleporterInteraction teleporter, Run run) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)DirectorCore.instance)) { return null; } DirectorPlacementRule val = new DirectorPlacementRule { placementMode = (PlacementMode)1, minDistance = 10f, maxDistance = 40f, position = ((Component)teleporter).transform.position, spawnOnTarget = ((Component)teleporter).transform }; DirectorSpawnRequest val2 = new DirectorSpawnRequest((SpawnCard)(object)route.SpawnCard, val, run.stageRng); return DirectorCore.instance.TrySpawnObject(val2); } private static List<SceneExitController> FindExistingPortalExits(PortalRoute route) { List<SceneExitController> list = new List<SceneExitController>(); if (!Object.op_Implicit((Object)(object)route.SpawnCard) || !Object.op_Implicit((Object)(object)((SpawnCard)route.SpawnCard).prefab)) { return list; } string name = ((Object)((SpawnCard)route.SpawnCard).prefab).name; SceneExitController[] array = Object.FindObjectsOfType<SceneExitController>(); foreach (SceneExitController val in array) { if (Object.op_Implicit((Object)(object)val) && HasNamedAncestor(((Component)val).transform, name)) { list.Add(val); } } return list; } private static bool HasNamedAncestor(Transform transform, string prefabName) { Transform val = transform; while (Object.op_Implicit((Object)(object)val)) { string name = ((Object)((Component)val).gameObject).name; if (name == prefabName || name == prefabName + "(Clone)") { return true; } val = val.parent; } return false; } private void RegisterDigitalPortal(GameObject portal, int completedStageNumber) { SceneExitController[] componentsInChildren = portal.GetComponentsInChildren<SceneExitController>(true); RegisterDigitalPortalExits(componentsInChildren, completedStageNumber); } private void RegisterDigitalPortalExits(IEnumerable<SceneExitController> exits, int completedStageNumber) { foreach (SceneExitController exit in exits) { if (Object.op_Implicit((Object)(object)exit)) { digitalPortalExitStages[((Object)exit).GetInstanceID()] = completedStageNumber; } } } private void OnSceneExitBegin(SceneExitController exitController) { if (NetworkServer.active && Object.op_Implicit((Object)(object)exitController) && Object.op_Implicit((Object)(object)Run.instance) && digitalPortalExitStages.TryGetValue(((Object)exitController).GetInstanceID(), out var value)) { digitalPortalExitStages.Clear(); Run instance = Run.instance; SceneDef val = instance.nextStageScene; if (value % 5 == 0) { val = SelectLoopStartScene(instance, val); } if (!Object.op_Implicit((Object)(object)val)) { log.LogWarning((object)"The Digital Portal was entered, but no normal continuation scene could be determined. Vanilla routing will be used."); return; } digitalContinuationScene = val; digitalJourneyActive = true; instance.nextStageScene = val; log.LogInfo((object)("Digital route will return to " + val.cachedName + " after the Collective offer is rejected.")); } } private static SceneDef SelectLoopStartScene(Run run, SceneDef currentSelection) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (IsLoopStartScene(currentSelection) && run.CanPickStage(currentSelection)) { return currentSelection; } WeightedSelection<SceneDef> val = new WeightedSelection<SceneDef>(16); Enumerator<SceneDef> enumerator = SceneCatalog.allStageSceneDefs.GetEnumerator(); try { while (enumerator.MoveNext()) { SceneDef current = enumerator.Current; if (IsLoopStartScene(current) && run.CanPickStage(current)) { val.AddChoice(current, 1f); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (val.Count == 0) { return null; } return val.Evaluate(run.nextStageRng.nextNormalizedFloat); } private static bool IsLoopStartScene(SceneDef sceneDef) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)sceneDef) || sceneDef.stageOrder != 1 || sceneDef.isFinalStage || !sceneDef.validForRandomSelection) { return false; } if ((int)sceneDef.sceneType != 1) { return (int)sceneDef.sceneType == 5; } return true; } private void OnServerStageBegin(Stage stage) { if (NetworkServer.active && digitalJourneyActive && Object.op_Implicit((Object)(object)digitalContinuationScene) && Object.op_Implicit((Object)(object)Run.instance)) { if (SameScene(Object.op_Implicit((Object)(object)stage) ? stage.sceneDef : null, digitalContinuationScene)) { log.LogInfo((object)("Returned from the Digital route to " + digitalContinuationScene.cachedName + ".")); digitalJourneyActive = false; digitalContinuationScene = null; } else { Run.instance.nextStageScene = digitalContinuationScene; } } } private static bool SameScene(SceneDef first, SceneDef second) { if ((Object)(object)first == (Object)(object)second) { return true; } if (Object.op_Implicit((Object)(object)first) && Object.op_Implicit((Object)(object)second)) { return string.Equals(first.baseSceneName, second.baseSceneName, StringComparison.OrdinalIgnoreCase); } return false; } } }