using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using NaturalHealthRegen.Patches;
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
{
[BepInPlugin("Swaggies.NaturalHealthRegen", "NaturalHealthRegen", "0.1.2")]
public class Plugin : BaseUnityPlugin
{
public const string _guid = "Swaggies.NaturalHealthRegen";
public const string _name = "NaturalHealthRegen";
public const string _ver = "0.1.2";
public static Plugin Instance;
private static Harmony harmony;
private static ManualLogSource naturallogger;
private static ConfigEntry<int> RegenAmount;
private static ConfigEntry<float> RegenInterval;
private static ConfigEntry<float> DamageCooldown;
private static ConfigEntry<int> RegenDailyLimit;
private static ConfigEntry<int> RegenMaxHealth;
private static ConfigEntry<bool> AutoDetermineMaxHealth;
private static ConfigEntry<bool> DisableWhileCrit;
private static ConfigEntry<float> StaminaRequirement;
private static ConfigEntry<float> StaminaMultiplier;
private static ConfigEntry<float> MaximumFear;
private static ConfigEntry<float> FearMultiplier;
private static ConfigEntry<float> InShipMultiplier;
private static ConfigEntry<float> OutsideMultiplier;
private static ConfigEntry<float> InsideMultiplier;
private static ConfigEntry<float> LastPlayerAliveMultiplier;
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));
CreateConfig();
}
private void CreateConfig()
{
CreateRegenPropertiesConfig();
CreateRegenMultipliersConfig();
}
private void CreateRegenPropertiesConfig()
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Expected O, but got Unknown
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: 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_00d3: Unknown result type (might be due to invalid IL or missing references)
//IL_00dd: Expected O, but got Unknown
//IL_0107: Unknown result type (might be due to invalid IL or missing references)
//IL_0111: Expected O, but got Unknown
RegenAmount = ((BaseUnityPlugin)this).Config.Bind<int>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<int>("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 = ((BaseUnityPlugin)this).Config.Bind<int>("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 = ((BaseUnityPlugin)this).Config.Bind<bool>("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 = ((BaseUnityPlugin)this).Config.Bind<bool>("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.");
}
private void CreateRegenMultipliersConfig()
{
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Expected O, but got Unknown
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: Expected O, but got Unknown
//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Expected O, but got Unknown
//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
//IL_00f0: Expected O, but got Unknown
//IL_0123: Unknown result type (might be due to invalid IL or missing references)
//IL_012d: Expected O, but got Unknown
//IL_0160: Unknown result type (might be due to invalid IL or missing references)
//IL_016a: Expected O, but got Unknown
//IL_019d: Unknown result type (might be due to invalid IL or missing references)
//IL_01a7: Expected O, but got Unknown
//IL_01da: Unknown result type (might be due to invalid IL or missing references)
//IL_01e4: Expected O, but got Unknown
StaminaRequirement = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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 = ((BaseUnityPlugin)this).Config.Bind<float>("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>()));
}
public static void Init()
{
RegenerationSettings.currentProperties = new RegenerationSettings.RegenProperties(RegenAmount.Value, RegenInterval.Value, DamageCooldown.Value, RegenDailyLimit.Value, RegenMaxHealth.Value, AutoDetermineMaxHealth.Value, DisableWhileCrit.Value);
RegenerationSettings.currentMultipliers = new RegenerationSettings.RegenMultipliers(StaminaRequirement.Value, MaximumFear.Value, StaminaMultiplier.Value, FearMultiplier.Value, InShipMultiplier.Value, OutsideMultiplier.Value, InsideMultiplier.Value, LastPlayerAliveMultiplier.Value);
}
}
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()
{
Plugin.Init();
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 = Math.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()
{
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(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);
}
}
[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();
}
}
}