Decompiled source of HellRevival v0.1.6

BepInEx/plugins/HellRevival/HellRevival.dll

Decompiled 15 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace HellRevival;

[BepInPlugin("AngelcoMilk.HellRevival", "HellRevival", "0.1.6")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "AngelcoMilk.HellRevival";

	public const string PluginName = "HellRevival";

	public const string PluginVersion = "0.1.6";

	internal static Plugin Instance;

	internal static ManualLogSource Log;

	private Harmony _harmony;

	private float _nextCapabilityPublishTime;

	private void Awake()
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		ModConfig.Bind(((BaseUnityPlugin)this).Config);
		InstantReviveController.Reset();
		ClientReviveProtection.Reset();
		MultiplayerCapabilitySync.PublishLocalCapabilities();
		_harmony = new Harmony("AngelcoMilk.HellRevival");
		_harmony.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"HellRevival 0.1.6 loaded.");
	}

	private void Update()
	{
		if (!(Time.time < _nextCapabilityPublishTime))
		{
			_nextCapabilityPublishTime = Time.time + 10f;
			MultiplayerCapabilitySync.PublishLocalCapabilities();
		}
	}

	private void OnDestroy()
	{
		InstantReviveController.Reset();
		ClientReviveProtection.Reset();
		MultiplayerCapabilitySync.Clear();
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
			_harmony = null;
		}
		if ((Object)(object)Instance == (Object)(object)this)
		{
			Instance = null;
		}
	}

	internal Coroutine StartReviveRoutine(IEnumerator routine)
	{
		return ((MonoBehaviour)this).StartCoroutine(routine);
	}

	internal Coroutine StartProtectionRoutine(PlayerAvatar player)
	{
		return ((MonoBehaviour)this).StartCoroutine(ClientReviveProtection.Run(player));
	}

	internal Coroutine StartProtectionRoutine(PlayerAvatar player, bool hasExpectedPosition, Vector3 expectedPosition, string reason)
	{
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		return ((MonoBehaviour)this).StartCoroutine(ClientReviveProtection.Run(player, hasExpectedPosition, expectedPosition, reason));
	}
}
internal enum ClientProtectionMode
{
	Auto,
	Always,
	Off
}
internal enum ReviveTimingPolicy
{
	Auto,
	Instant,
	StableDelayed
}
internal enum UnknownClientPolicy
{
	HostLocal,
	StableDelayed
}
internal static class ModConfig
{
	private const float FixedStableDelayedReviveDelay = 0.75f;

	private const float FixedProtectionWindow = 0.75f;

	internal static ConfigEntry<bool> EnableInstantExtractionRevive;

	internal static ConfigEntry<int> ReviveHealth;

	internal static void Bind(ConfigFile config)
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Expected O, but got Unknown
		EnableInstantExtractionRevive = config.Bind<bool>("General", "Enable Automatic Revive", true, "Host/singleplayer only. Automatically revives a player when their death head enters the extraction point.");
		ReviveHealth = config.Bind<int>("General", "Revive Health", 20, new ConfigDescription("Health after an extraction revive. Vanilla extraction revive gives 1 health; 20 matches More-Revive-HP's default +19 behavior.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), new object[0]));
	}

	internal static float SafeProtectionWindow()
	{
		return 0.75f;
	}

	internal static float SafeStableDelayedReviveDelay()
	{
		return 0.75f;
	}

	internal static ReviveTimingPolicy SafeReviveTimingPolicy()
	{
		return ReviveTimingPolicy.Auto;
	}

	internal static UnknownClientPolicy SafeUnknownClientPolicy()
	{
		return UnknownClientPolicy.HostLocal;
	}

	internal static bool ShouldOverrideReviveHealth()
	{
		return true;
	}

	internal static int SafeMinimumReviveHealth(int maxHealth)
	{
		int num = ((ReviveHealth == null) ? 20 : ReviveHealth.Value);
		return Mathf.Clamp(num, 1, Mathf.Max(1, maxHealth));
	}

	internal static bool IsDebugLoggingEnabled()
	{
		return false;
	}

	internal static ReviveTimingPolicy EffectiveReviveTimingPolicy(PlayerAvatar targetPlayer, out string reason)
	{
		ReviveTimingPolicy reviveTimingPolicy = SafeReviveTimingPolicy();
		if (reviveTimingPolicy != 0)
		{
			reason = "forced=" + reviveTimingPolicy;
			return reviveTimingPolicy;
		}
		return MultiplayerCapabilitySync.ResolveReviveTimingFor(targetPlayer, out reason);
	}

	internal static ReviveTimingPolicy EffectiveReviveTimingPolicy()
	{
		string reason;
		return EffectiveReviveTimingPolicy(null, out reason);
	}
}
internal static class InstantReviveController
{
	private sealed class ReviveState
	{
		internal bool ReviveCompleted;

		internal bool WasTriggered;

		internal bool ReviveQueued;

		internal float FirstInsideTime;

		internal float LastAttemptTime;

		internal float NextDebugLogTime;
	}

	private const float RetryIntervalSeconds = 0.12f;

	private const float DebugLogIntervalSeconds = 0.75f;

	private static readonly Dictionary<int, ReviveState> States = new Dictionary<int, ReviveState>();

	private static readonly Collider[] OverlapBuffer = (Collider[])(object)new Collider[32];

	private static readonly FieldInfo PlayerDeathHeadTriggeredField = typeof(PlayerDeathHead).GetField("triggered", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerDeathHeadRoomVolumeCheckField = typeof(PlayerDeathHead).GetField("roomVolumeCheck", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerDeathHeadInExtractionPointField = typeof(PlayerDeathHead).GetField("inExtractionPoint", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerDeathHeadPhysGrabObjectField = typeof(PlayerDeathHead).GetField("physGrabObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerAvatarDeadSetField = typeof(PlayerAvatar).GetField("deadSet", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerAvatarIsDisabledField = typeof(PlayerAvatar).GetField("isDisabled", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo RoomVolumeCheckInExtractionPointField = typeof(RoomVolumeCheck).GetField("inExtractionPoint", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo RoomVolumeCheckMaskField = typeof(RoomVolumeCheck).GetField("Mask", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo RoundDirectorExtractionPointCurrentField = typeof(RoundDirector).GetField("extractionPointCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo RoundDirectorExtractionPointListField = typeof(RoundDirector).GetField("extractionPointList", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	internal static void Reset()
	{
		States.Clear();
	}

	internal static void ProcessHead(PlayerDeathHead head)
	{
		if (!IsHostOrSingleplayer() || ModConfig.EnableInstantExtractionRevive == null || !ModConfig.EnableInstantExtractionRevive.Value || (Object)(object)head == (Object)null)
		{
			return;
		}
		int instanceID = ((Object)head).GetInstanceID();
		if (!States.TryGetValue(instanceID, out var value))
		{
			value = new ReviveState();
			States[instanceID] = value;
		}
		if (!IsTriggered(head))
		{
			value.ReviveCompleted = false;
			value.WasTriggered = false;
			value.ReviveQueued = false;
			value.FirstInsideTime = 0f;
			value.LastAttemptTime = 0f;
			return;
		}
		if (!value.WasTriggered)
		{
			value.ReviveCompleted = false;
			value.WasTriggered = true;
			value.ReviveQueued = false;
			value.FirstInsideTime = 0f;
			value.LastAttemptTime = 0f;
		}
		if (value.ReviveCompleted || value.ReviveQueued)
		{
			return;
		}
		if (!IsInsideExtractionPoint(head, out var roomCheckInside, out var headInside, out var fallbackInside))
		{
			value.FirstInsideTime = 0f;
			DebugLogState(value, "Revive scan: host=True, triggered=True, roomCheck=" + roomCheckInside + ", headField=" + headInside + ", fallback=" + fallbackInside + ", inside=False.");
			return;
		}
		if (value.FirstInsideTime <= 0f)
		{
			value.FirstInsideTime = Time.time;
		}
		if (value.LastAttemptTime > 0f && Time.time - value.LastAttemptTime < 0.12f)
		{
			return;
		}
		if (value.LastAttemptTime <= 0f)
		{
			value.NextDebugLogTime = 0f;
		}
		value.LastAttemptTime = Time.time;
		string reason;
		ReviveTimingPolicy reviveTimingPolicy = ModConfig.EffectiveReviveTimingPolicy(head.playerAvatar, out reason);
		DebugLogState(value, string.Concat("Revive timing selected: policy=", reviveTimingPolicy, ", reason=", reason, ", target=", MultiplayerCapabilitySync.DescribePlayer(head.playerAvatar), ", firstInsideAge=", (Time.time - value.FirstInsideTime).ToString("F3"), "."));
		if (reviveTimingPolicy == ReviveTimingPolicy.Instant || (Object)(object)Plugin.Instance == (Object)null)
		{
			ExecuteReviveAttempt(head, value, roomCheckInside, headInside, fallbackInside, "instant revive", requirePreflight: false, reviveTimingPolicy, reason);
			return;
		}
		value.ReviveQueued = true;
		DebugLogState(value, string.Concat("queued stable delayed revive. policy=", reviveTimingPolicy, ", repoFidelityLoaded=", ClientReviveProtection.IsREPOFidelityLoaded(), ", delay=", ModConfig.SafeStableDelayedReviveDelay().ToString("F2"), ", roomCheck=", roomCheckInside, ", headField=", headInside, ", fallback=", fallbackInside, "."));
		try
		{
			Plugin.Instance.StartReviveRoutine(RunStableDelayedRevive(head, value));
		}
		catch (Exception ex)
		{
			value.ReviveQueued = false;
			LogException("queue stable delayed revive", ex);
		}
	}

	private static IEnumerator RunStableDelayedRevive(PlayerDeathHead head, ReviveState state)
	{
		float reviveAllowedAt = Time.time + ModConfig.SafeStableDelayedReviveDelay();
		bool roomCheckInside;
		bool headInside;
		bool fallbackInside;
		while (true)
		{
			if ((Object)(object)head == (Object)null || ModConfig.EnableInstantExtractionRevive == null || !ModConfig.EnableInstantExtractionRevive.Value || !IsHostOrSingleplayer() || !IsTriggered(head) || (Object)(object)head.playerAvatar == (Object)null)
			{
				if (state != null)
				{
					state.ReviveQueued = false;
				}
				DebugLog("stable delayed revive aborted before attempt; death head/player state changed.");
				yield break;
			}
			if (!IsInsideExtractionPoint(head, out roomCheckInside, out headInside, out fallbackInside))
			{
				if (state != null)
				{
					state.ReviveQueued = false;
					state.FirstInsideTime = 0f;
				}
				DebugLogState(state, "stable delayed revive aborted; head left extraction point. roomCheck=" + roomCheckInside + ", headField=" + headInside + ", fallback=" + fallbackInside + ".");
				yield break;
			}
			string currentPolicyReason;
			ReviveTimingPolicy currentPolicy = ModConfig.EffectiveReviveTimingPolicy(head.playerAvatar, out currentPolicyReason);
			if (currentPolicy == ReviveTimingPolicy.Instant)
			{
				if (state != null)
				{
					state.ReviveQueued = false;
				}
				DebugLogState(state, "stable delayed revive switched to instant after policy refresh. reason=" + currentPolicyReason + ".");
				ExecuteReviveAttempt(head, state, roomCheckInside, headInside, fallbackInside, "instant revive after policy refresh", requirePreflight: false, currentPolicy, currentPolicyReason);
				yield break;
			}
			if (Time.time < reviveAllowedAt)
			{
				DebugLogState(state, "stable delayed revive waiting for delay. remaining=" + Mathf.Max(0f, reviveAllowedAt - Time.time).ToString("F2") + ", roomCheck=" + roomCheckInside + ", headField=" + headInside + ".");
				yield return null;
			}
			else
			{
				if (ReviveDiagnostics.AreCriticalDependenciesReadyNoLog(head.playerAvatar))
				{
					break;
				}
				DebugLogState(state, "stable delayed revive waiting; vanilla ReviveRPC dependencies are not ready. " + ReviveDiagnostics.BuildReadinessReport(head.playerAvatar, ready: false));
				yield return null;
			}
		}
		if (state != null)
		{
			state.ReviveQueued = false;
		}
		ExecuteReviveAttempt(policy: ModConfig.EffectiveReviveTimingPolicy(head.playerAvatar, out var policyReason), head: head, state: state, roomCheckInside: roomCheckInside, headInside: headInside, fallbackInside: fallbackInside, stage: "stable delayed revive", requirePreflight: false, policyReason: policyReason);
	}

	private static void ExecuteReviveAttempt(PlayerDeathHead head, ReviveState state, bool roomCheckInside, bool headInside, bool fallbackInside, string stage, bool requirePreflight, ReviveTimingPolicy policy, string policyReason)
	{
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)head == (Object)null || (Object)(object)head.playerAvatar == (Object)null)
		{
			return;
		}
		PlayerAvatar playerAvatar = head.playerAvatar;
		Vector3 position;
		bool hasExpectedPosition = TryGetExpectedRevivePosition(head, out position);
		if (requirePreflight && !ReviveDiagnostics.AreCriticalDependenciesReady(playerAvatar, stage + " preflight"))
		{
			DebugLogState(state, stage + ": revive delayed; vanilla ReviveRPC dependencies are not ready.");
			return;
		}
		SetExtractionState(head, value: true);
		if (TryRevive(head, state, roomCheckInside, headInside, fallbackInside, stage, hasExpectedPosition, position, policy, policyReason, out var hadException, out var _) && !hadException && state != null)
		{
			state.ReviveCompleted = true;
		}
		if (hadException)
		{
			DebugLogState(state, stage + ": skipped post revive protection because vanilla ReviveRPC threw.");
		}
	}

	private static bool TryRevive(PlayerDeathHead head, ReviveState state, bool roomCheckInside, bool headInside, bool fallbackInside, string stage, bool hasExpectedPosition, Vector3 expectedPosition, ReviveTimingPolicy policy, string policyReason, out bool hadException, out bool successObserved)
	{
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		hadException = false;
		successObserved = false;
		try
		{
			float num = ((state == null || state.FirstInsideTime <= 0f) ? 0f : (Time.time - state.FirstInsideTime));
			DebugLogState(state, string.Concat(stage, ": revive attempt. effectivePolicy=", policy, ", policyReason=", policyReason, ", target=", MultiplayerCapabilitySync.DescribePlayer(head.playerAvatar), ", firstInsideAge=", num.ToString("F3"), ", roomCheck=", roomCheckInside, ", headFieldBefore=", headInside, ", fallback=", fallbackInside, ", expectedPosition=", FormatExpectedPosition(hasExpectedPosition, expectedPosition), ", repoFidelityProtection=", ClientReviveProtection.WouldRunFor(head.playerAvatar), "."));
			head.Revive();
			successObserved = HasReviveClearlySucceeded(head);
			bool flag = IsMultiplayer();
			DebugLogState(state, stage + ": revive returned. multiplayer=" + flag + ", headFieldAfter=" + GetBool(PlayerDeathHeadInExtractionPointField, head, defaultValue: false, "PlayerDeathHead.inExtractionPoint") + ", triggeredAfter=" + IsTriggered(head) + ", playerDeadSet=" + GetPlayerBool(head, PlayerAvatarDeadSetField, "PlayerAvatar.deadSet") + ", playerIsDisabled=" + GetPlayerBool(head, PlayerAvatarIsDisabledField, "PlayerAvatar.isDisabled") + ", successObserved=" + successObserved + ".");
			return successObserved || flag;
		}
		catch (Exception ex)
		{
			hadException = true;
			LogException(stage + " revive attempt", ex);
			successObserved = HasReviveClearlySucceeded(head);
			DebugLogState(state, stage + ": revive threw. triggeredAfter=" + IsTriggered(head) + ", playerDeadSet=" + GetPlayerBool(head, PlayerAvatarDeadSetField, "PlayerAvatar.deadSet") + ", playerIsDisabled=" + GetPlayerBool(head, PlayerAvatarIsDisabledField, "PlayerAvatar.isDisabled") + ", successObservedAfterException=" + successObserved + ".");
			return successObserved;
		}
	}

	private static bool IsTriggered(PlayerDeathHead head)
	{
		return GetBool(PlayerDeathHeadTriggeredField, head, defaultValue: false, "PlayerDeathHead.triggered");
	}

	private static bool IsInsideExtractionPoint(PlayerDeathHead head, out bool roomCheckInside, out bool headInside, out bool fallbackInside)
	{
		roomCheckInside = false;
		headInside = GetBool(PlayerDeathHeadInExtractionPointField, head, defaultValue: false, "PlayerDeathHead.inExtractionPoint");
		fallbackInside = false;
		RoomVolumeCheck roomVolumeCheck = GetRoomVolumeCheck(head);
		if ((Object)(object)roomVolumeCheck != (Object)null && RoomVolumeCheckInExtractionPointField != null)
		{
			roomCheckInside = GetBool(RoomVolumeCheckInExtractionPointField, roomVolumeCheck, defaultValue: false, "RoomVolumeCheck.inExtractionPoint");
			if (roomCheckInside)
			{
				return true;
			}
		}
		if (IsFallbackExtractionDetectionEnabled())
		{
			fallbackInside = IsIndependentlyInsideExtractionPoint(head, roomVolumeCheck);
		}
		if (!headInside)
		{
			return fallbackInside;
		}
		return true;
	}

	private static bool HasReviveClearlySucceeded(PlayerDeathHead head)
	{
		if ((Object)(object)head == (Object)null)
		{
			return true;
		}
		PlayerAvatar playerAvatar = head.playerAvatar;
		if ((Object)(object)playerAvatar == (Object)null)
		{
			return !IsTriggered(head);
		}
		bool @bool = GetBool(PlayerAvatarDeadSetField, playerAvatar, defaultValue: true, "PlayerAvatar.deadSet");
		bool bool2 = GetBool(PlayerAvatarIsDisabledField, playerAvatar, defaultValue: true, "PlayerAvatar.isDisabled");
		if (!@bool)
		{
			return !bool2;
		}
		return false;
	}

	private static RoomVolumeCheck GetRoomVolumeCheck(PlayerDeathHead head)
	{
		if (PlayerDeathHeadRoomVolumeCheckField == null || (Object)(object)head == (Object)null)
		{
			return null;
		}
		try
		{
			object? value = PlayerDeathHeadRoomVolumeCheckField.GetValue(head);
			return (RoomVolumeCheck)((value is RoomVolumeCheck) ? value : null);
		}
		catch (Exception ex)
		{
			DebugLog("PlayerDeathHead.roomVolumeCheck read failed: " + ex.Message);
			return null;
		}
	}

	private static bool IsIndependentlyInsideExtractionPoint(PlayerDeathHead head, RoomVolumeCheck check)
	{
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)check != (Object)null)
		{
			Vector3 val = check.currentSize;
			if (val == Vector3.zero)
			{
				val = ((Component)check).transform.localScale;
			}
			LayerMask roomVolumeMask = GetRoomVolumeMask(check);
			Vector3 val2 = ((Component)check).transform.position + ((Component)check).transform.rotation * check.CheckPosition;
			int count = Physics.OverlapBoxNonAlloc(val2, val * 0.5f, OverlapBuffer, ((Component)check).transform.rotation, LayerMask.op_Implicit(roomVolumeMask), (QueryTriggerInteraction)2);
			if (AnyExtractionRoomVolume(count))
			{
				return IsNearKnownExtractionPoint(((Component)head).transform.position);
			}
			return false;
		}
		int count2 = Physics.OverlapSphereNonAlloc(((Component)head).transform.position, 0.35f, OverlapBuffer, -1, (QueryTriggerInteraction)2);
		if (AnyExtractionRoomVolume(count2))
		{
			return IsNearKnownExtractionPoint(((Component)head).transform.position);
		}
		return false;
	}

	private static bool IsFallbackExtractionDetectionEnabled()
	{
		return false;
	}

	private static bool IsNearKnownExtractionPoint(Vector3 position)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if ((Object)(object)RoundDirector.instance == (Object)null)
			{
				return false;
			}
			ExtractionPoint field = GetField<ExtractionPoint>(RoundDirectorExtractionPointCurrentField, RoundDirector.instance);
			if ((Object)(object)field != (Object)null && Vector3.Distance(position, ((Component)field).transform.position) <= 12f)
			{
				return true;
			}
			List<GameObject> field2 = GetField<List<GameObject>>(RoundDirectorExtractionPointListField, RoundDirector.instance);
			if (field2 == null)
			{
				return false;
			}
			for (int i = 0; i < field2.Count; i++)
			{
				GameObject val = field2[i];
				if ((Object)(object)val != (Object)null && Vector3.Distance(position, val.transform.position) <= 12f)
				{
					return true;
				}
			}
		}
		catch (Exception ex)
		{
			DebugLog("Known extraction point fallback check failed: " + ex.Message);
		}
		return false;
	}

	private static LayerMask GetRoomVolumeMask(RoomVolumeCheck check)
	{
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)check != (Object)null && RoomVolumeCheckMaskField != null)
		{
			object value = RoomVolumeCheckMaskField.GetValue(check);
			if (value is LayerMask)
			{
				return (LayerMask)value;
			}
		}
		return LayerMask.op_Implicit(-1);
	}

	private static bool AnyExtractionRoomVolume(int count)
	{
		for (int i = 0; i < count; i++)
		{
			Collider val = OverlapBuffer[i];
			if (!((Object)(object)val == (Object)null))
			{
				RoomVolume val2 = ((Component)val).GetComponent<RoomVolume>();
				if ((Object)(object)val2 == (Object)null)
				{
					val2 = ((Component)val).GetComponentInParent<RoomVolume>();
				}
				if ((Object)(object)val2 != (Object)null && val2.Extraction)
				{
					return true;
				}
			}
		}
		return false;
	}

	private static void SetExtractionState(PlayerDeathHead head, bool value)
	{
		SetBool(PlayerDeathHeadInExtractionPointField, head, value, "PlayerDeathHead.inExtractionPoint");
		RoomVolumeCheck roomVolumeCheck = GetRoomVolumeCheck(head);
		if ((Object)(object)roomVolumeCheck != (Object)null)
		{
			SetBool(RoomVolumeCheckInExtractionPointField, roomVolumeCheck, value, "RoomVolumeCheck.inExtractionPoint");
		}
	}

	private static bool TryGetExpectedRevivePosition(PlayerDeathHead head, out Vector3 position)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		position = Vector3.zero;
		if ((Object)(object)head == (Object)null)
		{
			return false;
		}
		try
		{
			PhysGrabObject physGrabObject = GetPhysGrabObject(head);
			if ((Object)(object)physGrabObject != (Object)null)
			{
				position = physGrabObject.centerPoint - Vector3.up * 0.25f;
				return true;
			}
		}
		catch (Exception ex)
		{
			DebugLog("Expected revive position from death head PhysGrabObject failed: " + ex.Message);
		}
		position = ((Component)head).transform.position;
		return true;
	}

	private static PhysGrabObject GetPhysGrabObject(PlayerDeathHead head)
	{
		if (PlayerDeathHeadPhysGrabObjectField == null || (Object)(object)head == (Object)null)
		{
			return null;
		}
		try
		{
			object? value = PlayerDeathHeadPhysGrabObjectField.GetValue(head);
			return (PhysGrabObject)((value is PhysGrabObject) ? value : null);
		}
		catch (Exception ex)
		{
			DebugLog("PlayerDeathHead.physGrabObject read failed: " + ex.Message);
			return null;
		}
	}

	private static bool IsHostOrSingleplayer()
	{
		try
		{
			return SemiFunc.IsMasterClientOrSingleplayer();
		}
		catch
		{
			return true;
		}
	}

	private static bool IsMultiplayer()
	{
		try
		{
			return GameManager.Multiplayer();
		}
		catch
		{
			return false;
		}
	}

	private static bool GetBool(FieldInfo field, object instance, bool defaultValue, string name)
	{
		if (field == null || instance == null)
		{
			return defaultValue;
		}
		try
		{
			object value = field.GetValue(instance);
			if (value is bool)
			{
				return (bool)value;
			}
		}
		catch (Exception ex)
		{
			DebugLog(name + " read failed: " + ex.Message);
		}
		return defaultValue;
	}

	internal static T GetField<T>(FieldInfo field, object instance) where T : class
	{
		if (field == null || instance == null)
		{
			return null;
		}
		try
		{
			return field.GetValue(instance) as T;
		}
		catch (Exception ex)
		{
			DebugLog("Field read failed: " + ex.Message);
			return null;
		}
	}

	private static string GetPlayerBool(PlayerDeathHead head, FieldInfo field, string name)
	{
		PlayerAvatar val = (((Object)(object)head == (Object)null) ? null : head.playerAvatar);
		if (field == null || (Object)(object)val == (Object)null)
		{
			return "unknown";
		}
		try
		{
			object value = field.GetValue(val);
			if (value is bool)
			{
				return ((bool)value).ToString();
			}
		}
		catch (Exception ex)
		{
			DebugLog(name + " read failed: " + ex.Message);
		}
		return "unknown";
	}

	private static void SetBool(FieldInfo field, object instance, bool value, string name)
	{
		if (field == null || instance == null)
		{
			DebugLog(name + " is not available; vanilla Revive may no-op if the game field is still false.");
			return;
		}
		try
		{
			field.SetValue(instance, value);
		}
		catch (Exception ex)
		{
			LogException(name + " write", ex);
		}
	}

	private static void DebugLogState(ReviveState state, string message)
	{
		if (ModConfig.IsDebugLoggingEnabled() && state != null && !(Time.time < state.NextDebugLogTime))
		{
			state.NextDebugLogTime = Time.time + 0.75f;
			Plugin.Log.LogInfo((object)message);
		}
	}

	private static void DebugLog(string message)
	{
		if (ModConfig.IsDebugLoggingEnabled())
		{
			Plugin.Log.LogInfo((object)message);
		}
	}

	private static string FormatExpectedPosition(bool hasExpectedPosition, Vector3 expectedPosition)
	{
		if (!hasExpectedPosition)
		{
			return "unknown";
		}
		return "(" + expectedPosition.x.ToString("F2") + ", " + expectedPosition.y.ToString("F2") + ", " + expectedPosition.z.ToString("F2") + ")";
	}

	internal static void LogException(string stage, Exception ex)
	{
		if (Plugin.Log == null)
		{
			return;
		}
		Exception ex2 = ex;
		if (ex is TargetInvocationException && ex.InnerException != null)
		{
			ex2 = ex.InnerException;
		}
		Plugin.Log.LogWarning((object)(stage + " failed: " + ex2.GetType().Name + ": " + ex2.Message));
		if (ModConfig.IsDebugLoggingEnabled())
		{
			if (ex2.StackTrace != null)
			{
				Plugin.Log.LogWarning((object)(stage + " stack: " + ex2.StackTrace));
			}
			if (!object.ReferenceEquals(ex2, ex))
			{
				Plugin.Log.LogWarning((object)(stage + " wrapper: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
}
internal static class ClientReviveProtection
{
	internal const string REPOFidelityGuid = "Vippy.REPOFidelity";

	private static readonly FieldInfo SpectateCameraMainCameraField = typeof(SpectateCamera).GetField("MainCamera", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo SpectateCameraParentObjectField = typeof(SpectateCamera).GetField("ParentObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo SpectateCameraPreviousParentField = typeof(SpectateCamera).GetField("PreviousParent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo AudioManagerAudioListenerField = typeof(AudioManager).GetField("AudioListener", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerAvatarIsLocalField = typeof(PlayerAvatar).GetField("isLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	internal static void Reset()
	{
	}

	internal static void OnReviveRpc(PlayerAvatar player)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		if (WouldRunFor(player) && !((Object)(object)Plugin.Instance == (Object)null))
		{
			Plugin.Instance.StartProtectionRoutine(player, hasExpectedPosition: false, Vector3.zero, "post revive rpc protection");
		}
	}

	internal static IEnumerator Run(PlayerAvatar player)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		return Run(player, hasExpectedPosition: false, Vector3.zero, "post revive protection");
	}

	internal static IEnumerator Run(PlayerAvatar player, bool hasExpectedPosition, Vector3 expectedPosition, string reason)
	{
		if (WouldRunFor(player))
		{
			float endTime = Time.time + ModConfig.SafeProtectionWindow();
			DebugLog("Starting local " + reason + ".");
			yield return null;
			while (Time.time <= endTime)
			{
				ApplyLocalFixes(player);
				yield return null;
			}
			ApplyLocalFixes(player);
			DebugLog("Finished local " + reason + ".");
		}
	}

	internal static bool WouldRunFor(PlayerAvatar player)
	{
		if ((Object)(object)player == (Object)null || !IsLocalPlayer(player))
		{
			return false;
		}
		return IsREPOFidelityLoaded();
	}

	private static bool IsLocalPlayer(PlayerAvatar player)
	{
		try
		{
			if (PlayerAvatarIsLocalField != null)
			{
				object value = PlayerAvatarIsLocalField.GetValue(player);
				if (value is bool)
				{
					return (bool)value;
				}
			}
		}
		catch
		{
		}
		return (Object)(object)player == (Object)(object)PlayerAvatar.instance;
	}

	internal static bool IsREPOFidelityLoaded()
	{
		try
		{
			return Chainloader.PluginInfos.ContainsKey("Vippy.REPOFidelity");
		}
		catch
		{
			return false;
		}
	}

	private static void ApplyLocalFixes(PlayerAvatar player)
	{
		if ((Object)(object)player == (Object)null)
		{
			player = PlayerAvatar.instance;
		}
		InspectSpectateCamera(SpectateCamera.instance);
		if ((Object)(object)player != (Object)null && (Object)(object)player.localCamera != (Object)null)
		{
			SafeCall((Action)player.localCamera.Teleported, "PlayerLocalCamera.Teleported");
		}
		EnsureObject("CameraZoom.Instance", (Object)(object)CameraZoom.Instance);
		EnsureObject("PostProcessing.Instance", (Object)(object)PostProcessing.Instance);
		EnsureObject("AudioManager.instance", (Object)(object)AudioManager.instance);
		if ((Object)(object)AudioManager.instance != (Object)null)
		{
			EnsureObject("AudioManager.AudioListener", (Object)(object)GetField<AudioListenerFollow>(AudioManagerAudioListenerField, AudioManager.instance));
		}
	}

	private static void InspectSpectateCamera(SpectateCamera spectate)
	{
		if ((Object)(object)spectate == (Object)null)
		{
			DebugLog("SpectateCamera.instance is not ready during local revive protection.");
			return;
		}
		EnsureObject("SpectateCamera.MainCamera", (Object)(object)GetField<Camera>(SpectateCameraMainCameraField, spectate));
		EnsureObject("SpectateCamera.ParentObject", (Object)(object)GetField<Transform>(SpectateCameraParentObjectField, spectate));
		EnsureObject("SpectateCamera.PreviousParent", (Object)(object)GetField<Transform>(SpectateCameraPreviousParentField, spectate));
		EnsureObject("SpectateCamera.normalTransformPivot", (Object)(object)spectate.normalTransformPivot);
	}

	private static void SafeCall(Action action, string name)
	{
		try
		{
			action();
		}
		catch (Exception ex)
		{
			DebugLog(name + " failed: " + ex.Message);
		}
	}

	private static T GetField<T>(FieldInfo field, object instance) where T : class
	{
		if (field == null || instance == null)
		{
			return null;
		}
		try
		{
			return field.GetValue(instance) as T;
		}
		catch (Exception ex)
		{
			DebugLog("Field read failed: " + ex.Message);
			return null;
		}
	}

	private static void EnsureObject(string name, Object obj)
	{
		if (obj == (Object)null)
		{
			DebugLog(name + " is not ready during local revive protection.");
		}
	}

	private static void DebugLog(string message)
	{
		if (ModConfig.IsDebugLoggingEnabled())
		{
			Plugin.Log.LogInfo((object)message);
		}
	}
}
internal static class ReviveHealthController
{
	private static readonly FieldInfo PlayerHealthHealthField = typeof(PlayerHealth).GetField("health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerHealthMaxHealthField = typeof(PlayerHealth).GetField("maxHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	internal static void OnReviveRpc(PlayerAvatar player, bool revivedByTruck)
	{
		if (!ModConfig.ShouldOverrideReviveHealth() || (Object)(object)player == (Object)null || (Object)(object)player.playerHealth == (Object)null || !IsHostOrSingleplayer())
		{
			return;
		}
		if (revivedByTruck)
		{
			DebugLog("Skipped revive health override for truck revive.");
			return;
		}
		int num = Mathf.Max(1, GetPlayerHealthInt(PlayerHealthMaxHealthField, player.playerHealth, 100, "PlayerHealth.maxHealth"));
		int num2 = ModConfig.SafeMinimumReviveHealth(num);
		int num3 = Mathf.Max(0, num2 - 1);
		if (num3 <= 0)
		{
			DebugLog("Skipped revive health override because configured target is vanilla revive health.");
			return;
		}
		int playerHealthInt = GetPlayerHealthInt(PlayerHealthHealthField, player.playerHealth, 0, "PlayerHealth.health");
		if (IsOwnedByLocalClient(player) && playerHealthInt >= num2)
		{
			DebugLog("Skipped revive health override because local owner health is already " + playerHealthInt + ".");
			return;
		}
		try
		{
			player.playerHealth.HealOther(num3, true);
			DebugLog("Applied revive health override. target=" + num2 + ", extra=" + num3 + ", max=" + num + ", player=" + MultiplayerCapabilitySync.DescribePlayer(player) + ".");
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("HellRevival revive health override failed: " + ex.Message));
		}
	}

	private static int GetPlayerHealthInt(FieldInfo field, PlayerHealth playerHealth, int defaultValue, string name)
	{
		if (field == null || (Object)(object)playerHealth == (Object)null)
		{
			return defaultValue;
		}
		try
		{
			object value = field.GetValue(playerHealth);
			if (value is int)
			{
				return (int)value;
			}
		}
		catch (Exception ex)
		{
			DebugLog(name + " read failed: " + ex.Message);
		}
		return defaultValue;
	}

	private static bool IsHostOrSingleplayer()
	{
		try
		{
			return SemiFunc.IsMasterClientOrSingleplayer();
		}
		catch
		{
			try
			{
				return !GameManager.Multiplayer() || PhotonNetwork.IsMasterClient;
			}
			catch
			{
				return true;
			}
		}
	}

	private static bool IsOwnedByLocalClient(PlayerAvatar player)
	{
		try
		{
			return (Object)(object)player.photonView == (Object)null || player.photonView.IsMine;
		}
		catch
		{
			return false;
		}
	}

	private static void DebugLog(string message)
	{
		if (ModConfig.IsDebugLoggingEnabled())
		{
			Plugin.Log.LogInfo((object)message);
		}
	}
}
internal static class MultiplayerCapabilitySync
{
	private const byte HasModKey = 201;

	private const byte ModVersionKey = 202;

	private const byte HasREPOFidelityKey = 203;

	internal static void Clear()
	{
	}

	internal static void PublishLocalCapabilities()
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Expected O, but got Unknown
		try
		{
			if (GameManager.Multiplayer() && PhotonNetwork.LocalPlayer != null)
			{
				Hashtable val = new Hashtable();
				val[(byte)201] = true;
				val[(byte)202] = "0.1.6";
				val[(byte)203] = ClientReviveProtection.IsREPOFidelityLoaded();
				PhotonNetwork.LocalPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
				DebugLog("Published multiplayer capabilities: hasMod=True, version=0.1.6, hasREPOFidelity=" + ClientReviveProtection.IsREPOFidelityLoaded() + ".");
			}
		}
		catch (Exception ex)
		{
			DebugLog("Publish multiplayer capabilities failed: " + ex.Message);
		}
	}

	internal static ReviveTimingPolicy ResolveReviveTimingFor(PlayerAvatar targetPlayer, out string reason)
	{
		Player photonPlayer = GetPhotonPlayer(targetPlayer);
		if (photonPlayer == null)
		{
			ReviveTimingPolicy reviveTimingPolicy = LocalPolicy();
			reason = string.Concat("noPhotonPlayer; localPolicy=", reviveTimingPolicy, ", localRepoFidelity=", ClientReviveProtection.IsREPOFidelityLoaded());
			return reviveTimingPolicy;
		}
		bool value;
		bool known = TryGetBool(photonPlayer, 201, out value);
		bool value2;
		bool flag = TryGetBool(photonPlayer, 203, out value2);
		string @string = GetString(photonPlayer, 202);
		if (flag && value2)
		{
			reason = "targetRepoFidelity=True, targetHasMod=" + FormatKnownBool(known, value) + ", targetVersion=" + FormatString(@string);
			return ReviveTimingPolicy.StableDelayed;
		}
		if (flag && !value2)
		{
			reason = "targetRepoFidelity=False, targetHasMod=" + FormatKnownBool(known, value) + ", targetVersion=" + FormatString(@string);
			return ReviveTimingPolicy.Instant;
		}
		if (photonPlayer.IsLocal)
		{
			ReviveTimingPolicy reviveTimingPolicy2 = LocalPolicy();
			reason = string.Concat("targetLocalNoReport; localPolicy=", reviveTimingPolicy2, ", localRepoFidelity=", ClientReviveProtection.IsREPOFidelityLoaded());
			return reviveTimingPolicy2;
		}
		if (ModConfig.SafeUnknownClientPolicy() == UnknownClientPolicy.StableDelayed)
		{
			reason = "targetReportUnknown; unknownClientPolicy=StableDelayed, targetHasMod=" + FormatKnownBool(known, value) + ", targetVersion=" + FormatString(@string);
			return ReviveTimingPolicy.StableDelayed;
		}
		ReviveTimingPolicy reviveTimingPolicy3 = LocalPolicy();
		reason = string.Concat("targetReportUnknown; unknownClientPolicy=HostLocal, hostPolicy=", reviveTimingPolicy3, ", targetHasMod=", FormatKnownBool(known, value), ", targetVersion=", FormatString(@string));
		return reviveTimingPolicy3;
	}

	internal static string DescribePlayer(PlayerAvatar targetPlayer)
	{
		Player photonPlayer = GetPhotonPlayer(targetPlayer);
		if (photonPlayer == null)
		{
			return "unknown";
		}
		return "actor=" + photonPlayer.ActorNumber + ", nick=" + FormatString(photonPlayer.NickName);
	}

	private static ReviveTimingPolicy LocalPolicy()
	{
		if (!ClientReviveProtection.IsREPOFidelityLoaded())
		{
			return ReviveTimingPolicy.Instant;
		}
		return ReviveTimingPolicy.StableDelayed;
	}

	private static Player GetPhotonPlayer(PlayerAvatar targetPlayer)
	{
		try
		{
			if (!GameManager.Multiplayer())
			{
				return PhotonNetwork.LocalPlayer;
			}
			if ((Object)(object)targetPlayer == (Object)null)
			{
				return PhotonNetwork.LocalPlayer;
			}
			if ((Object)(object)targetPlayer.photonView != (Object)null && targetPlayer.photonView.Owner != null)
			{
				return targetPlayer.photonView.Owner;
			}
			if ((Object)(object)targetPlayer == (Object)(object)PlayerAvatar.instance)
			{
				return PhotonNetwork.LocalPlayer;
			}
		}
		catch (Exception ex)
		{
			DebugLog("Resolve target Photon player failed: " + ex.Message);
		}
		return null;
	}

	private static bool TryGetBool(Player player, byte key, out bool value)
	{
		value = false;
		try
		{
			if (player == null || player.CustomProperties == null || !player.CustomProperties.ContainsKey(key))
			{
				return false;
			}
			object obj = player.CustomProperties[key];
			if (obj is bool)
			{
				value = (bool)obj;
				return true;
			}
		}
		catch (Exception ex)
		{
			DebugLog("Read multiplayer capability bool failed: " + ex.Message);
		}
		return false;
	}

	private static string GetString(Player player, byte key)
	{
		try
		{
			if (player == null || player.CustomProperties == null || !player.CustomProperties.ContainsKey(key))
			{
				return null;
			}
			return player.CustomProperties[key] as string;
		}
		catch (Exception ex)
		{
			DebugLog("Read multiplayer capability string failed: " + ex.Message);
			return null;
		}
	}

	private static string FormatKnownBool(bool known, bool value)
	{
		if (!known)
		{
			return "unknown";
		}
		return value.ToString();
	}

	private static string FormatString(string value)
	{
		if (!string.IsNullOrEmpty(value))
		{
			return value;
		}
		return "unknown";
	}

	private static void DebugLog(string message)
	{
		if (ModConfig.IsDebugLoggingEnabled())
		{
			Plugin.Log.LogInfo((object)message);
		}
	}
}
internal static class ReviveDiagnostics
{
	private static readonly FieldInfo PlayerAvatarPlayerDeathHeadField = typeof(PlayerAvatar).GetField("playerDeathHead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerAvatarPlayerAvatarCollisionField = typeof(PlayerAvatar).GetField("playerAvatarCollision", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerAvatarIsLocalField = typeof(PlayerAvatar).GetField("isLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	private static readonly FieldInfo PlayerDeathHeadPhysGrabObjectField = typeof(PlayerDeathHead).GetField("physGrabObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

	internal static bool AreCriticalDependenciesReady(PlayerAvatar player, string reason)
	{
		PlayerDeathHead deathHead = GetDeathHead(player);
		bool flag = (Object)(object)player != (Object)null && (Object)(object)deathHead != (Object)null && (Object)(object)GetPhysGrabObject(deathHead) != (Object)null && (Object)(object)player.playerHealth != (Object)null && (Object)(object)player.playerAvatarVisuals != (Object)null && (Object)(object)player.playerDeathEffects != (Object)null && (Object)(object)player.playerReviveEffects != (Object)null && (Object)(object)GetAvatarCollision(player) != (Object)null && (Object)(object)player.RoomVolumeCheck != (Object)null;
		if ((Object)(object)player != (Object)null && IsLocalPlayer(player))
		{
			flag = flag && (Object)(object)player.playerTransform != (Object)null && (Object)(object)player.playerTransform.parent != (Object)null && (Object)(object)CameraAim.Instance != (Object)null && (Object)(object)CameraPosition.instance != (Object)null && (Object)(object)GameDirector.instance != (Object)null && (Object)(object)SpectateCamera.instance != (Object)null && (Object)(object)PlayerController.instance != (Object)null && (Object)(object)CameraGlitch.Instance != (Object)null;
		}
		if (ModConfig.IsDebugLoggingEnabled())
		{
			Plugin.Log.LogInfo((object)(reason + ": " + BuildReadinessReport(player, flag)));
		}
		return flag;
	}

	internal static void LogReviveRpcPrefix(PlayerAvatar player)
	{
		if (ModConfig.IsDebugLoggingEnabled())
		{
			Plugin.Log.LogInfo((object)("ReviveRPC prefix: " + BuildReadinessReport(player, AreCriticalDependenciesReadyNoLog(player))));
		}
	}

	internal static bool AreCriticalDependenciesReadyNoLog(PlayerAvatar player)
	{
		if ((Object)(object)player == (Object)null || (Object)(object)player.playerHealth == (Object)null || (Object)(object)player.playerAvatarVisuals == (Object)null || (Object)(object)player.playerDeathEffects == (Object)null || (Object)(object)player.playerReviveEffects == (Object)null || (Object)(object)player.RoomVolumeCheck == (Object)null)
		{
			return false;
		}
		PlayerDeathHead deathHead = GetDeathHead(player);
		if ((Object)(object)deathHead == (Object)null || (Object)(object)GetPhysGrabObject(deathHead) == (Object)null || (Object)(object)GetAvatarCollision(player) == (Object)null)
		{
			return false;
		}
		if (!IsLocalPlayer(player))
		{
			return true;
		}
		if ((Object)(object)player.playerTransform != (Object)null && (Object)(object)player.playerTransform.parent != (Object)null && (Object)(object)CameraAim.Instance != (Object)null && (Object)(object)CameraPosition.instance != (Object)null && (Object)(object)GameDirector.instance != (Object)null && (Object)(object)SpectateCamera.instance != (Object)null && (Object)(object)PlayerController.instance != (Object)null)
		{
			return (Object)(object)CameraGlitch.Instance != (Object)null;
		}
		return false;
	}

	internal static string BuildReadinessReport(PlayerAvatar player, bool ready)
	{
		if ((Object)(object)player == (Object)null)
		{
			return "ready=False, playerAvatar=null.";
		}
		PlayerDeathHead deathHead = GetDeathHead(player);
		bool flag = IsLocalPlayer(player);
		string text = (((Object)(object)player.playerTransform == (Object)null) ? "unknown" : FormatReady((Object)(object)player.playerTransform.parent != (Object)null));
		return "ready=" + ready + ", isLocal=" + flag + ", playerDeathHead=" + FormatReady((Object)(object)deathHead != (Object)null) + ", physGrabObject=" + FormatReady((Object)(object)deathHead != (Object)null && (Object)(object)GetPhysGrabObject(deathHead) != (Object)null) + ", playerHealth=" + FormatReady((Object)(object)player.playerHealth != (Object)null) + ", playerAvatarVisuals=" + FormatReady((Object)(object)player.playerAvatarVisuals != (Object)null) + ", playerDeathEffects=" + FormatReady((Object)(object)player.playerDeathEffects != (Object)null) + ", playerReviveEffects=" + FormatReady((Object)(object)player.playerReviveEffects != (Object)null) + ", playerAvatarCollision=" + FormatReady((Object)(object)GetAvatarCollision(player) != (Object)null) + ", playerTransform=" + FormatReady((Object)(object)player.playerTransform != (Object)null) + ", playerTransform.parent=" + text + ", CameraAim.Instance=" + FormatReady((Object)(object)CameraAim.Instance != (Object)null) + ", CameraPosition.instance=" + FormatReady((Object)(object)CameraPosition.instance != (Object)null) + ", GameDirector.instance=" + FormatReady((Object)(object)GameDirector.instance != (Object)null) + ", SpectateCamera.instance=" + FormatReady((Object)(object)SpectateCamera.instance != (Object)null) + ", PlayerController.instance=" + FormatReady((Object)(object)PlayerController.instance != (Object)null) + ", CameraGlitch.Instance=" + FormatReady((Object)(object)CameraGlitch.Instance != (Object)null) + ", RoomVolumeCheck=" + FormatReady((Object)(object)player.RoomVolumeCheck != (Object)null) + ".";
	}

	private static bool IsLocalPlayer(PlayerAvatar player)
	{
		if ((Object)(object)player == (Object)null)
		{
			return false;
		}
		try
		{
			if (PlayerAvatarIsLocalField != null)
			{
				object value = PlayerAvatarIsLocalField.GetValue(player);
				if (value is bool)
				{
					return (bool)value;
				}
			}
		}
		catch
		{
		}
		return (Object)(object)player == (Object)(object)PlayerAvatar.instance;
	}

	private static PlayerDeathHead GetDeathHead(PlayerAvatar player)
	{
		return InstantReviveController.GetField<PlayerDeathHead>(PlayerAvatarPlayerDeathHeadField, player);
	}

	private static PhysGrabObject GetPhysGrabObject(PlayerDeathHead head)
	{
		return InstantReviveController.GetField<PhysGrabObject>(PlayerDeathHeadPhysGrabObjectField, head);
	}

	private static PlayerAvatarCollision GetAvatarCollision(PlayerAvatar player)
	{
		return InstantReviveController.GetField<PlayerAvatarCollision>(PlayerAvatarPlayerAvatarCollisionField, player);
	}

	private static string FormatReady(bool ready)
	{
		if (!ready)
		{
			return "null";
		}
		return "ok";
	}
}
[HarmonyPatch(typeof(PlayerDeathHead), "Update")]
internal static class PlayerDeathHeadUpdatePatch
{
	private static void Postfix(PlayerDeathHead __instance)
	{
		InstantReviveController.ProcessHead(__instance);
	}
}
[HarmonyPatch(typeof(PlayerAvatar), "ReviveRPC")]
internal static class PlayerAvatarReviveRpcPatch
{
	private static void Prefix(PlayerAvatar __instance)
	{
		ReviveDiagnostics.LogReviveRpcPrefix(__instance);
	}

	private static void Postfix(PlayerAvatar __instance, bool _revivedByTruck)
	{
		ReviveHealthController.OnReviveRpc(__instance, _revivedByTruck);
		ClientReviveProtection.OnReviveRpc(__instance);
	}
}
[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
internal static class RunManagerChangeLevelPatch
{
	private static void Prefix()
	{
		InstantReviveController.Reset();
		ClientReviveProtection.Reset();
		MultiplayerCapabilitySync.PublishLocalCapabilities();
	}
}