Decompiled source of balrond secondchance v1.0.17

plugins/BalrondSecondChance.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using Balrond.Shared;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using JetBrains.Annotations;
using LitJson2;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("BalrondSecondChance")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.17.0")]
[assembly: AssemblyInformationalVersion("1.0.17")]
[assembly: AssemblyProduct("BalrondSecondChance")]
[assembly: AssemblyTitle("BalrondSecondChance")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.17.0")]
[module: UnverifiableCode]
namespace BalrondReviveSoul
{
	internal static class BRSConfig
	{
		internal static ConfigEntry<bool> LockConfiguration;

		internal static ConfigEntry<bool> DebugLogging;

		internal static ConfigEntry<float> DownedSeconds;

		internal static ConfigEntry<bool> AutoReleaseWhenTimerEnds;

		internal static ConfigEntry<bool> AllowSelfPotionRevive;

		internal static ConfigEntry<bool> RequireItemForSelfRevive;

		internal static ConfigEntry<bool> AllowFriendRevive;

		internal static ConfigEntry<bool> RequirePotionForFriendRevive;

		internal static ConfigEntry<bool> EnableTimedFriendRevive;

		internal static ConfigEntry<float> FriendReviveHoldSeconds;

		internal static ConfigEntry<string> ResurrectionPotionPrefabName;

		internal static ConfigEntry<int> ResurrectionItemAmount;

		internal static ConfigEntry<float> ResurrectionCooldownMinutes;

		internal static ConfigEntry<float> ReviveHealthPercent;

		internal static ConfigEntry<float> ReviveDistance;

		internal static ConfigEntry<string> DownedPresentationMode;

		private static string _lastItemConfig;

		private static string[] _cachedItemPrefabs = new string[0];

		internal static string[] ResurrectionItemPrefabs
		{
			get
			{
				string text = ((ResurrectionPotionPrefabName != null) ? (ResurrectionPotionPrefabName.Value ?? string.Empty) : string.Empty);
				if (text == _lastItemConfig)
				{
					return _cachedItemPrefabs;
				}
				string[] array = text.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
				List<string> list = new List<string>(array.Length);
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				string[] array2 = array;
				foreach (string text2 in array2)
				{
					string text3 = text2.Trim();
					if (text3.Length > 0 && hashSet.Add(text3))
					{
						list.Add(text3);
					}
				}
				_cachedItemPrefabs = list.ToArray();
				_lastItemConfig = text;
				return _cachedItemPrefabs;
			}
		}

		internal static string ResurrectionItemPrefab
		{
			get
			{
				string[] resurrectionItemPrefabs = ResurrectionItemPrefabs;
				return (resurrectionItemPrefabs.Length != 0) ? resurrectionItemPrefabs[0] : string.Empty;
			}
		}

		internal static string ResurrectionItemOptionsDisplay => string.Join(" / ", ResurrectionItemPrefabs);

		internal static bool HasResurrectionItemConfigured => ResurrectionItemPrefabs.Length != 0;

		internal static bool RequireItemForSelfReviveEffective => RequireItemForSelfRevive != null && RequireItemForSelfRevive.Value && HasResurrectionItemConfigured;

		internal static bool RequireItemForFriendReviveEffective => RequirePotionForFriendRevive != null && RequirePotionForFriendRevive.Value && HasResurrectionItemConfigured;

		internal static void Init(Launch plugin)
		{
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Expected O, but got Unknown
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Expected O, but got Unknown
			LockConfiguration = plugin.config("0 General", "Lock Configuration", value: true, "If on, server values are enforced.");
			DebugLogging = plugin.config("0 General", "Debug Logging", value: false, "Local-only detailed logging for Downed/UI initialization, recovery and fallback. Not synchronized by ServerSync.", synchronizedSetting: false);
			DownedSeconds = plugin.config("0 General", "Downed Seconds", 120f, "How long player stays downed.");
			AutoReleaseWhenTimerEnds = plugin.config("0 General", "Auto Release When Timer Ends", value: true, "Auto death after timer.");
			ResurrectionCooldownMinutes = plugin.config("0 General", "Cooldown Minutes", 10f, "Cooldown after revive.");
			ReviveHealthPercent = plugin.config("0 General", "Revive Health Percent", 0.25f, "Health after revive.");
			ReviveDistance = plugin.config("0 General", "Revive Distance", 3f, "Max revive distance.");
			DownedPresentationMode = plugin.config("0 General", "Downed Presentation Mode", "Sit", new ConfigDescription("Server-synchronized pose/animation used for every player while downed.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[4] { "Sit", "Bed", "Dance", "Headbang" }), Array.Empty<object>()));
			AllowSelfPotionRevive = plugin.config("1 Self Revive", "Allow Self Revive", value: true, "Allow self revive.");
			RequireItemForSelfRevive = plugin.config("1 Self Revive", "Require Item For Self Revive", value: true, "Require item for self revive. If Resurrection Item Prefab is blank, item requirement is disabled.");
			AllowFriendRevive = plugin.config("2 Friend Revive", "Allow Friend Revive", value: true, "Allow other players to revive a downed player.");
			RequirePotionForFriendRevive = plugin.config("2 Friend Revive", "Require Item For Friend Revive", value: true, "Require item for reviving others. If Resurrection Item Prefab is blank, item requirement is disabled.");
			EnableTimedFriendRevive = plugin.config("2 Friend Revive", "Enable Timed Friend Revive", value: false, "Optional hold-to-revive with the Use key. Disabled by default; existing instant revival stays unchanged.");
			FriendReviveHoldSeconds = plugin.config("2 Friend Revive", "Friend Revive Hold Seconds", 4f, new ConfigDescription("Seconds to hold Use before completing a friend revive. 0 means instant, even when timed mode is enabled. Applies only to reviving other players, not self-revive.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>()));
			ResurrectionPotionPrefabName = plugin.config("3 Items", "Resurrection Item Prefab", "MeadHealthMinor", "Allowed item prefab IDs separated by commas or semicolons (OR logic). Only one type is needed in the configured amount. Leave blank to disable item requirement.");
			ResurrectionItemAmount = plugin.config("3 Items", "Resurrection Item Amount", 1, "Amount required.");
		}
	}
	[BepInPlugin("balrond.astafaraios.BalrondSecondChance", "BalrondSecondChance", "1.0.17")]
	public class Launch : BaseUnityPlugin
	{
		private readonly Harmony harmony = new Harmony("balrond.astafaraios.BalrondSecondChance");

		public const string PluginGUID = "balrond.astafaraios.BalrondSecondChance";

		public const string PluginName = "BalrondSecondChance";

		public const string PluginVersion = "1.0.17";

		public static JsonLoader jsonLoader = new JsonLoader();

		internal static Launch Instance;

		internal static Harmony Harmony;

		internal static bool AllowOriginalDeath;

		internal static readonly ConfigSync configSync = new ConfigSync("balrond.astafaraios.BalrondSecondChance")
		{
			DisplayName = "BalrondSecondChance",
			CurrentVersion = "1.0.17",
			MinimumRequiredVersion = "1.0.17"
		};

		internal static Launch _self;

		internal ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true)
		{
			ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description);
			SyncedConfigEntry<T> syncedConfigEntry = configSync.AddConfigEntry<T>(val);
			syncedConfigEntry.SynchronizedConfig = synchronizedSetting;
			return val;
		}

		internal ConfigEntry<T> config<T>(string group, string name, T value, string description, bool synchronizedSetting = true)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting);
		}

		private void Awake()
		{
			Instance = this;
			_self = this;
			AudioRoutingService.ConfigureLogging(delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)message);
			}, delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)message);
			}, delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)message);
			});
			AudioRoutingService.DebugLogging = false;
			jsonLoader.loadJson();
			BRSConfig.Init(this);
			if (BRSConfig.LockConfiguration != null)
			{
				configSync.AddLockingConfigEntry<bool>(BRSConfig.LockConfiguration);
			}
			Harmony = harmony;
			harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"BalrondSecondChance loaded.");
		}

		private void OnDestroy()
		{
			Harmony obj = Harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}
	}
	internal sealed class BRSDownedBehaviour : MonoBehaviour, Hoverable, Interactable
	{
		private sealed class PaidReceipt
		{
			internal BRSDownedBehaviour Target;

			internal ZDOID PayerId;

			internal int Session;

			internal float Expires;
		}

		private const string RpcReviveRequest = "BRS_ReviveRequest";

		private const float ReviveGraceSeconds = 3f;

		private Player _player;

		private ZNetView _nview;

		private const float StatePollInterval = 0.1f;

		private const float PresentationHeartbeatInterval = 0.25f;

		private float _nextPresentationTick;

		private bool _registered;

		private bool _lastObservedDowned;

		private bool _hasObservedDownedState;

		private const string RpcHoldBegin = "BRS_ReviveHoldBegin";

		private const string RpcHoldAccepted = "BRS_ReviveHoldAccepted";

		private const string RpcHoldCancel = "BRS_ReviveHoldCancel";

		private const string RpcHoldComplete = "BRS_ReviveHoldComplete";

		private const float NetworkTimingToleranceSeconds = 0.15f;

		private const float OwnerSessionExpiryPaddingSeconds = 6f;

		private const float BeginAckTimeoutSeconds = 4f;

		private static BRSDownedBehaviour _localActiveHold;

		private static int _nextHoldSession;

		private Player _localHoldReviver;

		private float _localHoldStart;

		private float _localBeginRequestTime;

		private bool _localHoldAccepted;

		private int _localHoldSession;

		private ZDOID _ownerHoldReviverId = ZDOID.None;

		private long _ownerHoldSender;

		private int _ownerHoldSession;

		private long _ownerHoldStartTicks;

		private const string RpcReviveSuccess = "BRS_FriendReviveSucceeded_v2";

		private const string RpcReviveRejected = "BRS_FriendReviveRejected_v2";

		private const string RpcRevivePaid = "BRS_FriendRevivePaid_v2";

		private const float ReceiptRetryInterval = 1f;

		private const float ReceiptLifetimeSeconds = 120f;

		private static BRSDownedBehaviour _localPendingTarget;

		private static Player _localPendingPayer;

		private static ZDOID _localPendingPayerId = ZDOID.None;

		private static int _localPendingSession;

		private static bool _localPendingRequireItem;

		private static int _localPendingAmount;

		private static ItemData _localPendingSelectedItem;

		private static bool _localPendingReceiptReceived;

		private static float _localNextChargeAttempt;

		private static float _localNextChargeWarning;

		private static float _localPendingSince;

		private static float _localNextAwaitWarning;

		private static int _nextPaymentTransaction;

		private static readonly List<PaidReceipt> PaidReceipts = new List<PaidReceipt>(8);

		private const int MaxPaidReceiptCache = 128;

		private long _ownerReceiptPeer;

		private ZDOID _ownerReceiptPayerId = ZDOID.None;

		private int _ownerReceiptSession;

		private float _ownerReceiptExpires;

		private float _ownerReceiptNextRetry;

		private static bool TimedFriendReviveEnabled => BRSConfig.EnableTimedFriendRevive != null && BRSConfig.EnableTimedFriendRevive.Value && BRSConfig.FriendReviveHoldSeconds != null && BRSConfig.FriendReviveHoldSeconds.Value > 0f;

		private static float RequiredHoldSeconds => Mathf.Clamp(BRSConfig.FriendReviveHoldSeconds.Value, 0f, 60f);

		private void Awake()
		{
			_player = ((Component)this).GetComponent<Player>();
			_nview = ((Component)this).GetComponent<ZNetView>();
			TryRegisterRpc();
		}

		private void OnEnable()
		{
			TryRegisterRpc();
			((MonoBehaviour)this).CancelInvoke("Tick");
			((MonoBehaviour)this).InvokeRepeating("Tick", 0f, 0.1f);
		}

		private void OnDisable()
		{
			((MonoBehaviour)this).CancelInvoke("Tick");
			CancelLocalHold();
			ClearOwnerHold();
		}

		private void Tick()
		{
			if (!_registered)
			{
				TryRegisterRpc();
			}
			if ((Object)(object)_player == (Object)null || (Object)(object)_nview == (Object)null || !_nview.IsValid())
			{
				return;
			}
			float time = Time.time;
			bool flag = _player.IsDowned();
			if (((Character)_player).IsOwner())
			{
				MaintainOwnerHold(flag);
				TickOwnerReceipt();
			}
			if (!_hasObservedDownedState || flag != _lastObservedDowned)
			{
				_hasObservedDownedState = true;
				_lastObservedDowned = flag;
				BRSPlayerPresentation.SetDownedPresentation(_player, flag);
				_nextPresentationTick = time + 0.25f;
			}
			else if (flag && time >= _nextPresentationTick)
			{
				_nextPresentationTick = time + 0.25f;
				BRSPlayerPresentation.SetDownedPresentation(_player, downed: true);
			}
			else if (!flag && ((Character)_player).IsOwner() && time >= _nextPresentationTick)
			{
				_nextPresentationTick = time + 0.25f;
				BRSDownedVisualState component = ((Component)_player).GetComponent<BRSDownedVisualState>();
				if ((Object)(object)component != (Object)null && (Object)(object)component.AnchorRoot != (Object)null)
				{
					BRSPlayerPresentation.SetDownedPresentation(_player, downed: false);
				}
			}
			if (flag && ((Character)_player).IsOwner())
			{
				MaintainDownedOwnerState();
				if (_player.ReleaseRequested())
				{
					PerformRealDeath();
				}
				else if (BRSConfig.AutoReleaseWhenTimerEnds.Value && _player.GetRemainingDownedSeconds() <= 0f)
				{
					PerformRealDeath();
				}
			}
		}

		public string GetHoverName()
		{
			return ((Object)(object)_player != (Object)null) ? _player.GetPlayerName() : string.Empty;
		}

		public float GetHoverOffset()
		{
			return ((Object)(object)_player != (Object)null) ? ((Character)_player).GetHoverOffset() : 0f;
		}

		public string GetHoverText()
		{
			if ((Object)(object)_player == (Object)null || !_player.IsDowned())
			{
				return string.Empty;
			}
			Player localPlayer = Player.m_localPlayer;
			float remainingDownedSeconds = _player.GetRemainingDownedSeconds();
			string playerName = _player.GetPlayerName();
			string text = "$tag_brs_release_in_bal <color=orange>" + remainingDownedSeconds.ToString("0") + "s</color>";
			string reason;
			bool flag = BRSReviveRules.CanFriendRevive(localPlayer, _player, out reason);
			if (BRSConfig.RequireItemForFriendReviveEffective)
			{
				string resurrectionItemDisplayName = BRSHudController.GetResurrectionItemDisplayName();
				int num = Mathf.Max(1, BRSConfig.ResurrectionItemAmount.Value);
				if (flag)
				{
					return LocalizeHover(playerName + "\n" + FriendReviveHoverPrompt() + "\n" + num + "x " + resurrectionItemDisplayName + "\n" + text);
				}
				return LocalizeHover(playerName + "\n" + text + "\n" + reason);
			}
			if (flag)
			{
				return LocalizeHover(playerName + "\n" + FriendReviveHoverPrompt() + "\n" + text);
			}
			return LocalizeHover(playerName + "\n" + text + "\n" + reason);
		}

		public bool Interact(Humanoid user, bool repeat, bool alt)
		{
			if (alt)
			{
				return false;
			}
			Player val = (Player)(object)((user is Player) ? user : null);
			if ((Object)(object)val == (Object)null || (Object)(object)_player == (Object)null || !_player.IsDowned())
			{
				return false;
			}
			if (TimedFriendReviveEnabled)
			{
				return InteractTimed(val, repeat);
			}
			if (repeat)
			{
				return false;
			}
			return TryReviveFromActor(val, null, fromUseItem: false);
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			Player val = (Player)(object)((user is Player) ? user : null);
			if ((Object)(object)val == (Object)null || item == null || (Object)(object)_player == (Object)null || !_player.IsDowned())
			{
				return false;
			}
			if (!IsResurrectionPotion(item))
			{
				return false;
			}
			if (TimedFriendReviveEnabled)
			{
				((Character)val).Message((MessageType)2, "Hold your Use key to revive this player.", 0, (Sprite)null, false);
				return true;
			}
			return TryReviveFromActor(val, item, fromUseItem: true);
		}

		internal void TrySelfReviveLocal()
		{
			if ((Object)(object)_player == (Object)null || !((Character)_player).IsOwner() || !_player.IsDowned())
			{
				Debug.LogWarning((object)"[BalrondSecondChance] Self-revive click rejected before rules: player/owner/downed state invalid.");
				return;
			}
			TryRegisterRpc();
			if ((Object)(object)_nview == (Object)null || !_nview.IsValid())
			{
				Debug.LogWarning((object)"[BalrondSecondChance] Self-revive click rejected: ZNetView is not valid.");
				return;
			}
			if (!BRSReviveRules.CanSelfRevive(_player, out var reason))
			{
				Debug.LogWarning((object)("[BalrondSecondChance] Self-revive click rejected by rules: " + reason));
				return;
			}
			bool requireItemForSelfReviveEffective = BRSConfig.RequireItemForSelfReviveEffective;
			if (ReviveHere(usedPotion: true, selfRevive: true) && requireItemForSelfReviveEffective && !BRSItemUtils.ConsumeResurrectionPotion(_player))
			{
				Debug.LogError((object)"[BalrondSecondChance] Self-revive succeeded but local payment failed; inspect inventory mods.");
			}
		}

		private bool TryReviveFromActor(Player reviver, ItemData directItem, bool fromUseItem, int holdSession = 0)
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)reviver == (Object)null || (Object)(object)reviver != (Object)(object)Player.m_localPlayer || !((Character)reviver).IsOwner() || (Object)(object)_player == (Object)null || (Object)(object)_nview == (Object)null || !_nview.IsValid() || (Object)(object)((Character)reviver).m_nview == (Object)null || !((Character)reviver).m_nview.IsValid())
			{
				return false;
			}
			ZDO zDO = ((Character)reviver).m_nview.GetZDO();
			if (zDO == null)
			{
				return false;
			}
			if (!BRSReviveRules.CanFriendRevive(reviver, _player, out var reason))
			{
				((Character)reviver).Message((MessageType)2, reason, 0, (Sprite)null, false);
				return true;
			}
			if (!BeginLocalPayment(reviver, directItem, fromUseItem, zDO.m_uid, (holdSession != 0) ? holdSession : NextPaymentSession(), holdSession != 0, out var transaction))
			{
				((Character)reviver).Message((MessageType)2, "Another revive is pending or the configured item is missing.", 0, (Sprite)null, false);
				return true;
			}
			if (holdSession != 0)
			{
				_nview.InvokeRPC("BRS_ReviveHoldComplete", new object[2] { zDO.m_uid, transaction });
			}
			else
			{
				_nview.InvokeRPC("BRS_ReviveRequest", new object[2] { zDO.m_uid, transaction });
			}
			return true;
		}

		private static bool IsResurrectionPotion(ItemData item)
		{
			return BRSItemUtils.IsResurrectionPotion(item);
		}

		private static string LocalizeHover(string text)
		{
			Localization instance = Localization.instance;
			if (instance == null)
			{
				return text;
			}
			string text2 = instance.Localize(text);
			if (text2.Contains("$KEY_USE"))
			{
				text2 = instance.Localize(text2.Replace("$KEY_USE", "$KEY_Use"));
			}
			return text2;
		}

		private void TryRegisterRpc()
		{
			if (!_registered && !((Object)(object)_nview == (Object)null) && _nview.IsValid())
			{
				_registered = true;
				_nview.Register<ZDOID, int>("BRS_ReviveRequest", (Action<long, ZDOID, int>)RPC_ReviveRequest);
				_nview.Register<ZDOID, int>("BRS_FriendReviveSucceeded_v2", (Action<long, ZDOID, int>)RPC_ReviveSuccess);
				_nview.Register<ZDOID, int>("BRS_FriendReviveRejected_v2", (Action<long, ZDOID, int>)RPC_ReviveRejected);
				_nview.Register<ZDOID, int>("BRS_FriendRevivePaid_v2", (Action<long, ZDOID, int>)RPC_RevivePaid);
				_nview.Register<ZDOID, int>("BRS_ReviveHoldBegin", (Action<long, ZDOID, int>)RPC_HoldBegin);
				_nview.Register<ZDOID, int>("BRS_ReviveHoldAccepted", (Action<long, ZDOID, int>)RPC_HoldAccepted);
				_nview.Register<ZDOID, int>("BRS_ReviveHoldCancel", (Action<long, ZDOID, int>)RPC_HoldCancel);
				_nview.Register<ZDOID, int>("BRS_ReviveHoldComplete", (Action<long, ZDOID, int>)RPC_HoldComplete);
			}
		}

		private void RPC_ReviveRequest(long sender, ZDOID reviverId, int transaction)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_player == (Object)null || !((Character)_player).IsOwner() || transaction == 0 || TryResendCompletedReceipt(sender, reviverId, transaction))
			{
				return;
			}
			if (!_player.IsDowned() || TimedFriendReviveEnabled || _player.ReleaseRequested() || _ownerReceiptSession != 0)
			{
				RejectFriendRevive(sender, reviverId, transaction);
				return;
			}
			Player val = ResolveRemoteReviver(sender, reviverId);
			if ((Object)(object)val == (Object)null || !BRSReviveRules.CanFriendRevive(val, _player, out var _, checkItem: false))
			{
				RejectFriendRevive(sender, reviverId, transaction);
			}
			else
			{
				CommitFriendRevive(sender, reviverId, transaction);
			}
		}

		private void MaintainDownedOwnerState()
		{
			float num = Mathf.Max(1f, ((Character)_player).GetMaxHealth() * 0.01f);
			if (((Character)_player).GetHealth() > num)
			{
				((Character)_player).SetHealth(num);
			}
		}

		private bool ReviveHere(bool usedPotion, bool selfRevive)
		{
			if ((Object)(object)_player == (Object)null || !((Character)_player).IsOwner() || !_player.IsDowned() || _player.ReleaseRequested())
			{
				return false;
			}
			CancelLocalHold();
			ClearOwnerHold();
			float health = Mathf.Max(1f, ((Character)_player).GetMaxHealth() * Mathf.Clamp01(BRSConfig.ReviveHealthPercent.Value));
			_player.SetReviveGrace(3f);
			ClearReviveDamageOverTime();
			_player.SetDowned(value: false, 0f);
			_player.ClearReleaseRequest();
			((Character)_player).SetHealth(health);
			try
			{
				BRSPlayerPresentation.SetDownedPresentation(_player, downed: false);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[BalrondSecondChance] Revive presentation failed: " + ex));
			}
			if (_player.IsDowned() || !(((Character)_player).GetHealth() > 0f) || ((Character)_player).IsDead())
			{
				Debug.LogError((object)"[BalrondSecondChance] Revive postcondition failed; refusing success receipt.");
				return false;
			}
			if (usedPotion)
			{
				try
				{
					BRSReviveRules.MarkPotionReviveUsed(_player);
				}
				catch (Exception ex2)
				{
					Debug.LogError((object)("[BalrondSecondChance] Cooldown state update failed after revive: " + ex2));
				}
			}
			Debug.Log((object)("[BalrondSecondChance] Revive completed; self=" + selfRevive + ", downed=" + _player.IsDowned() + ", hp=" + ((Character)_player).GetHealth().ToString("0.0") + ", grace=" + _player.InReviveGrace() + "."));
			if ((Object)(object)_player == (Object)(object)Player.m_localPlayer)
			{
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)2, selfRevive ? "$tag_brs_self_revived_bal" : "$tag_brs_revived_by_player_bal", 0, (Sprite)null, false, true);
				}
			}
			return true;
		}

		private void ClearReviveDamageOverTime()
		{
			try
			{
				SEMan sEMan = ((Character)_player).GetSEMan();
				if (sEMan != null)
				{
					sEMan.RemoveStatusEffect(SEMan.s_statusEffectPoison, true);
					sEMan.RemoveStatusEffect(SEMan.s_statusEffectBurning, true);
					sEMan.RemoveStatusEffect(SEMan.s_statusEffectSpirit, true);
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[BalrondSecondChance] Could not clear a revive DoT: " + ex));
			}
		}

		private void PerformRealDeath()
		{
			CancelLocalHold();
			ClearOwnerHold();
			_player.SetDowned(value: false, 0f);
			_player.ClearReleaseRequest();
			BRSPlayerPresentation.SetDownedPresentation(_player, downed: false);
			Launch.AllowOriginalDeath = true;
			try
			{
				((Character)_player).SetHealth(0f);
				((Character)_player).OnDeath();
			}
			finally
			{
				Launch.AllowOriginalDeath = false;
			}
		}

		private string FriendReviveHoverPrompt()
		{
			if (!TimedFriendReviveEnabled)
			{
				return "$tag_brs_hover_use_revive_bal";
			}
			string text = "[<color=yellow><b>HOLD $KEY_Use</b></color>] Revive";
			if ((Object)(object)_localActiveHold == (Object)(object)this && (Object)(object)_localHoldReviver == (Object)(object)Player.m_localPlayer)
			{
				if (_localHoldAccepted)
				{
					float num = Mathf.Clamp(Time.time - _localHoldStart, 0f, RequiredHoldSeconds);
					text = text + " <color=orange>" + num.ToString("0.0") + " / " + RequiredHoldSeconds.ToString("0.0") + "s</color>";
				}
				else
				{
					text += " <color=orange>...</color>";
				}
			}
			return text;
		}

		private bool InteractTimed(Player reviver, bool repeat)
		{
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)reviver != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			if (repeat)
			{
				return (Object)(object)_localActiveHold == (Object)(object)this && (Object)(object)_localHoldReviver == (Object)(object)reviver;
			}
			if ((Object)(object)_localActiveHold != (Object)null)
			{
				_localActiveHold.CancelLocalHold();
			}
			if (!BRSReviveRules.CanFriendRevive(reviver, _player, out var reason))
			{
				((Character)reviver).Message((MessageType)2, reason, 0, (Sprite)null, false);
				return true;
			}
			TryRegisterRpc();
			ZDO val = (((Object)(object)((Character)reviver).m_nview != (Object)null && ((Character)reviver).m_nview.IsValid()) ? ((Character)reviver).m_nview.GetZDO() : null);
			if ((Object)(object)_nview == (Object)null || !_nview.IsValid() || val == null)
			{
				return false;
			}
			_localHoldReviver = reviver;
			_localHoldStart = 0f;
			_localBeginRequestTime = Time.time;
			_localHoldAccepted = false;
			_localHoldSession = ++_nextHoldSession;
			if (_localHoldSession == 0)
			{
				_localHoldSession = ++_nextHoldSession;
			}
			_localActiveHold = this;
			_nview.InvokeRPC("BRS_ReviveHoldBegin", new object[2] { val.m_uid, _localHoldSession });
			return true;
		}

		private void Update()
		{
			RetryLocalCharge();
			if ((Object)(object)_localActiveHold != (Object)(object)this || (Object)(object)_localHoldReviver == (Object)null)
			{
				return;
			}
			Player localHoldReviver = _localHoldReviver;
			string reason;
			if (!TimedFriendReviveEnabled || (Object)(object)_player == (Object)null || !_player.IsDowned() || (Object)(object)localHoldReviver != (Object)(object)Player.m_localPlayer || (!ZInput.GetButton("Use") && !ZInput.GetButton("JoyUse")) || !IsHoveringDownedPlayer(localHoldReviver))
			{
				CancelLocalHold();
			}
			else if (!BRSReviveRules.CanFriendRevive(localHoldReviver, _player, out reason))
			{
				CancelLocalHold();
			}
			else if (!_localHoldAccepted)
			{
				if (Time.time - _localBeginRequestTime >= 4f)
				{
					CancelLocalHold();
				}
			}
			else if (!(Time.time - _localHoldStart < RequiredHoldSeconds + 0.15f))
			{
				int localHoldSession = _localHoldSession;
				_localActiveHold = null;
				_localHoldReviver = null;
				_localHoldSession = 0;
				_localHoldAccepted = false;
				_localHoldStart = 0f;
				_localBeginRequestTime = 0f;
				TryReviveFromActor(localHoldReviver, null, fromUseItem: false, localHoldSession);
			}
		}

		private bool IsHoveringDownedPlayer(Player reviver)
		{
			GameObject hoverObject = ((Humanoid)reviver).GetHoverObject();
			return (Object)(object)hoverObject != (Object)null && (Object)(object)hoverObject.GetComponentInParent<Player>() == (Object)(object)_player;
		}

		private void CancelLocalHold(bool sendCancel = true)
		{
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_localActiveHold != (Object)(object)this)
			{
				return;
			}
			Player localHoldReviver = _localHoldReviver;
			int localHoldSession = _localHoldSession;
			_localActiveHold = null;
			_localHoldReviver = null;
			_localHoldSession = 0;
			_localHoldStart = 0f;
			_localBeginRequestTime = 0f;
			_localHoldAccepted = false;
			if (sendCancel && localHoldSession != 0 && !((Object)(object)_nview == (Object)null) && _nview.IsValid() && !((Object)(object)localHoldReviver == (Object)null) && !((Object)(object)((Character)localHoldReviver).m_nview == (Object)null) && ((Character)localHoldReviver).m_nview.IsValid())
			{
				ZDO zDO = ((Character)localHoldReviver).m_nview.GetZDO();
				if (zDO != null)
				{
					_nview.InvokeRPC("BRS_ReviveHoldCancel", new object[2] { zDO.m_uid, localHoldSession });
				}
			}
		}

		private void ClearOwnerHold()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			_ownerHoldReviverId = ZDOID.None;
			_ownerHoldSender = 0L;
			_ownerHoldSession = 0;
			_ownerHoldStartTicks = 0L;
		}

		private void MaintainOwnerHold(bool downed)
		{
			if (_ownerHoldSession != 0 && (!downed || !TimedFriendReviveEnabled || _player.ReleaseRequested() || !OwnerHoldReviverPresent() || ElapsedOwnerHoldSeconds() > RequiredHoldSeconds + 6f))
			{
				ClearOwnerHold();
			}
		}

		private bool OwnerHoldReviverPresent()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			return _ownerHoldReviverId != ZDOID.None && ZDOMan.instance != null && ZDOMan.instance.GetZDO(_ownerHoldReviverId) != null;
		}

		private float ElapsedOwnerHoldSeconds()
		{
			if (_ownerHoldStartTicks == 0L || (Object)(object)ZNet.instance == (Object)null)
			{
				return 0f;
			}
			return (float)((double)(ZNet.instance.GetTime().Ticks - _ownerHoldStartTicks) / 10000000.0);
		}

		private Player ResolveRemoteReviver(long sender, ZDOID id)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (id == ZDOID.None || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return null;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(id);
			if (zDO == null || zDO.GetOwner() != sender)
			{
				return null;
			}
			ZNetView val = ZNetScene.instance.FindInstance(zDO);
			if ((Object)(object)val == (Object)null || !val.IsValid())
			{
				return null;
			}
			Player component = ((Component)val).GetComponent<Player>();
			return ((Object)(object)component != (Object)(object)_player) ? component : null;
		}

		private void RPC_HoldBegin(long sender, ZDOID reviverId, int session)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_player == (Object)null || !((Character)_player).IsOwner() || !_player.IsDowned() || !TimedFriendReviveEnabled || session == 0 || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			Player val = ResolveRemoteReviver(sender, reviverId);
			if ((Object)(object)val == (Object)null || !BRSReviveRules.CanFriendRevive(val, _player, out var _, checkItem: false))
			{
				return;
			}
			MaintainOwnerHold(downed: true);
			if (_ownerHoldSession != 0 && (_ownerHoldSender != sender || _ownerHoldReviverId != reviverId))
			{
				return;
			}
			if (_ownerHoldSession != 0)
			{
				if (_ownerHoldSession == session && _ownerHoldSender == sender && _ownerHoldReviverId == reviverId)
				{
					_nview.InvokeRPC(sender, "BRS_ReviveHoldAccepted", new object[2] { reviverId, session });
				}
			}
			else
			{
				_ownerHoldSender = sender;
				_ownerHoldReviverId = reviverId;
				_ownerHoldSession = session;
				_ownerHoldStartTicks = ZNet.instance.GetTime().Ticks;
				_nview.InvokeRPC(sender, "BRS_ReviveHoldAccepted", new object[2] { reviverId, session });
			}
		}

		private void RPC_HoldAccepted(long sender, ZDOID reviverId, int session)
		{
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_localActiveHold != (Object)(object)this) && _localHoldSession == session && !((Object)(object)_localHoldReviver == (Object)null) && !_localHoldAccepted && !((Object)(object)_nview == (Object)null) && _nview.IsValid())
			{
				ZDO zDO = _nview.GetZDO();
				ZDO val = (((Object)(object)((Character)_localHoldReviver).m_nview != (Object)null && ((Character)_localHoldReviver).m_nview.IsValid()) ? ((Character)_localHoldReviver).m_nview.GetZDO() : null);
				if (zDO != null && val != null && sender == zDO.GetOwner() && !(reviverId != val.m_uid) && !((Object)(object)_localHoldReviver != (Object)(object)Player.m_localPlayer) && (ZInput.GetButton("Use") || ZInput.GetButton("JoyUse")))
				{
					_localHoldAccepted = true;
					_localHoldStart = Time.time;
				}
			}
		}

		private void RPC_HoldCancel(long sender, ZDOID reviverId, int session)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_player == (Object)null) && ((Character)_player).IsOwner() && _ownerHoldSession == session && _ownerHoldSender == sender && _ownerHoldReviverId == reviverId)
			{
				ClearOwnerHold();
			}
		}

		private void RPC_HoldComplete(long sender, ZDOID reviverId, int session)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_player == (Object)null || !((Character)_player).IsOwner() || session == 0 || TryResendCompletedReceipt(sender, reviverId, session))
			{
				return;
			}
			if (!_player.IsDowned() || !TimedFriendReviveEnabled || _player.ReleaseRequested() || _ownerReceiptSession != 0 || _ownerHoldSession != session || _ownerHoldSender != sender || _ownerHoldReviverId != reviverId)
			{
				RejectFriendRevive(sender, reviverId, session);
				return;
			}
			float num = ElapsedOwnerHoldSeconds();
			Player val = ResolveRemoteReviver(sender, reviverId);
			if ((Object)(object)val == (Object)null || !BRSReviveRules.CanFriendRevive(val, _player, out var _, checkItem: false) || num + 0.15f < RequiredHoldSeconds)
			{
				ClearOwnerHold();
				RejectFriendRevive(sender, reviverId, session);
				Debug.LogWarning((object)"[BalrondSecondChance] Rejected timed revive: actor, range, cooldown or hold time invalid.");
			}
			else
			{
				ClearOwnerHold();
				CommitFriendRevive(sender, reviverId, session);
			}
		}

		private static int NextPaymentSession()
		{
			int num = ++_nextPaymentTransaction;
			if (num == 0)
			{
				num = ++_nextPaymentTransaction;
			}
			return num;
		}

		private bool BeginLocalPayment(Player payer, ItemData selectedItem, bool fromUseItem, ZDOID payerId, int requestedSession, bool timed, out int transaction)
		{
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: 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_007d: Unknown result type (might be due to invalid IL or missing references)
			transaction = 0;
			if (requestedSession == 0 || (Object)(object)payer == (Object)null || !((Character)payer).IsOwner() || (Object)(object)_localPendingTarget != (Object)null || (Object)(object)_player == (Object)null || !_player.IsDowned() || (Object)(object)((Character)payer).m_nview == (Object)null || !((Character)payer).m_nview.IsValid() || ((Character)payer).m_nview.GetZDO() == null || ((Character)payer).m_nview.GetZDO().m_uid != payerId || timed != TimedFriendReviveEnabled)
			{
				return false;
			}
			bool requireItemForFriendReviveEffective = BRSConfig.RequireItemForFriendReviveEffective;
			int num = Mathf.Max(1, BRSConfig.ResurrectionItemAmount.Value);
			if (requireItemForFriendReviveEffective)
			{
				if (!BRSItemUtils.TryFindResurrectionPotion(payer, out var _))
				{
					return false;
				}
				if (fromUseItem && !BRSItemUtils.CanPayWithSpecificItem(payer, selectedItem, num))
				{
					return false;
				}
			}
			_localPendingTarget = this;
			_localPendingPayer = payer;
			_localPendingPayerId = payerId;
			_localPendingSession = requestedSession;
			_localPendingRequireItem = requireItemForFriendReviveEffective;
			_localPendingAmount = num;
			_localPendingSelectedItem = (fromUseItem ? selectedItem : null);
			_localPendingReceiptReceived = false;
			_localNextChargeAttempt = 0f;
			_localNextChargeWarning = 0f;
			_localPendingSince = Time.unscaledTime;
			_localNextAwaitWarning = Time.unscaledTime + 12f;
			transaction = requestedSession;
			return true;
		}

		private static void ClearLocalPayment()
		{
			//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)
			_localPendingTarget = null;
			_localPendingPayer = null;
			_localPendingPayerId = ZDOID.None;
			_localPendingSession = 0;
			_localPendingRequireItem = false;
			_localPendingAmount = 0;
			_localPendingSelectedItem = null;
			_localPendingReceiptReceived = false;
			_localNextChargeAttempt = 0f;
			_localNextChargeWarning = 0f;
			_localPendingSince = 0f;
			_localNextAwaitWarning = 0f;
		}

		private bool VerifyOwnerResponse(long sender, ZDOID payerId, int transaction)
		{
			//IL_0043: 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_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			if (transaction == 0 || (Object)(object)_nview == (Object)null || !_nview.IsValid())
			{
				return false;
			}
			ZDO zDO = _nview.GetZDO();
			return zDO != null && sender == zDO.GetOwner() && payerId != ZDOID.None && (Object)(object)_localPendingTarget == (Object)(object)this && (Object)(object)_localPendingPayer == (Object)(object)Player.m_localPlayer && _localPendingPayerId == payerId && _localPendingSession == transaction;
		}

		private void RPC_ReviveSuccess(long sender, ZDOID payerId, int transaction)
		{
			//IL_0003: 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_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if (VerifyOwnerResponse(sender, payerId, transaction))
			{
				_localPendingReceiptReceived = true;
				AttemptLocalCharge();
				return;
			}
			ZDO val = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null);
			if (val == null || sender != val.GetOwner())
			{
				return;
			}
			PrunePaidReceipts();
			foreach (PaidReceipt paidReceipt in PaidReceipts)
			{
				if ((Object)(object)paidReceipt.Target == (Object)(object)this && paidReceipt.PayerId == payerId && paidReceipt.Session == transaction)
				{
					_nview.InvokeRPC("BRS_FriendRevivePaid_v2", new object[2] { payerId, transaction });
					break;
				}
			}
		}

		private static void PrunePaidReceipts()
		{
			float unscaledTime = Time.unscaledTime;
			for (int num = PaidReceipts.Count - 1; num >= 0; num--)
			{
				PaidReceipt paidReceipt = PaidReceipts[num];
				if ((Object)(object)paidReceipt.Target == (Object)null || unscaledTime >= paidReceipt.Expires)
				{
					PaidReceipts.RemoveAt(num);
				}
			}
		}

		private void RPC_ReviveRejected(long sender, ZDOID payerId, int transaction)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			if (VerifyOwnerResponse(sender, payerId, transaction) && !_localPendingReceiptReceived)
			{
				Player localPendingPayer = _localPendingPayer;
				ClearLocalPayment();
				if ((Object)(object)localPendingPayer != (Object)null)
				{
					((Character)localPendingPayer).Message((MessageType)2, "Revive rejected; no item was spent.", 0, (Sprite)null, false);
				}
			}
		}

		private void AttemptLocalCharge()
		{
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_localPendingTarget != (Object)(object)this || !_localPendingReceiptReceived || Time.unscaledTime < _localNextChargeAttempt)
			{
				return;
			}
			_localNextChargeAttempt = Time.unscaledTime + 0.5f;
			Player localPendingPayer = _localPendingPayer;
			if ((Object)(object)localPendingPayer == (Object)null || (Object)(object)localPendingPayer != (Object)(object)Player.m_localPlayer || !((Character)localPendingPayer).IsOwner())
			{
				return;
			}
			bool flag = !_localPendingRequireItem;
			if (!flag && _localPendingSelectedItem != null)
			{
				flag = BRSItemUtils.ConsumeSpecificItem(localPendingPayer, _localPendingSelectedItem, _localPendingAmount);
			}
			if (!flag)
			{
				flag = BRSItemUtils.ConsumeResurrectionPotion(localPendingPayer, _localPendingAmount);
			}
			if (!flag)
			{
				if (Time.unscaledTime >= _localNextChargeWarning)
				{
					_localNextChargeWarning = Time.unscaledTime + 15f;
					Debug.LogError((object)"[BalrondSecondChance] Revive succeeded, but required items are unavailable for settlement. Waiting for inventory recovery.");
					((Character)localPendingPayer).Message((MessageType)2, "Revive completed, but payment is pending: required item unavailable.", 0, (Sprite)null, false);
				}
				return;
			}
			ZDOID localPendingPayerId = _localPendingPayerId;
			int localPendingSession = _localPendingSession;
			PrunePaidReceipts();
			if (PaidReceipts.Count >= 128)
			{
				PaidReceipts.RemoveAt(0);
			}
			PaidReceipts.Add(new PaidReceipt
			{
				Target = this,
				PayerId = localPendingPayerId,
				Session = localPendingSession,
				Expires = Time.unscaledTime + 120f + 30f
			});
			ClearLocalPayment();
			if ((Object)(object)_nview != (Object)null && _nview.IsValid())
			{
				_nview.InvokeRPC("BRS_FriendRevivePaid_v2", new object[2] { localPendingPayerId, localPendingSession });
			}
		}

		private void RetryLocalCharge()
		{
			if ((Object)(object)_localPendingTarget != (Object)(object)this)
			{
				return;
			}
			if (_localPendingReceiptReceived)
			{
				AttemptLocalCharge();
			}
			else if (Time.unscaledTime >= _localNextAwaitWarning)
			{
				_localNextAwaitWarning = Time.unscaledTime + 30f;
				Debug.LogWarning((object)("[BalrondSecondChance] Revive outcome still unknown after " + (Time.unscaledTime - _localPendingSince).ToString("0") + " seconds; keeping inventory untouched until a verified receipt/rejection."));
				if ((Object)(object)_localPendingPayer != (Object)null)
				{
					((Character)_localPendingPayer).Message((MessageType)2, "Revive confirmation delayed; no item has been spent.", 0, (Sprite)null, false);
				}
			}
		}

		private void CommitFriendRevive(long sender, ZDOID payerId, int transaction)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (_ownerReceiptSession != 0 || !ReviveHere(usedPotion: true, selfRevive: false))
			{
				RejectFriendRevive(sender, payerId, transaction);
				return;
			}
			_ownerReceiptPeer = sender;
			_ownerReceiptPayerId = payerId;
			_ownerReceiptSession = transaction;
			_ownerReceiptExpires = Time.unscaledTime + 120f;
			_ownerReceiptNextRetry = 0f;
			SendOwnerReceipt();
		}

		private void RejectFriendRevive(long sender, ZDOID payerId, int transaction)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_nview != (Object)null && _nview.IsValid() && transaction != 0)
			{
				_nview.InvokeRPC(sender, "BRS_FriendReviveRejected_v2", new object[2] { payerId, transaction });
			}
		}

		private void SendOwnerReceipt()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			if (_ownerReceiptSession != 0 && !((Object)(object)_nview == (Object)null) && _nview.IsValid())
			{
				_ownerReceiptNextRetry = Time.unscaledTime + 1f;
				_nview.InvokeRPC(_ownerReceiptPeer, "BRS_FriendReviveSucceeded_v2", new object[2] { _ownerReceiptPayerId, _ownerReceiptSession });
			}
		}

		private bool TryResendCompletedReceipt(long sender, ZDOID payerId, int transaction)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (_ownerReceiptSession == 0 || _ownerReceiptPeer != sender || _ownerReceiptPayerId != payerId || _ownerReceiptSession != transaction)
			{
				return false;
			}
			SendOwnerReceipt();
			return true;
		}

		private void TickOwnerReceipt()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			if (_ownerReceiptSession != 0)
			{
				if (Time.unscaledTime >= _ownerReceiptExpires)
				{
					Debug.LogError((object)"[BalrondSecondChance] Revive receipt expired without payment ACK; network settlement unconfirmed.");
					_ownerReceiptSession = 0;
					_ownerReceiptPayerId = ZDOID.None;
					_ownerReceiptPeer = 0L;
				}
				else if (Time.unscaledTime >= _ownerReceiptNextRetry)
				{
					SendOwnerReceipt();
				}
			}
		}

		private void RPC_RevivePaid(long sender, ZDOID payerId, int transaction)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: 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)
			if (!((Object)(object)_player == (Object)null) && ((Character)_player).IsOwner() && _ownerReceiptSession == transaction && !(_ownerReceiptPayerId != payerId) && _ownerReceiptPeer == sender)
			{
				_ownerReceiptSession = 0;
				_ownerReceiptPayerId = ZDOID.None;
				_ownerReceiptPeer = 0L;
			}
		}
	}
	internal static class BRSKeys
	{
		public const string Downed = "brs_downed";

		public const string DownedUntil = "brs_downed_until";

		public const string DownedUntilNetworkTicks = "brs_downed_until_net_ticks";

		public const string ReleaseRequested = "brs_release_requested";

		public const string LastPotionReviveTime = "brs_last_potion_revive_time";
	}
	internal static class BRSPlayerExtensions
	{
		private const string ReviveGraceUntilKey = "brs_revive_grace_until_net_ticks";

		private static ZDO GetValidZdo(Player player)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)((Character)player).m_nview == (Object)null || !((Character)player).m_nview.IsValid())
			{
				return null;
			}
			return ((Character)player).m_nview.GetZDO();
		}

		public static bool IsDowned(this Player player)
		{
			ZDO validZdo = GetValidZdo(player);
			return validZdo != null && validZdo.GetBool("brs_downed", false);
		}

		public static float GetDownedUntil(this Player player)
		{
			ZDO validZdo = GetValidZdo(player);
			return (validZdo != null) ? validZdo.GetFloat("brs_downed_until", 0f) : 0f;
		}

		public static long GetDownedUntilNetworkTicks(this Player player)
		{
			ZDO validZdo = GetValidZdo(player);
			return (validZdo != null) ? validZdo.GetLong("brs_downed_until_net_ticks", 0L) : 0;
		}

		public static void SetDowned(this Player player, bool value, float until)
		{
			ZDO validZdo = GetValidZdo(player);
			if (validZdo != null && ((Character)player).IsOwner())
			{
				validZdo.Set("brs_downed", value);
				validZdo.Set("brs_downed_until", value ? until : 0f);
				long num = 0L;
				if (value && (Object)(object)ZNet.instance != (Object)null)
				{
					float num2 = Mathf.Max(0f, until - Time.time);
					num = ZNet.instance.GetTime().AddSeconds(num2).Ticks;
				}
				validZdo.Set("brs_downed_until_net_ticks", num);
				validZdo.Set("brs_release_requested", false);
			}
		}

		public static bool ReleaseRequested(this Player player)
		{
			ZDO validZdo = GetValidZdo(player);
			return validZdo != null && validZdo.GetBool("brs_release_requested", false);
		}

		public static void SetReleaseRequested(this Player player, bool value)
		{
			ZDO validZdo = GetValidZdo(player);
			if (validZdo != null && ((Character)player).IsOwner())
			{
				validZdo.Set("brs_release_requested", value);
			}
		}

		public static void RequestRelease(this Player player)
		{
			player.SetReleaseRequested(value: true);
		}

		public static void ClearReleaseRequest(this Player player)
		{
			player.SetReleaseRequested(value: false);
		}

		public static void SetReviveGrace(this Player player, float seconds)
		{
			ZDO validZdo = GetValidZdo(player);
			if (validZdo != null && !((Object)(object)ZNet.instance == (Object)null) && ((Character)player).IsOwner())
			{
				long num = ((seconds > 0f) ? ZNet.instance.GetTime().AddSeconds(seconds).Ticks : 0);
				validZdo.Set("brs_revive_grace_until_net_ticks", num);
			}
		}

		public static bool InReviveGrace(this Player player)
		{
			ZDO validZdo = GetValidZdo(player);
			if (validZdo == null || (Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			long num = validZdo.GetLong("brs_revive_grace_until_net_ticks", 0L);
			return num > 0 && ZNet.instance.GetTime().Ticks < num;
		}

		public static long GetLastPotionReviveTicks(this Player player)
		{
			ZDO validZdo = GetValidZdo(player);
			return (validZdo != null) ? validZdo.GetLong("brs_last_potion_revive_time", 0L) : 0;
		}

		public static void SetLastPotionReviveTicks(this Player player, long ticks)
		{
			ZDO validZdo = GetValidZdo(player);
			if (validZdo != null && ((Character)player).IsOwner())
			{
				validZdo.Set("brs_last_potion_revive_time", ticks);
			}
		}

		public static float GetRemainingDownedSeconds(this Player player)
		{
			long downedUntilNetworkTicks = player.GetDownedUntilNetworkTicks();
			if (downedUntilNetworkTicks > 0 && (Object)(object)ZNet.instance != (Object)null)
			{
				try
				{
					DateTime dateTime = new DateTime(downedUntilNetworkTicks);
					return Mathf.Max(0f, (float)(dateTime - ZNet.instance.GetTime()).TotalSeconds);
				}
				catch
				{
				}
			}
			return Mathf.Max(0f, player.GetDownedUntil() - Time.time);
		}

		public static bool HasActivePotionReviveCooldown(this Player player)
		{
			return player.GetRemainingPotionReviveCooldownMinutes() > 0f;
		}

		public static float GetRemainingPotionReviveCooldownMinutes(this Player player)
		{
			long lastPotionReviveTicks = player.GetLastPotionReviveTicks();
			if (lastPotionReviveTicks <= 0)
			{
				return 0f;
			}
			float value = BRSConfig.ResurrectionCooldownMinutes.Value;
			if (value <= 0f)
			{
				return 0f;
			}
			DateTime dateTime;
			try
			{
				dateTime = new DateTime(lastPotionReviveTicks, DateTimeKind.Utc);
			}
			catch
			{
				return 0f;
			}
			double totalMinutes = (DateTime.UtcNow - dateTime).TotalMinutes;
			return Mathf.Max(0f, value - (float)totalMinutes);
		}
	}
	internal static class BRSPlayerPresentation
	{
		private const string AttachRootName = "BRS_DownedAttachRoot";

		private const string AttachPointName = "attachpoint";

		private const string BedAnimation = "attach_bed";

		private const string SitAnimation = "emote_sit";

		private static readonly Vector3 DetachOffset = new Vector3(0f, 0.5f, 0f);

		private static readonly Dictionary<string, string> DownedAnimations = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "Sit", "emote_sit" },
			{ "Bed", "attach_bed" },
			{ "Dance", "emote_dance" },
			{ "Headbang", "emote_headbang" }
		};

		internal static void SetDownedPresentation(Player player, bool downed)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			bool flag = (Object)(object)((Character)player).m_nview != (Object)null && ((Character)player).m_nview.IsValid() && ((Character)player).IsOwner();
			if (!downed)
			{
				BRSDownedVisualState component = ((Component)player).GetComponent<BRSDownedVisualState>();
				if ((Object)(object)component == (Object)null)
				{
					return;
				}
				ClearObserverAnimation(player, component);
				if (flag)
				{
					if (!DetachPlayer(player, component))
					{
						return;
					}
				}
				else
				{
					ClearLocalAttachBookkeeping(component);
				}
				DestroyAnchor(component);
			}
			else
			{
				BRSDownedVisualState orCreateState = GetOrCreateState(player);
				string configuredAnimation = GetConfiguredAnimation();
				if (flag)
				{
					ClearObserverAnimation(player, orCreateState);
					EnsureAnchorAtPlayer(player, orCreateState);
					ApplyDownedPhysics(player);
					AttachPlayer(player, orCreateState, configuredAnimation);
				}
				else
				{
					ApplyObserverAnimation(player, orCreateState, configuredAnimation);
				}
			}
		}

		private static BRSDownedVisualState GetOrCreateState(Player player)
		{
			BRSDownedVisualState bRSDownedVisualState = ((Component)player).GetComponent<BRSDownedVisualState>();
			if ((Object)(object)bRSDownedVisualState == (Object)null)
			{
				bRSDownedVisualState = ((Component)player).gameObject.AddComponent<BRSDownedVisualState>();
			}
			return bRSDownedVisualState;
		}

		private static void EnsureAnchorAtPlayer(Player player, BRSDownedVisualState state)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Expected O, but got Unknown
			if ((Object)(object)state.AnchorRoot == (Object)null)
			{
				state.AnchorRoot = new GameObject("BRS_DownedAttachRoot");
				((Object)state.AnchorRoot).hideFlags = (HideFlags)61;
				state.AnchorRoot.layer = ((Component)player).gameObject.layer;
			}
			state.AnchorRoot.transform.position = ((Component)player).transform.position;
			state.AnchorRoot.transform.rotation = ((Component)player).transform.rotation;
			if ((Object)(object)state.AttachPoint == (Object)null)
			{
				GameObject val = new GameObject("attachpoint");
				((Object)val).hideFlags = (HideFlags)61;
				val.transform.SetParent(state.AnchorRoot.transform, false);
				state.AttachPoint = val.transform;
			}
			state.AttachPoint.localPosition = Vector3.zero;
			state.AttachPoint.localRotation = Quaternion.identity;
		}

		private static void ApplyDownedPhysics(Player player)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)((Character)player).m_body != (Object)null)
				{
					((Character)player).m_body.linearVelocity = Vector3.zero;
					((Character)player).m_body.angularVelocity = Vector3.zero;
				}
			}
			catch
			{
			}
		}

		private static void AttachPlayer(Player player, BRSDownedVisualState state, string wantedAnimation)
		{
			if ((Object)(object)state.AttachPoint == (Object)null)
			{
				return;
			}
			if (!state.Attached || !string.Equals(state.AnimationName, wantedAnimation, StringComparison.Ordinal))
			{
				if (!state.Attached || DetachPlayer(player, state))
				{
					string text = wantedAnimation;
					bool flag = TryAttach(player, state.AttachPoint, text);
					if (!flag && !string.Equals(text, "emote_sit", StringComparison.Ordinal))
					{
						text = "emote_sit";
						flag = TryAttach(player, state.AttachPoint, text);
					}
					if (flag)
					{
						SnapPlayerToAnchor(player, state.AttachPoint);
						state.Attached = true;
						state.AnimationName = text;
					}
				}
			}
			else
			{
				SnapPlayerToAnchor(player, state.AttachPoint);
			}
		}

		private static string GetConfiguredAnimation()
		{
			string text = ((BRSConfig.DownedPresentationMode != null) ? BRSConfig.DownedPresentationMode.Value : "Sit");
			if (string.IsNullOrWhiteSpace(text))
			{
				return "emote_sit";
			}
			if (DownedAnimations.TryGetValue(text.Trim(), out var value))
			{
				return value;
			}
			return "emote_sit";
		}

		private static bool TryAttach(Player player, Transform attachPoint, string animationName)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				((Character)player).AttachStart(attachPoint, (GameObject)null, false, false, false, animationName, DetachOffset, (Transform)null);
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static void SnapPlayerToAnchor(Player player, Transform attachPoint)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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)
			Quaternion rotation = attachPoint.rotation;
			Vector3 position = attachPoint.position;
			try
			{
				((Component)player).transform.position = position;
				((Component)player).transform.rotation = rotation;
			}
			catch
			{
			}
			try
			{
				if ((Object)(object)((Character)player).m_body != (Object)null)
				{
					((Character)player).m_body.position = position;
					((Character)player).m_body.rotation = rotation;
					((Character)player).m_body.linearVelocity = Vector3.zero;
					((Character)player).m_body.angularVelocity = Vector3.zero;
				}
			}
			catch
			{
			}
		}

		private static void ApplyObserverAnimation(Player player, BRSDownedVisualState state, string wantedAnimation)
		{
			ZSyncAnimation val = (((Object)(object)player != (Object)null) ? ((Character)player).m_zanim : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			string text = (HasBoolParameter(val, wantedAnimation) ? wantedAnimation : "emote_sit");
			if (HasBoolParameter(val, text))
			{
				if (!string.IsNullOrEmpty(state.ObserverAnimationName) && !string.Equals(state.ObserverAnimationName, text, StringComparison.Ordinal))
				{
					SetSyncedAnimatorBoolLocal(val, state.ObserverAnimationName, value: false);
					state.ObserverAnimationName = string.Empty;
				}
				SetSyncedAnimatorBoolLocal(val, text, value: true);
				state.ObserverAnimationName = text;
			}
		}

		private static void ClearObserverAnimation(Player player, BRSDownedVisualState state)
		{
			if (!string.IsNullOrEmpty(state.ObserverAnimationName))
			{
				ZSyncAnimation val = (((Object)(object)player != (Object)null) ? ((Character)player).m_zanim : null);
				if ((Object)(object)val != (Object)null)
				{
					SetSyncedAnimatorBoolLocal(val, state.ObserverAnimationName, value: false);
				}
				state.ObserverAnimationName = string.Empty;
			}
		}

		private static bool HasBoolParameter(ZSyncAnimation syncAnimation, string parameterName)
		{
			if ((Object)(object)syncAnimation == (Object)null || string.IsNullOrEmpty(parameterName))
			{
				return false;
			}
			try
			{
				return syncAnimation.HasParameter(parameterName, (AnimatorControllerParameterType)4);
			}
			catch
			{
				return false;
			}
		}

		private static void SetSyncedAnimatorBoolLocal(ZSyncAnimation syncAnimation, string parameterName, bool value)
		{
			if ((Object)(object)syncAnimation == (Object)null || string.IsNullOrEmpty(parameterName))
			{
				return;
			}
			try
			{
				if (HasBoolParameter(syncAnimation, parameterName))
				{
					syncAnimation.SetBool(parameterName, value);
				}
			}
			catch
			{
			}
		}

		private static bool DetachPlayer(Player player, BRSDownedVisualState state)
		{
			if (!state.Attached && (Object)(object)state.AttachPoint == (Object)null)
			{
				return true;
			}
			try
			{
				((Character)player).AttachStop();
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[BalrondSecondChance] Downed attachment release failed; will retry: " + ex));
				return false;
			}
			state.Attached = false;
			state.AnimationName = string.Empty;
			return true;
		}

		private static void ClearLocalAttachBookkeeping(BRSDownedVisualState state)
		{
			state.Attached = false;
			state.AnimationName = string.Empty;
		}

		private static void DestroyAnchor(BRSDownedVisualState state)
		{
			state.AttachPoint = null;
			if ((Object)(object)state.AnchorRoot != (Object)null)
			{
				Object.Destroy((Object)(object)state.AnchorRoot);
				state.AnchorRoot = null;
			}
		}
	}
	internal sealed class BRSDownedVisualState : MonoBehaviour
	{
		internal GameObject AnchorRoot;

		internal Transform AttachPoint;

		internal bool Attached;

		internal string AnimationName = string.Empty;

		internal string ObserverAnimationName = string.Empty;

		private void OnDestroy()
		{
			if ((Object)(object)AnchorRoot != (Object)null)
			{
				Object.Destroy((Object)(object)AnchorRoot);
			}
		}
	}
	internal static class BRSReviveRules
	{
		internal static bool IsWithinReviveDistance(Player reviver, Player target)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)reviver == (Object)null || (Object)(object)target == (Object)null)
			{
				return false;
			}
			float num = Mathf.Max(0f, BRSConfig.ReviveDistance.Value);
			return Vector3.Distance(((Component)reviver).transform.position, ((Component)target).transform.position) <= num;
		}

		internal static bool IsPotionReviveOnCooldown(Player target, out float remainingMinutes)
		{
			remainingMinutes = 0f;
			if ((Object)(object)target == (Object)null)
			{
				return false;
			}
			float num = Mathf.Max(0f, BRSConfig.ResurrectionCooldownMinutes.Value);
			if (num <= 0f)
			{
				return false;
			}
			long lastPotionReviveTicks = target.GetLastPotionReviveTicks();
			if (lastPotionReviveTicks <= 0)
			{
				return false;
			}
			DateTime dateTime;
			try
			{
				dateTime = new DateTime(lastPotionReviveTicks, DateTimeKind.Utc);
			}
			catch
			{
				return false;
			}
			double totalMinutes = (DateTime.UtcNow - dateTime).TotalMinutes;
			double num2 = (double)num - totalMinutes;
			if (num2 <= 0.0)
			{
				return false;
			}
			remainingMinutes = (float)num2;
			return true;
		}

		internal static bool CanFriendRevive(Player reviver, Player target, out string reason)
		{
			return CanFriendRevive(reviver, target, out reason, checkItem: true);
		}

		internal static bool CanFriendRevive(Player reviver, Player target, out string reason, bool checkItem)
		{
			reason = string.Empty;
			if ((Object)(object)reviver == (Object)null)
			{
				reason = "$tag_brs_reason_no_reviver_bal";
				return false;
			}
			if ((Object)(object)target == (Object)null)
			{
				reason = "$tag_brs_reason_invalid_revive_target_bal";
				return false;
			}
			if ((Object)(object)reviver == (Object)(object)target)
			{
				reason = "$tag_brs_reason_use_self_revive_instead_bal";
				return false;
			}
			if (!target.IsDowned())
			{
				reason = "$tag_brs_reason_target_not_downed_bal";
				return false;
			}
			if (BRSConfig.AllowFriendRevive != null && !BRSConfig.AllowFriendRevive.Value)
			{
				reason = "Friend revive is disabled.";
				return false;
			}
			if (!IsWithinReviveDistance(reviver, target))
			{
				reason = "$msg_toofar";
				return false;
			}
			if (IsPotionReviveOnCooldown(target, out var remainingMinutes))
			{
				reason = BuildCooldownReason(remainingMinutes);
				return false;
			}
			if (checkItem && BRSConfig.RequireItemForFriendReviveEffective && !BRSItemUtils.TryFindResurrectionPotion(reviver, out var _))
			{
				reason = "$tag_brs_need_item_bal " + BRSConfig.ResurrectionItemOptionsDisplay + ".";
				return false;
			}
			return true;
		}

		internal static bool CanSelfRevive(Player target, out string reason)
		{
			reason = string.Empty;
			if ((Object)(object)target == (Object)null)
			{
				reason = "$tag_brs_reason_invalid_player_bal";
				return false;
			}
			if (!target.IsDowned())
			{
				reason = "$tag_brs_reason_you_are_not_downed_bal";
				return false;
			}
			if (!BRSConfig.AllowSelfPotionRevive.Value)
			{
				reason = "$tag_brs_reason_self_revive_disabled_bal";
				return false;
			}
			if (IsPotionReviveOnCooldown(target, out var remainingMinutes))
			{
				reason = BuildCooldownReason(remainingMinutes);
				return false;
			}
			if (BRSConfig.RequireItemForSelfReviveEffective && !BRSItemUtils.TryFindResurrectionPotion(target, out var _))
			{
				reason = "$tag_brs_need_item_bal " + BRSConfig.ResurrectionItemOptionsDisplay + " $tag_brs_in_your_inventory_bal";
				return false;
			}
			return true;
		}

		internal static void MarkPotionReviveUsed(Player target)
		{
			if ((Object)(object)target == (Object)null)
			{
				return;
			}
			target.SetLastPotionReviveTicks(DateTime.UtcNow.Ticks);
			try
			{
				BRSStatusEffectManager.ApplyCooldownVisual(target);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[BalrondSecondChance] Could not apply cooldown visual: " + ex));
			}
		}

		internal static string BuildCooldownReason(float remainingMinutes)
		{
			return "$tag_brs_reason_soul_unstable_bal " + Mathf.Max(0f, remainingMinutes).ToString("0.0") + " $tag_brs_reason_cooldown_left_bal";
		}
	}
	internal static class BRSStatusEffectManager
	{
		private const string SourceEffectName = "SoftDeath";

		private const string CooldownEffectObjectName = "RessurectCooldown_bal";

		private const string CooldownDisplayName = "$tag_brs_ressurection_sickness_bal";

		private const string CooldownTooltip = "$tag_brs_ressurection_sickness_tooltip_bal";

		private static StatusEffect _cachedEffect;

		internal static void ResetCachedEffect()
		{
			_cachedEffect = null;
		}

		internal static StatusEffect EnsureCooldownEffect()
		{
			if ((Object)(object)_cachedEffect != (Object)null)
			{
				return _cachedEffect;
			}
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return null;
			}
			StatusEffect val = FindEffectByObjectName("RessurectCooldown_bal");
			if ((Object)(object)val != (Object)null)
			{
				_cachedEffect = val;
				return _cachedEffect;
			}
			StatusEffect val2 = FindEffectByObjectName("SoftDeath");
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			StatusEffect val3 = Object.Instantiate<StatusEffect>(val2);
			((Object)val3).name = "RessurectCooldown_bal";
			val3.m_name = "$tag_brs_ressurection_sickness_bal";
			val3.m_tooltip = "$tag_brs_ressurection_sickness_tooltip_bal";
			val3.m_ttl = Mathf.Max(1f, BRSConfig.ResurrectionCooldownMinutes.Value * 60f);
			if (ObjectDB.instance.m_StatusEffects == null)
			{
				ObjectDB.instance.m_StatusEffects = new List<StatusEffect>();
			}
			ObjectDB.instance.m_StatusEffects.Add(val3);
			_cachedEffect = val3;
			return _cachedEffect;
		}

		internal static void ApplyCooldownVisual(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			EnsureCooldownEffect();
			SEMan sEMan = ((Character)player).GetSEMan();
			if (sEMan == null)
			{
				return;
			}
			int stableHashCode = StringExtensionMethods.GetStableHashCode("RessurectCooldown_bal");
			StatusEffect statusEffect = sEMan.GetStatusEffect(stableHashCode);
			if ((Object)(object)statusEffect != (Object)null)
			{
				sEMan.RemoveStatusEffect(stableHashCode, true);
			}
			sEMan.AddStatusEffect(stableHashCode, true, 0, 0f, (short)(-1));
			StatusEffect statusEffect2 = sEMan.GetStatusEffect(stableHashCode);
			if ((Object)(object)statusEffect2 != (Object)null)
			{
				float num = player.GetRemainingPotionReviveCooldownMinutes();
				if (num <= 0f)
				{
					num = Mathf.Max(0f, BRSConfig.ResurrectionCooldownMinutes.Value);
				}
				statusEffect2.m_ttl = Mathf.Max(1f, num * 60f);
			}
		}

		private static StatusEffect FindEffectByObjectName(string objectName)
		{
			if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_StatusEffects == null)
			{
				return null;
			}
			for (int i = 0; i < ObjectDB.instance.m_StatusEffects.Count; i++)
			{
				StatusEffect val = ObjectDB.instance.m_StatusEffects[i];
				if (!((Object)(object)val == (Object)null) && ((Object)val).name == objectName)
				{
					return val;
				}
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	internal static class BRSObjectDBAwakeStatusEffectPatch
	{
		private static void Postfix()
		{
			BRSStatusEffectManager.ResetCachedEffect();
			BRSStatusEffectManager.EnsureCooldownEffect();
			BRSHudController.ClearCachedItemDisplayName();
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	internal static class BRSObjectDBCopyOtherDBStatusEffectPatch
	{
		private static void Postfix()
		{
			BRSStatusEffectManager.ResetCachedEffect();
			BRSStatusEffectManager.EnsureCooldownEffect();
			BRSHudController.ClearCachedItemDisplayName();
		}
	}
	internal static class BRSItemUtils
	{
		private static bool MatchesOption(ItemData item, string allowed)
		{
			if (item == null || string.IsNullOrEmpty(allowed))
			{
				return false;
			}
			string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null);
			string text2 = ((item.m_shared != null) ? item.m_shared.m_name : null);
			return (!string.IsNullOrEmpty(text) && string.Equals(text, allowed, StringComparison.OrdinalIgnoreCase)) || (!string.IsNullOrEmpty(text2) && string.Equals(text2, allowed, StringComparison.OrdinalIgnoreCase));
		}

		internal static bool IsResurrectionPotion(ItemData item)
		{
			string[] resurrectionItemPrefabs = BRSConfig.ResurrectionItemPrefabs;
			foreach (string allowed in resurrectionItemPrefabs)
			{
				if (MatchesOption(item, allowed))
				{
					return true;
				}
			}
			return false;
		}

		private static bool FindOption(Inventory inventory, int amount, out ItemData representative, out List<ItemData> stacks)
		{
			representative = null;
			stacks = null;
			if (inventory == null || amount < 1)
			{
				return false;
			}
			string[] resurrectionItemPrefabs = BRSConfig.ResurrectionItemPrefabs;
			foreach (string allowed in resurrectionItemPrefabs)
			{
				List<ItemData> list = new List<ItemData>();
				int num = 0;
				foreach (ItemData allItem in inventory.GetAllItems())
				{
					if (MatchesOption(allItem, allowed) && allItem.m_stack > 0)
					{
						list.Add(allItem);
						num += allItem.m_stack;
						if (num >= amount)
						{
							break;
						}
					}
				}
				if (num >= amount)
				{
					representative = list[0];
					stacks = list;
					return true;
				}
			}
			return false;
		}

		internal static bool TryFindResurrectionPotion(Player player, out ItemData foundItem)
		{
			foundItem = null;
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			List<ItemData> stacks;
			return FindOption(((Humanoid)player).GetInventory(), Mathf.Max(1, BRSConfig.ResurrectionItemAmount.Value), out foundItem, out stacks);
		}

		internal static bool ConsumeResurrectionPotion(Player player)
		{
			return ConsumeResurrectionPotion(player, Mathf.Max(1, BRSConfig.ResurrectionItemAmount.Value));
		}

		internal static bool ConsumeResurrectionPotion(Player player, int amount)
		{
			if ((Object)(object)player == (Object)null || !((Character)player).IsOwner())
			{
				return false;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			amount = Mathf.Max(1, amount);
			if (!FindOption(inventory, amount, out var _, out var stacks))
			{
				return false;
			}
			return Deduct(inventory, player, stacks, amount);
		}

		internal static bool CanPayWithSpecificItem(Player player, ItemData item, int amount)
		{
			if ((Object)(object)player == (Object)null || item == null || ((Humanoid)player).GetInventory() == null)
			{
				return false;
			}
			List<ItemData> allItems = ((Humanoid)player).GetInventory().GetAllItems();
			bool flag = false;
			foreach (ItemData item2 in allItems)
			{
				if (item2 == item)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				return false;
			}
			string[] resurrectionItemPrefabs = BRSConfig.ResurrectionItemPrefabs;
			foreach (string allowed in resurrectionItemPrefabs)
			{
				if (!MatchesOption(item, allowed))
				{
					continue;
				}
				int num = 0;
				foreach (ItemData item3 in allItems)
				{
					if (MatchesOption(item3, allowed) && item3.m_stack > 0)
					{
						num += item3.m_stack;
						if (num >= amount)
						{
							return true;
						}
					}
				}
			}
			return false;
		}

		internal static bool ConsumeSpecificItem(Player player, ItemData item, int amount)
		{
			if ((Object)(object)player == (Object)null || !((Character)player).IsOwner() || item == null)
			{
				return false;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return false;
			}
			amount = Mathf.Max(1, amount);
			List<ItemData> allItems = inventory.GetAllItems();
			bool flag = false;
			foreach (ItemData item2 in allItems)
			{
				if (item2 == item)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				return false;
			}
			string[] resurrectionItemPrefabs = BRSConfig.ResurrectionItemPrefabs;
			foreach (string allowed in resurrectionItemPrefabs)
			{
				if (!MatchesOption(item, allowed))
				{
					continue;
				}
				List<ItemData> list = new List<ItemData>();
				int num = 0;
				if (item.m_stack > 0)
				{
					list.Add(item);
					num += item.m_stack;
				}
				foreach (ItemData item3 in allItems)
				{
					if (num >= amount)
					{
						break;
					}
					if (item3 != item && MatchesOption(item3, allowed) && item3.m_stack > 0)
					{
						list.Add(item3);
						num += item3.m_stack;
					}
				}
				if (num < amount)
				{
					return false;
				}
				return Deduct(inventory, player, list, amount);
			}
			return false;
		}

		private static bool Deduct(Inventory inventory, Player player, List<ItemData> stacks, int amount)
		{
			int num = 0;
			foreach (ItemData stack in stacks)
			{
				if (stack != null && stack.m_stack > 0)
				{
					num += stack.m_stack;
				}
			}
			if (num < amount)
			{
				return false;
			}
			int num2 = amount;
			foreach (ItemData stack2 in stacks)
			{
				if (num2 <= 0)
				{
					break;
				}
				int num3 = Mathf.Min(num2, stack2.m_stack);
				if (num3 > 0)
				{
					inventory.RemoveItem(stack2, num3);
					num2 -= num3;
				}
			}
			try
			{
				player.OnInventoryChanged();
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[BalrondSecondChance] Inventory changed callback failed after deduction: " + ex));
			}
			return num2 == 0;
		}
	}
	public class BalrondTranslator
	{
		public static Dictionary<string, Dictionary<string, string>> translations = new Dictionary<string, Dictionary<string, string>>();

		public static Dictionary<string, string> getLanguage(string language)
		{
			if (string.IsNullOrEmpty(language))
			{
				return null;
			}
			if (translations.TryGetValue(language, out var value))
			{
				return value;
			}
			return null;
		}
	}
	public class JsonLoader
	{
		public string defaultPath = string.Empty;

		public void loadJson()
		{
			LoadTranslations();
			justDefaultPath();
		}

		public void justDefaultPath()
		{
			string configPath = Paths.ConfigPath;
			string text = Path.Combine(configPath, "BalrondSecondChance-translation/");
			defaultPath = text;
		}

		public void createDefaultPath()
		{
			string configPath = Paths.ConfigPath;
			string text = Path.Combine(configPath, "BalrondSecondChance-translation/");
			if (!Directory.Exists(text))
			{
				CreateFolder(text);
			}
			else
			{
				Debug.Log((object)("BalrondSecondChance: Folder already exists: " + text));
			}
			defaultPath = text;
		}

		private string[] jsonFilePath(string folderName, string extension)
		{
			string configPath = Paths.ConfigPath;
			string text = Path.Combine(configPath, "BalrondSecondChance-translation/");
			if (!Directory.Exists(text))
			{
				CreateFolder(text);
			}
			else
			{
				Debug.Log((object)("BalrondSecondChance: Folder already exists: " + text));
			}
			string[] files = Directory.GetFiles(text, extension);
			Debug.Log((object)("BalrondSecondChance:" + folderName + " Json Files Found: " + files.Length));
			return files;
		}

		private static void CreateFolder(string path)
		{
			try
			{
				Directory.CreateDirectory(path);
				Debug.Log((object)"BalrondSecondChance: Folder created successfully.");
			}
			catch (Exception ex)
			{
				Debug.Log((object)("BalrondSecondChance: Error creating folder: " + ex.Message));
			}
		}

		private void LoadTranslations()
		{
			int num = 0;
			string[] array;
			try
			{
				array = jsonFilePath("Translation", "*.json");
			}
			catch (Exception ex)
			{
				Debug.LogError((object)("BalrondSecondChance: Could not enumerate translation files: " + ex));
				return;
			}
			string[] array2 = array;
			foreach (string text in array2)
			{
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
				try
				{
					string json = File.ReadAllText(text);
					JsonData jsonData = JsonMapper.ToObject(json);
					if (jsonData == null)
					{
						throw new FormatException("Expected a JSON object containing translation keys.");
					}
					Dictionary<string, string> dictionary = new Dictionary<string, string>();
					foreach (string key in jsonData.Keys)
					{
						if (jsonData[key] != null)
						{
							dictionary[key] = jsonData[key].ToString();
						}
					}
					BalrondTranslator.translations[fileNameWithoutExtension] = dictionary;
					Debug.Log((object)("BalrondSecondChance: Json Files Language: " + fileNameWithoutExtension));
					num++;
				}
				catch (Exception ex2)
				{
					Debug.LogError((object)("BalrondSecondChance: Translation load failed for '" + text + "': " + ex2));
				}
			}
			Debug.Log((object)("BalrondSecondChance: Translation JsonFiles Loaded: " + num));
		}
	}
	[HarmonyPatch]
	internal static class TranslationPatches
	{
		[HarmonyPatch(typeof(FejdStartup), "SetupGui")]
		private class FejdStartup_SetupGUI
		{
			private static void Postfix()
			{
				string selectedLanguage = Localization.instance.GetSelectedLanguage();
				Dictionary<string, string> translations = GetTranslations(selectedLanguage);
				AddTranslations(translations);
			}
		}

		[HarmonyPriority(800)]
		[HarmonyPatch(typeof(Localization), "SetupLanguage")]
		private class Translation_SetupLanguage
		{
			private static void Prefix(Localization __instance, string language)
			{
				Dictionary<string, string> translations = GetTranslations(language);
				AddTranslations(translations, __instance);
			}
		}

		[HarmonyPriority(800)]
		[HarmonyPatch(typeof(Localization), "LoadCSV")]
		private class Translation_LoadCSV
		{
			private static void Prefix(Localization __instance, string language)
			{
				Dictionary<string, string> translations = GetTranslations(language);
				AddTranslations(translations, __instance);
			}
		}

		private static Dictionary<string, string> GetTranslations(string language)
		{
			Dictionary<string, string> result = BalrondTranslator.getLanguage("English");
			if (!string.Equals(language, "English", StringComparison.OrdinalIgnoreCase))
			{
				Dictionary<string, string> language2 = BalrondTranslator.getLanguage(language);
				if (language2 != null)
				{
					result = language2;
				}
				else
				{
					Debug.Log((object)("BalrondSecondChance: Did not find translation file for '" + language + "', loading English"));
				}
			}
			return result;
		}

		private static void AddTranslations(Dictionary<string, string> translations, Localization localizationInstance = null)
		{
			if (translations == null)
			{
				Debug.LogWarning((object)"BalrondSecondChance: No translation file found!");
				return;
			}
			if (localizationInstance != null)
			{
				foreach (KeyValuePair<string, string> translation in translations)
				{
					localizationInstance.AddWord(translation.Key, translation.Value);
				}
				return;
			}
			foreach (KeyValuePair<string, string> translation2 in translations)
			{
				Localization.instance.AddWord(translation2.Key, translation2.Value);
			}
		}
	}
	[HarmonyPatch(typeof(AudioMan), "Awake")]
	internal static class BRSAudioManAwakePatch
	{
		private static void Postfix(AudioMan __instance)
		{
			AudioRoutingService.OnAudioManAwake(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "Awake")]
	internal static class BRSPlayerAwakePatch
	{
		private static void Postfix(Player __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent<BRSDownedBehaviour>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<BRSDownedBehaviour>();
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class BRSPlayerOnDeathPatch
	{
		private static bool Prefix(Player __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if (Launch.AllowOriginalDeath)
			{
				return true;
			}
			if ((Object)(object)((Character)__instance).m_nview == (Object)null || !((Character)__instance).m_nview.IsValid())
			{
				return true;
			}
			if (!((Character)__instance).IsOwner())
			{
				return true;
			}
			if (__instance.IsDowned())
			{
				return false;
			}
			if (__instance.InReviveGrace())
			{
				if (((Character)__instance).GetHealth() <= 0f)
				{
					((Character)__instance).SetHealth(1f);
				}
				if (BRSConfig.DebugLogging != null && BRSConfig.DebugLogging.Value)
				{
					Debug.LogWarning((object)"[BalrondSecondChance] Prevented death during revive grace.");
				}
				return false;
			}
			bool flag = BRSConfig.AllowSelfPotionRevive != null && BRSConfig.AllowSelfPotionRevive.Value;
			bool flag2 = BRSConfig.AllowFriendRevive == null || BRSConfig.AllowFriendRevive.Value;
			if (!flag && !flag2)
			{
				return true;
			}
			if (BRSReviveRules.IsPotionReviveOnCooldown(__instance, out var remainingMinutes))
			{
				if (BRSConfig.DebugLogging != null && BRSConfig.DebugLogging.Value)
				{
					Debug.LogWarning((object)("[BalrondSecondChance] Normal death allowed: potion-revive cooldown active (" + remainingMinutes.ToString("0.0") + " minutes left)."));
				}
				return true;
			}
			float num = Mathf.Max(1f, BRSConfig.DownedSeconds.Value);
			float until = Time.time + num;
			float health = Mathf.Max(1f, ((Character)__instance).GetMaxHealth() * 0.01f);
			__instance.SetDowned(value: true, until);
			if (BRSConfig.DebugLogging != null && BRSConfig.DebugLogging.Value)
			{
				Debug.Log((object)("[BalrondSecondChance][Downed] Owner entered Downed State; local=" + ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) + ", duration=" + num + "s, HUD=" + (((Object)(object)Hud.instance != (Object)null) ? "present" : "missing")));
			}
			BRSAIIgnoreDownedPlayers.ClearExistingTargetsFor(__instance);
			__instance.ClearReleaseRequest();
			((Character)__instance).SetHealth(health);
			BRSPlayerPresentation.SetDownedPresentation(__instance, downed: true);
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)2, "$tag_brs_death_wait_message_bal", 0, (Sprite)null, false, true);
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Game), "UpdateRespawn")]
	internal static class BRSGameUpdateRespawnPatch
	{
		private static bool Prefix()
		{
			Player localPlayer = Player.m_localPlayer;
			return (Object)(object)localPlayer == (Object)null || !localPlayer.IsDowned();
		}
	}
	[HarmonyPatch(typeof(Player), "CreateTombStone")]
	internal static class BRSCreateTombStonePatch
	{
		private static bool Prefix(Player __instance)
		{
			return (Object)(object)__instance == (Object)null || !__instance.IsDowned();
		}
	}
	internal static class BRSAIIgnoreDownedPlayers
	{
		internal static bool IsDownedPlayer(Character character)
		{
			Player val = (Player)(object)((character is Player) ? character : null);
			return (Object)(object)val != (Object)null && val.IsDowned();
		}

		internal static void ClearExistingTargetsFor(Player downedPlayer)
		{
			if ((Object)(object)downedPlayer == (Object)null)
			{
				return;
			}
			MonsterAI[] array = Object.FindObjectsOfType<MonsterAI>();
			foreach (MonsterAI val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)((BaseAI)val).GetTargetCreature() != (Object)(object)downedPlayer))
				{
					val.SetTarget((Character)null);
					((BaseAI)val).SetAlerted(false);
				}
			}
		}
	}
	[HarmonyPatch(typeof(MonsterAI), "SetTarget")]
	internal static class BRSMonsterAISetTargetPatch
	{
		private static bool Prefix(MonsterAI __instance, Character __0)
		{
			if (!BRSAIIgnoreDownedPlayers.IsDownedPlayer(__0))
			{
				return true;
			}
			if ((Object)(object)__instance != (Object)null)
			{
				((BaseAI)__instance).SetAlerted(false);
			}
			return false;
		}
	}
	internal static class BRSReviveDamageProtection
	{
		internal static bool IsProtected(Character character)
		{
			Player val = (Player)(object)((character is Player) ? character : null);
			return (Object)(object)val != (Object)null && (val.IsDowned() || val.InReviveGrace());
		}
	}
	[HarmonyPatch(typeof(Character), "Damage")]
	internal static class BRSDownedPlayerDamagePatch
	{
		private static bool Prefix(Character __instance)
		{
			return !BRSReviveDamageProtection.IsProtected(__instance);
		}
	}
	[HarmonyPatch(typeof(Character), "ApplyDamage")]
	internal static class BRSReviveApplyDamagePatch
	{
		private static bool Prefix(Character __instance)
		{
			return !BRSReviveDamageProtection.IsProtected(__instance);
		}
	}
	[HarmonyPatch(typeof(Hud), "Awake")]
	internal static class BRSHudAwakePatch
	{
		private static void Postfix(Hud __instance)
		{
			BRSHudController.EnsureInstance();
			BRSHudController.Instance.BindHud(__instance);
		}
	}
	[HarmonyPatch(typeof(Hud), "OnDestroy")]
	internal static class BRSHudOnDestroyPatch
	{
		private static void Prefix(Hud __instance)
		{
			if (BRSHudController.HasInstance)
			{
				BRSHudController.Instance.UnbindHud(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerController), "TakeInput")]
	internal static class BRSPlayerControllerTakeInputPatch
	{
		private static bool Prefix(ref bool __result)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !localPlayer.IsDowned())
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class BRSPlayerUpdatePatch
	{
		private static bool Prefix(Player __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if ((Object)(object)((Character)__instance).m_nview == (Object)null || !((Character)__instance).m_nview.IsValid() || !((Character)__instance).m_nview.IsOwner())
			{
				return true;
			}
			if (!__instance.IsDowned())
			{
				return true;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "UseHotbarItem")]
	internal static class BRSUseHotbarItemPatch
	{
		private static bool Prefix(Player __instance)
		{
			return (Object)(object)__instance == (Object)null || !__instance.IsDowned();
		}
	}
	[HarmonyPatch(typeof(Player), "HandleRadialInput")]
	internal static class BRSHandleRadialInputPatch
	{
		private static bool Prefix(Player __instance)
		{
			return (Object)(object)__instance == (Object)null || !__instance.IsDowned();
		}
	}
	[HarmonyPatch(typeof(Player), "UpdatePlacement")]
	internal static class BRSUpdatePlacementPatch
	{
		private static bool Prefix(Player __instance)
		{
			return (Object)(object)__instance == (Object)null || !__instance.IsDowned();
		}
	}
	[HarmonyPatch(typeof(ZInput), "IsMouseActive")]
	internal static class BRSZInputIsMouseActivePatch
	{
		private static void Postfix(ref bool __result)
		{
			if (BRSHudController.HasInstance && BRSHudController.Instance.ShouldForceMouseActive())
			{
				__result = true;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "GetHoverText")]
	internal static class BRSPlayerHoverTextPatch
	{
		private static bool Prefix(Player __instance, ref string __result)
		{
			if ((Object)(object)__instance == (Object)null || !__instance.IsDowned())
			{
				return true;
			}
			BRSDownedBehaviour component = ((Component)__instance).GetComponent<BRSDownedBehaviour>();
			if ((Object)(object)component == (Object)null)
			{
				return true;
			}
			__result = component.GetHoverText();
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "Interact", new Type[]
	{
		typeof(GameObject),
		typeof(bool),
		typeof(bool)
	})]
	internal static class BRSPlayerInteractBridgePatch
	{
		private static bool Prefix(Player __instance, GameObject __0, bool __1, bool __2)
		{
			if ((Object)(object)__instance == (Object)null || (Object)(object)__0 == (Object)null)
			{
				return true;
			}
			Player componentInParent = __0.GetComponentInParent<Player>();
			if ((Object)(object)componentInParent == (Object)null || (Object)(object)componentInParent == (Object)(object)__instance || !componentInParent.IsDowned())
			{
				return true;
			}
			BRSDownedBehaviour component = ((Component)componentInParent).GetComponent<BRSDownedBehaviour>();
			if ((Object)(object)component == (Object)null)
			{
				return true;
			}
			return !component.Interact((Humanoid)(object)__instance, __1, __2);
		}
	}
	internal sealed class BRSHudController : MonoBehaviour
	{
		private const string UnifiedPopupPath = "_GameMain/LoadingGUI/PixelFix/IngameGui/UnifiedPopup";

		private static BRSHudController _instance;

		private static string _cachedItemDisplayName;

		private static string _cachedItemConfig;

		private GameObject _downedPopupRoot;

		private bool _isSetup;

		private bool _isBinding;

		private float _nextRefreshTime;

		private bool _suppressReleaseUntilEscapeUp;

		private bool _wantPopupCursor;

		private GameCamera _gameCameraDisabledByPopup;

		private Hud _boundHud;

		private Coroutine _setupRoutine;

		private float _nextRecoveryAttempt;

		private float _nextDiagnosticTime;

		private string _lastFailure;

		private bool _fallbackLogged;

		private bool _downedObserved;

		private GameObject _popupBackground;

		private GameObject _popupPanel;

		private GameObject _popupBkg;

		private Component _headerText;

		private Component _bodyText;

		private Button _buttonYes;

		private Button _buttonOk;

		private Button _buttonNo;

		private Component _buttonYesText;

		private Component _buttonOkText;

		private Component _buttonNoText;

		private readonly List<Button> _extraPopupButtons = new List<Button>();

		private string _sourceLeftButtonPath;

		private string _sourceCenterButtonPath;

		private string _sourceRightButtonPath;

		private string _sourceConfirmButtonPath;

		private string _sourceLeftTextPath;

		private string _sourceCenterTextPath;

		private string _sourceRightTextPath;

		private static bool DebugEnabled => BRSConfig.DebugLogging != null && BRSConfig.DebugLogging.Value;

		internal static BRSHudController Instance => EnsureInstance();

		internal static bool HasInstance => (Object)(object)_instance != (Object)null;

		private void Diagnostic(string message)
		{
			if (DebugEnabled)
			{
				Debug.Log((object)("[BalrondSecondChance][UI] " + message));
			}
		}

		private void SetupFailure(string reason)
		{
			if (DebugEnabled && (_lastFailure != reason || Time.unscaledTime >= _nextDiagnosticTime))
			{
				Debug.LogWarning((object)("[BalrondSecondChance][UI] " + reason));
				_lastFailure = reason;
				_nextDiagnosticTime = Time.unscaledTime + 15f;
			}
		}

		internal static BRSHudController EnsureInstance()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			if ((Object)(object)_instance != (Object)null)
			{
				return _instance;
			}
			GameObject val = new GameObject("BRS_HudController");
			Object.DontDestroyOnLoad((Object)(object)val);
			_instance = val.AddComponent<BRSHudController>();
			return _instance;
		}

		internal static void ClearCachedItemDisplayName()
		{
			_cachedItemDisplayName = null;
			_cachedItemConfig = null;
		}

		internal bool ShouldForceMouseActive()
		{
			return _wantPopupCursor;
		}

		internal void BindHud(Hud hud)
		{
			if ((Object)(object)hud == (Object)null)
			{
				return;
			}
			if ((Object)(object)_boundHud != (Object)(object)hud)
			{
				if ((Object)(object)_boundHud != (Object)null)
				{
					UnbindHud(_boundHud);
				}
				_boundHud = hud;
				Diagnostic("HUD bound: " + ((Object)hud).name);
			}
			BeginSetup();
		}

		private void BeginSetup()
		{
			if (!((Object)(object)_boundHud == (Object)null) && !_isSetup && !_isBinding)
			{
				_setupRoutine = ((MonoBehaviour)this).StartCoroutine(SetupPopupWhenReady());
			}
		}

		internal void UnbindHud(Hud hud)
		{
			if (!((Object)(object)_boundHud != (Object)null) || !((Object)(object)hud != (Object)(object)_boundHud))
			{
				if (_setupRoutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_setupRoutine);
				}
				_setupRoutine = null;
				_isBinding = false;
				_isSetup = false;
				_boundHud = null;
				ReleaseInputCapture();
				DestroyOwnedPopup();
				_popupBackground = null;
				_popupPanel = null;
				_popupBkg = null;
				_headerText = null;
				_bodyText = null;
				_buttonYes = null;
				_buttonOk = null;
				_buttonNo = null;
				_buttonYesText = null;
				_buttonOkText = null;
				_buttonNoText = null;
				_extraPopupButtons.Clear();
				ClearSourcePopupSemanticPaths();
				Diagnostic("HUD unbound; popup invalidated.");
			}
		}

		private void DestroyOwnedPopup()
		{
			if (!((Object)(object)_downedPopupRoot == (Object)null))
			{
				AudioRoutingService.Forget(_downedPopupRoot);
				Object.Destroy((Object)(object)_downedPopupRoot);
				_downedPopupRoot = null;
			}
		}

		private IEnumerator SetupPopupWhenReady()
		{
			_isBinding = true;
			while (!_isSetup && (Object)(object)_boundHud != (Object)null)
			{
				try
				{
					if (TrySetupPopup())
					{
						Diagnostic("UnifiedPopup clone initialized successfully.");
						break;
					}
				}
				catch (Exception ex)
				{
					SetupFailure("Popup initialization exception: " + ex);
					DestroyOwnedPopup();
				}
				yield return (object)new WaitForSecondsRealtime(0.5f);
			}
			_isBinding = false;
			_setupRoutine = null;
		}

		private bool TrySetupPopup()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			Transform val = FindUnifiedPopupTransform();
			if ((Object)(object)val == (Object)null)
			{
				SetupFailure("UnifiedPopup not found in active UI hierarchy.");
				return false;
			}
			Transform val2 = val.Find("PopupBlockingBackground");
			if ((Object)(object)val2 == (Object)null)
			{
				SetupFailure("UnifiedPopup exists without PopupBlockingBackground.");
				return false;
			}
			CaptureSourcePopupSemanticPaths(val, val2);
			if ((Object)(object)_downedPopupRoot == (Object)null)
			{
				_downedPopupRoot = new GameObject("BRS_DownedPopupCanvas");
				_downedPopupRoot.transform.SetParent(((Component)this).transform, false);
				RectTransform val3 = _downedPopupRoot.AddComponent<RectTransform>();
				val3.anchorMin = Vector2.zero;
				val3.anchorMax = Vector2.one;
				val3.offsetMin = Vector2.zero;
				val3.offsetMax = Vector2.zero;
				((Transform)val3).localScale = Vector3.one;
				Canvas val4 = _downedPopupRoot.AddComponent<Canvas>();
				val4.renderMode = (RenderMode)0;
				val4.overrideSorting = true;
				val4.sortingOrder = 1000;
				CanvasScaler val5 = _downedPopupRoot.AddComponent<CanvasScaler>();
				val5.uiScaleMode = (ScaleMode)0;
				_downedPopupRoot.AddComponent<GuiScaler>();
				_downedPopupRoot.AddComponent<GraphicRaycaster>();
				GameObject val6 = Object.Instantiate<GameObject>(((Component)val2).gameObject, _downedPopupRoot.transform, false);
				((Object)val6).name = "PopupBlockingBackground";
				AudioRoutingService.Process(_downedPopupRoot);
			}
			if (!CachePopupRefs(_downedPopupRoot.transform))
			{
				SetupFailure("Cloned popup missing required text or action button references.");
				return false;
			}
			HideClonedPopupInputFields(_downedPopupRoot.transform);
			PrepareCloneForDownedUsage();
			WireButtons();
			_isSetup = true;
			Diagnostic("Independent downed Canvas initialized; vanilla UnifiedPopup active=" + ((Component)val).gameObject.activeInHierarchy + ".");
			HidePopup();
			return true;
		}

		private Transform FindUnifiedPopupTransform()
		{
			Transform[] array = Resources.FindObjectsOfTypeAll<Transform>();
			Transform val = null;
			foreach (Transform val2 in array)
			{
				if ((Object)(object)val2 == (Object)null || ((Object)val2).name != "UnifiedPopup")
				{
					continue;
				}
				Transform val3 = val2.Find("PopupBlockingBackground");
				if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3.Find("Popup") == (Object)null))
				{
					string hierarchyPath = GetHierarchyPath(val2);
					if (hierarchyPath.Contains("_GameMain/LoadingGUI/PixelFix