Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BetterHeals v2.0.1
BetterHeals.dll
Decompiled 5 months agousing 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(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace BetterHeals { [BepInPlugin("MrBytesized.REPO.BetterHeals", "Better Heals", "2.1.0")] 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.0"; 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"); harmony.PatchAll(typeof(TeamHealsPlugin)); harmony.PatchAll(typeof(PunManagerPatch)); Log.LogInfo((object)"PunManager patch applied."); harmony.PatchAll(typeof(ItemHealthPackPatch)); Log.LogInfo((object)"ItemHealthPack patch applied."); 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 i = 0; i < array.Length; i++) { var (configEntry, enablePatch, disablePatch, description) = array[i]; 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) { if (configEntry.Value) { enablePatch(); Log.LogInfo((object)(description + " patch enabled.")); } else { disablePatch(); Log.LogInfo((object)(description + " patch disabled.")); } } } } 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_008e: Unknown result type (might be due to invalid IL or missing references) RunManager instance = RunManager.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelMainMenu) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelLobbyMenu) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelLobby) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelShop) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelRecording) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelSplashScreen) && PhotonNetwork.IsMasterClient) { new GameObject("HealCoroutineRunner").AddComponent<HealRunner>(); } } } public class HealRunner : MonoBehaviour { [CompilerGenerated] private sealed class <HealAllPlayersToFull>d__1 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public HealRunner <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <HealAllPlayersToFull>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown int num = <>1__state; HealRunner healRunner = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; case 1: { <>1__state = -1; 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)healRunner).gameObject); return false; } 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)healRunner).gameObject); return false; } foreach (PlayerAvatar item in playerList) { if ((Object)(object)item != (Object)null && (Object)(object)item.playerHealth != (Object)null) { int num2 = (int)field.GetValue(item.playerHealth); int num3 = (int)field2.GetValue(item.playerHealth); int num4 = num3 - num2; TeamHealsPlugin.Log.LogInfo((object)$"Player {item.photonView.Owner.NickName} current health: {num2}, max health: {num3}, calculated heal amount: {num4}"); if (num4 > 0) { item.playerHealth.HealOther(num4, true); TeamHealsPlugin.Log.LogInfo((object)$"Healed player {item.photonView.Owner.NickName} to full health: {num3}"); } } } Object.Destroy((Object)(object)((Component)healRunner).gameObject); return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private void Awake() { ((MonoBehaviour)this).StartCoroutine(HealAllPlayersToFull()); } [IteratorStateMachine(typeof(<HealAllPlayersToFull>d__1))] private IEnumerator HealAllPlayersToFull() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <HealAllPlayersToFull>d__1(0) { <>4__this = this }; } } public class RegenUpdater : MonoBehaviour { private float updateThrottle; public void Init() { } private void Update() { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.levelCurrent == (Object)(object)instance.levelMainMenu || (Object)(object)instance.levelCurrent == (Object)(object)instance.levelLobbyMenu || (Object)(object)instance.levelCurrent == (Object)(object)instance.levelLobby || (Object)(object)instance.levelCurrent == (Object)(object)instance.levelShop || (Object)(object)instance.levelCurrent == (Object)(object)instance.levelRecording || (Object)(object)instance.levelCurrent == (Object)(object)instance.levelSplashScreen || !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(PlayerController __instance) { RunManager instance = RunManager.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelMainMenu) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelLobbyMenu) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelLobby) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelShop) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelRecording) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelSplashScreen) && (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient) && (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")] 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>())); } } }