Decompiled source of ReviveRegen v1.1.0

plugins/ReviveRegen.dll

Decompiled 11 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("ReviveRegen")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+22bed68e05b1ac71689b71d9e4993f56903f94d5")]
[assembly: AssemblyProduct("ReviveRegen")]
[assembly: AssemblyTitle("ReviveRegen")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.0.0")]
[module: UnverifiableCode]
[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 ReviveRegen
{
	[BepInPlugin("girthy_mods.ReviveRegen", "ReviveRegen", "1.1.0")]
	public class Plugin : BasePlugin
	{
		public const string Guid = "girthy_mods.ReviveRegen";

		public const string Name = "ReviveRegen";

		public const string Version = "1.1.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> SecondsPerRevive;

		internal static ConfigEntry<float> ExtraSecondsAfterEachRevive;

		internal static ConfigEntry<int> ExtraAboveStart;

		internal static ConfigEntry<bool> ShowRefillTimer;

		internal static ConfigEntry<bool> DamageSpeedsUpRecharge;

		internal static ConfigEntry<float> SecondsPerPercentHealthLost;

		public override void Load()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Expected O, but got Unknown
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Expected O, but got Unknown
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Expected O, but got Unknown
			Log = ((BasePlugin)this).Log;
			Enabled = ((BasePlugin)this).Config.Bind<bool>("Regeneration", "Enabled", true, "Revives refill one at a time until you're back to the number the run started with. Off = vanilla (only earns one back when you have none).");
			SecondsPerRevive = ((BasePlugin)this).Config.Bind<float>("Regeneration", "SecondsPerRevive", 0f, new ConfigDescription("Seconds to earn each revive back. 0 = use the game's own timer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 900f), Array.Empty<object>()));
			ExtraSecondsAfterEachRevive = ((BasePlugin)this).Config.Bind<float>("Regeneration", "ExtraSecondsAfterEachRevive", 10f, new ConfigDescription("After each revive earned back, the next one takes this much longer (vanilla: 10). 0 = always the same time.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
			ExtraAboveStart = ((BasePlugin)this).Config.Bind<int>("Regeneration", "ExtraAboveStart", 0, new ConfigDescription("Let revives refill this many above the starting number. 0 = stop at the starting number.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>()));
			DamageSpeedsUpRecharge = ((BasePlugin)this).Config.Bind<bool>("Regeneration", "DamageSpeedsUpRecharge", true, "Taking damage makes the next revive come back sooner (any player in the party).");
			SecondsPerPercentHealthLost = ((BasePlugin)this).Config.Bind<float>("Regeneration", "SecondsPerPercentHealthLost", 1f, new ConfigDescription("Seconds taken off the refill timer for each 1% of a player's max health they lose. 1 = losing a full health bar saves 100 s.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			ShowRefillTimer = ((BasePlugin)this).Config.Bind<bool>("Display", "ShowRefillTimer", true, "Show \"+1 in 0:42\" under the revive counter while a revive is coming back, and \"+1 Revive!\" when it arrives. (The game's own ring only shows at 0 revives.)");
			new Harmony("girthy_mods.ReviveRegen").PatchAll(typeof(RegenPatches));
			((BasePlugin)this).AddComponent<RefillTimer>();
			ManualLogSource log = Log;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(8, 2, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("ReviveRegen");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("1.1.0");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ready.");
			}
			log.LogInfo(val);
		}
	}
	internal static class Raw
	{
		private static readonly Dictionary<string, int> fieldOffsets = new Dictionary<string, int>();

		private static int hasValueAt = -1;

		private static int valueAt = -1;

		private static int FieldOffset<TOwner>(string field) where TOwner : Il2CppObjectBase
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			string text = typeof(TOwner).FullName + "." + field;
			if (fieldOffsets.TryGetValue(text, out var value))
			{
				return value;
			}
			IntPtr il2CppField = IL2CPP.GetIl2CppField(Il2CppClassPointerStore<TOwner>.NativeClassPtr, field);
			value = ((il2CppField == IntPtr.Zero) ? (-1) : ((int)IL2CPP.il2cpp_field_get_offset(il2CppField)));
			fieldOffsets[text] = value;
			if (value < 0)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(17, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Field ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" not found.");
				}
				log.LogError(val);
			}
			return value;
		}

		private static void NullableLayout()
		{
			if (hasValueAt >= 0)
			{
				return;
			}
			hasValueAt = 0;
			valueAt = 4;
			try
			{
				IntPtr nativeClassPtr = Il2CppClassPointerStore<Nullable<float>>.NativeClassPtr;
				IntPtr il2CppField = IL2CPP.GetIl2CppField(nativeClassPtr, "hasValue");
				IntPtr il2CppField2 = IL2CPP.GetIl2CppField(nativeClassPtr, "value");
				if (il2CppField != IntPtr.Zero && il2CppField2 != IntPtr.Zero)
				{
					int num = IntPtr.Size * 2;
					int num2 = (int)IL2CPP.il2cpp_field_get_offset(il2CppField) - num;
					int num3 = (int)IL2CPP.il2cpp_field_get_offset(il2CppField2) - num;
					if (num2 >= 0 && num3 >= 0 && num2 < 16 && num3 < 16)
					{
						hasValueAt = num2;
						valueAt = num3;
					}
				}
			}
			catch
			{
			}
		}

		internal unsafe static bool TryGetNullable4<TOwner>(TOwner obj, string field, out int bits) where TOwner : Il2CppObjectBase
		{
			bits = 0;
			int num = FieldOffset<TOwner>(field);
			if (num < 0 || obj == null)
			{
				return false;
			}
			NullableLayout();
			byte* ptr = (byte*)(void*)((Il2CppObjectBase)obj).Pointer + num;
			if (ptr[hasValueAt] == 0)
			{
				return false;
			}
			bits = *(int*)(ptr + valueAt);
			return true;
		}
	}
	public class RefillTimer : MonoBehaviour
	{
		private ReviveManager manager;

		private ReviveManagerDisplay display;

		private TextMeshProUGUI label;

		private int lastCount = -1;

		private float flashUntil;

		private float nextFind;

		private float nextErrorLog;

		public RefillTimer(IntPtr ptr)
			: base(ptr)
		{
		}

		private static bool Alive(Object o)
		{
			if (o != null && !((Il2CppObjectBase)o).WasCollected)
			{
				return o != (Object)null;
			}
			return false;
		}

		private void Update()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			try
			{
				Tick();
			}
			catch (Exception ex)
			{
				if (!(Time.unscaledTime < nextErrorLog))
				{
					nextErrorLog = Time.unscaledTime + 10f;
					ManualLogSource log = Plugin.Log;
					bool flag = default(bool);
					BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(14, 1, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Refill timer: ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
					}
					log.LogError(val);
				}
			}
		}

		private void Tick()
		{
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.ShowRefillTimer.Value || !Plugin.Enabled.Value)
			{
				Hide();
				return;
			}
			if ((!Alive((Object)(object)manager) || !Alive((Object)(object)display)) && Time.unscaledTime >= nextFind)
			{
				nextFind = Time.unscaledTime + 1f;
				manager = Object.FindObjectOfType<ReviveManager>();
				display = Object.FindObjectOfType<ReviveManagerDisplay>();
				if (!Alive((Object)(object)label))
				{
					label = null;
				}
				lastCount = -1;
			}
			if (!Alive((Object)(object)manager) || !Alive((Object)(object)display))
			{
				Hide();
				return;
			}
			TMP_Text reviveAmountText = display.reviveAmountText;
			if (!Alive((Object)(object)reviveAmountText))
			{
				Hide();
				return;
			}
			if ((Object)(object)label == (Object)null)
			{
				Build(reviveAmountText);
			}
			if ((Object)(object)label == (Object)null)
			{
				return;
			}
			NetworkVariable<int> currentRevives = manager.CurrentRevives;
			NetworkVariable<float> earningTokenStartTime = manager.earningTokenStartTime;
			NetworkVariable<float> timeUntilReviveTokenEarned = manager.timeUntilReviveTokenEarned;
			ICanGetNetworkTime networkTimeGetter = manager.networkTimeGetter;
			if (currentRevives == null || earningTokenStartTime == null || timeUntilReviveTokenEarned == null || networkTimeGetter == null)
			{
				Hide();
				return;
			}
			int value = currentRevives.Value;
			if (lastCount >= 0 && value > lastCount)
			{
				flashUntil = Time.unscaledTime + 2.5f;
			}
			lastCount = value;
			RectTransform rectTransform = reviveAmountText.rectTransform;
			Rect rect = rectTransform.rect;
			float num = ((Rect)(ref rect)).height * ((Transform)rectTransform).lossyScale.y * 0.95f;
			((Transform)((TMP_Text)label).rectTransform).position = ((Transform)rectTransform).position - new Vector3(0f, num, 0f);
			if (Time.unscaledTime < flashUntil)
			{
				Show("<color=#7CFC7C>+1 Revive!</color>");
				return;
			}
			int limit = RegenPatches.Limit;
			if (limit <= 0 || value >= limit || Raw.TryGetNullable4<ReviveManager>(manager, "pauseStartTime", out int _))
			{
				Hide();
				return;
			}
			float num2 = timeUntilReviveTokenEarned.Value - (networkTimeGetter.ServerTime - earningTokenStartTime.Value);
			if (num2 <= 0f || num2 > timeUntilReviveTokenEarned.Value + 1f)
			{
				Hide();
				return;
			}
			int num3 = Mathf.CeilToInt(num2);
			bool flag = Time.unscaledTime - RegenPatches.LastBoostAt < 0.6f;
			Show(flag ? $"<color=#FF9A3C>+1 in {num3 / 60}:{num3 % 60:00}</color>" : $"+1 in {num3 / 60}:{num3 % 60:00}");
		}

		private void Build(TMP_Text amount)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("ReviveRegen_Timer");
			RectTransform obj = val.AddComponent<RectTransform>();
			val.transform.SetParent(((Component)display).transform, false);
			obj.sizeDelta = new Vector2(240f, 40f);
			label = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)label).font = amount.font;
			((TMP_Text)label).fontSize = Mathf.Max(14f, amount.fontSize * 0.5f);
			((TMP_Text)label).alignment = (TextAlignmentOptions)514;
			((TMP_Text)label).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)label).overflowMode = (TextOverflowModes)0;
			((TMP_Text)label).richText = true;
			((Graphic)label).raycastTarget = false;
			((Graphic)label).color = new Color(1f, 0.85f, 0.35f);
			((TMP_Text)label).outlineWidth = 0.2f;
			((TMP_Text)label).outlineColor = new Color32((byte)0, (byte)0, (byte)0, (byte)200);
			((TMP_Text)label).text = "";
		}

		private void Show(string text)
		{
			if (!((Component)label).gameObject.activeSelf)
			{
				((Component)label).gameObject.SetActive(true);
			}
			if (((TMP_Text)label).text != text)
			{
				((TMP_Text)label).text = text;
			}
		}

		private void Hide()
		{
			if (Alive((Object)(object)label) && ((Component)label).gameObject.activeSelf)
			{
				((Component)label).gameObject.SetActive(false);
			}
		}
	}
	[HarmonyPatch]
	internal static class RegenPatches
	{
		private const float VanillaEscalation = 10f;

		private static int startingTotal;

		private static int lastCount = -1;

		private static IntPtr durationSetFor;

		internal static float LastBoostAt = -100f;

		private static ReviveManager cachedManager;

		internal static int Limit
		{
			get
			{
				if (startingTotal > 0)
				{
					return startingTotal + Math.Max(0, Plugin.ExtraAboveStart.Value);
				}
				return 0;
			}
		}

		[HarmonyPatch(typeof(ReviveManager), "InitializeOnClientsRpc")]
		[HarmonyPrefix]
		[HarmonyPriority(0)]
		private static void InitClients_Prefix(ReviveManager __instance, int totalRevives)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			if (totalRevives > 0)
			{
				startingTotal = totalRevives;
				lastCount = -1;
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(19, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Starting revives: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(startingTotal);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(".");
				}
				log.LogInfo(val);
			}
		}

		[HarmonyPatch(typeof(ReviveManager), "OnNetworkSpawn")]
		[HarmonyPostfix]
		private static void Spawn()
		{
			startingTotal = 0;
			lastCount = -1;
			durationSetFor = IntPtr.Zero;
		}

		[HarmonyPatch(typeof(ReviveManager), "HandleReviveTokenEarning")]
		[HarmonyPrefix]
		private static bool Earning_Prefix(ReviveManager __instance)
		{
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Expected O, but got Unknown
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Expected O, but got Unknown
			if (!Plugin.Enabled.Value || startingTotal <= 0)
			{
				return true;
			}
			bool flag = default(bool);
			try
			{
				NetworkVariable<int> currentRevives = __instance.CurrentRevives;
				NetworkVariable<float> earningTokenStartTime = __instance.earningTokenStartTime;
				NetworkVariable<float> timeUntilReviveTokenEarned = __instance.timeUntilReviveTokenEarned;
				ICanGetNetworkTime networkTimeGetter = __instance.networkTimeGetter;
				if (currentRevives == null || earningTokenStartTime == null || timeUntilReviveTokenEarned == null || networkTimeGetter == null)
				{
					return true;
				}
				bool isOwner = ((NetworkBehaviour)__instance).IsOwner;
				int value = currentRevives.Value;
				int num = startingTotal + Math.Max(0, Plugin.ExtraAboveStart.Value);
				float serverTime = networkTimeGetter.ServerTime;
				if (isOwner)
				{
					if (Plugin.SecondsPerRevive.Value > 0f && durationSetFor != ((Il2CppObjectBase)__instance).Pointer)
					{
						durationSetFor = ((Il2CppObjectBase)__instance).Pointer;
						timeUntilReviveTokenEarned.Value = Plugin.SecondsPerRevive.Value;
					}
					if (lastCount >= num && value < num)
					{
						earningTokenStartTime.Value = serverTime;
					}
					lastCount = value;
				}
				if (value >= num)
				{
					return false;
				}
				if (Raw.TryGetNullable4<ReviveManager>(__instance, "pauseStartTime", out int _))
				{
					return false;
				}
				float num2 = (serverTime - earningTokenStartTime.Value) / Math.Max(0.01f, timeUntilReviveTokenEarned.Value);
				__instance.OnReviveTokenEarningChanged?.Invoke(num2);
				if (!isOwner || num2 < 1f)
				{
					return false;
				}
				timeUntilReviveTokenEarned.Value += Plugin.ExtraSecondsAfterEachRevive.Value;
				currentRevives.Value = value + 1;
				lastCount = value + 1;
				if (value + 1 < num)
				{
					earningTokenStartTime.Value = serverTime;
				}
				ManualLogSource log = Plugin.Log;
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(36, 3, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Revive earned back: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(value + 1);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(". Next takes ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(timeUntilReviveTokenEarned.Value, "0");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("s.");
				}
				log.LogInfo(val);
				return false;
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(14, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Revive regen: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex);
				}
				log2.LogError(val2);
				return true;
			}
		}

		[HarmonyPatch(typeof(PlayerHealthContainer), "NotifyDamageTakenOnServerRpc")]
		[HarmonyPrefix]
		private static void DamageTaken_Prefix(PlayerHealthContainer __instance, float amount)
		{
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Expected O, but got Unknown
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Invalid comparison between Unknown and I4
			try
			{
				if (!Plugin.Enabled.Value || !Plugin.DamageSpeedsUpRecharge.Value || amount <= 0f || startingTotal <= 0 || (int)((NetworkBehaviour)__instance).__rpc_exec_stage != 1)
				{
					return;
				}
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
				{
					return;
				}
				if (cachedManager == null || ((Il2CppObjectBase)cachedManager).WasCollected || (Object)(object)cachedManager == (Object)null)
				{
					cachedManager = Object.FindObjectOfType<ReviveManager>();
				}
				ReviveManager val = cachedManager;
				if ((Object)(object)val == (Object)null || val.CurrentRevives == null || val.earningTokenStartTime == null || val.timeUntilReviveTokenEarned == null)
				{
					return;
				}
				int limit = Limit;
				if (val.CurrentRevives.Value >= limit || Raw.TryGetNullable4<ReviveManager>(val, "pauseStartTime", out int _))
				{
					return;
				}
				float maxHealth = ((HealthContainer)__instance).MaxHealth;
				if (!(maxHealth <= 0f))
				{
					float num = Math.Min(amount, maxHealth) / maxHealth * 100f * Plugin.SecondsPerPercentHealthLost.Value;
					if (!(num <= 0f))
					{
						val.earningTokenStartTime.Value = val.earningTokenStartTime.Value - num;
						LastBoostAt = Time.unscaledTime;
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(27, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Damage speeds up recharge: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message);
				}
				log.LogError(val2);
			}
		}
	}
}