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 Medic v1.1.4
plugins/callmehill-Medic/Medic!.dll
Decompiled 9 months agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Medic!")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+068df2e1f8d23c6cfdc691f2f04f8fb680a31ff3")] [assembly: AssemblyProduct("Medic!")] [assembly: AssemblyTitle("Medic!")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace Medic_ { public sealed class AvatarReviveRelay : MonoBehaviourPun { [CompilerGenerated] private sealed class <NotifyRevivedDelayed>d__4 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int actorNumber; public AvatarReviveRelay <>4__this; private PlayerAvatar <local>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <NotifyRevivedDelayed>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <local>5__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = null; <>1__state = 2; return true; case 2: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.2f); <>1__state = 3; return true; case 3: <>1__state = -1; <local>5__1 = MedicHeadReviver.GetLocalAvatar(); if ((Object)(object)<local>5__1 != (Object)null && <local>5__1.photonView.IsMine && <local>5__1.photonView.OwnerActorNr == actorNumber) { MedicHeadReviver.SendRevivedChatMessage(<local>5__1); } 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(); } } [PunRPC] public void SyncHealth(int actorNumber, float health) { PlayerAvatar avatarByActorNumber = MedicHeadReviver.GetAvatarByActorNumber(actorNumber); if ((Object)(object)avatarByActorNumber != (Object)null) { MedicHeadReviver.SetHealth(avatarByActorNumber, health); } } [PunRPC] public void SyncRevivedHealth(int actorNumber, float health) { SyncHealth(actorNumber, health); } [PunRPC] public void SyncReviverHealth(int actorNumber, float health) { SyncHealth(actorNumber, health); } [PunRPC] public void NotifyRevived(int actorNumber) { ((MonoBehaviour)this).StartCoroutine(NotifyRevivedDelayed(actorNumber)); } [IteratorStateMachine(typeof(<NotifyRevivedDelayed>d__4))] private IEnumerator NotifyRevivedDelayed(int actorNumber) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <NotifyRevivedDelayed>d__4(0) { <>4__this = this, actorNumber = actorNumber }; } } [BepInPlugin("com.callmehill.medic", "Medic Mod", "1.1.4")] public class MedicPlugin : BaseUnityPlugin { [CompilerGenerated] private sealed class <BindDeniedSound>d__26 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public MedicPlugin <>4__this; private Sound <sound>5__1; object? IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <BindDeniedSound>d__26(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <sound>5__1 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if ((Object)(object)MenuManager.instance == (Object)null) { <>2__current = null; <>1__state = 1; return true; } <sound>5__1 = MenuManager.instance.soundDeny; if (<sound>5__1 != null && <sound>5__1.Sounds != null && <sound>5__1.Sounds.Length != 0) { DeniedSoundClip = <sound>5__1.Sounds[0]; } if ((Object)(object)DeniedSoundClip == (Object)null && DeniedSoundNativeName != null) { DeniedSoundClip = ((IEnumerable<AudioClip>)Resources.FindObjectsOfTypeAll<AudioClip>()).FirstOrDefault((Func<AudioClip, bool>)((AudioClip c) => ((Object)c).name == DeniedSoundNativeName.Value)); } 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(); } } internal static bool IsHoldingPlayerDeathHead; internal static string? PlayerDeathHeadOwner; internal static PlayerDeathHead? HeldPlayerDeathHead; internal static ConfigEntry<float>? HealthTransferAmount; internal static ConfigEntry<bool>? EnableGratitude; internal static ConfigEntry<string>? DeniedSoundNativeName; internal static AudioClip? DeniedSoundClip; internal static ConfigEntry<float>? DenyActionCooldown; private static float _lastDenySoundTime = -999f; private static AudioSource? _localAudioSource; private static float _lastAudioTime = -999f; internal static ConfigEntry<bool>? EnableFailedReviveMessage; internal static ConfigEntry<string>? FailedReviveMessage; internal static readonly List<string> FailedRevivePhrases = new List<string>(); private static readonly string[] DefaultFailedPhrases = new string[5] { "I don't have enough health to revive you!", "Need more health to revive!", "Can't revive - low on health!", "Not enough HP to help you up!", "Too weak to revive you right now!" }; internal static ConfigEntry<float>? ChatCooldownDuration; internal static float _lastFailedReviveMessageTime = -999f; internal static ConfigEntry<string>? PhrasesFileName; internal static ConfigEntry<string>? FallbackPhrases; internal static readonly List<string> GratitudePhrases = new List<string>(); private static readonly string[] DefaultPhrases = new string[5] { "Wow! Thank you, friend!", "You saved me!", "Back in the fight!", "Clutch revive!", "I owe you one!" }; private static readonly Random s_rng = new Random(); internal static string? PluginDirectory; private static bool _initialized; private Harmony? _harmony; private void Awake() { //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Expected O, but got Unknown if (_initialized) { Object.Destroy((Object)(object)this); return; } _initialized = true; PluginDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Medic! mod activated."); HealthTransferAmount = ((BaseUnityPlugin)this).Config.Bind<float>("General", "HealthTransferAmount", 20f, "Amount of health transferred from reviver to revivee."); EnableGratitude = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "EnableGratitude", true, "Enable or disable the revive gratitude chat message."); PhrasesFileName = ((BaseUnityPlugin)this).Config.Bind<string>("Chat", "PhrasesFile", "gratitude_phrases.txt", "Text file name to load phrases from (one phrase per line). Searched in the plugin directory and BepInEx/config."); FallbackPhrases = ((BaseUnityPlugin)this).Config.Bind<string>("Chat", "FallbackPhrases", "Wow! Thank you, friend!|You saved me!|I owe you one!|Back in the fight!|That was clutch!", "Fallback phrases separated by |, used if the phrases file is missing or empty."); DeniedSoundNativeName = ((BaseUnityPlugin)this).Config.Bind<string>("Sound", "DeniedSoundNativeName", "soundDeny", "Native AudioClip name played when chat input exceeds the character limit."); EnableFailedReviveMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "EnableFailedReviveMessage", true, "Enable or disable the failed revive chat message."); FailedReviveMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Chat", "FailedReviveMessage", "I don't have enough health to revive you!|Need more health to revive!|Can't revive - low on health!|Not enough HP to help you up!|Too weak to revive you right now!", "Failed revive messages separated by |, randomly selected when a revive fails due to low health."); ChatCooldownDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Chat", "ChatCooldownDuration", 1f, "Cooldown in seconds between failed revive chat messages to prevent spam."); DenyActionCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "DenyActionCooldown", 0.5f, "Cooldown in seconds between deny actions when health is insufficient."); LoadFailedRevivePhrases(); ((MonoBehaviour)this).StartCoroutine(BindDeniedSound()); LoadPhrases(); Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); _harmony = new Harmony("com.callmehill.medic"); _harmony.PatchAll(); } [IteratorStateMachine(typeof(<BindDeniedSound>d__26))] private IEnumerator BindDeniedSound() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <BindDeniedSound>d__26(0) { <>4__this = this }; } private void OnDestroy() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _initialized = false; GameObject val = new GameObject("MedicPlugin"); val.AddComponent<MedicPlugin>(); } internal static void PlayDenySound(Vector3 position) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown if ((Object)(object)DeniedSoundClip == (Object)null) { return; } float num = 0.3f; if (!(Time.time - _lastAudioTime < num)) { _lastAudioTime = Time.time; if ((Object)(object)_localAudioSource == (Object)null) { GameObject val = new GameObject("MedicAudio"); _localAudioSource = val.AddComponent<AudioSource>(); Object.DontDestroyOnLoad((Object)(object)val); } ((Component)_localAudioSource).transform.position = position; _localAudioSource.PlayOneShot(DeniedSoundClip); } } private void Update() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown((KeyCode)104)) { if ((Object)(object)HeldPlayerDeathHead == (Object)null) { return; } try { PlayerAvatar localAvatar = MedicHeadReviver.GetLocalAvatar(); if ((Object)(object)localAvatar == (Object)null) { return; } if (!MedicHeadReviver.HasRequiredHealth(localAvatar)) { float num = DenyActionCooldown?.Value ?? 0.5f; if (!(Time.time - _lastDenySoundTime < num)) { _lastDenySoundTime = Time.time; Vector3 position = ((Component)localAvatar).transform.position; PlayDenySound(position); MedicHeadReviver.SendFailedReviveMessage(localAvatar, HeldPlayerDeathHead); } return; } if (PhotonNetwork.IsMasterClient) { MedicHeadReviver.ReviveHead(HeldPlayerDeathHead, localAvatar); } else { MedicHeadReviver component = ((Component)HeldPlayerDeathHead).GetComponent<MedicHeadReviver>(); if (component != null) { ((MonoBehaviourPun)component).photonView.RPC("RequestRevive", (RpcTarget)2, Array.Empty<object>()); } } } catch (Exception) { } } if (Input.GetKey((KeyCode)306) && Input.GetKeyDown((KeyCode)112)) { LoadPhrases(); } } internal static void LoadPhrases() { try { GratitudePhrases.Clear(); string text = PhrasesFileName?.Value ?? "gratitude_phrases.txt"; List<string> list = new List<string>(); if (!string.IsNullOrEmpty(PluginDirectory)) { list.Add(Path.Combine(PluginDirectory, text)); } list.Add(Path.Combine(Paths.ConfigPath, text)); if (File.Exists(text)) { list.Add(text); } foreach (string item2 in list) { if (!File.Exists(item2)) { continue; } string[] array = File.ReadAllLines(item2); foreach (string text2 in array) { string text3 = (text2 ?? string.Empty).Trim(); if (text3.Length != 0 && !text3.StartsWith("#")) { string item = ((text3.Length > 160) ? text3.Substring(0, 160).TrimEnd() : text3); GratitudePhrases.Add(item); } } if (GratitudePhrases.Count > 0) { return; } } string text4 = FallbackPhrases?.Value ?? string.Empty; string[] array2 = text4.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text5 in array2) { string text6 = text5.Trim(); if (text6.Length > 0) { GratitudePhrases.Add(text6); } } if (GratitudePhrases.Count == 0) { GratitudePhrases.AddRange(DefaultPhrases); } } catch (Exception) { if (GratitudePhrases.Count == 0) { GratitudePhrases.AddRange(DefaultPhrases); } } } internal static string GetRandomGratitude() { if (GratitudePhrases.Count == 0) { LoadPhrases(); } int index = s_rng.Next(GratitudePhrases.Count); return GratitudePhrases[index]; } internal static void LoadFailedRevivePhrases() { try { FailedRevivePhrases.Clear(); string text = FailedReviveMessage?.Value ?? string.Empty; string[] array = text.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { string text3 = text2.Trim(); if (text3.Length > 0) { FailedRevivePhrases.Add(text3); } } if (FailedRevivePhrases.Count == 0) { FailedRevivePhrases.AddRange(DefaultFailedPhrases); } } catch (Exception) { if (FailedRevivePhrases.Count == 0) { FailedRevivePhrases.AddRange(DefaultFailedPhrases); } } } internal static string GetRandomFailedReviveMessage() { if (FailedRevivePhrases.Count == 0) { LoadFailedRevivePhrases(); } int index = s_rng.Next(FailedRevivePhrases.Count); return FailedRevivePhrases[index]; } } [HarmonyPatch(typeof(PhysGrabber), "PhysGrabStarted")] internal class PhysGrabberStartedPatch { private static readonly FieldInfo GrabbedPhysGrabObjectField = AccessTools.Field(typeof(PhysGrabber), "grabbedPhysGrabObject"); private static readonly FieldInfo PlayerAvatarField = AccessTools.Field(typeof(PlayerDeathHead), "playerAvatar"); private static readonly FieldInfo PlayerNameField = AccessTools.Field(typeof(PlayerAvatar), "playerName"); private static void Postfix(PhysGrabber __instance) { object? value = GrabbedPhysGrabObjectField.GetValue(__instance); PhysGrabObject val = (PhysGrabObject)((value is PhysGrabObject) ? value : null); MedicPlugin.IsHoldingPlayerDeathHead = false; MedicPlugin.PlayerDeathHeadOwner = null; MedicPlugin.HeldPlayerDeathHead = null; PlayerDeathHead val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<PlayerDeathHead>() : null); if ((Object)(object)val2 != (Object)null) { MedicPlugin.IsHoldingPlayerDeathHead = true; MedicPlugin.HeldPlayerDeathHead = val2; object? value2 = PlayerAvatarField.GetValue(val2); PlayerAvatar val3 = (PlayerAvatar)((value2 is PlayerAvatar) ? value2 : null); if ((Object)(object)val3 != (Object)null) { MedicPlugin.PlayerDeathHeadOwner = PlayerNameField.GetValue(val3) as string; } } } } [HarmonyPatch(typeof(PhysGrabber), "PhysGrabEnded")] internal class PhysGrabberEndedPatch { private static void Postfix() { MedicPlugin.IsHoldingPlayerDeathHead = false; MedicPlugin.PlayerDeathHeadOwner = null; MedicPlugin.HeldPlayerDeathHead = null; } } [HarmonyPatch(typeof(PlayerDeathHead), "Start")] internal class PlayerDeathHeadStartPatch { private static void Postfix(PlayerDeathHead __instance) { if ((Object)(object)((Component)__instance).GetComponent<MedicHeadReviver>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<MedicHeadReviver>(); } } } [HarmonyPatch(typeof(PlayerAvatar), "Awake")] internal static class PlayerAvatar_Awake_AddRelay { private static void Postfix(PlayerAvatar __instance) { if ((Object)(object)((Component)__instance).GetComponent<AvatarReviveRelay>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<AvatarReviveRelay>(); } MedicHeadReviver.RegisterAvatar(__instance); } } [HarmonyPatch(typeof(PlayerAvatar), "OnDestroy")] internal static class PlayerAvatar_OnDestroy_RemoveFromCache { private static void Prefix(PlayerAvatar __instance) { MedicHeadReviver.UnregisterAvatar(__instance); } } internal class MedicHeadReviver : MonoBehaviourPun { private struct CachedHealthEntry { public float Health; public float Timestamp; } private static readonly FieldInfo PlayerAvatarField = AccessTools.Field(typeof(PlayerDeathHead), "playerAvatar"); private static readonly FieldInfo PlayerNameField = AccessTools.Field(typeof(PlayerAvatar), "playerName"); private static readonly Type? PlayerHealthType = AccessTools.TypeByName("PlayerHealth"); private static readonly FieldInfo? HealthField = AccessTools.Field(PlayerHealthType, "health"); private static readonly FieldInfo? HealthSetField = AccessTools.Field(PlayerHealthType, "healthSet"); private static readonly FieldInfo? MaxHealthField = AccessTools.Field(PlayerHealthType, "maxHealth"); private static PlayerAvatar? _cachedLocalAvatar; private static readonly Dictionary<int, PlayerAvatar> _avatarsByActorNumber = new Dictionary<int, PlayerAvatar>(); private static readonly Dictionary<int, CachedHealthEntry> _healthCache = new Dictionary<int, CachedHealthEntry>(); private static MethodInfo? _playerChatMessageSendMethod; internal static PlayerAvatar? GetLocalAvatar() { CleanupAvatarCache(); if (TryGetPhotonView(_cachedLocalAvatar, out PhotonView view) && view.IsMine) { return _cachedLocalAvatar; } _cachedLocalAvatar = null; return ScanForLocalAvatar(); } internal static PlayerAvatar? GetAvatarByActorNumber(int actorNumber) { if (actorNumber <= 0) { return null; } CleanupAvatarCache(); if (_avatarsByActorNumber.TryGetValue(actorNumber, out PlayerAvatar value)) { if (TryGetPhotonView(value, out PhotonView view) && view.OwnerActorNr == actorNumber) { return value; } _avatarsByActorNumber.Remove(actorNumber); } return ScanForAvatarByActorNumber(actorNumber); } internal static void RegisterAvatar(PlayerAvatar avatar) { if (TryGetPhotonView(avatar, out PhotonView view)) { if (view.OwnerActorNr > 0) { _avatarsByActorNumber[view.OwnerActorNr] = avatar; } if (view.IsMine) { _cachedLocalAvatar = avatar; } } } internal static void UnregisterAvatar(PlayerAvatar avatar) { if ((Object)(object)avatar == (Object)null) { return; } if (_cachedLocalAvatar == avatar) { _cachedLocalAvatar = null; } List<int> list = new List<int>(); foreach (KeyValuePair<int, PlayerAvatar> item in _avatarsByActorNumber) { if (!TryGetPhotonView(item.Value, out PhotonView _) || item.Value == avatar) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { _avatarsByActorNumber.Remove(list[i]); } CleanupAvatarCache(); } private static void CleanupAvatarCache() { if ((Object)(object)_cachedLocalAvatar != (Object)null && (!TryGetPhotonView(_cachedLocalAvatar, out PhotonView view) || !view.IsMine)) { _cachedLocalAvatar = null; } if (_avatarsByActorNumber.Count == 0) { return; } List<int> list = new List<int>(); foreach (KeyValuePair<int, PlayerAvatar> item in _avatarsByActorNumber) { if (!TryGetPhotonView(item.Value, out PhotonView view2)) { list.Add(item.Key); } else if (view2.OwnerActorNr <= 0 || view2.OwnerActorNr != item.Key) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { _avatarsByActorNumber.Remove(list[i]); } } private static PlayerAvatar? ScanForLocalAvatar() { PlayerAvatar[] array = Object.FindObjectsOfType<PlayerAvatar>(); foreach (PlayerAvatar val in array) { RegisterAvatar(val); if (TryGetPhotonView(val, out PhotonView view) && view.IsMine) { return val; } } return null; } private static PlayerAvatar? ScanForAvatarByActorNumber(int actorNumber) { PlayerAvatar[] array = Object.FindObjectsOfType<PlayerAvatar>(); foreach (PlayerAvatar val in array) { RegisterAvatar(val); if (TryGetPhotonView(val, out PhotonView view) && view.OwnerActorNr == actorNumber) { return val; } } return null; } private static bool TryGetPhotonView(PlayerAvatar? avatar, out PhotonView view) { if ((Object)(object)avatar != (Object)null) { PhotonView photonView = avatar.photonView; if ((Object)(object)photonView != (Object)null) { view = photonView; return true; } } view = null; return false; } private static float GetHealth(PlayerAvatar avatar) { Component val = ((PlayerHealthType != null) ? ((Component)avatar).GetComponent(PlayerHealthType) : null); if ((Object)(object)val != (Object)null && HealthField != null) { object value = HealthField.GetValue(val); if (value != null) { return Convert.ToSingle(value); } } return 0f; } private static float GetCachedHealth(PlayerAvatar avatar) { int instanceID = ((Object)avatar).GetInstanceID(); if (_healthCache.TryGetValue(instanceID, out var value) && Time.time - value.Timestamp < 0.5f) { return value.Health; } float health = GetHealth(avatar); _healthCache[instanceID] = new CachedHealthEntry { Health = health, Timestamp = Time.time }; if (_healthCache.Count > 100) { _healthCache.Clear(); } return health; } internal static void SetHealth(PlayerAvatar avatar, float amount) { Component val = ((PlayerHealthType != null) ? ((Component)avatar).GetComponent(PlayerHealthType) : null); if (!((Object)(object)val != (Object)null) || !(HealthField != null)) { return; } try { object value = Convert.ChangeType(amount, HealthField.FieldType); HealthField.SetValue(val, value); HealthSetField?.SetValue(val, true); if (_healthCache.Count >= 100) { _healthCache.Clear(); } int instanceID = ((Object)avatar).GetInstanceID(); _healthCache[instanceID] = new CachedHealthEntry { Health = amount, Timestamp = Time.time }; } catch (InvalidCastException) { } } private static float GetMaxHealth(PlayerAvatar avatar) { Component val = ((PlayerHealthType != null) ? ((Component)avatar).GetComponent(PlayerHealthType) : null); if ((Object)(object)val != (Object)null && MaxHealthField != null) { object value = MaxHealthField.GetValue(val); if (value != null) { return Convert.ToSingle(value); } } return 100f; } private static void SendChatMessage(PlayerAvatar avatar, string message) { try { if ((object)_playerChatMessageSendMethod == null) { _playerChatMessageSendMethod = AccessTools.Method(typeof(PlayerAvatar), "ChatMessageSend", new Type[1] { typeof(string) }, (Type[])null); } if (!(_playerChatMessageSendMethod == null)) { _playerChatMessageSendMethod.Invoke(avatar, new object[1] { message }); } } catch (Exception) { } } internal static void SendRevivedChatMessage(PlayerAvatar avatar) { if (MedicPlugin.EnableGratitude == null || MedicPlugin.EnableGratitude.Value) { string randomGratitude = MedicPlugin.GetRandomGratitude(); SendChatMessage(avatar, randomGratitude); } } internal static void SendFailedReviveMessage(PlayerAvatar avatar, PlayerDeathHead? head) { if (MedicPlugin.EnableFailedReviveMessage == null || !MedicPlugin.EnableFailedReviveMessage.Value) { return; } float num = MedicPlugin.ChatCooldownDuration?.Value ?? 1f; if (Time.time - MedicPlugin._lastFailedReviveMessageTime < num) { return; } string text = MedicPlugin.GetRandomFailedReviveMessage(); if ((Object)(object)head != (Object)null) { object? value = PlayerAvatarField.GetValue(head); PlayerAvatar val = (PlayerAvatar)((value is PlayerAvatar) ? value : null); if ((Object)(object)val != (Object)null && PlayerNameField != null) { string text2 = PlayerNameField.GetValue(val) as string; if (!string.IsNullOrEmpty(text2)) { text = text2 + ", " + text.ToLower(); } } } MedicPlugin._lastFailedReviveMessageTime = Time.time; SendChatMessage(avatar, text); } internal static bool HasRequiredHealth(PlayerAvatar avatar) { float num = MedicPlugin.HealthTransferAmount?.Value ?? 20f; return GetCachedHealth(avatar) >= num + 1f; } internal static void ReviveHead(PlayerDeathHead? head, PlayerAvatar? reviver) { if ((Object)(object)head == (Object)null || (Object)(object)reviver == (Object)null) { return; } object? value = PlayerAvatarField.GetValue(head); PlayerAvatar val = (PlayerAvatar)((value is PlayerAvatar) ? value : null); if ((Object)(object)val == (Object)null) { return; } float num = MedicPlugin.HealthTransferAmount?.Value ?? 20f; float health = GetHealth(reviver); if (!(health < num + 1f)) { float num2 = health - num; SetHealth(reviver, num2); if ((Object)(object)reviver.photonView != (Object)null) { reviver.photonView.RPC("SyncReviverHealth", (RpcTarget)0, new object[2] { reviver.photonView.OwnerActorNr, num2 }); } val.photonView.RPC("ReviveRPC", (RpcTarget)0, new object[1] { true }); float maxHealth = GetMaxHealth(val); float num3 = Mathf.Min(num, maxHealth); SetHealth(val, num3); val.photonView.RPC("SyncRevivedHealth", (RpcTarget)0, new object[2] { val.photonView.OwnerActorNr, num3 }); val.photonView.RPC("NotifyRevived", val.photonView.Owner, new object[1] { val.photonView.OwnerActorNr }); } } [PunRPC] public void RequestRevive(PhotonMessageInfo info) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar avatarByActorNumber = GetAvatarByActorNumber(info.Sender.ActorNumber); ReviveHead(((Component)this).GetComponent<PlayerDeathHead>(), avatarByActorNumber); } } }