Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of balrond secondchance v1.0.17
plugins/BalrondSecondChance.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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