Decompiled source of BetterHeals v2.1.1

BetterHeals.dll

Decompiled 12 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterHeals.Config;
using BetterHeals.Patches;
using HarmonyLib;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("TeamHeals")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TeamHeals")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("8b363090-6f7a-451a-92a9-ed78838c26e0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace BetterHeals
{
	[BepInPlugin("MrBytesized.REPO.BetterHeals", "Better Heals", "2.1.1")]
	public class TeamHealsPlugin : BaseUnityPlugin
	{
		private const string mod_guid = "MrBytesized.REPO.BetterHeals";

		private const string mod_name = "Better Heals";

		private const string mod_version = "2.1.1";

		private readonly Harmony harmony = new Harmony("MrBytesized.REPO.BetterHeals");

		private static TeamHealsPlugin instance;

		internal static ManualLogSource Log;

		internal static readonly FieldRef<ItemHealthPack, ItemToggle> item_toggle_ref = AccessTools.FieldRefAccess<ItemHealthPack, ItemToggle>("itemToggle");

		internal static readonly FieldRef<ItemToggle, int> player_photon_id_ref = AccessTools.FieldRefAccess<ItemToggle, int>("playerTogglePhotonID");

		internal static readonly FieldRef<PlayerHealth, int> health_ref = AccessTools.FieldRefAccess<PlayerHealth, int>("health");

		internal static readonly FieldRef<PlayerHealth, int> max_health_ref = AccessTools.FieldRefAccess<PlayerHealth, int>("maxHealth");

		private (ConfigEntry<bool> configEntry, Action enablePatch, Action disablePatch, string description)[] patchArray;

		private void Awake()
		{
			if ((Object)(object)instance == (Object)null)
			{
				instance = this;
			}
			Configuration.Init(((BaseUnityPlugin)this).Config);
			Log = Logger.CreateLogSource("MrBytesized.REPO.BetterHeals");
			Log.LogInfo((object)"Team Heals mod has been activated");
			TryPatch("plugin", delegate
			{
				harmony.PatchAll(typeof(TeamHealsPlugin));
			});
			TryPatch("PunManager", delegate
			{
				harmony.PatchAll(typeof(PunManagerPatch));
			});
			TryPatch("ItemHealthPack", delegate
			{
				harmony.PatchAll(typeof(ItemHealthPackPatch));
			});
			patchArray = new(ConfigEntry<bool>, Action, Action, string)[5]
			{
				(Configuration.EnableCustomReviveHealthPatch, delegate
				{
					harmony.PatchAll(typeof(PlayerReviveHealthPatch));
				}, delegate
				{
					harmony.Unpatch((MethodBase)AccessTools.Method(typeof(PlayerAvatar), "ReviveRPC", (Type[])null, (Type[])null), AccessTools.Method(typeof(PlayerReviveHealthPatch), "ReviveRPC_Postfix", (Type[])null, (Type[])null));
				}, "Custom Revive Health"),
				(Configuration.EnableExtractionHealPatch, delegate
				{
					harmony.PatchAll(typeof(ExtractionPointHealPatch));
				}, delegate
				{
					harmony.Unpatch((MethodBase)AccessTools.Method(typeof(ExtractionPoint), "StateSet", (Type[])null, (Type[])null), AccessTools.Method(typeof(ExtractionPointHealPatch), "StateSet_Postfix", (Type[])null, (Type[])null));
				}, "Extraction Heal"),
				(Configuration.EnableHealthRegenPatch, delegate
				{
					harmony.PatchAll(typeof(HealthRegenPatch));
				}, delegate
				{
					harmony.Unpatch((MethodBase)AccessTools.Method(typeof(LevelGenerator), "GenerateDone", (Type[])null, (Type[])null), AccessTools.Method(typeof(HealthRegenPatch), "Start_Postfix", (Type[])null, (Type[])null));
				}, "Health Regeneration"),
				(Configuration.EnableFullHealthAtStartPatch, delegate
				{
					harmony.PatchAll(typeof(FullHealthAtStartPatch));
				}, delegate
				{
					harmony.Unpatch((MethodBase)AccessTools.Method(typeof(LevelGenerator), "GenerateDone", (Type[])null, (Type[])null), AccessTools.Method(typeof(FullHealthAtStartPatch), "GenerateDone_Postfix", (Type[])null, (Type[])null));
				}, "Full Health At Start"),
				(Configuration.EnableTeamHealthPackPatch, delegate
				{
					//IL_002d: Unknown result type (might be due to invalid IL or missing references)
					//IL_003a: Expected O, but got Unknown
					harmony.Patch((MethodBase)AccessTools.Method(typeof(ItemHealthPack), "UsedRPC", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ItemHealthPackPatch), "TeamHealthPackSync", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}, delegate
				{
					harmony.Unpatch((MethodBase)AccessTools.Method(typeof(ItemHealthPack), "UsedRPC", (Type[])null, (Type[])null), AccessTools.Method(typeof(ItemHealthPackPatch), "TeamHealthPackSync", (Type[])null, (Type[])null));
				}, "Health Pack Team Healing")
			};
			(ConfigEntry<bool>, Action, Action, string)[] array = patchArray;
			for (int num = 0; num < array.Length; num++)
			{
				var (configEntry, enablePatch, disablePatch, description) = array[num];
				UpdatePatchFromConfig(configEntry, enablePatch, disablePatch, description);
				configEntry.SettingChanged += delegate
				{
					UpdatePatchFromConfig(configEntry, enablePatch, disablePatch, description);
				};
			}
		}

		private void UpdatePatchFromConfig(ConfigEntry<bool> configEntry, Action enablePatch, Action disablePatch, string description)
		{
			try
			{
				if (configEntry.Value)
				{
					enablePatch();
					Log.LogInfo((object)(description + " patch enabled."));
				}
				else
				{
					disablePatch();
					Log.LogInfo((object)(description + " patch disabled."));
				}
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"{description} patch failed: {arg}");
			}
		}

		private void TryPatch(string description, Action apply)
		{
			try
			{
				apply();
				Log.LogInfo((object)(description + " patch applied."));
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"{description} patch failed: {arg}");
			}
		}
	}
}
namespace BetterHeals.Patches
{
	internal static class ExtractionPointHealPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(ExtractionPoint), "StateSet")]
		private static void StateSet_Postfix(ExtractionPoint __instance, State newState)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			if ((int)newState != 7 || SemiFunc.RunIsShop() || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			int num = Configuration.ExtractionHealAmount.Value;
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if ((Object)(object)player != (Object)null && (Object)(object)player.playerHealth != (Object)null)
				{
					if (Configuration.EnableFullReviveHealth.Value)
					{
						int num2 = TeamHealsPlugin.health_ref.Invoke(player.playerHealth);
						num = TeamHealsPlugin.max_health_ref.Invoke(player.playerHealth) - num2;
					}
					FieldInfo field = typeof(PlayerHealth).GetField("health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					int num3 = ((!(field != null)) ? 1 : ((int)field.GetValue(player.playerHealth)));
					FieldInfo field2 = typeof(PlayerAvatar).GetField("isDisabled", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					bool flag = field2 != null && (bool)field2.GetValue(player);
					if (num3 > 0 && !flag)
					{
						player.playerHealth.HealOther(num, true);
					}
				}
			}
			TeamHealsPlugin.Log.LogInfo((object)$"All alive players healed to {num} after extraction.");
		}
	}
	internal static class FullHealthAtStartPatch
	{
		[HarmonyPatch(typeof(LevelGenerator), "GenerateDone")]
		[HarmonyPostfix]
		private static void GenerateDone_Postfix()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			if (SemiFunc.RunIsLevel() && SemiFunc.IsMasterClientOrSingleplayer())
			{
				new GameObject("HealCoroutineRunner").AddComponent<HealRunner>();
			}
		}
	}
	public class HealRunner : MonoBehaviour
	{
		private void Awake()
		{
			((MonoBehaviour)this).StartCoroutine(HealAllPlayersToFull());
		}

		private IEnumerator HealAllPlayersToFull()
		{
			yield return (object)new WaitForSeconds(1f);
			TeamHealsPlugin.Log.LogInfo((object)"Proceeding to heal all players to full health.");
			if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null)
			{
				TeamHealsPlugin.Log.LogWarning((object)"GameDirector.instance or PlayerList is null.");
				Object.Destroy((Object)(object)((Component)this).gameObject);
				yield break;
			}
			List<PlayerAvatar> playerList = GameDirector.instance.PlayerList;
			FieldInfo field = typeof(PlayerHealth).GetField("health", BindingFlags.Instance | BindingFlags.NonPublic);
			FieldInfo field2 = typeof(PlayerHealth).GetField("maxHealth", BindingFlags.Instance | BindingFlags.NonPublic);
			if (field == null || field2 == null)
			{
				TeamHealsPlugin.Log.LogError((object)"Could not find health or maxHealth fields via reflection.");
				Object.Destroy((Object)(object)((Component)this).gameObject);
				yield break;
			}
			foreach (PlayerAvatar item in playerList)
			{
				if ((Object)(object)item != (Object)null && (Object)(object)item.playerHealth != (Object)null)
				{
					int num = (int)field.GetValue(item.playerHealth);
					int num2 = (int)field2.GetValue(item.playerHealth);
					int num3 = num2 - num;
					TeamHealsPlugin.Log.LogInfo((object)$"Player {item.photonView.Owner.NickName} current health: {num}, max health: {num2}, calculated heal amount: {num3}");
					if (num3 > 0)
					{
						item.playerHealth.HealOther(num3, true);
						TeamHealsPlugin.Log.LogInfo((object)$"Healed player {item.photonView.Owner.NickName} to full health: {num2}");
					}
				}
			}
			Object.Destroy((Object)(object)((Component)this).gameObject);
		}
	}
	public class RegenUpdater : MonoBehaviour
	{
		private float updateThrottle;

		public void Init()
		{
		}

		private void Update()
		{
			if (!SemiFunc.RunIsLevel() || !Configuration.EnableHealthRegenPatch.Value)
			{
				return;
			}
			updateThrottle += Time.deltaTime;
			if (updateThrottle < (float)Configuration.CustomRegenIntervalAmount.Value)
			{
				return;
			}
			updateThrottle = 0f;
			int value = Configuration.HealthRegenAmount.Value;
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if ((Object)(object)player.playerHealth != (Object)null && TeamHealsPlugin.health_ref.Invoke(player.playerHealth) > 0)
				{
					player.playerHealth.HealOther(value, false);
				}
			}
			TeamHealsPlugin.Log.LogInfo((object)$"Players healed by regeneration: {value}");
		}
	}
	internal static class HealthRegenPatch
	{
		[HarmonyPatch(typeof(LevelGenerator), "GenerateDone")]
		[HarmonyPostfix]
		private static void Start_Postfix(LevelGenerator __instance)
		{
			if (SemiFunc.RunIsLevel() && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)__instance != (Object)null)
			{
				GameObject gameObject = ((Component)__instance).gameObject;
				if ((Object)(object)gameObject.GetComponent<RegenUpdater>() == (Object)null)
				{
					gameObject.AddComponent<RegenUpdater>().Init();
					TeamHealsPlugin.Log.LogInfo((object)("RegenUpdater attached to " + ((Object)__instance).name + "."));
				}
			}
		}
	}
	internal static class ItemHealthPackPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(ItemHealthPack), "Start")]
		private static void OverrideHealAmount(ItemHealthPack __instance)
		{
			float num = (float)Math.Round(Configuration.HealAmountMultiplier.Value, 4);
			if (num == 1f)
			{
				TeamHealsPlugin.Log.LogInfo((object)"Heal amount multiplier is 1. No changes made to health pack heal amount.");
			}
			else
			{
				__instance.healAmount = (int)Math.Ceiling((float)__instance.healAmount * num);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ItemAttributes), "Start")]
		private static void OverrideHealthPackName(ItemAttributes __instance)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			if (!((Object)(object)__instance.item != (Object)null) || (int)__instance.item.itemType != 8)
			{
				return;
			}
			ItemHealthPack component = ((Component)__instance).GetComponent<ItemHealthPack>();
			if ((Object)(object)component != (Object)null)
			{
				int healAmount = component.healAmount;
				FieldInfo field = typeof(ItemAttributes).GetField("itemName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					string text = (string)field.GetValue(__instance);
					if (!string.IsNullOrEmpty(text))
					{
						string text2 = Regex.Replace(text, "\\(\\d+\\)", $"({healAmount})");
						field.SetValue(__instance, text2);
						TeamHealsPlugin.Log.LogInfo((object)("Health pack name changed from '" + text + "' to '" + text2 + "'"));
					}
					else
					{
						TeamHealsPlugin.Log.LogWarning((object)"itemName value is null or empty in ItemAttributes.Start postfix.");
					}
				}
				else
				{
					TeamHealsPlugin.Log.LogWarning((object)"itemName field not found in ItemAttributes.");
				}
			}
			else
			{
				TeamHealsPlugin.Log.LogWarning((object)"ItemHealthPack component not found on ItemAttributes.");
			}
		}

		private static void TeamHealthPackSync(ItemHealthPack __instance)
		{
			ItemToggle val = TeamHealsPlugin.item_toggle_ref.Invoke(__instance);
			PlayerAvatar val2 = SemiFunc.PlayerAvatarGetFromPhotonID(TeamHealsPlugin.player_photon_id_ref.Invoke(val));
			List<PlayerAvatar> list = SemiFunc.PlayerGetAll();
			foreach (PlayerAvatar item in list)
			{
				if (item.photonView.ViewID != val2.photonView.ViewID)
				{
					if (Configuration.EnableEqualSplitTeamHealth.Value)
					{
						item.playerHealth.HealOther(__instance.healAmount / list.Count, true);
					}
					else
					{
						item.playerHealth.HealOther(__instance.healAmount, true);
					}
					TeamHealsPlugin.Log.LogInfo((object)($"Healed player {item.photonView.ViewID} for {__instance.healAmount} HP " + $"(used by {val2.photonView.ViewID})"));
				}
			}
			TeamHealsPlugin.Log.LogInfo((object)$"Health pack used by player {val2.photonView.ViewID} healed all teammates");
		}
	}
	internal static class PlayerReviveHealthPatch
	{
		[HarmonyPatch(typeof(PlayerAvatar), "ReviveRPC")]
		[HarmonyPostfix]
		private static void ReviveRPC_Postfix(PlayerAvatar __instance)
		{
			int num = Configuration.CustomReviveHealthAmount.Value - 1;
			if (__instance.photonView.IsMine)
			{
				if (Configuration.EnableFullReviveHealth.Value)
				{
					int num2 = TeamHealsPlugin.health_ref.Invoke(__instance.playerHealth);
					num = TeamHealsPlugin.max_health_ref.Invoke(__instance.playerHealth) - num2;
				}
				__instance.playerHealth.HealOther(num, true);
				TeamHealsPlugin.Log.LogInfo((object)$"Player revived with custom health: {num}");
			}
		}
	}
	[HarmonyPatch(typeof(PunManager), "ShopPopulateItemVolumes", new Type[] { })]
	internal class PunManagerPatch
	{
		private static readonly FieldRef<PunManager, ShopManager> shop_manager_ref = AccessTools.FieldRefAccess<PunManager, ShopManager>("shopManager");

		public static bool Prefix(PunManager __instance)
		{
			List<Item> list = new HashSet<Item>(shop_manager_ref.Invoke(__instance).potentialItemHealthPacks).Distinct().ToList();
			for (int i = 0; i < list.Count; i++)
			{
				string itemName = Regex.Replace(list[i].itemName, "\\d+", delegate(Match match)
				{
					int num = int.Parse(match.Value);
					return ((int)Math.Ceiling((float)num * Configuration.HealAmountMultiplier.Value)).ToString();
				});
				list[i].itemName = itemName;
			}
			return true;
		}
	}
}
namespace BetterHeals.Config
{
	internal class Configuration
	{
		public static ConfigEntry<bool> EnableFullHealthAtStartPatch;

		public static ConfigEntry<bool> EnableTeamHealthPackPatch;

		public static ConfigEntry<bool> EnableEqualSplitTeamHealth;

		public static ConfigEntry<bool> EnableHealthRegenPatch;

		public static ConfigEntry<int> HealthRegenAmount;

		public static ConfigEntry<int> CustomRegenIntervalAmount;

		public static ConfigEntry<float> HealAmountMultiplier;

		public static ConfigEntry<bool> EnableCustomReviveHealthPatch;

		public static ConfigEntry<int> CustomReviveHealthAmount;

		public static ConfigEntry<bool> EnableFullReviveHealth;

		public static ConfigEntry<bool> EnableExtractionHealPatch;

		public static ConfigEntry<bool> EnableExtractionFullHeal;

		public static ConfigEntry<int> ExtractionHealAmount;

		public static void Init(ConfigFile config)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Expected O, but got Unknown
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Expected O, but got Unknown
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Expected O, but got Unknown
			HealAmountMultiplier = config.Bind<float>("General Health Settings", "HealthPackAmountMultiplier", 1f, new ConfigDescription("Multiplier applied to the health pack healing", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
			EnableFullHealthAtStartPatch = config.Bind<bool>("General Health Settings", "EnableFullHealthAtStart", false, "If enabled, players will start the match with full health");
			EnableHealthRegenPatch = config.Bind<bool>("General Health Settings", "EnableHealthRegen", false, "If enabled, players will slowly regenerate health over time");
			HealthRegenAmount = config.Bind<int>("General Health Settings", "HealthRegenAmount", 10, new ConfigDescription("The amount of health players will regenerate each interval if the Health Regeneration patch is enabled", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
			CustomRegenIntervalAmount = config.Bind<int>("General Health Settings", "CustomRegenIntervalAmount", 60, new ConfigDescription("The interval in seconds at which players will regenerate health if the Health Regeneration patch is enabled", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 120), Array.Empty<object>()));
			EnableExtractionHealPatch = config.Bind<bool>("General Health Settings", "EnableExtractionHeal", true, "Enable the patch that heals alive players after extraction is completed");
			EnableExtractionFullHeal = config.Bind<bool>("General Health Settings", "ExtractionFullHeal", false, "If enabled, players will be healed to full health upon extraction regardless of the ExtractionHealAmount setting");
			ExtractionHealAmount = config.Bind<int>("General Health Settings", "ExtractionHealAmount", 20, new ConfigDescription("The health that alive players receive after extraction is completed", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 200), Array.Empty<object>()));
			EnableTeamHealthPackPatch = config.Bind<bool>("Team Health Settings", "EnableTeamHealthPack", true, "Enable the patch that allows health packs to heal all teammates");
			EnableEqualSplitTeamHealth = config.Bind<bool>("Team Health Settings", "EnableEqualSplitTeamHealth", false, "If enabled, health packs will split their healing amount equally among all teammates");
			EnableCustomReviveHealthPatch = config.Bind<bool>("Team Health Settings", "EnableCustomReviveHealth", true, "Enable the patch that sets a custom value to the health received when a player is revived");
			EnableFullReviveHealth = config.Bind<bool>("Team Health Settings", "FullReviveHealth", false, "If enabled, players will be revived with full health regardless of the CustomReviveHealthAmount setting");
			CustomReviveHealthAmount = config.Bind<int>("Team Health Settings", "CustomReviveHealthAmount", 20, new ConfigDescription("The health that a player receives upon revival", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
		}
	}
}