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 RunItBack v0.1.2
RunItBack.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using RoR2; using RoR2.Networking; using RoR2.UI; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.UI; [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("RunItBack")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.2.0")] [assembly: AssemblyInformationalVersion("0.1.2+1bc5a15a0c8f10b1e0f39eb3e64e0a228fcf7b3f")] [assembly: AssemblyProduct("RunItBack")] [assembly: AssemblyTitle("RunItBack")] [assembly: AssemblyVersion("0.1.2.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 RunItBack { [BepInPlugin("kel-z.RunItBack", "RunItBack", "0.1.0")] public sealed class RunItBackPlugin : BaseUnityPlugin { private sealed class PlayerSnap { public CharacterMaster Master; public int[] Items; public uint Money; public EquipmentState EquipmentA; public EquipmentState EquipmentB; } private sealed class RngSet { public Xoroshiro128Plus StageRng; public Xoroshiro128Plus NextStageRng; public Xoroshiro128Plus BossRewardRng; public Xoroshiro128Plus TreasureRng; public Xoroshiro128Plus SpawnRng; public Xoroshiro128Plus RandomSurvivorOnRespawnRng; } private sealed class Snap { public readonly List<PlayerSnap> Players = new List<PlayerSnap>(); public uint TeamLevel; public float FixedTime; public float RunStopwatch; public RngSet Rng; } private sealed class MinionSnap { public CharacterMaster Master; public string BodyName; public CharacterMaster Owner; } [HarmonyPatch] private static class Hooks { [HarmonyPostfix] [HarmonyPatch(typeof(GameEndReportPanelController), "Awake")] private static void PanelAwake(GameEndReportPanelController __instance) { ((Component)__instance).gameObject.AddComponent<RunItBackButton>(); } [HarmonyPostfix] [HarmonyPatch(typeof(GameEndReportPanelController), "SetDisplayData")] private static void PanelDataChanged(GameEndReportPanelController __instance) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ((Component)__instance).GetComponent<RunItBackButton>()?.OnDisplayData(__instance.displayData); } [HarmonyPostfix] [HarmonyPatch(typeof(NetworkManagerSystem), "ServerChangeScene")] private static void SceneChangeBeginning() { Run instance = Run.instance; if (!Object.op_Implicit((Object)(object)instance)) { return; } if (_pendingRestore != null) { Log.LogInfo((object)"[RunItBack] pre-load: applying pending restore"); if (SameStageLayout.Value && _pendingRestore.Rng != null) { ApplyRng(instance, _pendingRestore.Rng); } ApplyInventory(_pendingRestore); PruneExtraMinions(); ResurrectMissingMinions(); if (Object.op_Implicit((Object)(object)TeamManager.instance)) { uint teamLevel = TeamManager.instance.GetTeamLevel((TeamIndex)1); TeamManager.instance.SetTeamLevel((TeamIndex)1, _pendingRestore.TeamLevel); Log.LogInfo((object)$"[RunItBack] teamLevel {teamLevel} -> {TeamManager.instance.GetTeamLevel((TeamIndex)1)} (snapshot {_pendingRestore.TeamLevel})"); } if (RewindTimer.Value) { instance.SetRunStopwatch(_pendingRestore.RunStopwatch); instance.RecalculateDifficultyCoefficent(); } _pendingRestore = null; } if (RewindTimer.Value) { Log.LogInfo((object)$"[RunItBack] pre-load: difficultyCoefficient {instance.difficultyCoefficient:F2} (costs spawn from this)"); } _stageStart = Capture(); _loadRng = CaptureRng(instance); } [HarmonyPostfix] [HarmonyPatch(typeof(GameOverController), "OnDisable")] private static void GameOverControllerTornDown() { foreach (HUD readOnlyInstance in HUD.readOnlyInstanceList) { if (!Object.op_Implicit((Object)(object)readOnlyInstance)) { continue; } GameEndReportPanelController[] componentsInChildren = ((Component)readOnlyInstance).GetComponentsInChildren<GameEndReportPanelController>(true); foreach (GameEndReportPanelController val in componentsInChildren) { if (Object.op_Implicit((Object)(object)val)) { Object.Destroy((Object)(object)((Component)val).gameObject); } } } } } private sealed class RunItBackButton : MonoBehaviour { internal const string LabelToken = "ICYANEON_RUNITBACK_BUTTON_LABEL"; private GameEndReportPanelController _panel; private Transform _button; private void Awake() { _panel = ((Component)this).GetComponent<GameEndReportPanelController>(); } public void OnDisplayData(DisplayData data) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) bool flag = ShouldShow(data); if (flag && !Object.op_Implicit((Object)(object)_button)) { Create(); } if (Object.op_Implicit((Object)(object)_button)) { ((Component)_button).gameObject.SetActive(flag); } } private bool ShouldShow(DisplayData data) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active) { return false; } if (_stageStart == null) { return false; } Run instance = Run.instance; if (!Object.op_Implicit((Object)(object)instance)) { return false; } GameEndingDef val = ((data.runReport != null) ? data.runReport.gameEnding : null); if (!Object.op_Implicit((Object)(object)val)) { return false; } if (val.isWin) { return false; } bool flag = !NetworkServer.dontListen; if (flag) { bool flag2 = ((instance is EclipseRun || instance is WeeklyRun || instance is InfiniteTowerRun) ? true : false); flag = flag2; } if (flag) { return false; } return true; } private void Create() { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown MPButton val = ((Object.op_Implicit((Object)(object)_panel) && Object.op_Implicit((Object)(object)_panel.continueButton)) ? _panel.continueButton : null); if (Object.op_Implicit((Object)(object)val)) { GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent); ((Object)val2).name = "RunItBackButton"; val2.transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex()); HGButton component = val2.GetComponent<HGButton>(); ((Button)component).onClick = new ButtonClickedEvent(); ((UnityEvent)((Button)component).onClick).AddListener(new UnityAction(OnClick)); LanguageTextMeshController componentInChildren = val2.GetComponentInChildren<LanguageTextMeshController>(); if (Object.op_Implicit((Object)(object)componentInChildren)) { componentInChildren.token = "ICYANEON_RUNITBACK_BUTTON_LABEL"; } Transform val3 = val2.transform.Find("GenericGlyph"); if (Object.op_Implicit((Object)(object)val3)) { ((Component)val3).gameObject.SetActive(false); } _button = val2.transform; } } private void OnClick() { if (_pendingRestore == null && _stageStart != null) { Run instance = Run.instance; GameOverController instance2 = GameOverController.instance; if (Object.op_Implicit((Object)(object)instance) && Object.op_Implicit((Object)(object)instance2)) { Log.LogInfo((object)string.Format("[RunItBack] retry clicked: arming restore (snapshot players={0}, rng={1})", _stageStart.Players.Count, (_loadRng != null) ? "captured" : "none")); _pendingRestore = _stageStart; _pendingRestore.Rng = _loadRng; instance.isRunning = true; Object.Destroy((Object)(object)((Component)instance2).gameObject); NetworkManager.singleton.ServerChangeScene(SceneCatalog.mostRecentSceneDef.cachedName); } } } } public const string GUID = "kel-z.RunItBack"; public const string NAME = "RunItBack"; public const string VERSION = "0.1.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> RewindTimer; internal static ConfigEntry<bool> SameStageLayout; private static Snap _stageStart; private static Snap _pendingRestore; private static RngSet _loadRng; private static readonly List<MinionSnap> _stageMinions = new List<MinionSnap>(); private Harmony _harmony; private static RngSet CaptureRng(Run run) { return new RngSet { StageRng = CloneRng(run.stageRng), NextStageRng = CloneRng(run.nextStageRng), BossRewardRng = CloneRng(run.bossRewardRng), TreasureRng = CloneRng(run.treasureRng), SpawnRng = CloneRng(run.spawnRng), RandomSurvivorOnRespawnRng = CloneRng(run.randomSurvivorOnRespawnRng) }; } private static void ApplyRng(Run run, RngSet rng) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown if (rng.StageRng != null) { run.stageRng = new Xoroshiro128Plus(rng.StageRng); } if (rng.NextStageRng != null) { run.nextStageRng = new Xoroshiro128Plus(rng.NextStageRng); } if (rng.BossRewardRng != null) { run.bossRewardRng = new Xoroshiro128Plus(rng.BossRewardRng); } if (rng.TreasureRng != null) { run.treasureRng = new Xoroshiro128Plus(rng.TreasureRng); } if (rng.SpawnRng != null) { run.spawnRng = new Xoroshiro128Plus(rng.SpawnRng); } if (rng.RandomSurvivorOnRespawnRng != null) { run.randomSurvivorOnRespawnRng = new Xoroshiro128Plus(rng.RandomSurvivorOnRespawnRng); } } private static Xoroshiro128Plus CloneRng(Xoroshiro128Plus rng) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown if (rng == null) { return null; } return new Xoroshiro128Plus(rng); } private void Awake() { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; RewindTimer = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RewindRunTimer", true, "Rewind the run/difficulty timer to the stage-start value when retrying."); SameStageLayout = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SameStageLayout", true, "On retry, restore the RNG state from the stage start so the stage rolls identically (same layout, monsters and elites) instead of continuing the run's RNG stream."); string langDir = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? ".", "lang"); Language.collectLanguageRootFolders += delegate(List<string> folders) { if (Directory.Exists(langDir)) { folders.Add(langDir); } }; _harmony = new Harmony("kel-z.RunItBack"); _harmony.PatchAll(typeof(Hooks)); Log.LogInfo((object)string.Format("{0} {1} loaded (RewindTimer={2} SameStageLayout={3})", "RunItBack", "0.1.0", RewindTimer.Value, SameStageLayout.Value)); } private static Snap Capture() { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) Snap snap = new Snap { FixedTime = Run.instance.fixedTime, RunStopwatch = Run.instance.GetRunStopwatch() }; if (Object.op_Implicit((Object)(object)TeamManager.instance)) { snap.TeamLevel = TeamManager.instance.GetTeamLevel((TeamIndex)1); } foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances) { CharacterMaster val = (Object.op_Implicit((Object)(object)instance) ? instance.master : null); Inventory val2 = (Object.op_Implicit((Object)(object)val) ? val.inventory : null); if (Object.op_Implicit((Object)(object)val2)) { PlayerSnap playerSnap = new PlayerSnap { Master = val, Money = val.money }; playerSnap.Items = new int[ItemCatalog.itemCount]; for (int i = 0; i < playerSnap.Items.Length; i++) { playerSnap.Items[i] = val2.GetItemCountPermanent((ItemIndex)i); } playerSnap.EquipmentA = val2.currentEquipmentState; playerSnap.EquipmentB = val2.nextSetEquipmentState; snap.Players.Add(playerSnap); } } _stageMinions.Clear(); foreach (CharacterMaster readOnlyInstances in CharacterMaster.readOnlyInstancesList) { if (Object.op_Implicit((Object)(object)readOnlyInstances) && Object.op_Implicit((Object)(object)readOnlyInstances.minionOwnership) && Object.op_Implicit((Object)(object)readOnlyInstances.minionOwnership.ownerMaster)) { _stageMinions.Add(new MinionSnap { Master = readOnlyInstances, BodyName = (Object.op_Implicit((Object)(object)readOnlyInstances.bodyPrefab) ? ((Object)readOnlyInstances.bodyPrefab).name : null), Owner = readOnlyInstances.minionOwnership.ownerMaster }); } } int num = 0; foreach (PlayerSnap player in snap.Players) { for (int j = 0; j < player.Items.Length; j++) { num += player.Items[j]; } } Log.LogInfo((object)string.Format("[RunItBack] snapshot: players={0} teamLevel={1} runStopwatch={2:F0} totalItems={3} moneyPerPlayer=[{4}]", snap.Players.Count, snap.TeamLevel, snap.RunStopwatch, num, string.Join(",", snap.Players.ConvertAll((PlayerSnap p) => p.Money)))); return snap; } private static bool IsSnapshotMinion(CharacterMaster master) { foreach (MinionSnap stageMinion in _stageMinions) { if (Object.op_Implicit((Object)(object)stageMinion.Master) && (Object)(object)stageMinion.Master == (Object)(object)master) { return true; } } return false; } private static void PruneExtraMinions() { int num = 0; foreach (CharacterMaster readOnlyInstances in CharacterMaster.readOnlyInstancesList) { if (!Object.op_Implicit((Object)(object)readOnlyInstances) || IsSnapshotMinion(readOnlyInstances)) { continue; } MinionOwnership minionOwnership = readOnlyInstances.minionOwnership; if (Object.op_Implicit((Object)(object)minionOwnership) && Object.op_Implicit((Object)(object)minionOwnership.ownerMaster)) { GameObject bodyObject = readOnlyInstances.GetBodyObject(); if (Object.op_Implicit((Object)(object)bodyObject)) { Object.Destroy((Object)(object)bodyObject); } Object.Destroy((Object)(object)((Component)readOnlyInstances).gameObject); num++; } } if (num > 0) { Log.LogInfo((object)$"[RunItBack] pruned {num} minion(s) acquired during the stage"); } } private static void ResurrectMissingMinions() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Invalid comparison between Unknown and I4 //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) foreach (MinionSnap stageMinion in _stageMinions) { if (Object.op_Implicit((Object)(object)stageMinion.Master) || !Object.op_Implicit((Object)(object)stageMinion.Owner) || string.IsNullOrEmpty(stageMinion.BodyName)) { continue; } BodyIndex val = BodyCatalog.FindBodyIndex(stageMinion.BodyName); if ((int)val == -1) { Log.LogWarning((object)("[RunItBack] could not resurrect minion: unknown body '" + stageMinion.BodyName + "'")); continue; } GameObject masterPrefab = MasterCatalog.GetMasterPrefab(MasterCatalog.FindAiMasterIndexForBody(val)); if (!Object.op_Implicit((Object)(object)masterPrefab)) { Log.LogWarning((object)("[RunItBack] could not resurrect minion: no master prefab for '" + stageMinion.BodyName + "'")); continue; } CharacterMaster val2 = new MasterSummon { masterPrefab = masterPrefab, position = Vector3.zero, rotation = Quaternion.identity, teamIndexOverride = (TeamIndex)1, ignoreTeamMemberLimit = true }.Perform(); if (Object.op_Implicit((Object)(object)val2)) { val2.minionOwnership.SetOwner(stageMinion.Owner); Log.LogInfo((object)("[RunItBack] resurrected minion '" + stageMinion.BodyName + "'")); } } } private static void ApplyInventory(Snap snap) { //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) Run instance = Run.instance; float dt = (Object.op_Implicit((Object)(object)instance) ? (instance.fixedTime - snap.FixedTime) : 0f); for (int i = 0; i < snap.Players.Count; i++) { PlayerSnap playerSnap = snap.Players[i]; Inventory val = (Object.op_Implicit((Object)(object)playerSnap.Master) ? playerSnap.Master.inventory : null); if (!Object.op_Implicit((Object)(object)val)) { continue; } int num = 0; for (int j = 0; j < ItemCatalog.itemCount; j++) { num += val.GetItemCountPermanent((ItemIndex)j); } uint money = playerSnap.Master.money; Log.LogInfo((object)$"[RunItBack] restore player#{i}: money {money}->{playerSnap.Money}"); int num2 = Mathf.Min(ItemCatalog.itemCount, playerSnap.Items.Length); for (int k = 0; k < num2; k++) { int num3 = playerSnap.Items[k]; if (val.GetItemCountPermanent((ItemIndex)k) != num3) { val.ResetItemPermanent((ItemIndex)k); if (num3 > 0) { val.GiveItemPermanent((ItemIndex)k, num3); } } } RestoreEquipment(val, playerSnap.EquipmentA, 0u, dt); RestoreEquipment(val, playerSnap.EquipmentB, 1u, dt); if (Object.op_Implicit((Object)(object)playerSnap.Master)) { playerSnap.Master.money = playerSnap.Money; } } if (Object.op_Implicit((Object)(object)TeamManager.instance)) { uint teamLevel = TeamManager.instance.GetTeamLevel((TeamIndex)1); TeamManager.instance.SetTeamLevel((TeamIndex)1, snap.TeamLevel); Log.LogInfo((object)$"[RunItBack] teamLevel {teamLevel} -> {TeamManager.instance.GetTeamLevel((TeamIndex)1)} (snapshot {snap.TeamLevel})"); } } private static void RestoreEquipment(Inventory inv, EquipmentState want, uint set, float dt) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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_0014: 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) EquipmentState val = default(EquipmentState); ((EquipmentState)(ref val))..ctor(want.equipmentIndex, want.chargeFinishTime + dt, want.charges, false); EquipmentLocation val2 = new EquipmentLocation { slot = 0u, set = set }; inv.SetEquipment(ref val, ref val2); } } }