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 UltimateRevive v1.1.1
UltimateRevive.dll
Decompiled 5 days 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.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using LethalCompanyInputUtils.Api; using Microsoft.CodeAnalysis; using TMPro; using UltimateRevive.Patches; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("UltimateRevive")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.1.0")] [assembly: AssemblyInformationalVersion("1.1.1")] [assembly: AssemblyProduct("UltimateRevive")] [assembly: AssemblyTitle("UltimateRevive")] [assembly: AssemblyVersion("1.1.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace UltimateRevive { public static class CadaverBloomTracker { public static readonly Dictionary<int, CadaverBloomAI> PlayerBlooms = new Dictionary<int, CadaverBloomAI>(); public static readonly Dictionary<int, DeadBodyInfo> PlayerBodies = new Dictionary<int, DeadBodyInfo>(); public static readonly HashSet<int> PendingBodyCheck = new HashSet<int>(); } public static class DeathMarkerManager { [CompilerGenerated] private sealed class <BuildCandidateBundlePaths>d__31 : IEnumerable<(string Path, bool FromConfiguredPath)>, IEnumerable, IEnumerator<(string Path, bool FromConfiguredPath)>, IEnumerator, IDisposable { private int <>1__state; private (string Path, bool FromConfiguredPath) <>2__current; private int <>l__initialThreadId; private HashSet<string> <unique>5__2; private (string RawPath, bool FromConfiguredPath)[] <>7__wrap2; private int <>7__wrap3; private (string RawPath, bool FromConfiguredPath) <source>5__5; private string <resolved>5__6; (string, bool) IEnumerator<(string, bool)>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <BuildCandidateBundlePaths>d__31(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <unique>5__2 = null; <>7__wrap2 = null; <source>5__5 = default((string, bool)); <resolved>5__6 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; <unique>5__2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase); (string, bool)[] array = new(string, bool)[2] { (ModConfig.DeathTombAssetBundlePath, true), ("chaps-UltimateRevive/sm_tombstone_3_dark_rip.bundle", false) }; <>7__wrap2 = array; <>7__wrap3 = 0; goto IL_016d; } case 1: <>1__state = -1; goto IL_00f9; case 2: { <>1__state = -1; goto IL_014c; } IL_016d: if (<>7__wrap3 < <>7__wrap2.Length) { <source>5__5 = <>7__wrap2[<>7__wrap3]; string item = <source>5__5.RawPath; if (!string.IsNullOrWhiteSpace(item)) { <resolved>5__6 = (Path.IsPathRooted(item) ? item : Path.Combine(Paths.PluginPath, item)); if (<unique>5__2.Add(<resolved>5__6)) { <>2__current = (<resolved>5__6, <source>5__5.FromConfiguredPath); <>1__state = 1; return true; } goto IL_00f9; } goto IL_015f; } <>7__wrap2 = null; return false; IL_00f9: if (!Path.HasExtension(<resolved>5__6)) { string text = <resolved>5__6 + ".bundle"; if (<unique>5__2.Add(text)) { <>2__current = (text, <source>5__5.FromConfiguredPath); <>1__state = 2; return true; } } goto IL_014c; IL_014c: <resolved>5__6 = null; <source>5__5 = default((string, bool)); goto IL_015f; IL_015f: <>7__wrap3++; goto IL_016d; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<(string Path, bool FromConfiguredPath)> IEnumerable<(string, bool)>.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new <BuildCandidateBundlePaths>d__31(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<(string, bool)>)this).GetEnumerator(); } } private const float TombWidth = 0.6f; private const float TombHeight = 1.35f; private const float TombDepth = 0.35f; private static readonly Dictionary<int, GameObject> ActiveMarkers = new Dictionary<int, GameObject>(); private static readonly Dictionary<int, string> LastMarkerDecision = new Dictionary<int, string>(); private static readonly Dictionary<int, int> MarkerRadarIndexes = new Dictionary<int, int>(); private static readonly Dictionary<int, DeadBodyInfo> TombDeadBodies = new Dictionary<int, DeadBodyInfo>(); private static ManualLogSource _log; private static AssetBundle _assetBundle; private static bool _assetBundleLoadAttempted; private static GameObject _cachedPrefab; public static IReadOnlyDictionary<int, DeadBodyInfo> TombDeadBodiesPublic => TombDeadBodies; public static void SetLogSource(ManualLogSource logSource) { _log = logSource; } public static void InvalidateBundleCache() { _assetBundleLoadAttempted = false; _cachedPrefab = null; if ((Object)(object)_assetBundle != (Object)null) { _assetBundle.Unload(false); _assetBundle = null; } } public static bool IsTombDeadBody(int playerClientId, DeadBodyInfo deadBody) { if ((Object)(object)deadBody != (Object)null && TombDeadBodies.TryGetValue(playerClientId, out var value)) { return (Object)(object)value == (Object)(object)deadBody; } return false; } public static bool TryGetTombOwnerPlayerId(DeadBodyInfo deadBody, out int playerClientId) { playerClientId = -1; if ((Object)(object)deadBody == (Object)null) { return false; } foreach (KeyValuePair<int, DeadBodyInfo> tombDeadBody in TombDeadBodies) { if ((Object)(object)tombDeadBody.Value == (Object)(object)deadBody) { playerClientId = tombDeadBody.Key; return true; } } return false; } private static void DebugLog(string message) { if (ModConfig.EnableDebugLogs) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)message); } } } public static bool IsTombRagdollGrabbable(RagdollGrabbableObject body) { if ((Object)(object)body == (Object)null) { return false; } foreach (DeadBodyInfo value in TombDeadBodies.Values) { if ((Object)(object)value != (Object)null && (Object)(object)((Component)value).gameObject == (Object)(object)((Component)body).gameObject) { return true; } } return false; } public static bool TrySpawnTombForPlayer(PlayerControllerB player, Vector3 position, string reason = null) { //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) if (!ModState.Active || !ModConfig.EnableDeathTombFallback || (Object)(object)player == (Object)null) { return false; } int num = (int)player.playerClientId; if (CadaverBloomTracker.PendingBodyCheck.Contains(num)) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"[DeathMarker] Prevent tomb for player={num} (CadaverBloom pending body)"); } return false; } if (ActiveMarkers.TryGetValue(num, out var value) && (Object)(object)value != (Object)null) { DeadBodyInfo component = value.GetComponent<DeadBodyInfo>(); if ((Object)(object)component != (Object)null) { TombDeadBodies[num] = component; } GeneralUtil.SetPlayerDeathState(num, PlayerDeathType.Body, 0uL, value.transform.position, string.IsNullOrWhiteSpace(reason) ? "TombAlreadyActive" : (reason + ":alreadyActive")); ReviveNetworkSync.TryApplyPendingTombPosition(num); return true; } GameObject val = null; try { GameObject markerPrefab = GetMarkerPrefab(); val = (((Object)(object)markerPrefab != (Object)null) ? Object.Instantiate<GameObject>(markerPrefab) : CreateProceduralTombMarker()); } catch (Exception ex) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("Tomb prefab instantiate failed, using procedural marker: " + ex.Message)); } val = CreateProceduralTombMarker(); } if ((Object)(object)val == (Object)null) { return false; } ((Object)val).name = $"UltimateReviveTomb_{num}"; position = ProjectTombPositionToGround(position, num); val.transform.position = position; KeepMarkerUpright(val); DeadBodyInfo val2 = TombDeadBodySetup.SetupTombAsDeadBody(player, val, position); if ((Object)(object)val2 == (Object)null) { Object.Destroy((Object)(object)val); return false; } ActiveMarkers[num] = val; TombDeadBodies[num] = val2; GeneralUtil.SetPlayerDeathState(num, PlayerDeathType.Body, 0uL, position, string.IsNullOrWhiteSpace(reason) ? "TombSpawned" : reason); ReviveNetworkSync.TryApplyPendingTombPosition(num); ManualLogSource log3 = _log; if (log3 != null) { log3.LogInfo((object)string.Format("[DeathMarker] tombSpawned player={0} pos=({1:0.00},{2:0.00},{3:0.00}) reason={4}", num, position.x, position.y, position.z, string.IsNullOrWhiteSpace(reason) ? "unspecified" : reason)); } return true; } private static Vector3 ProjectTombPositionToGround(Vector3 deathPosition, int playerClientId) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(0f, ModConfig.DeathTombGroundOffset); float num2 = Mathf.Max(1f, ModConfig.DeathTombSpawnHeight); if (TryRaycastGroundBelow(deathPosition + Vector3.up * num2, out var point)) { Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(deathPosition.x, point.y + num, deathPosition.z); DebugLog($"[DeathMarker] tombGroundSnap player={playerClientId} " + $"from=({deathPosition.x:0.00},{deathPosition.y:0.00},{deathPosition.z:0.00}) " + $"to=({val.x:0.00},{val.y:0.00},{val.z:0.00})"); return val; } ManualLogSource log = _log; if (log != null) { log.LogWarning((object)($"[DeathMarker] tombGroundSnapFailed player={playerClientId} " + $"pos=({deathPosition.x:0.00},{deathPosition.y:0.00},{deathPosition.z:0.00})")); } return deathPosition; } private static bool TryRaycastGroundBelow(Vector3 origin, out Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_010b: 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) point = Vector3.zero; RaycastHit[] array = Physics.RaycastAll(origin, Vector3.down, 120f, -1, (QueryTriggerInteraction)1); if (array == null || array.Length == 0) { return false; } RaycastHit val = default(RaycastHit); float num = float.PositiveInfinity; bool flag = false; for (int i = 0; i < array.Length; i++) { RaycastHit val2 = array[i]; Collider collider = ((RaycastHit)(ref val2)).collider; if (!((Object)(object)collider == (Object)null) && collider.enabled && !collider.isTrigger && !((Object)(object)((Component)collider).GetComponentInParent<PlayerControllerB>() != (Object)null) && !((Object)(object)((Component)collider).GetComponentInParent<DeadBodyInfo>() != (Object)null) && !((Object)(object)((Component)collider).GetComponentInParent<MaskedPlayerEnemy>() != (Object)null) && !((Object)(object)((Component)collider).GetComponentInParent<ForestGiantAI>() != (Object)null) && ((Component)collider).gameObject.layer != LayerMask.NameToLayer("Enemies")) { Transform root = ((Component)collider).transform.root; if ((!((Object)(object)root != (Object)null) || !((Object)root).name.StartsWith("UltimateReviveTomb_")) && ((RaycastHit)(ref val2)).distance < num) { num = ((RaycastHit)(ref val2)).distance; val = val2; flag = true; } } } if (!flag) { return false; } point = ((RaycastHit)(ref val)).point; return true; } public static void ClearMarker(int playerClientId) { ReviveNetworkSync.ClearPendingTombPosition(playerClientId); if (!ActiveMarkers.TryGetValue(playerClientId, out var value)) { return; } if (TombDeadBodies.TryGetValue(playerClientId, out var value2)) { PlayerControllerB playerByClientId = GeneralUtil.GetPlayerByClientId(playerClientId); if ((Object)(object)playerByClientId != (Object)null && (Object)(object)playerByClientId.deadBody == (Object)(object)value2) { playerByClientId.deadBody = null; } TombDeadBodies.Remove(playerClientId); } UnregisterMarkerFromRadar(playerClientId); if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } ActiveMarkers.Remove(playerClientId); } public static bool HasActiveMarker(int playerClientId) { if (ActiveMarkers.TryGetValue(playerClientId, out var value)) { return (Object)(object)value != (Object)null; } return false; } public static bool TryGetMarkerObject(int playerClientId, out GameObject markerObject) { if (ActiveMarkers.TryGetValue(playerClientId, out var value) && (Object)(object)value != (Object)null) { markerObject = value; return true; } markerObject = null; return false; } public static bool TryGetMarkerPosition(int playerClientId, out Vector3 position) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (ActiveMarkers.TryGetValue(playerClientId, out var value) && (Object)(object)value != (Object)null && (Object)(object)value.transform != (Object)null) { position = value.transform.position; return true; } position = default(Vector3); return false; } public static void Reset() { ReviveNetworkSync.ClearAllPendingTombPositions(); foreach (KeyValuePair<int, DeadBodyInfo> tombDeadBody in TombDeadBodies) { PlayerControllerB playerByClientId = GeneralUtil.GetPlayerByClientId(tombDeadBody.Key); if ((Object)(object)playerByClientId != (Object)null && (Object)(object)playerByClientId.deadBody == (Object)(object)tombDeadBody.Value) { playerByClientId.deadBody = null; } } TombDeadBodies.Clear(); foreach (GameObject value in ActiveMarkers.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } ActiveMarkers.Clear(); LastMarkerDecision.Clear(); MarkerRadarIndexes.Clear(); InvalidateBundleCache(); } private static void UnregisterMarkerFromRadar(int playerClientId) { MarkerRadarIndexes.Remove(playerClientId); } private static void KeepMarkerUpright(GameObject marker) { //IL_001e: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_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)marker == (Object)null) && !((Object)(object)marker.transform == (Object)null)) { Quaternion rotation = marker.transform.rotation; Vector3 eulerAngles = ((Quaternion)(ref rotation)).eulerAngles; marker.transform.rotation = Quaternion.Euler(0f, eulerAngles.y, 0f); } } private static GameObject CreateProceduralTombMarker() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) GameObject obj = GameObject.CreatePrimitive((PrimitiveType)3); ((Object)obj).name = "ReviveDeathMarker_ProceduralCube"; obj.transform.localScale = new Vector3(0.6f, 1.35f, 0.35f); obj.transform.localPosition = new Vector3(0f, 0.675f, 0f); Renderer component = obj.GetComponent<Renderer>(); if ((Object)(object)component != (Object)null) { Material val = new Material(Shader.Find("Standard")); val.color = new Color(0.42f, 0.42f, 0.42f, 1f); component.material = val; } return obj; } private static GameObject GetMarkerPrefab() { if ((Object)(object)_cachedPrefab != (Object)null) { return _cachedPrefab; } if (_assetBundleLoadAttempted) { return null; } _assetBundleLoadAttempted = true; List<(string, bool)> list = BuildCandidateBundlePaths().ToList(); if (list.Count == 0) { return null; } for (int i = 0; i < list.Count; i++) { string item = list[i].Item1; bool flag = !list[i].Item2; if (!File.Exists(item)) { continue; } try { _assetBundle = AssetBundle.LoadFromFile(item); if ((Object)(object)_assetBundle == (Object)null) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("Failed to load death marker AssetBundle: " + item)); } continue; } string deathTombPrefabName = ModConfig.DeathTombPrefabName; if (!string.IsNullOrWhiteSpace(deathTombPrefabName)) { _cachedPrefab = _assetBundle.LoadAsset<GameObject>(deathTombPrefabName); } if ((Object)(object)_cachedPrefab == (Object)null) { _cachedPrefab = _assetBundle.LoadAsset<GameObject>("sm_tombstone_3_dark_rip"); } if ((Object)(object)_cachedPrefab == (Object)null) { GameObject[] array = _assetBundle.LoadAllAssets<GameObject>(); _cachedPrefab = ((array != null && array.Length != 0) ? array[0] : null); } if ((Object)(object)_cachedPrefab != (Object)null) { if (flag) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("Configured death marker bundle failed, fallback loaded: " + item)); } } return _cachedPrefab; } ManualLogSource log3 = _log; if (log3 != null) { log3.LogWarning((object)("Death marker AssetBundle has no GameObject prefab: " + item)); } _assetBundle.Unload(false); _assetBundle = null; } catch (Exception ex) { ManualLogSource log4 = _log; if (log4 != null) { log4.LogWarning((object)("Death marker loading failed for " + item + ": " + ex.Message)); } if ((Object)(object)_assetBundle != (Object)null) { _assetBundle.Unload(false); _assetBundle = null; } } } ManualLogSource log5 = _log; if (log5 != null) { log5.LogWarning((object)"No valid death marker bundle found. Using primitive fallback marker."); } return null; } [IteratorStateMachine(typeof(<BuildCandidateBundlePaths>d__31))] private static IEnumerable<(string Path, bool FromConfiguredPath)> BuildCandidateBundlePaths() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <BuildCandidateBundlePaths>d__31(-2); } } public static class GeneralUtil { private static ManualLogSource _log; private static readonly BindingFlags InstanceFieldFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly FieldInfo BodyPlayerScriptField = typeof(RagdollGrabbableObject).GetField("playerScript", InstanceFieldFlags); private static readonly FieldInfo BodyPlayerObjectScriptField = typeof(RagdollGrabbableObject).GetField("playerObjectScript", InstanceFieldFlags); private static readonly FieldInfo BodySourcePlayerField = typeof(RagdollGrabbableObject).GetField("sourcePlayer", InstanceFieldFlags); public static void SetLogSource(ManualLogSource logSource) { _log = logSource; } public static PlayerControllerB GetPlayerByClientId(int playerClientId) { if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null) { return null; } PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if ((Object)(object)val != (Object)null && (int)val.playerClientId == playerClientId) { return val; } } return null; } public static void TryMarkTeleportedIfDead(int playerClientId) { PlayerControllerB playerByClientId = GetPlayerByClientId(playerClientId); if ((Object)(object)playerByClientId != (Object)null && playerByClientId.isPlayerDead) { MarkTeleported(playerClientId); } } public static PlayerControllerB GetClosestAlivePlayer(Vector3 position) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null) { return null; } PlayerControllerB result = null; float num = float.MaxValue; PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null) && !val.isPlayerDead && val.isPlayerControlled) { float num2 = Vector3.Distance(((Component)val).transform.position, position); if (num2 < num) { num = num2; result = val; } } } return result; } public static RagdollGrabbableObject GetBodyForPlayer(int playerClientId) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) PlayerReviveInfo orCreateInfo = GetOrCreateInfo(playerClientId); if (orCreateInfo.LastDeathNetworkObjectId != 0L) { RagdollGrabbableObject bodyByNetworkObjectId = GetBodyByNetworkObjectId(orCreateInfo.LastDeathNetworkObjectId); if ((Object)(object)bodyByNetworkObjectId != (Object)null) { return bodyByNetworkObjectId; } } if (orCreateInfo.HasLastDeathPosition) { RagdollGrabbableObject bodyNearPosition = GetBodyNearPosition(orCreateInfo.LastDeathPosition); if ((Object)(object)bodyNearPosition != (Object)null) { return bodyNearPosition; } } RagdollGrabbableObject bodyFromDeadBodyReference = GetBodyFromDeadBodyReference(GetPlayerByClientId(playerClientId)); if ((Object)(object)bodyFromDeadBodyReference != (Object)null) { return bodyFromDeadBodyReference; } RagdollGrabbableObject[] array = Object.FindObjectsOfType<RagdollGrabbableObject>(); foreach (RagdollGrabbableObject val in array) { if (TryGetBodyOwner(val, out var targetPlayerClientId, out var _) && targetPlayerClientId == playerClientId) { return val; } } return null; } public static RagdollGrabbableObject GetBodyFromDeadBodyReference(PlayerControllerB player) { if ((Object)(object)player?.deadBody == (Object)null) { return null; } DeadBodyInfo deadBody = player.deadBody; RagdollGrabbableObject val = ((Component)deadBody).GetComponent<RagdollGrabbableObject>() ?? ((Component)deadBody).GetComponentInChildren<RagdollGrabbableObject>() ?? ((Component)deadBody).GetComponentInParent<RagdollGrabbableObject>(); if ((Object)(object)val != (Object)null) { return val; } if ((Object)(object)((Component)deadBody).transform == (Object)null) { return null; } RagdollGrabbableObject[] array = Object.FindObjectsOfType<RagdollGrabbableObject>(); foreach (RagdollGrabbableObject val2 in array) { if (!((Object)(object)((val2 != null) ? ((Component)val2).transform : null) == (Object)null) && ((Object)(object)((Component)val2).transform == (Object)(object)((Component)deadBody).transform || ((Component)val2).transform.IsChildOf(((Component)deadBody).transform) || ((Component)deadBody).transform.IsChildOf(((Component)val2).transform))) { return val2; } } return null; } public static RagdollGrabbableObject GetBodyByNetworkObjectId(ulong networkObjectId) { if (networkObjectId == 0L) { return null; } if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.SpawnManager != null && NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var value) && (Object)(object)value != (Object)null) { RagdollGrabbableObject component = ((Component)value).GetComponent<RagdollGrabbableObject>(); if ((Object)(object)component != (Object)null) { return component; } component = ((Component)value).GetComponentInChildren<RagdollGrabbableObject>(); if ((Object)(object)component != (Object)null) { return component; } } RagdollGrabbableObject[] array = Object.FindObjectsOfType<RagdollGrabbableObject>(); foreach (RagdollGrabbableObject val in array) { if ((Object)(object)((val != null) ? ((NetworkBehaviour)val).NetworkObject : null) != (Object)null && ((NetworkBehaviour)val).NetworkObject.NetworkObjectId == networkObjectId) { return val; } } return null; } public static RagdollGrabbableObject GetBodyNearPosition(Vector3 position, float maxDistance = 12f) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_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) RagdollGrabbableObject[] array = Object.FindObjectsOfType<RagdollGrabbableObject>(); RagdollGrabbableObject result = null; float num = maxDistance * maxDistance; RagdollGrabbableObject[] array2 = array; foreach (RagdollGrabbableObject val in array2) { if (!((Object)(object)((val != null) ? ((Component)val).transform : null) == (Object)null)) { Vector3 val2 = ((Component)val).transform.position - position; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; result = val; } } } return result; } public static DeadBodyInfo GetDeadBodyNearPosition(Vector3 position, float maxDistance = 12f) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_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) DeadBodyInfo[] array = Object.FindObjectsOfType<DeadBodyInfo>(); DeadBodyInfo result = null; float num = maxDistance * maxDistance; DeadBodyInfo[] array2 = array; foreach (DeadBodyInfo val in array2) { if (!((Object)(object)((val != null) ? ((Component)val).transform : null) == (Object)null)) { Vector3 val2 = ((Component)val).transform.position - position; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; result = val; } } } return result; } public static bool TryGetBodyOwner(RagdollGrabbableObject body, out int targetPlayerClientId, out PlayerControllerB targetPlayer) { targetPlayerClientId = -1; targetPlayer = null; if ((Object)(object)body == (Object)null) { return false; } targetPlayer = body.ragdoll?.playerScript; if ((Object)(object)targetPlayer != (Object)null) { targetPlayerClientId = (int)targetPlayer.playerClientId; return true; } object? obj = BodyPlayerScriptField?.GetValue(body); PlayerControllerB val = (PlayerControllerB)(((obj is PlayerControllerB) ? obj : null) ?? ((object)(/*isinst with value type is only supported in some contexts*/ ?? /*isinst with value type is only supported in some contexts*/))); if ((Object)(object)val != (Object)null) { targetPlayer = val; targetPlayerClientId = (int)val.playerClientId; return true; } if (StartOfRound.Instance?.allPlayerScripts != null) { PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val2 in allPlayerScripts) { if (!((Object)(object)val2 == (Object)null)) { DeadBodyInfo deadBody = val2.deadBody; if (!((Object)(object)deadBody == (Object)null) && (deadBody == body || ((Object)(object)((Component)deadBody).transform != (Object)null && (Object)(object)((Component)body).transform != (Object)null && ((Object)(object)((Component)deadBody).transform == (Object)(object)((Component)body).transform || ((Component)deadBody).transform.IsChildOf(((Component)body).transform) || ((Component)body).transform.IsChildOf(((Component)deadBody).transform))))) { targetPlayer = val2; targetPlayerClientId = (int)val2.playerClientId; return true; } } } } return false; } public static PlayerReviveInfo GetOrCreateInfo(int playerClientId) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) PlayerReviveInfo playerReviveInfo = ModState.PlayerInfos.FirstOrDefault((PlayerReviveInfo x) => x.PlayerClientId == playerClientId); if (playerReviveInfo != null) { return playerReviveInfo; } playerReviveInfo = new PlayerReviveInfo { PlayerClientId = playerClientId, TimeDiedAt = Time.time, LastDeathType = PlayerDeathType.Unknown, LastDeathNetworkObjectId = 0uL, LastDeathPosition = Vector3.zero, HasLastDeathPosition = false }; ModState.PlayerInfos.Add(playerReviveInfo); return playerReviveInfo; } public static void ResetAllPlayerInfos() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) ModState.PlayerInfos.Clear(); if (StartOfRound.Instance?.allPlayerScripts == null) { return; } PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null)) { ModState.PlayerInfos.Add(new PlayerReviveInfo { PlayerClientId = (int)val.playerClientId, TimeDiedAt = Time.time, HasBeenTeleported = false, TimesRevivedThisLevel = 0, LastDeathType = PlayerDeathType.Unknown, LastDeathNetworkObjectId = 0uL, LastDeathPosition = Vector3.zero, HasLastDeathPosition = false }); } } } public static void SetPlayerDeathState(int playerClientId, PlayerDeathType deathType, ulong networkObjectId, Vector3 deathPosition, string reason = null) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) SetPlayerDeathState(playerClientId, deathType, networkObjectId, deathPosition, hasPosition: true, reason, skipDeathTimeUpdate: false); } public static void SetPlayerDeathState(int playerClientId, PlayerDeathType deathType, ulong networkObjectId, Vector3 deathPosition, bool hasPosition, string reason) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) SetPlayerDeathState(playerClientId, deathType, networkObjectId, deathPosition, hasPosition, reason, skipDeathTimeUpdate: false); } public static void SetPlayerDeathState(int playerClientId, PlayerDeathType deathType, ulong networkObjectId, Vector3 deathPosition, bool hasPosition, string reason, bool skipDeathTimeUpdate) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) PlayerReviveInfo orCreateInfo = GetOrCreateInfo(playerClientId); PlayerDeathType lastDeathType = orCreateInfo.LastDeathType; ulong lastDeathNetworkObjectId = orCreateInfo.LastDeathNetworkObjectId; bool hasLastDeathPosition = orCreateInfo.HasLastDeathPosition; Vector3 lastDeathPosition = orCreateInfo.LastDeathPosition; if (!skipDeathTimeUpdate) { orCreateInfo.TimeDiedAt = Time.time; } orCreateInfo.LastDeathType = deathType; orCreateInfo.LastDeathNetworkObjectId = networkObjectId; orCreateInfo.HasLastDeathPosition = hasPosition; orCreateInfo.LastDeathPosition = (hasPosition ? deathPosition : Vector3.zero); if (lastDeathType == deathType && lastDeathNetworkObjectId == networkObjectId && hasLastDeathPosition == hasPosition) { if (!hasPosition) { return; } if (hasLastDeathPosition) { Vector3 val = lastDeathPosition - deathPosition; if (!(((Vector3)(ref val)).sqrMagnitude > 0.0025f)) { return; } } } string text = (hasLastDeathPosition ? $"{lastDeathType}/obj:{lastDeathNetworkObjectId}/pos:{FormatVector(lastDeathPosition)}" : $"{lastDeathType}/obj:{lastDeathNetworkObjectId}/pos:none"); string text2 = (hasPosition ? $"{deathType}/obj:{networkObjectId}/pos:{FormatVector(deathPosition)}" : $"{deathType}/obj:{networkObjectId}/pos:none"); Plugin.DebugLog(string.Format("[DeathState] player={0} from={1} to={2} reason={3}", playerClientId, text, text2, string.IsNullOrWhiteSpace(reason) ? "unspecified" : reason)); } private static string FormatVector(Vector3 value) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return $"({value.x:0.00},{value.y:0.00},{value.z:0.00})"; } public static PlayerDeathType GetPlayerLastDeathType(int playerClientId) { return GetOrCreateInfo(playerClientId).LastDeathType; } public static ulong GetPlayerLastDeathNetworkObjectId(int playerClientId) { return GetOrCreateInfo(playerClientId).LastDeathNetworkObjectId; } public static bool TryGetPlayerLastDeathPosition(int playerClientId, out Vector3 position) { //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) PlayerReviveInfo orCreateInfo = GetOrCreateInfo(playerClientId); position = orCreateInfo.LastDeathPosition; return orCreateInfo.HasLastDeathPosition; } public static void ClearPlayerDeathState(int playerClientId) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) PlayerReviveInfo orCreateInfo = GetOrCreateInfo(playerClientId); orCreateInfo.LastDeathType = PlayerDeathType.Unknown; orCreateInfo.LastDeathNetworkObjectId = 0uL; orCreateInfo.LastDeathPosition = Vector3.zero; orCreateInfo.HasLastDeathPosition = false; orCreateInfo.TimeDiedAt = 0f; } public static float GetPlayerDiedAtTime(int playerClientId) { return GetOrCreateInfo(playerClientId).TimeDiedAt; } public static void MarkTeleported(int playerClientId) { GetOrCreateInfo(playerClientId).HasBeenTeleported = true; } public static bool HasPlayerTeleported(int playerClientId) { return GetOrCreateInfo(playerClientId).HasBeenTeleported; } public static int IncrementReviveCountAndGetHealth(int playerClientId) { PlayerReviveInfo orCreateInfo = GetOrCreateInfo(playerClientId); orCreateInfo.TimesRevivedThisLevel++; return Mathf.Max(1, ModConfig.ReviveHealth - (orCreateInfo.TimesRevivedThisLevel - 1) * ModConfig.ExtraHealthLostPerRevive); } public static bool CanRevivePlayer(int targetPlayerClientId, out string reason) { reason = null; switch (ModConfig.CurrentReviveMode) { case ModConfig.ReviveMode.Unlimited: return true; case ModConfig.ReviveMode.PerLevelMultiplier: if (ModState.RemainingRevives > 0) { return true; } reason = "no revives left "; return false; case ModConfig.ReviveMode.FixedPerLevel: if (ModState.RemainingRevives > 0) { return true; } reason = "no revives left"; return false; case ModConfig.ReviveMode.PerPlayer: if (GetOrCreateInfo(targetPlayerClientId).RemainingRevivesForPlayer > 0) { return true; } reason = "no revives left for this player"; return false; default: reason = "unknown revive limit mode"; return false; } } public static bool TryConsumePlayerRevive(int playerClientId, out int revivesLeft) { PlayerReviveInfo orCreateInfo = GetOrCreateInfo(playerClientId); if (ModConfig.CurrentReviveMode != ModConfig.ReviveMode.PerPlayer) { revivesLeft = int.MaxValue; return true; } if (orCreateInfo.RemainingRevivesForPlayer > 0) { orCreateInfo.RemainingRevivesForPlayer--; revivesLeft = orCreateInfo.RemainingRevivesForPlayer; return true; } revivesLeft = 0; return false; } public static void InitializeReviveLimits() { int num = 0; if (StartOfRound.Instance?.allPlayerScripts != null) { num = StartOfRound.Instance.allPlayerScripts.Count((PlayerControllerB p) => (Object)(object)p != (Object)null && p.isPlayerControlled); } switch (ModConfig.CurrentReviveMode) { case ModConfig.ReviveMode.Unlimited: { ModState.RemainingRevives = int.MaxValue; foreach (PlayerReviveInfo playerInfo in ModState.PlayerInfos) { playerInfo.RemainingRevivesForPlayer = int.MaxValue; } ManualLogSource log4 = _log; if (log4 != null) { log4.LogInfo((object)"[UltimateRevive] ReviveMode=Unlimited, RemainingRevives=∞"); } break; } case ModConfig.ReviveMode.PerLevelMultiplier: { ModState.RemainingRevives = Mathf.RoundToInt((float)num * ModConfig.RevivesPerLevelMultiplier); foreach (PlayerReviveInfo playerInfo2 in ModState.PlayerInfos) { playerInfo2.RemainingRevivesForPlayer = int.MaxValue; } ManualLogSource log3 = _log; if (log3 != null) { log3.LogInfo((object)$"[UltimateRevive] ReviveMode=PerLevelMultiplier, RemainingRevives={ModState.RemainingRevives}"); } break; } case ModConfig.ReviveMode.FixedPerLevel: { ModState.RemainingRevives = ModConfig.FixedRevivesPerLevel; foreach (PlayerReviveInfo playerInfo3 in ModState.PlayerInfos) { playerInfo3.RemainingRevivesForPlayer = int.MaxValue; } ManualLogSource log2 = _log; if (log2 != null) { log2.LogInfo((object)$"[UltimateRevive] ReviveMode=FixedPerLevel, RemainingRevives={ModState.RemainingRevives}"); } break; } case ModConfig.ReviveMode.PerPlayer: { ModState.RemainingRevives = int.MaxValue; PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null)) { GetOrCreateInfo((int)val.playerClientId).RemainingRevivesForPlayer = ModConfig.RevivesPerPlayer; } } ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"[UltimateRevive] ReviveMode=PerPlayer, RevivesPerPlayer={ModConfig.RevivesPerPlayer}"); } break; } } } public static bool IsNetworkReady() { if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.CustomMessagingManager != null) { return NetworkManager.Singleton.IsListening; } return false; } public static float GetReviveValidationDistance(PlayerControllerB reviver) { if ((Object)(object)reviver == (Object)null) { return 0f; } return Mathf.Max(0.1f, ModConfig.MaxReviveDistance); } public static float GetTombReviveValidationDistance(PlayerControllerB reviver) { if ((Object)(object)reviver == (Object)null) { return 0f; } return Mathf.Max(0.1f, ModConfig.MaxReviveDistanceTomb); } public static bool IsReviverCloseEnough(PlayerControllerB reviver, Vector3 targetPosition) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)reviver == (Object)null) { return false; } float reviveValidationDistance = GetReviveValidationDistance(reviver); return IsReviverCloseEnoughInternal(reviver, targetPosition, reviveValidationDistance); } public static bool IsReviverCloseEnoughToTomb(PlayerControllerB reviver, Vector3 targetPosition) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)reviver == (Object)null) { return false; } float tombReviveValidationDistance = GetTombReviveValidationDistance(reviver); return IsReviverCloseEnoughInternal(reviver, targetPosition, tombReviveValidationDistance); } private static bool IsReviverCloseEnoughInternal(PlayerControllerB reviver, Vector3 targetPosition, float maxDistance) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; Vector3 val2; if ((Object)(object)val != (Object)null && (Object)(object)val == (Object)(object)reviver && (Object)(object)reviver.gameplayCamera != (Object)null) { val2 = ((Component)reviver.gameplayCamera).transform.position; } else { if (!((Object)(object)((Component)reviver).transform != (Object)null)) { return false; } val2 = ((Component)reviver).transform.position + Vector3.up * 1.6f; maxDistance += 1.5f; } return Vector3.Distance(val2, targetPosition) <= maxDistance; } public static MaskedPlayerEnemy GetConvertedMaskedForPlayer(int playerClientId, bool requireDead = false) { if (!ModConfig.EnableMaskedPlayerRevive) { return null; } MaskedPlayerEnemy[] array = Object.FindObjectsOfType<MaskedPlayerEnemy>(); foreach (MaskedPlayerEnemy val in array) { if (!((Object)(object)val == (Object)null) && (!requireDead || MaskedConversionTracker.IsMaskedDead(val)) && TryResolveConvertedMaskedPlayer(val, out var playerClientId2, out var _) && playerClientId2 == playerClientId) { return val; } } return null; } public static MaskedPlayerEnemy GetConvertedMaskedCorpseForPlayer(int playerClientId) { return GetConvertedMaskedForPlayer(playerClientId, requireDead: true); } public static bool TryResolveConvertedMaskedPlayer(MaskedPlayerEnemy masked, out int playerClientId, out string source) { playerClientId = -1; source = "none"; if ((Object)(object)masked == (Object)null) { return false; } if (ModConfig.EnableMaskedPlayerRevive && MaskedConversionTracker.IsConvertedMasked(masked, out playerClientId)) { source = "InternalTag"; return true; } return false; } public static MaskedPlayerEnemy GetMaskedByNetworkObjectId(ulong networkObjectId) { if (networkObjectId == 0L) { return null; } if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.SpawnManager != null && NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var value) && (Object)(object)value != (Object)null) { MaskedPlayerEnemy component = ((Component)value).GetComponent<MaskedPlayerEnemy>(); if ((Object)(object)component != (Object)null) { return component; } component = ((Component)value).GetComponentInChildren<MaskedPlayerEnemy>(); if ((Object)(object)component != (Object)null) { return component; } } MaskedPlayerEnemy[] array = Object.FindObjectsOfType<MaskedPlayerEnemy>(); foreach (MaskedPlayerEnemy val in array) { if (!((Object)(object)val == (Object)null)) { NetworkObject component2 = ((Component)val).GetComponent<NetworkObject>(); if ((Object)(object)component2 != (Object)null && component2.NetworkObjectId == networkObjectId) { return val; } } } return null; } } public static class MaskedConversionTracker { internal sealed class PendingConversion { public int PlayerClientId; public MaskedOrigin Origin; public Vector3 Position; public float TimeRegistered; } public static readonly Dictionary<int, ModState.MaskedInfo> MaskedByPlayerId = ModState.MaskedByPlayerId; private const float PendingLifetimeSeconds = 20f; private static readonly List<PendingConversion> PendingConversions = new List<PendingConversion>(); private static ManualLogSource _log; public static void SetLogSource(ManualLogSource logSource) { _log = logSource; } public static void Reset() { PendingConversions.Clear(); ModState.MaskedByNetworkObjectId.Clear(); } public static int? GetPlayerIdForMasked(MaskedPlayerEnemy masked) { if ((Object)(object)masked == (Object)null) { return null; } if ((Object)(object)masked.mimickingPlayer != (Object)null) { return (int)masked.mimickingPlayer.playerClientId; } return null; } public static bool HasPendingForPlayer(int playerClientId) { CleanupExpiredPending(); return PendingConversions.Any((PendingConversion x) => x.PlayerClientId == playerClientId); } public static MaskedPlayerEnemy GetMaskedForPlayer(int playerClientId) { MaskedPlayerEnemy[] array = Object.FindObjectsOfType<MaskedPlayerEnemy>(); foreach (MaskedPlayerEnemy val in array) { MaskedOriginInfo component = ((Component)val).GetComponent<MaskedOriginInfo>(); if ((Object)(object)component != (Object)null && component.FormerPlayerClientId == playerClientId && (component.Origin == MaskedOrigin.ConvertedByMaskItem || component.Origin == MaskedOrigin.ConvertedByMaskedEnemy)) { return val; } } return null; } public static ulong GetNetworkObjectId(MaskedPlayerEnemy masked) { if ((Object)(object)masked == (Object)null) { return 0uL; } if ((Object)(object)((masked != null) ? ((NetworkBehaviour)masked).NetworkObject : null) == (Object)null) { return 0uL; } return ((NetworkBehaviour)masked).NetworkObject.NetworkObjectId; } public static void TagMaskedAsConverted(MaskedPlayerEnemy masked, int playerClientId, MaskedOrigin origin, string reason = null) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)masked == (Object)null) && ModConfig.EnableMaskedPlayerRevive && playerClientId >= 0) { if (origin != MaskedOrigin.ConvertedByMaskItem && origin != MaskedOrigin.ConvertedByMaskedEnemy) { origin = MaskedOrigin.ConvertedByMaskedEnemy; } MaskedOriginInfo orAddInfo = GetOrAddInfo(masked); orAddInfo.Origin = origin; orAddInfo.FormerPlayerClientId = playerClientId; orAddInfo.TaggedAt = Time.time; ulong networkObjectId = GetNetworkObjectId(masked); Vector3 val = (((Object)(object)((Component)masked).transform != (Object)null) ? ((Component)masked).transform.position : Vector3.zero); ModState.MaskedInfo value = new ModState.MaskedInfo { PlayerClientId = playerClientId, Masked = masked, NetworkObjectId = networkObjectId, LastPosition = val, IsDead = false, LastUpdateTime = Time.time }; MaskedByPlayerId[playerClientId] = value; ModState.MaskedByNetworkObjectId[networkObjectId] = value; PendingConversions.RemoveAll((PendingConversion x) => x.PlayerClientId == playerClientId); string reason2 = (string.IsNullOrWhiteSpace(reason) ? "MaskedTag:direct" : reason); string text = (string.IsNullOrWhiteSpace(reason) ? "direct" : reason); GeneralUtil.SetPlayerDeathState(playerClientId, PlayerDeathType.Masked, networkObjectId, val, reason2); ReviveInteractTriggerManager.ForceRefreshPlayer(playerClientId, reason2); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"Tagged Masked {networkObjectId} as {origin} from player {playerClientId} ({text})."); } } } public static void RegisterPending(PlayerControllerB player, MaskedOrigin origin) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (ModConfig.EnableMaskedPlayerRevive && !((Object)(object)player == (Object)null) && (origin == MaskedOrigin.ConvertedByMaskItem || origin == MaskedOrigin.ConvertedByMaskedEnemy)) { int playerClientId = (int)player.playerClientId; Vector3 val = (((Object)(object)((Component)player).transform != (Object)null) ? ((Component)player).transform.position : Vector3.zero); PendingConversions.RemoveAll((PendingConversion x) => x.PlayerClientId == playerClientId); PendingConversions.Add(new PendingConversion { PlayerClientId = playerClientId, Origin = origin, Position = val, TimeRegistered = Time.time }); GeneralUtil.SetPlayerDeathState(playerClientId, PlayerDeathType.Masked, 0uL, val, $"MaskedPending:{origin}"); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"Registered pending Masked conversion: player={playerClientId}, origin={origin}."); } } } public static bool TryTagMaskedFromPending(MaskedPlayerEnemy masked) { if ((Object)(object)masked == (Object)null || !ModConfig.EnableMaskedPlayerRevive) { return false; } CleanupExpiredPending(); if (PendingConversions.Count == 0) { return false; } PendingConversion pendingConversion = null; int mimicId = GetMimickingPlayerClientId(masked); if (mimicId >= 0) { pendingConversion = PendingConversions.FirstOrDefault((PendingConversion x) => x.PlayerClientId == mimicId); } if (pendingConversion == null) { return false; } ApplyConvertedTag(masked, GetOrAddInfo(masked), pendingConversion); return true; } public static void TagMaskedOnSpawn(MaskedPlayerEnemy masked) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)masked == (Object)null || !ModConfig.EnableMaskedPlayerRevive) { return; } MaskedOriginInfo orAddInfo = GetOrAddInfo(masked); if (orAddInfo.Origin != MaskedOrigin.ConvertedByMaskItem && orAddInfo.Origin != MaskedOrigin.ConvertedByMaskedEnemy) { int? playerIdForMasked = GetPlayerIdForMasked(masked); if (playerIdForMasked.HasValue && MaskedByPlayerId.TryGetValue(playerIdForMasked.Value, out var value)) { value.Masked = masked; value.NetworkObjectId = GetNetworkObjectId(masked); value.LastPosition = (((Object)(object)((Component)masked).transform != (Object)null) ? ((Component)masked).transform.position : Vector3.zero); value.IsDead = false; value.LastUpdateTime = Time.time; } if (!TryTagMaskedFromPending(masked) && orAddInfo.Origin == MaskedOrigin.Unknown) { orAddInfo.Origin = MaskedOrigin.NaturalSpawn; orAddInfo.FormerPlayerClientId = -1; orAddInfo.TaggedAt = Time.time; } } } public static bool IsConvertedMasked(MaskedPlayerEnemy masked, out int playerClientId) { playerClientId = -1; if ((Object)(object)masked == (Object)null || !ModConfig.EnableMaskedPlayerRevive) { return false; } MaskedOriginInfo component = ((Component)masked).GetComponent<MaskedOriginInfo>(); if ((Object)(object)component == (Object)null) { TagMaskedOnSpawn(masked); component = ((Component)masked).GetComponent<MaskedOriginInfo>(); } if ((Object)(object)component == (Object)null) { return false; } if (component.Origin != MaskedOrigin.ConvertedByMaskItem && component.Origin != MaskedOrigin.ConvertedByMaskedEnemy) { return false; } playerClientId = component.FormerPlayerClientId; return playerClientId >= 0; } public static bool IsMaskedDead(MaskedPlayerEnemy masked) { if ((Object)(object)masked != (Object)null) { return ((EnemyAI)masked).isEnemyDead; } return false; } public static void OnMaskedDeath(MaskedPlayerEnemy masked) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) int? playerIdForMasked = GetPlayerIdForMasked(masked); if (playerIdForMasked.HasValue && MaskedByPlayerId.TryGetValue(playerIdForMasked.Value, out var value)) { value.IsDead = true; value.LastPosition = (((Object)(object)((Component)masked).transform != (Object)null) ? ((Component)masked).transform.position : value.LastPosition); value.LastUpdateTime = Time.time; ModState.BodyByPlayerId[playerIdForMasked.Value] = null; ulong networkObjectId = GetNetworkObjectId(masked); if (ModState.MaskedByNetworkObjectId.ContainsKey(networkObjectId)) { ModState.MaskedByNetworkObjectId.Remove(networkObjectId); } } } public static void ForgetPlayer(int playerClientId) { PendingConversions.RemoveAll((PendingConversion x) => x.PlayerClientId == playerClientId); } private static void ApplyConvertedTag(MaskedPlayerEnemy masked, MaskedOriginInfo info, PendingConversion pending) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) info.Origin = pending.Origin; info.FormerPlayerClientId = pending.PlayerClientId; info.TaggedAt = Time.time; ulong networkObjectId = GetNetworkObjectId(masked); Vector3 deathPosition = (((Object)(object)((Component)masked).transform != (Object)null) ? ((Component)masked).transform.position : pending.Position); GeneralUtil.SetPlayerDeathState(pending.PlayerClientId, PlayerDeathType.Masked, networkObjectId, deathPosition, "MaskedTagAssigned"); ReviveInteractTriggerManager.ForceRefreshPlayer(pending.PlayerClientId, "MaskedTagAssigned"); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"Tagged Masked {GetNetworkObjectId(masked)} as {pending.Origin} from player {pending.PlayerClientId}."); } } private static MaskedOriginInfo GetOrAddInfo(MaskedPlayerEnemy masked) { MaskedOriginInfo maskedOriginInfo = ((Component)masked).GetComponent<MaskedOriginInfo>(); if ((Object)(object)maskedOriginInfo == (Object)null) { maskedOriginInfo = ((Component)masked).gameObject.AddComponent<MaskedOriginInfo>(); } return maskedOriginInfo; } private static int GetMimickingPlayerClientId(MaskedPlayerEnemy masked) { try { PlayerControllerB value = Traverse.Create((object)masked).Field("mimickingPlayer").GetValue<PlayerControllerB>(); if ((Object)(object)value != (Object)null) { return (int)value.playerClientId; } } catch { } return -1; } private static void CleanupExpiredPending() { float now = Time.time; PendingConversions.RemoveAll((PendingConversion x) => now - x.TimeRegistered > 20f); } } public enum MaskedOrigin { Unknown, NaturalSpawn, ConvertedByMaskItem, ConvertedByMaskedEnemy } public sealed class MaskedOriginInfo : MonoBehaviour { public MaskedOrigin Origin; public int FormerPlayerClientId = -1; public float TaggedAt; } public static class ModConfig { public enum ReviveMode { Unlimited, PerLevelMultiplier, FixedPerLevel, PerPlayer } public static Color TombMonitorDotColor = new Color(0.15f, 0.75f, 1f, 1f); public const string DefaultDeathTombBundlePath = "chaps-UltimateRevive/sm_tombstone_3_dark_rip.bundle"; public const string DefaultDeathTombPrefabName = "sm_tombstone_3_dark_rip"; public static float ReviveTime; public static bool CanPickUpBodies; public static float DeadBodyWeight; public static bool CanReviveTeleportedBodies; public static int ReviveHealth; public static int ExtraHealthLostPerRevive; public static float RevivesPerLevelMultiplier; public static int FixedRevivesPerLevel; public static int RevivesPerPlayer; public static bool InfiniteReviveTime; public static int TimeUntilCannotBeRevived; public static float MaxReviveDistance; public static float MaxReviveDistanceTomb; public static bool EnableMaskedPlayerRevive; public static bool EnableDeathTombFallback; public static bool EnableDeathTombCollider; public static string DeathTombAssetBundlePath; public static string DeathTombPrefabName; public static float DeathTombGroundOffset; public static float DeathTombDownOffset; public static float DeathTombSpawnHeight; public static bool EnableDebugLogs; public static ReviveMode CurrentReviveMode; public static bool DebugLogging { get; internal set; } public static void Bind(ConfigFile config) { //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Unknown result type (might be due to invalid IL or missing references) string value = config.Bind<string>("DeathMarker", "TombMonitorDotColor", "0.15,0.75,1,1", "Color of the tomb radar dot (format: r,g,b,a between 0 and 1).\n Examples: red=1,0,0,1; black=0,0,0,1; blue=0,0,1,1; green=0,1,0,1; yellow=1,1,0,1; white=1,1,1,1; orange=1,0.5,0,1; purple=0.5,0,0.5,1.").Value; ReviveTime = config.Bind<float>("Revive", "ReviveTime", 5f, "How long, in seconds, the revive button must be held.").Value; CanPickUpBodies = config.Bind<bool>("Bodies", "CanPickUpBodies", true, "If false, pressing interact on bodies is blocked so the revive prompt is easier to use.").Value; DeadBodyWeight = config.Bind<float>("Bodies", "DeadBodyWeight", 1.35f, "Weight multiplier applied to dead bodies.").Value; CanReviveTeleportedBodies = config.Bind<bool>("Rules", "CanReviveTeleported", true, "If false, bodies and tomb teleported back to the ship cannot be revived.").Value; ReviveHealth = config.Bind<int>("Rules", "HealthYouReviveWith", 35, "Base health after revive.").Value; ExtraHealthLostPerRevive = config.Bind<int>("Rules", "ExtraHealthLostPerRevive", 5, "Additional health penalty each time the same player is revived in the same round.").Value; CurrentReviveMode = config.Bind<ReviveMode>("Rules", "ReviveMode", ReviveMode.PerPlayer, "Revive limit mode: Unlimited, PerLevelMultiplier, FixedPerLevel, PerPlayer").Value; RevivesPerLevelMultiplier = config.Bind<float>("Rules", "RevivesPerLevelMultiplier", 1.25f, "Revives per level = controlled players * this value, unless FixedRevivesPerLevel is above 0 (used if ReviveMode=PerLevelMultiplier).").Value; FixedRevivesPerLevel = config.Bind<int>("Rules", "FixedRevivesPerLevel", 4, "Maximum number of revives allowed per level (used if ReviveMode=FixedPerLevel)").Value; RevivesPerPlayer = config.Bind<int>("Rules", "RevivesPerPlayer", 2, "Maximum number of revives allowed per player per level (used if ReviveMode=PerPlayer)").Value; InfiniteReviveTime = config.Bind<bool>("Rules", "InfiniteReviveTime", false, "If true, players can be revived no matter how long they have been dead.").Value; TimeUntilCannotBeRevived = config.Bind<int>("Rules", "TimeUntilCannotBeRevived", 120, "How long someone can be dead and still be revived, in seconds.").Value; MaxReviveDistance = config.Bind<float>("Revive", "MaxReviveDistance", 3f, "Maximum distance from reviver camera to body.").Value; MaxReviveDistanceTomb = config.Bind<float>("Revive", "MaxReviveDistanceTomb", 3f, "Maximum distance from reviver camera to tomb revive source.").Value; EnableMaskedPlayerRevive = config.Bind<bool>("Masked", "EnableMaskedPlayerRevive", true, "If true, a player converted into a Masked by a mask item or by a Masked enemy can be revived after the converted Masked is killed.").Value; EnableDeathTombFallback = config.Bind<bool>("DeathMarker", "EnableDeathTombFallback", true, "If true, spawn a 3D tomb marker at the last death position when the latest revive source is missing.").Value; EnableDeathTombCollider = config.Bind<bool>("DeathMarker", "EnableDeathTombCollider", true, "If false, the death tomb will have no collision, including colliders already present on bundle prefabs.").Value; DeathTombAssetBundlePath = config.Bind<string>("DeathMarker", "DeathTombAssetBundlePath", "chaps-UltimateRevive/sm_tombstone_3_dark_rip.bundle", "Optional absolute or relative path to an AssetBundle containing your tomb prefab.\n Valid options for this mod:\n -chaps-UltimateRevive/sm_tombstone_3_dark_rip.bundle.\n -chaps-UltimateRevive/sm_tombstone_3_dark_text.bundle.\n -chaps-UltimateRevive/sm_tombstone_3_light_rip.bundle.\n -chaps-UltimateRevive/sm_tombstone_3_light_text.bundle.").Value; DeathTombPrefabName = config.Bind<string>("DeathMarker", "DeathTombPrefabName", "sm_tombstone_3_dark_rip", "Prefab name inside the AssetBundle to instantiate as tomb marker.\n Valid options for this mod:\n -sm_tombstone_3_dark_rip.\n -sm_tombstone_3_dark_text.\n -sm_tombstone_3_light_rip.\n -sm_tombstone_3_light_text.").Value; DeathTombGroundOffset = config.Bind<float>("DeathMarker", "DeathTombGroundOffset", 0.02f, "Vertical offset applied above detected ground.").Value; DeathTombDownOffset = config.Bind<float>("DeathMarker", "DeathTombDownOffset", 2.6f, "How much to move the tomb down after teleport correction (default 2.6).").Value; DeathTombSpawnHeight = config.Bind<float>("DeathMarker", "DeathTombSpawnHeight", 0f, "Height above death position used for downward ground detection and gravity settle.").Value; TombMonitorDotColor = ParseColorConfig(value, new Color(0.15f, 0.75f, 1f, 1f)); EnableDebugLogs = config.Bind<bool>("Debug", "EnableDebugLogs", false, "If true, emit local-only debug logs (not synchronized from host).").Value; } public static void ApplySyncedConfig(float reviveTime, bool canPickUpBodies, float deadBodyWeight, bool canReviveTeleportedBodies, int reviveHealth, int extraHealthLostPerRevive, float revivesPerLevelMultiplier, int fixedRevivesPerLevel, bool infiniteReviveTime, int timeUntilCannotBeRevived, float maxReviveDistance, float maxReviveDistanceTomb, bool enableMaskedPlayerRevive, bool enableDeathTombFallback, bool enableDeathTombCollider, string deathTombAssetBundlePath, string deathTombPrefabName, float deathTombGroundOffset, float deathTombDownOffset, float deathTombSpawnHeight, int reviveMode = -1, int revivesPerPlayer = -1) { ReviveTime = reviveTime; CanPickUpBodies = canPickUpBodies; DeadBodyWeight = deadBodyWeight; CanReviveTeleportedBodies = canReviveTeleportedBodies; ReviveHealth = reviveHealth; ExtraHealthLostPerRevive = extraHealthLostPerRevive; RevivesPerLevelMultiplier = revivesPerLevelMultiplier; FixedRevivesPerLevel = fixedRevivesPerLevel; InfiniteReviveTime = infiniteReviveTime; TimeUntilCannotBeRevived = timeUntilCannotBeRevived; MaxReviveDistance = maxReviveDistance; MaxReviveDistanceTomb = maxReviveDistanceTomb; EnableMaskedPlayerRevive = enableMaskedPlayerRevive; EnableDeathTombFallback = enableDeathTombFallback; EnableDeathTombCollider = enableDeathTombCollider; DeathTombAssetBundlePath = deathTombAssetBundlePath; DeathTombPrefabName = deathTombPrefabName; DeathTombGroundOffset = deathTombGroundOffset; DeathTombDownOffset = deathTombDownOffset; DeathTombSpawnHeight = deathTombSpawnHeight; if (reviveMode >= 0) { CurrentReviveMode = (ReviveMode)reviveMode; } if (revivesPerPlayer >= 0) { RevivesPerPlayer = revivesPerPlayer; } DeathMarkerManager.InvalidateBundleCache(); } private static Color ParseColorConfig(string value, Color fallback) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) if (string.IsNullOrWhiteSpace(value)) { return fallback; } string[] array = value.Split(','); if (array.Length < 3) { return fallback; } try { float num = float.Parse(array[0].Trim(), CultureInfo.InvariantCulture); float num2 = float.Parse(array[1].Trim(), CultureInfo.InvariantCulture); float num3 = float.Parse(array[2].Trim(), CultureInfo.InvariantCulture); float num4 = ((array.Length > 3) ? float.Parse(array[3].Trim(), CultureInfo.InvariantCulture) : 1f); return new Color(num, num2, num3, num4); } catch { return fallback; } } } public static class ModState { public class MaskedInfo { public int PlayerClientId; public MaskedPlayerEnemy Masked; public ulong NetworkObjectId; public Vector3 LastPosition; public bool IsDead; public float LastUpdateTime; } public sealed class TombInfo { public int PlayerClientId; public GameObject TombObject; public DeadBodyInfo DeadBody; public Vector3 LastPosition; public float LastUpdateTime; } public static readonly Dictionary<int, TombInfo> TombByPlayerId = new Dictionary<int, TombInfo>(); public static readonly Dictionary<int, Vector3> PendingTombPositionByPlayerId = new Dictionary<int, Vector3>(); public static readonly Dictionary<int, RagdollGrabbableObject> BodyByPlayerId = new Dictionary<int, RagdollGrabbableObject>(); public static readonly Dictionary<ulong, RagdollGrabbableObject> BodyByNetworkObjectId = new Dictionary<ulong, RagdollGrabbableObject>(); public static readonly Dictionary<int, MaskedInfo> MaskedByPlayerId = new Dictionary<int, MaskedInfo>(); public static readonly Dictionary<ulong, MaskedInfo> MaskedByNetworkObjectId = new Dictionary<ulong, MaskedInfo>(); public static bool Active; public static int RemainingRevives = int.MaxValue; public static readonly List<PlayerReviveInfo> PlayerInfos = new List<PlayerReviveInfo>(); public static void Reset() { Active = false; RemainingRevives = int.MaxValue; PlayerInfos.Clear(); MaskedByPlayerId.Clear(); MaskedByNetworkObjectId.Clear(); BodyByNetworkObjectId.Clear(); MaskedConversionTracker.Reset(); DeathMarkerManager.Reset(); ReviveInteractTriggerManager.Reset(); BodyByPlayerId.Clear(); TombByPlayerId.Clear(); PendingTombPositionByPlayerId.Clear(); } public static void RegisterTomb(int playerClientId, GameObject tombObject, DeadBodyInfo deadBody, Vector3 position) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) TombByPlayerId[playerClientId] = new TombInfo { PlayerClientId = playerClientId, TombObject = tombObject, DeadBody = deadBody, LastPosition = position, LastUpdateTime = Time.time }; PendingTombPositionByPlayerId.Remove(playerClientId); } public static bool TryGetTombObject(int playerClientId, out GameObject tombObject) { tombObject = null; if (!TombByPlayerId.TryGetValue(playerClientId, out var value)) { return false; } if (value == null || (Object)(object)value.TombObject == (Object)null) { TombByPlayerId.Remove(playerClientId); return false; } tombObject = value.TombObject; return true; } public static void UnregisterTomb(int playerClientId) { TombByPlayerId.Remove(playerClientId); PendingTombPositionByPlayerId.Remove(playerClientId); } } public sealed class PlayerReviveInfo { public int PlayerClientId; public bool HasBeenTeleported; public float TimeDiedAt; public int TimesRevivedThisLevel; public PlayerDeathType LastDeathType; public ulong LastDeathNetworkObjectId; public Vector3 LastDeathPosition; public bool HasLastDeathPosition; public int RemainingRevivesForPlayer; } public enum PlayerDeathType { Unknown, Body, Masked } [BepInPlugin("chaps.UltimateRevive", "UltimateRevive", "1.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { private sealed class RuntimeDriver : MonoBehaviour { } public const string ModGuid = "chaps.UltimateRevive"; public const string ModName = "UltimateRevive"; public const string ModVersion = "1.1.1"; internal static ManualLogSource Log; internal static ReviveInputActions InputActions; private Harmony _harmony; private bool _isQuitting; private static RuntimeDriver _runtimeDriver; public static Plugin Instance { get; private set; } internal static InputAction ReviveAction => InputActions?.Revive; private void Awake() { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); InputActions = new ReviveInputActions(); ModConfig.Bind(((BaseUnityPlugin)this).Config); GeneralUtil.SetLogSource(Log); ReviveNetwork.SetLogSource(Log); ReviveLogic.SetLogSource(Log); MaskedConversionTracker.SetLogSource(Log); DeathMarkerManager.SetLogSource(Log); _harmony = new Harmony("chaps.UltimateRevive"); try { _harmony.PatchAll(); VerifyHarmonyHooks(); } catch (Exception arg) { Log.LogError((object)$"Harmony patching failed: {arg}"); } InteractTriggerPatch.TryPatch(_harmony); Log.LogInfo((object)"UltimateRevive 1.1.1 loaded."); } private void OnDestroy() { if (_isQuitting) { ReviveNetwork.UnregisterHandlers(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } private void OnApplicationQuit() { _isQuitting = true; } internal static void DebugLog(string message) { if (ModConfig.EnableDebugLogs) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)message); } } } internal static string GetReviveBindingLabel() { //IL_0043: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) InputAction reviveAction = ReviveAction; if (reviveAction == null) { return "key"; } int num = -1; if ((Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.localPlayerUsingController) { num = InputActionRebindingExtensions.GetBindingIndex(reviveAction, InputBinding.MaskByGroup("Gamepad")); } else { num = InputActionRebindingExtensions.GetBindingIndex(reviveAction, InputBinding.MaskByGroup("KeyboardAndMouse")); if (num == -1) { num = InputActionRebindingExtensions.GetBindingIndex(reviveAction, InputBinding.MaskByGroup("Keyboard&Mouse")); } if (num == -1) { num = InputActionRebindingExtensions.GetBindingIndex(reviveAction, InputBinding.MaskByGroup("Keyboard")); } } if (num == -1) { return "key"; } return InputActionRebindingExtensions.GetBindingDisplayString(reviveAction, num, (DisplayStringOptions)0); } private void VerifyHarmonyHooks() { VerifyPatched(typeof(PlayerControllerB), "Update"); VerifyPatched(typeof(PlayerControllerB), "SetHoverTipAndCurrentInteractTrigger"); VerifyPatched(typeof(PlayerControllerB), "KillPlayerClientRpc"); VerifyPatched(typeof(StartOfRound), "Start"); VerifyPatched(typeof(StartOfRound), "openingDoorsSequence"); } private void VerifyPatched(Type type, string methodName) { MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { Log.LogWarning((object)("Harmony target missing: " + type.Name + "." + methodName)); return; } Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo); if (patchInfo != null && ((patchInfo.Prefixes != null && patchInfo.Prefixes.Any((Patch p) => p.owner == "chaps.UltimateRevive")) || (patchInfo.Postfixes != null && patchInfo.Postfixes.Any((Patch p) => p.owner == "chaps.UltimateRevive")) || (patchInfo.Transpilers != null && patchInfo.Transpilers.Any((Patch p) => p.owner == "chaps.UltimateRevive")) || (patchInfo.Finalizers != null && patchInfo.Finalizers.Any((Patch p) => p.owner == "chaps.UltimateRevive")))) { Log.LogInfo((object)("Harmony hook active: " + type.Name + "." + methodName)); } else { Log.LogWarning((object)("Harmony hook NOT active: " + type.Name + "." + methodName)); } } private static void EnsureRuntimeDriver() { //IL_0025: 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) //IL_0030: Expected O, but got Unknown if (!((Object)(object)_runtimeDriver != (Object)null) || !((Object)(object)((Component)_runtimeDriver).gameObject != (Object)null)) { GameObject val = new GameObject("UltimateRevive.RuntimeDriver"); Object.DontDestroyOnLoad((Object)val); _runtimeDriver = val.AddComponent<RuntimeDriver>(); } } internal static void RunCoroutineSafe(IEnumerator routine) { if (routine == null) { return; } try { if ((Object)(object)Instance != (Object)null && (Object)(object)((Component)Instance).gameObject != (Object)null && ((Behaviour)Instance).isActiveAndEnabled) { ((MonoBehaviour)Instance).StartCoroutine(routine); return; } if ((Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)((Component)StartOfRound.Instance).gameObject != (Object)null && ((Behaviour)StartOfRound.Instance).isActiveAndEnabled) { ((MonoBehaviour)StartOfRound.Instance).StartCoroutine(routine); return; } EnsureRuntimeDriver(); if ((Object)(object)_runtimeDriver != (Object)null && (Object)(object)((Component)_runtimeDriver).gameObject != (Object)null && ((Behaviour)_runtimeDriver).isActiveAndEnabled) { ((MonoBehaviour)_runtimeDriver).StartCoroutine(routine); return; } ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"[UltimateRevive] No active MonoBehaviour available for coroutine."); } } catch (Exception arg) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)$"[UltimateRevive] Failed to start coroutine: {arg}"); } } } } public static class ReviveAPI { public static bool CanRevive(RagdollGrabbableObject body) { if ((Object)(object)body == (Object)null) { return false; } if (!GeneralUtil.TryGetBodyOwner(body, out var targetPlayerClientId, out var _)) { return false; } return CanRevivePlayer(targetPlayerClientId); } public static bool CanRevivePlayer(int targetPlayerClientId) { PlayerControllerB playerByClientId = GeneralUtil.GetPlayerByClientId(targetPlayerClientId); if (PassesGlobalRules(targetPlayerClientId, playerByClientId)) { return HasRevivableSource(targetPlayerClientId); } return false; } public static bool CanReviveMasked(MaskedPlayerEnemy masked) { if ((Object)(object)masked == (Object)null || !ModConfig.EnableMaskedPlayerRevive) { return false; } if (!GeneralUtil.TryResolveConvertedMaskedPlayer(masked, out var playerClientId, out var _)) { return false; } return CanRevivePlayer(playerClientId); } public static object GetReviveSourceForPlayer(PlayerControllerB player) { if ((Object)(object)player == (Object)null) { return null; } int num = (int)player.playerClientId; switch (GeneralUtil.GetPlayerLastDeathType(num)) { case PlayerDeathType.Body: { RagdollGrabbableObject bodyForPlayer = GeneralUtil.GetBodyForPlayer(num); if ((Object)(object)bodyForPlayer != (Object)null) { return bodyForPlayer; } if (DeathMarkerManager.TombDeadBodiesPublic != null && DeathMarkerManager.TombDeadBodiesPublic.TryGetValue(num, out var value) && (Object)(object)value != (Object)null) { return value; } break; } case PlayerDeathType.Masked: { MaskedPlayerEnemy val = GeneralUtil.GetMaskedByNetworkObjectId(GeneralUtil.GetPlayerLastDeathNetworkObjectId(num)) ?? GeneralUtil.GetConvertedMaskedCorpseForPlayer(num); if ((Object)(object)val != (Object)null) { return val; } break; } } return null; } public static bool CanReviveUniversal(object target) { RagdollGrabbableObject val = (RagdollGrabbableObject)((target is RagdollGrabbableObject) ? target : null); if (val != null) { return CanRevive(val); } MaskedPlayerEnemy val2 = (MaskedPlayerEnemy)((target is MaskedPlayerEnemy) ? target : null); if (val2 != null) { return CanReviveMasked(val2); } if (target is int targetPlayerClientId) { return CanRevivePlayer(targetPlayerClientId); } return false; } public static void RevivePlayer(int targetPlayerClientId) { ReviveNetwork.RequestRevive(targetPlayerClientId); } private static bool PassesGlobalRules(int targetPlayerClientId, PlayerControllerB targetPlayer) { if (!ModState.Active) { return false; } if ((Object)(object)targetPlayer == (Object)null || !targetPlayer.isPlayerDead) { return false; } if (!GeneralUtil.CanRevivePlayer(targetPlayerClientId, out var _)) { return false; } if (!ModConfig.CanReviveTeleportedBodies && GeneralUtil.HasPlayerTeleported(targetPlayerClientId)) { return false; } if (!ModConfig.InfiniteReviveTime && Time.time - GeneralUtil.GetPlayerDiedAtTime(targetPlayerClientId) > (float)ModConfig.TimeUntilCannotBeRevived) { return false; } return true; } private static bool HasRevivableSource(int targetPlayerClientId) { PlayerDeathType playerLastDeathType = GeneralUtil.GetPlayerLastDeathType(targetPlayerClientId); ulong playerLastDeathNetworkObjectId = GeneralUtil.GetPlayerLastDeathNetworkObjectId(targetPlayerClientId); Vector3 position; if (playerLastDeathType == PlayerDeathType.Masked) { if (!ModConfig.EnableMaskedPlayerRevive) { return false; } MaskedPlayerEnemy val = ((playerLastDeathNetworkObjectId != 0L) ? GeneralUtil.GetMaskedByNetworkObjectId(playerLastDeathNetworkObjectId) : null); if ((Object)(object)val == (Object)null) { val = GeneralUtil.GetConvertedMaskedCorpseForPlayer(targetPlayerClientId); } if ((Object)(object)val != (Object)null) { return true; } if (ModConfig.EnableDeathTombFallback) { return GeneralUtil.TryGetPlayerLastDeathPosition(targetPlayerClientId, out position); } return false; } if ((Object)(object)((playerLastDeathNetworkObjectId != 0L) ? GeneralUtil.GetBodyByNetworkObjectId(playerLastDeathNetworkObjectId) : GeneralUtil.GetBodyForPlayer(targetPlayerClientId)) != (Object)null) { return true; } if (ModConfig.EnableDeathTombFallback) { return GeneralUtil.TryGetPlayerLastDeathPosition(targetPlayerClientId, out position); } return false; } } public sealed class ReviveInputActions : LcInputActions { [InputAction(/*Could not decode attribute arguments.*/)] public InputAction Revive { get; set; } } public static class ReviveInteractTriggerManager { private enum ReviveSourceKind { None, Body, Masked, Tomb } private sealed class ReviveTriggerEntry { public int TargetPlayerId; public ReviveSourceKind SourceKind; public ulong SourceObjectId; public GameObject RootObject; public GameObject TriggerHostObject; public InteractTrigger Trigger; public Collider AddedCollider; public bool OwnsTrigger; public bool OwnsTriggerHostObject; public bool ChangedLayer; public int OriginalLayer; public GameObject LayerAdjustedObject; public Transform FollowTransform; public Vector3 StaticPosition; } private const string CustomBodyTriggerHostName = "UltimateReviveBodyTrigger"; private const string CustomTombTriggerHostName = "UltimateReviveTombTrigger"; private const float HoverGraceSeconds = 0.45f; private static readonly Vector3 MaskedTriggerAliveLocalOffset = new Vector3(0f, 0.9f, 0f); private static readonly Vector3 MaskedTriggerDeadLocalOffset = new Vector3(0f, 0.35f, 0f); private const float MaskedTriggerDeadForwardShift = 0.95f; private const float MaskedTriggerDeadVerticalLift = 0.35f; private static readonly Dictionary<int, ReviveTriggerEntry> EntriesByPlayer = new Dictionary<int, ReviveTriggerEntry>(); private static readonly Dictionary<int, int> TriggerInstanceToPlayer = new Dictionary<int, int>(); private static readonly string[] PreferredPlayerTriggerFieldNames = new string[4] { "hoveringOverTrigger", "currentInteractTrigger", "currentTriggerInRange", "hoveredOverTrigger" }; private static readonly BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static FieldInfo[] _cachedPlayerTriggerFields; private static PropertyInfo[] _cachedPlayerTriggerProperties; private static int _lastLoggedHoverTarget = -2; private static ReviveSourceKind _lastLoggedHoverKind = ReviveSourceKind.None; private static string _lastLoggedHoverPath = string.Empty; private static bool _lastLoggedHoverCanRevive; private static bool _hadHover; private static int _lastHoveredTargetId = -1; private static float _lastHoveredSeenAt = -999f; private static readonly Dictionary<int, string> _lastResolveDiagnosticsByPlayer = new Dictionary<int, string>(); private static readonly Dictionary<int, string> _lastTickDiagnosticsByPlayer = new Dictionary<int, string>(); private static readonly Dictionary<int, string> _lastForceDiagnosticsByPlayer = new Dictionary<int, string>(); private static readonly Dictionary<int, string> _lastObservedTriggerDiagnosticsByPlayer = new Dictionary<int, string>(); public static void DisableVanillaBodyTriggers(RagdollGrabbableObject body) { if ((Object)(object)body == (Object)null) { return; } InteractTrigger[] componentsInChildren = ((Component)body).GetComponentsInChildren<InteractTrigger>(true); foreach (InteractTrigger obj in componentsInChildren) { ((Behaviour)obj).enabled = false; Collider component = ((Component)obj).GetComponent<Collider>(); if ((Object)(object)component != (Object)null) { component.enabled = false; } } ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[ReviveTrigger] Disabled vanilla triggers on body " + ((Object)body).name)); } } public static void WarmUpCache() { CachePlayerTriggerMembers(); FieldInfo[] cachedPlayerTriggerFields = _cachedPlayerTriggerFields; Plugin.DebugLog($"[ReviveTrigger] cache warmed: fields={((cachedPlayerTriggerFields != null) ? cachedPlayerTriggerFields.Length : 0)}"); } public static void SetReviveTriggerEnabled(RagdollGrabbableObject body, bool enabled) { if ((Object)(object)body == (Object)null) { return; } InteractTrigger[] componentsInChildren = ((Component)body).GetComponentsInChildren<InteractTrigger>(true); foreach (InteractTrigger obj in componentsInChildren) { ((Behaviour)obj).enabled = enabled; Collider component = ((Component)obj).GetComponent<Collider>(); if ((Object)(object)component != (Object)null) { component.enabled = enabled; } } } public static void Reset() { foreach (int item in EntriesByPlayer.Keys.ToList()) { RemoveEntry(item); } EntriesByPlayer.Clear(); TriggerInstanceToPlayer.Clear(); _lastResolveDiagnosticsByPlayer.Clear(); _lastTickDiagnosticsByPlayer.Clear(); _lastForceDiagnosticsByPlayer.Clear(); _lastObservedTriggerDiagnosticsByPlayer.Clear(); _lastHoveredTargetId = -1; _lastHoveredSeenAt = -999f; } public static void ForceRefreshPlayer(int playerId, string reason = null) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) if (ModConfig.EnableDebugLogs) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[ReviveTrigger][DEBUG] Enter ForceRefreshPlayer: playerId={playerId} reason={reason}"); } } if (!ModState.Active) { if (ModConfig.EnableDebugLogs) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)$"[ReviveTrigger][DEBUG] Early return: ModState not active for playerId={playerId}"); } } return; } if (!TryResolveCurrentReviveSource(playerId, out var sourceKind, out var sourceObjectId, out var followTransform, out var staticPosition)) { if (ModConfig.EnableDebugLogs) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)$"[ReviveTrigger][DEBUG] Early return: Could not resolve revive source for playerId={playerId}"); } } RemoveEntry(playerId); LogForceDiagnostic(playerId, "result=none reason=" + (string.IsNullOrWhiteSpace(reason) ? "unspecified" : reason)); return; } if (ModConfig.EnableDebugLogs) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)$"[ReviveTrigger][DEBUG] EnsureOrUpdateEntry: playerId={playerId} sourceKind={sourceKind} sourceObjectId={sourceObjectId}"); } } EnsureOrUpdateEntry(playerId, sourceKind, sourceObjectId, followTransform, staticPosition); LogForceDiagnostic(playerId, string.Format("result={0} obj={1} reason={2}", sourceKind, sourceObjectId, string.IsNullOrWhiteSpace(reason) ? "unspecified" : reason)); } public static bool TryGetHoveredReviveTarget(PlayerControllerB reviver, out int targetPlayerId, out string reason, out bool canRevive) { targetPlayerId = -1; reason = "no revive trigger"; canRevive = false; if (!TryGetHoveredEntry(reviver, out var entry)) { if (_hadHover) { _hadHover = false; _lastLoggedHoverTarget = -2; _lastLoggedHoverKind = ReviveSourceKind.None; _lastLoggedHoverPath = string.Empty; _lastLoggedHoverCanRevive = false; Plugin.DebugLog("[ReviveHover] cleared"); } return false; } targetPlayerId = entry.TargetPlayerId; canRevive = EvaluateCanRevive(entry, reviver, out var resolvedTargetId, out reason); if (resolvedTargetId >= 0) { targetPlayerId = resolvedTargetId; } string text = ((entry.SourceKind == ReviveSourceKind.Body) ? "vanilla-body" : "custom"); if (entry.SourceKind != ReviveSourceKind.Body && ((Object)(object)entry.Trigger == (Object)null || !TriggerInstanceToPlayer.ContainsKey(((Object)entry.Trigger).GetInstanceID()))) { text = "unmapped"; } if (!_hadHover || _lastLoggedHoverTarget != targetPlayerId || _lastLoggedHoverKind != entry.SourceKind || _lastLoggedHoverPath != text || _lastLoggedHoverCanRevive != canRevive) { _hadHover = true; _lastLoggedHoverTarget = targetPlayerId; _lastLoggedHoverKind = entry.SourceKind; _lastLoggedHoverPath = text; _lastLoggedHoverCanRevive = canRevive; Plugin.DebugLog($"[ReviveHover] player={targetPlayerId} source={entry.SourceKind} path={text} canRevive={canRevive} reason={reason}"); } return true; } private static void EnsureOrUpdateEntry(int playerId, ReviveSourceKind sourceKind, ulong sourceObjectId, Transform followTransform, Vector3 staticPosition) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (!TryResolveSourceHostObject(playerId, sourceKind, sourceObjectId, followTransform, out var hostObject) || (Object)(object)hostObject == (Object)null) { RemoveEntry(playerId); return; } if (!EntriesByPlayer.TryGetValue(playerId, out var value) || value == null || (Object)(object)value.Trigger == (Object)null || (Object)(object)value.RootObject == (Object)null || (Object)(object)value.RootObject != (Object)(object)hostObject || value.SourceKind != sourceKind || value.SourceObjectId != sourceObjectId) { RemoveEntry(playerId); value = CreateEntry(playerId, sourceKind, sourceObjectId, hostObject); if (value == null) { return; } EntriesByPlayer[playerId] = value; } value.FollowTransform = followTransform; value.StaticPosition = staticPosition; if (sourceKind != ReviveSourceKind.Body) { ConfigureTrigger(value.Trigger, playerId, sourceKind); } if (sourceKind == ReviveSourceKind.Masked) { UpdateMaskedTriggerAnchor(value); } } private static ReviveTriggerEntry CreateEntry(int playerId, ReviveSourceKind sourceKind, ulong sourceObjectId, GameObject hostObject) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hostObject == (Object)null) { return null; } switch (sourceKind) { case ReviveSourceKind.Body: return CreateBodyEntryUsingVanillaTrigger(playerId, sourceObjectId, hostObject); case ReviveSourceKind.Masked: return CreateMaskedEntryUsingChildTrigger(playerId, sourceObjectId, hostObject); case ReviveSourceKind.Tomb: return CreateTombEntryUsingChildTrigger(playerId, sourceObjectId, hostObject); default: { bool flag = false; InteractTrigger obj = hostObject.GetComponent<InteractTrigger>(); bool flag2 = (Object)(object)obj == (Object)null; if (obj == null) { obj = hostObject.AddComponent<InteractTrigger>(); } InteractTrigger val = obj; Collider val2 = null; if (!HasUsableInteractCollider(hostObject)) { SphereCollider obj2 = hostObject.AddComponent<SphereCollider>(); ((Collider)obj2).isTrigger = true; obj2.radius = 0.8f; obj2.center = new Vector3(0f, 0.9f, 0f); val2 = (Collider)(object)obj2; } int num = LayerMask.NameToLayer("InteractableObject"); bool changedLayer = false; int layer = hostObject.layer; if (num >= 0 && hostObject.layer != num) { hostObject.layer = num; changedLayer = true; } ConfigureTrigger(val, playerId, sourceKind); RegisterTriggerMapping(val, playerId, sourceKind, hostObject); Collider component = hostObject.GetComponent<Collider>(); bool flag3 = HasUsableInteractCollider(hostObject); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[ReviveTrigger] attached source={sourceKind} player={playerId} host={((Object)hostObject).name} triggerHost={((Object)hostObject).name} ownsTrigger={flag2} ownsHost={flag} addedCollider={(Object)(object)val2 != (Object)null} hasCollider={(Object)(object)component != (Object)null} usableCollider={flag3} triggerId={((Object)val).GetInstanceID()} layer={hostObject.layer}"); } return new ReviveTriggerEntry { TargetPlayerId = playerId, SourceKind = sourceKind, SourceObjectId = sourceObjectId, RootObject = hostObject, TriggerHostObject = hostObject, Trigger = val, AddedCollider = val2, OwnsTrigger = flag2, OwnsTriggerHostObject = flag, ChangedLayer = changedLayer, OriginalLayer = layer, LayerAdjustedObject = hostObject, FollowTransform = null, StaticPosition = Vector3.zero }; } } } private static ReviveTriggerEntry CreateMaskedEntryUsingChildTrigger(int playerId, ulong sourceObjectId, GameObject hostObject) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hostObject == (Object)null) { return null; } Transform val = hostObject.transform.Find("UltimateReviveMaskedTrigger"); bool flag = (Object)(object)val == (Object)null; GameObject val2 = (GameObject)(((Object)(object)val != (Object)null) ? ((object)((Component)val).gameObject) : ((object)new GameObject("UltimateReviveMaskedTrigger"))); if (flag) { val2.transform.SetParent(hostObject.transform, false); val2.transform.localPosition = MaskedTriggerAliveLocalOffset; } SphereCollider obj = val2.GetComponent<SphereCollider>() ?? val2.AddComponent<SphereCollider>(); ((Collider)obj).isTrigger = true; obj.radius = 0.6f; obj.center = Vector3.zero; InteractTrigger obj2 = val2.GetComponent<InteractTrigger>(); bool ownsTrigger = (Object)(object)obj2 == (Object)null; if (obj2 == null) { obj2 = val2.AddComponent<InteractTrigger>(); } InteractTrigger val3 = obj2; int num = LayerMask.NameToLayer("InteractableObject"); bool changedLayer = false; int layer = val2.layer; if (num >= 0 && val2.layer != num) { val2.layer = num; changedLayer = true; } ConfigureTrigger(val3, playerId, ReviveSourceKind.Masked); RegisterTriggerMapping(val3, playerId, ReviveSourceKind.Masked, val2); bool flag2 = false; MaskedPlayerEnemy val4 = hostObject.GetComponent<MaskedPlayerEnemy>() ?? hostObject.GetComponentInChildren<MaskedPlayerEnemy>(true); if ((Object)(object)val4 != (Object)null) { flag2 = MaskedConversionTracker.IsMaskedDead(val4); } ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[ReviveTrigger] maskedChildAttached player={playerId} host={((Object)hostObject).name} child={((Object)val2).name} ownsTriggerHost={flag} triggerId={((Object)val3).GetInstanceID()} layer={val2.layer} maskedDead={flag2}"); } return new ReviveTriggerEntry { TargetPlayerId = playerId, SourceKind = ReviveSourceKind.Masked, SourceObjectId = s