using 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+fc858f24aa0a9890a2fa402bf499e3760b334511")]
[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
{
[PunRPC]
public void SyncRevivedHealth(int actorNumber, float health)
{
PlayerAvatar avatarByActorNumber = MedicHeadReviver.GetAvatarByActorNumber(actorNumber);
if ((Object)(object)avatarByActorNumber != (Object)null)
{
MedicHeadReviver.SetHealth(avatarByActorNumber, health);
}
}
[PunRPC]
public void SyncReviverHealth(int actorNumber, float health)
{
PlayerAvatar avatarByActorNumber = MedicHeadReviver.GetAvatarByActorNumber(actorNumber);
if ((Object)(object)avatarByActorNumber != (Object)null)
{
MedicHeadReviver.SetHealth(avatarByActorNumber, health);
}
}
[PunRPC]
public void NotifyRevived(int actorNumber)
{
((MonoBehaviour)this).StartCoroutine(NotifyRevivedDelayed(actorNumber));
}
private IEnumerator NotifyRevivedDelayed(int actorNumber)
{
yield return null;
yield return null;
yield return (object)new WaitForSeconds(0.2f);
PlayerAvatar local = MedicHeadReviver.GetLocalAvatar();
if ((Object)(object)local != (Object)null && local.photonView.IsMine && local.photonView.OwnerActorNr == actorNumber)
{
MedicHeadReviver.SendRevivedChatMessage(local);
}
}
}
[BepInPlugin("com.callmehill.medic", "Medic Mod", "1.1.3")]
public class MedicPlugin : BaseUnityPlugin
{
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();
}
private IEnumerator BindDeniedSound()
{
while ((Object)(object)MenuManager.instance == (Object)null)
{
yield return null;
}
Sound sound = MenuManager.instance.soundDeny;
if (sound != null && sound.Sounds != null && sound.Sounds.Length != 0)
{
DeniedSoundClip = sound.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));
}
}
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>();
}
}
}
internal class MedicHeadReviver : MonoBehaviourPun
{
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 float _lastAvatarCheckTime = -999f;
private static readonly Dictionary<int, float> _healthCache = new Dictionary<int, float>();
private static float _lastHealthCheckTime = -999f;
private static MethodInfo? _playerChatMessageSendMethod;
internal static PlayerAvatar? GetLocalAvatar()
{
if (Time.time - _lastAvatarCheckTime < 1f && (Object)(object)_cachedLocalAvatar != (Object)null)
{
return _cachedLocalAvatar;
}
_lastAvatarCheckTime = Time.time;
PlayerAvatar[] array = Object.FindObjectsOfType<PlayerAvatar>();
foreach (PlayerAvatar val in array)
{
if (val.photonView.IsMine)
{
_cachedLocalAvatar = val;
return val;
}
}
_cachedLocalAvatar = null;
return null;
}
internal static PlayerAvatar? GetAvatarByActorNumber(int actorNumber)
{
PlayerAvatar[] array = Object.FindObjectsOfType<PlayerAvatar>();
foreach (PlayerAvatar val in array)
{
if (val.photonView.OwnerActorNr == actorNumber)
{
return val;
}
}
return null;
}
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 (Time.time - _lastHealthCheckTime < 0.5f && _healthCache.TryGetValue(instanceID, out var value))
{
return value;
}
float health = GetHealth(avatar);
_healthCache[instanceID] = health;
_lastHealthCheckTime = 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)
{
try
{
object value = Convert.ChangeType(amount, HealthField.FieldType);
HealthField.SetValue(val, value);
HealthSetField?.SetValue(val, true);
}
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);
}
}
}