Decompiled source of RunItBack v0.1.2

RunItBack.dll

Decompiled a day ago
using 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);
		}
	}
}