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);
}
}
}
}