Decompiled source of NaturalHealthRegen v1.0.0

NaturalHealthRegen.dll

Decompiled 2 weeks ago
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CSync.Extensions;
using CSync.Lib;
using GameNetcodeStuff;
using HarmonyLib;
using NaturalHealthRegen.Patches;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("NaturalHealthRegen")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("NaturalHealthRegen")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("1710740a-1e2e-497f-8e95-3c00175f7090")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace NaturalHealthRegen
{
	public class Config : SyncedConfig2<Config>
	{
		[SyncedEntryField]
		public SyncedEntry<int> RegenAmount;

		[SyncedEntryField]
		public SyncedEntry<float> RegenInterval;

		[SyncedEntryField]
		public SyncedEntry<float> DamageCooldown;

		[SyncedEntryField]
		public SyncedEntry<int> RegenDailyLimit;

		[SyncedEntryField]
		public SyncedEntry<int> RegenMaxHealth;

		[SyncedEntryField]
		public SyncedEntry<bool> AutoDetermineMaxHealth;

		[SyncedEntryField]
		public SyncedEntry<bool> DisableWhileCrit;

		[SyncedEntryField]
		public SyncedEntry<float> StaminaRequirement;

		[SyncedEntryField]
		public SyncedEntry<float> StaminaMultiplier;

		[SyncedEntryField]
		public SyncedEntry<float> MaximumFear;

		[SyncedEntryField]
		public SyncedEntry<float> FearMultiplier;

		[SyncedEntryField]
		public SyncedEntry<float> InShipMultiplier;

		[SyncedEntryField]
		public SyncedEntry<float> OutsideMultiplier;

		[SyncedEntryField]
		public SyncedEntry<float> InsideMultiplier;

		[SyncedEntryField]
		public SyncedEntry<float> LastPlayerAliveMultiplier;

		public ConfigEntry<bool> ShowDebugInfo;

		public Config(ConfigFile cfg)
			: base("Swaggies.NaturalHealthRegen")
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Expected O, but got Unknown
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Expected O, but got Unknown
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Expected O, but got Unknown
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Expected O, but got Unknown
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Expected O, but got Unknown
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: Expected O, but got Unknown
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Expected O, but got Unknown
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Expected O, but got Unknown
			//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Expected O, but got Unknown
			//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0309: Expected O, but got Unknown
			RegenAmount = SyncedBindingExtensions.BindSyncedEntry<int>(cfg, "Base Properties", "Regen Amount Per Cycle", 1, new ConfigDescription("How much health to regenerate every cycle. I recommend keeping this at 1 and adjusting the cycle duration to change the speed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			RegenInterval = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Base Properties", "Cycle Duration", 3f, new ConfigDescription("Time in seconds between each cycle. Once a cycle happens, the amount of health above will be healed. Cycle time is sped up or slowed down by the multipliers section.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 300f), Array.Empty<object>()));
			DamageCooldown = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Base Properties", "Damage Cooldown", 5f, new ConfigDescription("Time in seconds where regeneration is blocked upon taking damage. I recommend keeping this above 0 so you don't instantly regenerate health upon being damaged.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 300f), Array.Empty<object>()));
			RegenDailyLimit = SyncedBindingExtensions.BindSyncedEntry<int>(cfg, "Base Properties", "Limit Per Day", 0, new ConfigDescription("How much health you can regenerate in a day before regeneration is blocked off for the rest of the day. Set to 0 for no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
			RegenMaxHealth = SyncedBindingExtensions.BindSyncedEntry<int>(cfg, "Base Properties", "Maximum Health", 100, new ConfigDescription("Maximum health you are allowed to regenerate up to.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 100), Array.Empty<object>()));
			AutoDetermineMaxHealth = SyncedBindingExtensions.BindSyncedEntry<bool>(cfg, "Base Properties", "Auto Determine Max Health", true, "Check the player's health at the start of each game to determine the max health. If this value is higher than the one set in Maximum Health, then this will be used as the regeneration limit instead. Useful with mods that allow the player to go above 100 HP.");
			DisableWhileCrit = SyncedBindingExtensions.BindSyncedEntry<bool>(cfg, "Base Properties", "Disable During Critical Injury", true, "If regeneration should be paused while the player is below 20 HP, allowing the vanilla regeneration to take over. If disabled, both the vanilla regeneration and the mod's regeneration will be active below 20 HP.");
			StaminaRequirement = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Regen Multipliers", "Low Stamina Limit", 0.3f, new ConfigDescription("Amount of stamina needed for when the Stamina Multiplier should NOT be triggered.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			StaminaMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Regen Multipliers", "Low Stamina Multiplier", 0.5f, new ConfigDescription("Regen speed multiplier for when you are below the Low Stamina Limit. Set to 0 to block regeneration when this condition is met.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			MaximumFear = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Regen Multipliers", "High Fear Limit", 0.4f, new ConfigDescription("Amount of fear needed before the Fear Multiplier should be triggered.\nLow action: 0.1\nMedium action: 0.4\nHigh action: 0.7+", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			FearMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Regen Multipliers", "High Fear Multiplier", 0.5f, new ConfigDescription("Regen speed multiplier for when you are above the High Fear Limit. Set to 0 to block regeneration when this condition is met.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			InShipMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Regen Multipliers", "Inside Ship Multiplier", 1.5f, new ConfigDescription("Regen speed multiplier for when you are inside ship. Note that this is not triggered if you are on the ship but not inside. Set to 0 to block regeneration when this condition is met.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			OutsideMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Regen Multipliers", "Outdoors Multiplier", 1.25f, new ConfigDescription("Regen speed multiplier for when you are outside. Note that this is triggered while on ship, but not while inside ship. Set to 0 to block regeneration when this condition is met.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			InsideMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Regen Multipliers", "Indoors Multiplier", 1f, new ConfigDescription("Regen speed multiplier for when you are inside the facility. Set to 0 to block regeneration when this condition is met.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			LastPlayerAliveMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(cfg, "Regen Multipliers", "Final Player Multiplier", 1.5f, new ConfigDescription("Regen speed multiplier for when you are the last player alive. Set to 0 to block regeneration when this condition is met.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			ShowDebugInfo = cfg.Bind<bool>("Debug", "Show debug info", false, "Shows debug info in the status effects text UI. Keep off if playing normally, but can be used to test your values.\nR = regen timer\nD = damage cooldown\nRD = remaining daily regen\nM = max health\nMUL = current multiplier");
			ConfigManager.Register<Config>((SyncedConfig2<Config>)this);
		}
	}
	[BepInPlugin("Swaggies.NaturalHealthRegen", "NaturalHealthRegen", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string _guid = "Swaggies.NaturalHealthRegen";

		public const string _name = "NaturalHealthRegen";

		public const string _ver = "1.0.0";

		public static Plugin Instance;

		private static Harmony harmony;

		private static ManualLogSource naturallogger;

		public static Config config;

		private void Awake()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
			}
			harmony = new Harmony("Swaggies.NaturalHealthRegen");
			naturallogger = Logger.CreateLogSource("Swaggies.NaturalHealthRegen");
			naturallogger.LogInfo((object)"NaturalHealthRegen up and running.");
			harmony.PatchAll(typeof(PlayerControllerBPatch));
			harmony.PatchAll(typeof(RoundManagerPatch));
			harmony.PatchAll(typeof(NetworkManagerPatch));
			config = new Config(((BaseUnityPlugin)this).Config);
			((SyncedConfig2<Config>)config).InitialSyncCompleted += Init;
		}

		public static void Init(object sender, EventArgs args)
		{
			RegenerationSettings.currentProperties = new RegenerationSettings.RegenProperties(config.RegenAmount.Value, config.RegenInterval.Value, config.DamageCooldown.Value, config.RegenDailyLimit.Value, config.RegenMaxHealth.Value, config.AutoDetermineMaxHealth.Value, config.DisableWhileCrit.Value);
			RegenerationSettings.currentMultipliers = new RegenerationSettings.RegenMultipliers(config.StaminaRequirement.Value, config.MaximumFear.Value, config.StaminaMultiplier.Value, config.FearMultiplier.Value, config.InShipMultiplier.Value, config.OutsideMultiplier.Value, config.InsideMultiplier.Value, config.LastPlayerAliveMultiplier.Value);
			naturallogger.LogDebug((object)"Got config sync!");
		}
	}
	internal class RegenerationManager
	{
		private static float regenerationTimer = 0f;

		private static float damageCooldown = 0f;

		private static int regenerationLeftToday = 0;

		private static int maxHealth = 0;

		private static float _lastMultiplier = -1f;

		public static void Start()
		{
			regenerationTimer = 0f;
			damageCooldown = 0f;
			regenerationLeftToday = 0;
			maxHealth = RegenerationSettings.currentProperties.maxHealth;
		}

		public static void StartGame()
		{
			regenerationTimer = 0f;
			damageCooldown = 0f;
			if (RegenerationSettings.currentProperties.dailyLimit == 0)
			{
				regenerationLeftToday = -1;
			}
			else
			{
				regenerationLeftToday = RegenerationSettings.currentProperties.dailyLimit;
			}
			if (RegenerationSettings.currentProperties.autoDetermineMaxHealth)
			{
				maxHealth = Mathf.Max(RegenerationSettings.currentProperties.maxHealth, GameNetworkManager.Instance.localPlayerController.health);
			}
		}

		private static float CalculateTimeToRemove(PlayerControllerB pl)
		{
			float num = 1f;
			if (pl.sprintMeter < RegenerationSettings.currentMultipliers.staminaRequirement)
			{
				num *= RegenerationSettings.currentMultipliers.stamina;
			}
			if (StartOfRound.Instance.fearLevel >= RegenerationSettings.currentMultipliers.fearRequirement)
			{
				num *= RegenerationSettings.currentMultipliers.fear;
			}
			num = (pl.isInHangarShipRoom ? (num * RegenerationSettings.currentMultipliers.inShip) : (pl.isInsideFactory ? (num * RegenerationSettings.currentMultipliers.inside) : (num * RegenerationSettings.currentMultipliers.outside)));
			if (StartOfRound.Instance.livingPlayers == 1)
			{
				num *= RegenerationSettings.currentMultipliers.lastPlayerAlive;
			}
			_lastMultiplier = num;
			return Time.deltaTime * num;
		}

		public static void PlayerUpdate(PlayerControllerB localp)
		{
			if (StartOfRound.Instance.inShipPhase || localp.isPlayerDead || localp.health >= maxHealth || regenerationLeftToday == 0 || (RegenerationSettings.currentProperties.disableWhileCrit && localp.health < 20))
			{
				return;
			}
			if (damageCooldown > 0f)
			{
				damageCooldown = Math.Max(0f, damageCooldown - Time.deltaTime);
				return;
			}
			regenerationTimer -= CalculateTimeToRemove(localp);
			if (regenerationTimer <= 0f)
			{
				RegenerateHealth(RegenerationSettings.currentProperties.amount, localp);
				regenerationTimer = RegenerationSettings.currentProperties.interval;
			}
		}

		public static void RegenerateHealth(int amount, PlayerControllerB pl)
		{
			if (pl.isPlayerDead)
			{
				return;
			}
			int num = Mathf.Min(amount, maxHealth - pl.health);
			if (regenerationLeftToday > 0)
			{
				num = Mathf.Min(num, regenerationLeftToday);
			}
			if (num > 0)
			{
				pl.health += num;
				HUDManager.Instance.UpdateHealthUI(pl.health, false);
				regenerationLeftToday -= num;
				if (pl.health >= 20 && pl.criticallyInjured)
				{
					pl.MakeCriticallyInjured(false);
				}
			}
		}

		public static void OnDamage()
		{
			damageCooldown = RegenerationSettings.currentProperties.cooldown;
			regenerationTimer = 0f;
		}

		public static void ShowInfo()
		{
			if (Plugin.config.ShowDebugInfo.Value)
			{
				string text = $"\nR: {regenerationTimer:0.00} / D: {damageCooldown:0.00}" + $"\nRD: {regenerationLeftToday} / M: {maxHealth}" + $"\nMUL: {_lastMultiplier:0.000}x";
				HUDManager.Instance.DisplayStatusEffect(text);
			}
		}
	}
	internal class RegenerationSettings
	{
		public class RegenProperties
		{
			public int amount;

			public float interval;

			public float cooldown;

			public int dailyLimit;

			public int maxHealth;

			public bool autoDetermineMaxHealth;

			public bool disableWhileCrit;

			public RegenProperties(int regenAmount, float regenInterval, float damageCooldown, int dailyLimit, int maxHealth, bool autoDetermineMaxHealth, bool disableWhileCrit)
			{
				amount = regenAmount;
				interval = regenInterval;
				cooldown = damageCooldown;
				this.dailyLimit = dailyLimit;
				this.maxHealth = maxHealth;
				this.autoDetermineMaxHealth = autoDetermineMaxHealth;
				this.disableWhileCrit = disableWhileCrit;
			}
		}

		public class RegenMultipliers
		{
			public float staminaRequirement;

			public float fearRequirement;

			public float stamina;

			public float fear;

			public float inShip;

			public float outside;

			public float inside;

			public float lastPlayerAlive;

			public RegenMultipliers(float staminaRequirement, float fearRequirement, float stamina, float fear, float inShip, float outside, float inside, float lastPlayerAlive)
			{
				this.staminaRequirement = staminaRequirement;
				this.fearRequirement = fearRequirement;
				this.stamina = stamina;
				this.fear = fear;
				this.inShip = inShip;
				this.outside = outside;
				this.inside = inside;
				this.lastPlayerAlive = lastPlayerAlive;
			}
		}

		public static RegenProperties currentProperties;

		public static RegenMultipliers currentMultipliers;
	}
}
namespace NaturalHealthRegen.Patches
{
	[HarmonyPatch(typeof(NetworkManager))]
	internal class NetworkManagerPatch
	{
		[HarmonyPatch("SetSingleton")]
		[HarmonyPostfix]
		private static void SetSingletonP()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_000e: 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)
			GameObject val = new GameObject("Swaggies.NaturalHealthRegen-NetworkPatchObject");
			((Object)val).hideFlags = (HideFlags)(((Object)val).hideFlags | 0x3D);
			Object.DontDestroyOnLoad((Object)(object)val);
			NetworkObject obj = val.AddComponent<NetworkObject>();
			uint num = "Swaggies.NaturalHealthRegen".Aggregate(17u, (uint u, char c) => (u * 31) ^ c);
			typeof(NetworkObject).GetField("GlobalObjectIdHash", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(obj, num);
			NetworkManager.Singleton.PrefabHandler.AddNetworkPrefab(val);
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB))]
	internal class PlayerControllerBPatch
	{
		[HarmonyPatch("ConnectClientToPlayerObject")]
		[HarmonyPostfix]
		private static void ConnectClientToPlayerObjectPostfix()
		{
			RegenerationManager.Start();
		}

		[HarmonyPatch("LateUpdate")]
		[HarmonyPostfix]
		private static void LateUpdatePostfix(ref PlayerControllerB __instance)
		{
			if (!((Object)(object)__instance != (Object)(object)GameNetworkManager.Instance.localPlayerController))
			{
				RegenerationManager.PlayerUpdate(__instance);
				RegenerationManager.ShowInfo();
			}
		}

		[HarmonyPatch("DamagePlayer")]
		[HarmonyPostfix]
		private static void DamagePlayerPostfix(ref PlayerControllerB __instance)
		{
			if (!((Object)(object)__instance != (Object)(object)GameNetworkManager.Instance.localPlayerController))
			{
				RegenerationManager.OnDamage();
			}
		}
	}
	[HarmonyPatch(typeof(RoundManager))]
	internal class RoundManagerPatch
	{
		[HarmonyPatch("RefreshEnemiesList")]
		[HarmonyPostfix]
		private static void RefreshEnemiesListPostfix()
		{
			RegenerationManager.StartGame();
		}
	}
}