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 ReviveAlliesRevived v0.1.2
ReviveAllies.dll
Decompiled 19 hours 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.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using ReviveAllies.Components; using ReviveAllies.Components.States; using ReviveAllies.E2E; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ReviveAllies")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("0.3.4.0")] [assembly: AssemblyInformationalVersion("0.3.4")] [assembly: AssemblyProduct("ReviveAllies")] [assembly: AssemblyTitle("ReviveAllies")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.4.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [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 ReviveAllies { public enum ReviveModeType { Hold, Press } [BepInPlugin("com.andres.reviveallies", "ReviveAllies", "0.3.4-1.0-revived")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.andres.reviveallies"; public const string PluginName = "ReviveAllies"; public const string PluginVersion = "0.3.4-1.0-revived"; internal static ConfigEntry<ReviveModeType> ReviveModeCfg = null; internal static ConfigEntry<float> ReviveHoldTimeCfg = null; internal static ConfigEntry<float> ReviveWindowCfg = null; internal const string RpcConfig = "ReviveAllies_Config"; private static bool s_hasServerConfig; private static float s_srvWindow; private static float s_srvHoldTime; private static bool s_srvPressMode; private static readonly float s_windowEnvOverride = ReadWindowOverride(); public const float ReviveHealthFraction = 0.25f; private Harmony? _harmony; internal static ManualLogSource Logger { get; private set; } = null; private static bool UseServerConfig => s_hasServerConfig && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer(); public static float ReviveWindow { get { if (s_windowEnvOverride > 0f) { return s_windowEnvOverride; } return UseServerConfig ? s_srvWindow : (ReviveWindowCfg?.Value ?? 30f); } } public static float ReviveDuration => Mathf.Max(0.1f, UseServerConfig ? s_srvHoldTime : (ReviveHoldTimeCfg?.Value ?? 4f)); public static bool RevivePressMode => UseServerConfig ? s_srvPressMode : (ReviveModeCfg != null && ReviveModeCfg.Value == ReviveModeType.Press); private static float ReadWindowOverride() { string environmentVariable = Environment.GetEnvironmentVariable("RR_E2E_WINDOW"); float result; return (float.TryParse(environmentVariable, out result) && result > 0f) ? result : 0f; } internal static void ApplyServerConfig(float window, float holdTime, bool pressMode) { s_srvWindow = window; s_srvHoldTime = holdTime; s_srvPressMode = pressMode; s_hasServerConfig = true; Logger.LogInfo((object)$"Adopted server revive config: window={window:F0}s hold={holdTime:F1}s press={pressMode}"); } internal static void ResetServerConfig() { s_hasServerConfig = false; } internal static void BroadcastConfig() { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZRoutedRpc.instance != null) { ZRoutedRpc instance = ZRoutedRpc.instance; long num = 0L; object[] obj = new object[3] { ReviveWindowCfg?.Value ?? 30f, Mathf.Max(0.1f, ReviveHoldTimeCfg?.Value ?? 4f), null }; ConfigEntry<ReviveModeType> reviveModeCfg = ReviveModeCfg; obj[2] = ((reviveModeCfg != null && reviveModeCfg.Value == ReviveModeType.Press) ? 1 : 0); instance.InvokeRoutedRPC(num, "ReviveAllies_Config", obj); } } private void Awake() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"ReviveAllies v0.3.4-1.0-revived loaded!"); ReviveModeCfg = ((BaseUnityPlugin)this).Config.Bind<ReviveModeType>("Revive", "Mode", ReviveModeType.Hold, "Hold: the interact key must be held for HoldTimeSeconds to revive. Press: a single press revives instantly."); ReviveHoldTimeCfg = ((BaseUnityPlugin)this).Config.Bind<float>("Revive", "HoldTimeSeconds", 4f, new ConfigDescription("How long the interact key must be held to complete a revive (Hold mode).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 60f), Array.Empty<object>())); ReviveWindowCfg = ((BaseUnityPlugin)this).Config.Bind<float>("Revive", "WindowSeconds", 30f, new ConfigDescription("How long a downed player can be revived before dying for real. Server-authoritative: the host's value governs everyone.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 600f), Array.Empty<object>())); ReviveModeCfg.SettingChanged += delegate { BroadcastConfig(); }; ReviveHoldTimeCfg.SettingChanged += delegate { BroadcastConfig(); }; ReviveWindowCfg.SettingChanged += delegate { BroadcastConfig(); }; _harmony = Harmony.CreateAndPatchAll(typeof(Plugin).Assembly, "com.andres.reviveallies"); if (Environment.GetEnvironmentVariable("RR_E2E") == "1") { E2ERunner.Bootstrap(); } } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace ReviveAllies.Patches { [HarmonyPatch(typeof(Character), "CheckDeath")] internal static class CheckDeathPatch { private static bool Prefix(Character __instance) { //IL_00c3: 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_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null) { return true; } if (!((Character)val).m_nview.IsOwner()) { return true; } if (((Character)val).IsDead()) { return true; } if (((Character)val).GetHealth() > 0f) { return true; } if ((Object)(object)((Component)val).GetComponent<DownedStateMachine>() == (Object)null) { return true; } if (!val.IsDowned()) { val.EnterDownedState(); return false; } if (!((Component)val).GetComponent<ChannelSignal>().IsChanneling && val.IsReviveWindowExpired()) { if (((Character)val).m_lastHit == null) { ((Character)val).m_lastHit = new HitData { m_hitType = (HitType)14 }; } val.ExpireDownedState(); return true; } return false; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class ZNetAwakeConfigSyncPatch { private static void Postfix() { Plugin.ResetServerConfig(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return; } instance.Register<float, float, int>("ReviveAllies_Config", (Action<long, float, float, int>)delegate(long sender, float window, float holdTime, int mode) { if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { Plugin.ApplyServerConfig(window, holdTime, mode != 0); } }); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] internal static class ZNetPeerInfoConfigSyncPatch { private static void Postfix(ZNet __instance) { if (__instance.IsServer()) { Plugin.BroadcastConfig(); } } } [HarmonyPatch(typeof(BaseAI), "IsEnemy", new Type[] { typeof(Character), typeof(Character) })] internal static class BaseAIIgnoreDownedPatch { private static void Postfix(Character a, Character b, ref bool __result) { if (__result && (IsDownedPlayer(a) || IsDownedPlayer(b))) { __result = false; } } private static bool IsDownedPlayer(Character c) { return ((Player?)(object)((c is Player) ? c : null))?.IsDowned() ?? false; } } [HarmonyPatch(typeof(ZNet), "OpenServer")] internal static class ZNetOpenServerSocketPatch { private static bool Prefix(ZNet __instance) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (!E2EConfig.MultiplayerMode) { return true; } if ((int)ZNet.m_onlineBackend != 3) { return true; } if (!__instance.IsServer()) { return true; } int port = E2EConfig.Port; ZSocket2 val = new ZSocket2(); if (!val.StartHost()) { Plugin.Logger.LogError((object)$"E2E: ZSocket2 host failed to bind port {port}"); return true; } __instance.m_hostSocket = (ISocket)(object)val; ZNet.m_openServer = true; E2ELog.Write($"E2E: opened CustomSocket (TCP) host on port {port}"); return false; } } [HarmonyPatch(typeof(ZNet), "Update")] internal static class ZNetUpdatePumpConnectorPatch { private static void Postfix(ZNet __instance) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 if (E2EConfig.MultiplayerMode && (int)ZNet.m_onlineBackend == 3 && __instance.m_serverConnector != null) { __instance.UpdateClientConnector(Time.deltaTime); } } } [HarmonyPatch(typeof(ZNet), "SendPeerInfo")] internal static class ZNetSendPeerInfoSocketPatch { private static bool Prefix(ZNet __instance, ZRpc rpc, string password) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) if (!E2EConfig.MultiplayerMode) { return true; } if ((int)ZNet.m_onlineBackend != 3) { return true; } if (__instance.IsServer()) { return true; } ZPackage val = new ZPackage(); val.Write(ZNet.GetUID()); val.Write(((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString()); val.Write(36u); val.Write(__instance.m_referencePosition); val.Write(Game.instance.GetPlayerProfile().GetName()); val.Write(""); rpc.Invoke("PeerInfo", new object[1] { val }); E2ELog.Write("E2E[client]: sent CustomSocket PeerInfo (no steam ticket)"); return false; } } [HarmonyPatch(typeof(ZSocket2), "Recv")] internal static class SocketLatencyPatch { private static readonly Dictionary<ZSocket2, Queue<(DateTime due, ZPackage pkg)>> s_delayed = new Dictionary<ZSocket2, Queue<(DateTime, ZPackage)>>(); private static void Postfix(ZSocket2 __instance, ref ZPackage? __result) { int latencyMs = E2EConfig.LatencyMs; if (latencyMs > 0 && E2EConfig.MultiplayerMode) { if (!s_delayed.TryGetValue(__instance, out Queue<(DateTime, ZPackage)> value)) { value = (s_delayed[__instance] = new Queue<(DateTime, ZPackage)>()); } if (__result != null) { value.Enqueue((DateTime.UtcNow.AddMilliseconds(latencyMs), __result)); } __result = ((value.Count > 0 && value.Peek().Item1 <= DateTime.UtcNow) ? value.Dequeue().Item2 : null); } } } [HarmonyPatch(typeof(Player), "Awake")] internal static class PlayerAwakePatch { private static void Postfix(Player __instance) { if ((Object)(object)((Component)__instance).GetComponent<GiveUp>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<GiveUp>(); } if ((Object)(object)((Component)__instance).GetComponent<ChannelSignal>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<ChannelSignal>(); } if ((Object)(object)((Component)__instance).GetComponent<DownedStateMachine>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<DownedStateMachine>(); } if ((Object)(object)((Component)__instance).GetComponent<ReviveRequest>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<ReviveRequest>(); } if ((Object)(object)((Component)__instance).GetComponent<ReviverStateMachine>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<ReviverStateMachine>(); } if ((Object)(object)((Component)__instance).GetComponent<ReconnectDeathCheck>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<ReconnectDeathCheck>(); } if ((Object)(object)((Component)__instance).GetComponent<DownedView>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<DownedView>(); } } } [HarmonyPatch(typeof(Player), "CanMove")] internal static class PlayerCanMovePatch { private static void Postfix(Player __instance, ref bool __result) { if (__result && __instance.IsDowned()) { __result = false; } } } [HarmonyPatch(typeof(Character), "UpdateMotion")] internal static class CharacterUpdateMotionPatch { private static bool Prefix(Character __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && val.IsDowned()) { return false; } return true; } } [HarmonyPatch(typeof(Player), "LateUpdate")] internal static class PlayerLateUpdatePatch { private static void Postfix(Player __instance) { if (((Character)__instance).IsDead()) { if ((Object)(object)((Character)__instance).m_collider != (Object)null && ((Collider)((Character)__instance).m_collider).enabled) { ((Collider)((Character)__instance).m_collider).enabled = false; } if ((Object)(object)((Character)__instance).m_nview != (Object)null && ((Character)__instance).m_nview.IsValid() && ((Character)__instance).m_nview.IsOwner() && (Object)(object)((Character)__instance).m_body != (Object)null && !((Character)__instance).m_body.isKinematic) { ((Character)__instance).m_body.isKinematic = true; } } } } [HarmonyPatch(typeof(Player), "UpdateHover")] internal static class PlayerUpdateHoverPatch { private static bool Prefix(Player __instance) { if (__instance.IsDowned()) { return false; } return true; } } [HarmonyPatch(typeof(Player), "OnRespawn")] internal static class PlayerOnRespawnPatch { private static bool Prefix(Player __instance) { if (__instance.IsDowned()) { return false; } return true; } } [HarmonyPatch(typeof(EnemyHud), "TestShow")] internal static class EnemyHudHideDownedPatch { private static void Postfix(Character c, ref bool __result) { if (__result) { Player val = (Player)(object)((c is Player) ? c : null); if (val != null && (val.IsDowned() || ((Character)val).IsDead())) { __result = false; } } } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class ZNetSceneAwakeRegisterPrefabsPatch { private static void Postfix(ZNetScene __instance) { MarkerPrefab.RegisterPrefab(__instance); } } [HarmonyPatch(typeof(TombStone), "Setup")] internal static class TombStoneSetupReplacePatch { private static void Postfix(TombStone __instance, long ownerUID) { //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) //IL_007e: 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) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)((Character)localPlayer).m_nview == (Object)null || !((Character)localPlayer).m_nview.IsValid() || ownerUID != localPlayer.GetPlayerID()) { return; } GraveReplaceView graveReplaceView = localPlayer.GraveReplace(); if (graveReplaceView.Pending) { graveReplaceView.Pending = false; Vector3 val = graveReplaceView.Pos; if (val == Vector3.zero) { val = ((Component)__instance).transform.position; } ((Component)__instance).transform.position = val; Rigidbody component = ((Component)__instance).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { component.position = val; component.linearVelocity = Vector3.zero; } GameObject marker = localPlayer.FindDownedMarker() ?? MarkerPrefab.FindFor(localPlayer.GetPlayerID()); DownedMarker.MarkReplaced(marker); DownedStateMachineView downedStateMachineView = localPlayer.State(); downedStateMachineView.Marker = ZDOID.None; } } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerOnDeathMarkerCrumblePatch { private static void Postfix(Player __instance) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) ZNetView nview = ((Character)__instance).m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner()) { GraveReplaceView graveReplaceView = __instance.GraveReplace(); if (graveReplaceView.Pending) { graveReplaceView.Pending = false; GameObject marker = __instance.FindDownedMarker() ?? MarkerPrefab.FindFor(__instance.GetPlayerID()); DownedStateMachineView downedStateMachineView = __instance.State(); downedStateMachineView.Marker = ZDOID.None; DownedMarker.Crumble(marker); Plugin.Logger.LogInfo((object)(__instance.GetPlayerName() + " died with no grave to drop; marker crumbled")); } } } } [HarmonyPatch(typeof(Player), "CreateDeathEffects")] internal static class SuppressPlayerRagdollPatch { private static bool Prefix() { return false; } } } namespace ReviveAllies.E2E { public static class E2EConfig { public static bool Enabled => Environment.GetEnvironmentVariable("RR_E2E") == "1"; public static string Role => (Environment.GetEnvironmentVariable("RR_E2E_ROLE") ?? "").ToLowerInvariant(); public static bool MultiplayerMode => Role == "host" || Role == "client"; public static bool IsHost => Role == "host"; public static bool IsClient => Role == "client"; public static string Scenario => (Environment.GetEnvironmentVariable("RR_E2E_SCENARIO") ?? "revive").ToLowerInvariant(); public static bool IsRejoinScenario => Scenario == "rejoin"; public static bool IsVanishScenario => Scenario == "vanish"; public static bool IsReviveLoopScenario => Scenario == "reviveloop"; public static bool IsConfigSyncScenario => Scenario == "configsync"; public static string LoopDownRole => (Environment.GetEnvironmentVariable("RR_E2E_LOOP_DOWN") ?? "host").ToLowerInvariant(); public static int LoopCycles { get { string environmentVariable = Environment.GetEnvironmentVariable("RR_E2E_LOOP_CYCLES"); int result; return (int.TryParse(environmentVariable, out result) && result > 0) ? result : 15; } } public static string LoopMode => (Environment.GetEnvironmentVariable("RR_E2E_LOOP_MODE") ?? "revive").ToLowerInvariant(); public static bool IsExpireLoop => LoopMode == "expire"; public static bool Manual => Environment.GetEnvironmentVariable("RR_E2E_MANUAL") == "1"; public static int Port { get { string environmentVariable = Environment.GetEnvironmentVariable("RR_E2E_PORT"); int result; return int.TryParse(environmentVariable, out result) ? result : 2456; } } public static string WorldName => Environment.GetEnvironmentVariable("RR_E2E_WORLD") ?? "e2e_mp"; public static string ServerHost => Environment.GetEnvironmentVariable("RR_E2E_HOST") ?? "127.0.0.1"; public static int LatencyMs { get { string environmentVariable = Environment.GetEnvironmentVariable("RR_E2E_LATENCY"); int result; return (int.TryParse(environmentVariable, out result) && result > 0) ? result : 0; } } } public static class E2ELog { private static readonly object s_lock = new object(); private static string? s_path; private static bool s_init; private static string? Path { get { if (!s_init) { s_path = Environment.GetEnvironmentVariable("RR_E2E_LOG"); if (string.IsNullOrEmpty(s_path)) { string environmentVariable = Environment.GetEnvironmentVariable("RR_E2E_RESULTS"); if (!string.IsNullOrEmpty(environmentVariable)) { s_path = environmentVariable + ".log"; } } s_init = true; if (!string.IsNullOrEmpty(s_path)) { try { File.WriteAllText(s_path, "# E2E log role=" + E2EConfig.Role + "\n"); } catch { } } } return s_path; } } public static void Write(string msg) { Plugin.Logger.LogInfo((object)msg); string path = Path; if (string.IsNullOrEmpty(path)) { return; } try { lock (s_lock) { File.AppendAllText(path, msg + "\n"); } } catch { } } } public class E2ERunner : MonoBehaviour { private const float HardTimeoutSeconds = 360f; private readonly List<(string name, bool pass, string detail)> _results = new List<(string, bool, string)>(); private bool _started; private bool _worldStartIssued; private float _elapsed; private const float LeakGraceSeconds = 8f; private int m_loopLeaks; private static string ResultPath => Environment.GetEnvironmentVariable("RR_E2E_RESULTS") ?? Path.Combine(Paths.GameRootPath ?? ".", "e2e-results.txt"); public static void Bootstrap() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown GameObject val = new GameObject("RevivalRevived_E2ERunner"); Object.DontDestroyOnLoad((Object)(object)val); val.AddComponent<E2ERunner>(); Plugin.Logger.LogInfo((object)("E2E: harness bootstrapped (role='" + E2EConfig.Role + "')")); } private void Start() { ((MonoBehaviour)this).StartCoroutine(RunAll()); } private void Update() { if (!E2EConfig.Manual) { _elapsed += Time.unscaledDeltaTime; if (_elapsed > 360f && _started) { Log("E2E: HARD TIMEOUT reached, aborting"); Finish(ok: false, "hard-timeout"); } } } private IEnumerator RunAll() { _started = true; Log($"E2E: run starting (role='{E2EConfig.Role}' manual={E2EConfig.Manual})"); if (E2EConfig.Manual) { yield return ((MonoBehaviour)this).StartCoroutine(RunManual()); yield break; } if (Environment.GetEnvironmentVariable("RR_E2E_DEMO") == "1") { yield return ((MonoBehaviour)this).StartCoroutine(RunDemo()); } else if (E2EConfig.IsHost) { yield return ((MonoBehaviour)this).StartCoroutine(RunHost()); } else if (E2EConfig.IsClient) { yield return ((MonoBehaviour)this).StartCoroutine(RunClient()); } else { yield return ((MonoBehaviour)this).StartCoroutine(RunSingleProcess()); } bool allPass = _results.Count > 0; foreach (var result in _results) { allPass &= result.pass; } Finish(allPass, allPass ? "all-pass" : "failures"); } private IEnumerator RunHost() { yield return ((MonoBehaviour)this).StartCoroutine(AutoStart()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForPlayerInWorld()); Player player = Player.m_localPlayer; if ((Object)(object)player == (Object)null) { Record("host_spawn", pass: false, "no local player"); yield break; } Record("host_spawn", pass: true, "name=" + player.GetPlayerName()); if (E2EConfig.IsConfigSyncScenario) { Plugin.ReviveHoldTimeCfg.Value = 7f; Plugin.ReviveModeCfg.Value = ReviveModeType.Press; Plugin.BroadcastConfig(); Log("E2E[host]: configsync -- authoritative hold=7 mode=Press"); } Log("E2E[host]: waiting for a client to connect..."); float waited = 0f; while (waited < 180f) { int peers = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeerConnections() : 0); int players = Player.GetAllPlayers().Count; if (peers >= 1 && players >= 2) { break; } waited += Time.unscaledDeltaTime; yield return null; } int peerCount = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeerConnections() : 0); int playerCount = Player.GetAllPlayers().Count; bool connected = peerCount >= 1 && playerCount >= 2; Record("host_client_connected", connected, $"peers={peerCount} players={playerCount}"); if (!connected) { yield break; } if (E2EConfig.IsRejoinScenario) { Log("E2E[host]: rejoin scenario -- idling as server"); yield return (object)new WaitForSecondsRealtime(160f); Record("host_idle_complete", pass: true, "server stayed up for client rejoin"); yield break; } if (E2EConfig.IsConfigSyncScenario) { Log("E2E[host]: configsync -- idling while client verifies"); yield return (object)new WaitForSecondsRealtime(30f); Record("host_idle_complete", pass: true, "server stayed up for configsync"); yield break; } if (E2EConfig.IsVanishScenario) { yield return ((MonoBehaviour)this).StartCoroutine(RunHostVanish(player)); yield break; } if (E2EConfig.IsReviveLoopScenario) { if (E2EConfig.LoopDownRole == "host") { yield return ((MonoBehaviour)this).StartCoroutine(RunLoopVictim(player)); } else { yield return ((MonoBehaviour)this).StartCoroutine(RunLoopReviver(player)); } yield break; } yield return (object)new WaitForSecondsRealtime(2f); Log("E2E[host]: downing self"); ((Character)player).SetHealth(0f); waited = 0f; while (waited < 5f && !player.IsDowned()) { waited += Time.unscaledDeltaTime; yield return null; } Record("host_downed", player.IsDowned(), $"downed={player.IsDowned()} dead={((Character)player).IsDead()}"); if (player.IsDowned()) { Log("E2E[host]: waiting to be revived by client..."); waited = 0f; float maxSeenProgress = 0f; while (waited < 28f && player.IsDowned() && !((Character)player).IsDead()) { maxSeenProgress = Mathf.Max(maxSeenProgress, player.GetReviveProgress()); waited += Time.unscaledDeltaTime; yield return null; } bool revived = !player.IsDowned() && !((Character)player).IsDead() && ((Character)player).GetHealth() > 0f; Record("host_revived_by_client", revived, $"downed={player.IsDowned()} dead={((Character)player).IsDead()} hp={((Character)player).GetHealth():F0}"); Record("host_saw_progress", maxSeenProgress > 0.5f, $"maxSeenProgress={maxSeenProgress:F2}"); yield return (object)new WaitForSecondsRealtime(6f); } } private IEnumerator RunHostVanish(Player me) { Log("E2E[host]: vanish scenario -- waiting for client to be downed..."); Player downed = null; float w = 0f; while (w < 60f) { downed = FindDownedRemotePlayer(me); if ((Object)(object)downed != (Object)null) { break; } w += Time.unscaledDeltaTime; yield return null; } Record("vanish_client_downed", (Object)(object)downed != (Object)null, ((Object)(object)downed != (Object)null) ? ("name=" + downed.GetPlayerName()) : "none"); if ((Object)(object)downed == (Object)null) { yield break; } long downedPid = downed.GetPlayerID(); yield return (object)new WaitForSecondsRealtime(1f); Log("E2E[host]: channeling; client will log out mid-hold..."); float maxProg = 0f; bool vanished = false; bool revived = false; w = 0f; while (w < 40f) { if ((Object)(object)downed == (Object)null || (Object)(object)((Character)downed).m_nview == (Object)null || !((Character)downed).m_nview.IsValid()) { vanished = true; break; } if (!downed.IsDowned() && ((Character)downed).GetHealth() > 0f) { revived = true; break; } FindMarkerInteractable(downed)?.Interact((Humanoid)(object)me, hold: true, alt: false); maxProg = Mathf.Max(maxProg, downed.GetReviveProgress()); w += Time.unscaledDeltaTime; yield return null; } Record("vanish_mid_channel", vanished && !revived && maxProg > 0.1f, $"vanished={vanished} revived={revived} maxProg={maxProg:F2}"); if (!vanished) { yield break; } GameObject orphan = MarkerPrefab.FindFor(downedPid); Revivable orphanInteractable = (((Object)(object)orphan != (Object)null) ? orphan.GetComponentInChildren<Revivable>() : null); w = 0f; while (w < 1.5f) { orphanInteractable?.Interact((Humanoid)(object)me, hold: true, alt: false); w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(3f); orphan = MarkerPrefab.FindFor(downedPid); bool orphanPersists = (Object)(object)orphan != (Object)null; string hover = ""; bool interactInert = true; if ((Object)(object)orphan != (Object)null) { Revivable inter = orphan.GetComponentInChildren<Revivable>(); if ((Object)(object)inter != (Object)null) { interactInert = !inter.Interact((Humanoid)(object)me, hold: true, alt: false); hover = inter.GetHoverText(); } } bool hoverExplains = hover.IndexOf("disconnected", StringComparison.OrdinalIgnoreCase) >= 0; bool onlyMe = Player.GetAllPlayers().Count == 1; Record("vanish_channel_fizzles", orphanPersists && interactInert && hoverExplains && onlyMe, $"orphanPersists={orphanPersists} interactInert={interactInert} " + string.Format("hoverExplains={0} hover=\"{1}\" onlyMe={2}", hoverExplains, hover.Replace("\n", " | "), onlyMe)); } private IEnumerator RunClient() { yield return ((MonoBehaviour)this).StartCoroutine(AutoStart()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForPlayerInWorld()); Player me = Player.m_localPlayer; if ((Object)(object)me == (Object)null) { Record("client_spawn", pass: false, "no local player"); yield break; } Record("client_spawn", pass: true, "name=" + me.GetPlayerName()); Log("E2E[client]: waiting to see the remote host player..."); float waited = 0f; while (waited < 60f && Player.GetAllPlayers().Count < 2) { waited += Time.unscaledDeltaTime; yield return null; } bool sawRemote = Player.GetAllPlayers().Count >= 2; Record("client_sees_host", sawRemote, $"players={Player.GetAllPlayers().Count}"); if (!sawRemote) { yield break; } if (E2EConfig.IsRejoinScenario) { yield return ((MonoBehaviour)this).StartCoroutine(RunClientRejoin(me)); yield break; } if (E2EConfig.IsVanishScenario) { yield return ((MonoBehaviour)this).StartCoroutine(RunClientVanish(me)); yield break; } if (E2EConfig.IsReviveLoopScenario) { if (E2EConfig.LoopDownRole == "client") { yield return ((MonoBehaviour)this).StartCoroutine(RunLoopVictim(me)); } else { yield return ((MonoBehaviour)this).StartCoroutine(RunLoopReviver(me)); } yield break; } if (E2EConfig.IsConfigSyncScenario) { yield return ((MonoBehaviour)this).StartCoroutine(RunClientConfigSync()); yield break; } Log("E2E[client]: waiting for remote player to be downed..."); Player downed = null; waited = 0f; while (waited < 60f) { downed = FindDownedRemotePlayer(me); if ((Object)(object)downed != (Object)null) { break; } waited += Time.unscaledDeltaTime; yield return null; } Record("client_detected_downed", (Object)(object)downed != (Object)null, ((Object)(object)downed != (Object)null) ? ("name=" + downed.GetPlayerName()) : "none"); if ((Object)(object)downed == (Object)null) { yield break; } yield return ((MonoBehaviour)this).StartCoroutine(ValidateMarker(downed)); Log("E2E[client]: channeling revive on remote player..."); waited = 0f; float interactSeconds = 0f; float maxUiFill = 0f; float lastProg = 0f; float worstRegression = 0f; bool uiSeen = false; bool sawFull = false; bool reboundAfterFull = false; while (waited < 20f && downed.IsDowned()) { Revivable interactable = FindInteractableViaHoverRay(downed, me); if ((Object)(object)interactable != (Object)null) { interactable.Interact((Humanoid)(object)me, hold: true, alt: false); interactSeconds += Time.unscaledDeltaTime; } float prog = downed.GetReviveProgress(); if (prog > 0.02f && prog < lastProg - 0.01f) { worstRegression = Mathf.Max(worstRegression, lastProg - prog); } lastProg = prog; if (prog >= 0.99f) { sawFull = true; } else if (sawFull && prog > 0.05f && prog < 0.9f) { reboundAfterFull = true; } if (ProgressUI.Visible) { uiSeen = true; maxUiFill = Mathf.Max(maxUiFill, ProgressUI.Fill); } waited += Time.unscaledDeltaTime; yield return null; } bool revivedRemote = !downed.IsDowned(); Record("client_revived_remote", revivedRemote, $"downed={downed.IsDowned()} channelSecs={interactSeconds:F1}"); bool monotonic = worstRegression < 0.05f; Record("client_progress_ui", uiSeen && maxUiFill > 0.3f && monotonic && !reboundAfterFull, $"uiSeen={uiSeen} maxFill={maxUiFill:F2} worstRegression={worstRegression:F2} monotonic={monotonic} " + $"sawFull={sawFull} reboundAfterFull={reboundAfterFull}"); float vw = 0f; bool visibleAgain = false; bool solidAgain = false; while (vw < 5f && (Object)(object)downed != (Object)null) { visibleAgain = (Object)(object)((Character)downed).m_visual != (Object)null && ((Character)downed).m_visual.activeSelf; solidAgain = (Object)(object)((Character)downed).m_collider != (Object)null && ((Collider)((Character)downed).m_collider).enabled; if (visibleAgain && solidAgain) { break; } vw += Time.unscaledDeltaTime; yield return null; } Record("client_host_visible_after_revive", visibleAgain && solidAgain, $"visible={visibleAgain} colliderOn={solidAgain} after={vw:F1}s"); float mw = 0f; bool markerDestroyed = false; while (mw < 6f) { markerDestroyed = Object.FindObjectsOfType<DownedMarker>().Length == 0; if (markerDestroyed) { break; } mw += Time.unscaledDeltaTime; yield return null; } Record("client_marker_gone_after_revive", markerDestroyed, $"markerDestroyed={markerDestroyed} after={mw:F1}s"); } private IEnumerator RunClientVanish(Player me) { Log("E2E[client]: vanish scenario -- downing self"); ((Character)me).SetHealth(0f); float w = 0f; while (w < 6f && !me.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } if (!me.IsDowned()) { Record("vanish_pre_down", pass: false, "could not down self"); yield break; } Record("vanish_pre_down", pass: true, "downed=True"); Log("E2E[client]: waiting for host channel progress before logging out..."); w = 0f; float seenProg = 0f; while (w < 30f && me.IsDowned()) { seenProg = Mathf.Max(seenProg, me.GetReviveProgress()); if (seenProg > 0.25f) { break; } w += Time.unscaledDeltaTime; yield return null; } Record("vanish_saw_host_channel", seenProg > 0.25f && me.IsDowned(), $"seenProg={seenProg:F2}"); if (!(seenProg <= 0.25f)) { Log("E2E[client]: logging out mid-channel"); Game.instance.Logout(true, true); w = 0f; while (w < 60f && (Object)(object)FejdStartup.instance == (Object)null) { w += Time.unscaledDeltaTime; yield return null; } Record("vanish_logged_out", (Object)(object)FejdStartup.instance != (Object)null, "back at menu"); } } private IEnumerator RunClientRejoin(Player me) { Log("E2E[client]: downing self before logout"); ((Character)me).SetHealth(0f); float w = 0f; while (w < 6f && !me.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } if (!me.IsDowned()) { Record("rejoin_pre_down", pass: false, "could not down self"); yield break; } yield return (object)new WaitForSecondsRealtime(1f); long pid = me.GetPlayerID(); bool hadMarker = (Object)(object)MarkerPrefab.FindFor(pid) != (Object)null; Record("rejoin_pre_down", hadMarker, $"downed=True marker={hadMarker}"); if (!hadMarker) { yield break; } Log("E2E[client]: logging out..."); Game.instance.Logout(true, true); w = 0f; while (w < 60f && (Object)(object)FejdStartup.instance == (Object)null) { w += Time.unscaledDeltaTime; yield return null; } if ((Object)(object)FejdStartup.instance == (Object)null) { Record("rejoin_reconnect", pass: false, "menu did not return"); yield break; } Log("E2E[client]: back at menu, reconnecting..."); _worldStartIssued = false; yield return ((MonoBehaviour)this).StartCoroutine(AutoStart()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForPlayerInWorld()); Player me2 = Player.m_localPlayer; if ((Object)(object)me2 == (Object)null) { Record("rejoin_reconnect", pass: false, "no local player after reconnect"); yield break; } Record("rejoin_reconnect", pass: true, "name=" + me2.GetPlayerName()); Log("E2E[client]: waiting for disconnect-death on reconnect..."); bool diedOnReconnect = false; bool markerGone = false; bool realTombstone = false; w = 0f; while (w < 20f) { if ((Object)(object)Player.m_localPlayer != (Object)null && ((Character)Player.m_localPlayer).IsDead()) { diedOnReconnect = true; } markerGone = (Object)(object)MarkerPrefab.FindFor(pid) == (Object)null; TombStone[] array = Object.FindObjectsOfType<TombStone>(); foreach (TombStone t in array) { ZNetView nv = ((Component)t).GetComponent<ZNetView>(); if ((Object)(object)nv != (Object)null && nv.IsValid() && !new DownedMarkerView(nv).IsMarker) { realTombstone = true; break; } } if (diedOnReconnect && markerGone) { break; } w += Time.unscaledDeltaTime; yield return null; } bool notDowned = (Object)(object)Player.m_localPlayer == (Object)null || !Player.m_localPlayer.IsDowned(); Record("reconnect_downed_dies", diedOnReconnect && markerGone && notDowned, $"died={diedOnReconnect} markerGone={markerGone} realTombstone={realTombstone} notDowned={notDowned}"); } private IEnumerator RunClientConfigSync() { Plugin.ReviveHoldTimeCfg.Value = 2f; Plugin.ReviveModeCfg.Value = ReviveModeType.Hold; Log("E2E[client]: configsync -- local hold=2 mode=Hold, expecting server hold=7 mode=Press"); float w = 0f; bool adopted = false; while (w < 20f && !adopted) { adopted = Mathf.Abs(Plugin.ReviveDuration - 7f) < 0.01f && Plugin.RevivePressMode; w += Time.unscaledDeltaTime; yield return null; } bool durationFromServer = Mathf.Abs(Plugin.ReviveDuration - 7f) < 0.01f; bool modeFromServer = Plugin.RevivePressMode; Record("client_adopts_server_config", durationFromServer && modeFromServer, $"durationFromServer={durationFromServer} (dur={Plugin.ReviveDuration:F1}, local was 2) " + $"modeFromServer={modeFromServer} (press; local was hold)"); } private static List<ZDO> MarkerZdos() { List<ZDO> list = new List<ZDO>(); int stableHashCode = StringExtensionMethods.GetStableHashCode("RevivalRevived_DownedMarker"); foreach (KeyValuePair<ZDOID, ZDO> item in ZDOMan.instance.m_objectsByID) { if (item.Value.GetPrefab() == stableHashCode) { list.Add(item.Value); } } return list; } private static List<DownedMarker> GhostMarkers() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) List<DownedMarker> list = new List<DownedMarker>(); DownedMarker[] array = Object.FindObjectsOfType<DownedMarker>(); foreach (DownedMarker downedMarker in array) { ZNetView component = ((Component)downedMarker).GetComponent<ZNetView>(); ZDO val = (((Object)(object)component != (Object)null) ? component.GetZDO() : null); ZDO val2 = ((val != null) ? ZDOMan.instance.GetZDO(val.m_uid) : null); if (val == null || val2 != val) { list.Add(downedMarker); } } return list; } private void DumpLeaks(string context) { //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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) foreach (ZDO item in MarkerZdos()) { DownedMarkerView downedMarkerView = new DownedMarkerView(item); ZDO val = ((downedMarkerView.LinkedPlayer != ZDOID.None) ? ZDOMan.instance.GetZDO(downedMarkerView.LinkedPlayer) : null); bool flag = val != null && new DownedStateMachineView(val).Downed; GameObject val2 = ZNetScene.instance.FindInstance(item.m_uid); Log($"E2E-LEAK[{context}] zdo {item.m_uid}: owner={item.GetOwner()} mine={item.IsOwner()} " + $"ownerRev={item.OwnerRevision} dataRev={item.DataRevision} " + string.Format("replaced={0} playerZdo={1} ", downedMarkerView.ReplacedByGrave, (val != null) ? "alive" : "gone") + $"playerDowned={flag} chan={new ReviveChannelDecayingProgressView(item).Channeling} anchorSec={new ReviveChannelDecayingProgressView(item).AnchorSeconds:F2} " + "instance=" + (((Object)(object)val2 != (Object)null) ? "yes" : "no")); } foreach (DownedMarker item2 in GhostMarkers()) { ZNetView component = ((Component)item2).GetComponent<ZNetView>(); ZDO val3 = (((Object)(object)component != (Object)null) ? component.GetZDO() : null); Log($"E2E-LEAK[{context}] GHOST instance at {((Component)item2).transform.position}: " + "zdoRef=" + ((val3 != null) ? ((object)Unsafe.As<ZDOID, ZDOID>(ref val3.m_uid)/*cast due to .constrained prefix*/).ToString() : "null") + " registered=" + ((val3 != null && ZDOMan.instance.GetZDO(val3.m_uid) != null) ? "different-object" : "none")); } } private IEnumerator LeakCheck(string context) { float w = 0f; while (w < 8f && (MarkerZdos().Count > 0 || GhostMarkers().Count > 0)) { w += Time.unscaledDeltaTime; yield return null; } int zdos = MarkerZdos().Count; int ghosts = GhostMarkers().Count; if (zdos > 0 || ghosts > 0) { m_loopLeaks++; DumpLeaks(context); } Log($"E2E loop [{context}]: zdosLeft={zdos} ghosts={ghosts} leaksSoFar={m_loopLeaks}"); } private IEnumerator RunLoopVictim(Player me) { if (E2EConfig.IsExpireLoop) { yield return ((MonoBehaviour)this).StartCoroutine(RunLoopVictimExpire()); yield break; } int cycles = E2EConfig.LoopCycles; int revives = 0; for (int cycle = 1; cycle <= cycles; cycle++) { ((Character)me).SetHealth(((Character)me).GetMaxHealth()); yield return (object)new WaitForSecondsRealtime(1.2f); Log($"E2E loop {cycle}: downing self"); ((Character)me).SetHealth(0f); float w = 0f; while (w < 5f && !me.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } if (!me.IsDowned()) { Record("reviveloop_victim", pass: false, $"cycle {cycle}: could not down"); yield break; } w = 0f; while (w < 40f && me.IsDowned() && !((Character)me).IsDead()) { w += Time.unscaledDeltaTime; yield return null; } if (me.IsDowned() || ((Character)me).IsDead()) { Record("reviveloop_victim", pass: false, $"cycle {cycle}: not revived (dead={((Character)me).IsDead()})"); yield break; } revives++; yield return ((MonoBehaviour)this).StartCoroutine(LeakCheck($"victim cycle {cycle}")); } Record("reviveloop_victim", revives == cycles && m_loopLeaks == 0, $"revives={revives}/{cycles} leakCycles={m_loopLeaks}"); yield return (object)new WaitForSecondsRealtime(8f); } private IEnumerator RunLoopVictimExpire() { int cycles = E2EConfig.LoopCycles; int deaths = 0; for (int cycle = 1; cycle <= cycles; cycle++) { Player me = Player.m_localPlayer; if ((Object)(object)me == (Object)null) { Record("reviveloop_victim", pass: false, $"cycle {cycle}: no local player"); yield break; } ((Character)me).SetHealth(((Character)me).GetMaxHealth()); GiveTestItem(me); yield return (object)new WaitForSecondsRealtime(1.2f); Log($"E2E loop {cycle}: downing self (expire mode)"); ((Character)me).SetHealth(0f); float w = 0f; while (w < 5f && !me.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } if (!me.IsDowned()) { Record("reviveloop_victim", pass: false, $"cycle {cycle}: could not down"); yield break; } w = 0f; while (w < 30f && me.GetReviveProgress() < 0.15f && me.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } if (!me.IsDowned()) { Record("reviveloop_victim", pass: false, $"cycle {cycle}: revived before expiry could fire"); yield break; } DownedStateMachineView s = new DownedStateMachineView(((Character)me).m_nview) { DownedTime = (float)ZNet.instance.GetTimeSeconds() - Plugin.ReviveWindow - 5f }; w = 0f; while (w < 12f && !((Character)me).IsDead()) { ((Character)me).SetHealth(0f); w += Time.unscaledDeltaTime; yield return null; } if (!((Character)me).IsDead()) { Record("reviveloop_victim", pass: false, $"cycle {cycle}: expiry did not kill"); yield break; } deaths++; yield return ((MonoBehaviour)this).StartCoroutine(LeakCheck($"victim expire cycle {cycle}")); yield return ((MonoBehaviour)this).StartCoroutine(WaitForAlivePlayer()); } Record("reviveloop_victim", deaths == cycles && m_loopLeaks == 0, $"deaths={deaths}/{cycles} leakCycles={m_loopLeaks}"); yield return (object)new WaitForSecondsRealtime(8f); } private IEnumerator RunLoopReviver(Player me) { if (E2EConfig.IsExpireLoop) { yield return ((MonoBehaviour)this).StartCoroutine(RunLoopReviverExpire(me)); yield break; } int revived = 0; while (true) { Player downed = null; float w = 0f; while (w < 30f && (Object)(object)downed == (Object)null) { downed = FindDownedRemotePlayer(me); w += Time.unscaledDeltaTime; yield return null; } if ((Object)(object)downed == (Object)null) { break; } yield return (object)new WaitForSecondsRealtime(0.5f); w = 0f; float overshoot = 0f; bool jittered = false; while (w < 40f && overshoot < 2f) { if (!jittered && downed.GetReviveProgress() > 0.45f) { jittered = true; yield return (object)new WaitForSecondsRealtime(0.65f); w += 0.65f; continue; } (FindInteractableViaHoverRay(downed, me) ?? Object.FindObjectOfType<Revivable>())?.Interact((Humanoid)(object)me, hold: true, alt: false); if (!downed.IsDowned()) { overshoot += Time.unscaledDeltaTime; } w += Time.unscaledDeltaTime; yield return null; } if (downed.IsDowned()) { Record("reviveloop_reviver", pass: false, "revive did not land"); yield break; } revived++; yield return ((MonoBehaviour)this).StartCoroutine(LeakCheck($"reviver after revive #{revived}")); } Record("reviveloop_reviver", revived > 0 && m_loopLeaks == 0, $"revived={revived} leakCycles={m_loopLeaks}"); } private IEnumerator RunLoopReviverExpire(Player me) { int deaths = 0; while (true) { Player downed = null; float w = 0f; while (w < 30f && (Object)(object)downed == (Object)null) { downed = FindDownedRemotePlayer(me); w += Time.unscaledDeltaTime; yield return null; } if ((Object)(object)downed == (Object)null) { break; } yield return (object)new WaitForSecondsRealtime(0.4f); w = 0f; float overshoot = 0f; while (w < 40f && overshoot < 2.5f) { (FindInteractableViaHoverRay(downed, me) ?? Object.FindObjectOfType<Revivable>())?.Interact((Humanoid)(object)me, hold: true, alt: false); if (((Character)downed).IsDead() || !downed.IsDowned()) { overshoot += Time.unscaledDeltaTime; } w += Time.unscaledDeltaTime; yield return null; } if (!((Character)downed).IsDead()) { Record("reviveloop_reviver", pass: false, "victim did not die mid-channel"); yield break; } deaths++; yield return ((MonoBehaviour)this).StartCoroutine(LeakCheck($"reviver expire #{deaths}")); } Record("reviveloop_reviver", deaths > 0 && m_loopLeaks == 0, $"deaths={deaths} leakCycles={m_loopLeaks}"); } private IEnumerator ValidateMarker(Player downed) { GameObject marker = downed.FindDownedMarker(); if ((Object)(object)marker == (Object)null) { float w = 0f; while (w < 6f && (Object)(object)marker == (Object)null) { marker = downed.FindDownedMarker(); w += Time.unscaledDeltaTime; yield return null; } } if ((Object)(object)marker == (Object)null) { Record("client_marker_sync", pass: false, "marker not found on client"); yield break; } ZNetView nview = marker.GetComponent<ZNetView>(); bool isMarkerFlag = (Object)(object)nview != (Object)null && nview.IsValid() && new DownedMarkerView(nview).IsMarker; DownedMarker dm = marker.GetComponent<DownedMarker>(); bool green = (Object)(object)dm != (Object)null && dm.IsGreen(); bool hasInteractable = (Object)(object)marker.GetComponentInChildren<Revivable>() != (Object)null; bool noTombScript = (Object)(object)marker.GetComponent<TombStone>() == (Object)null; float maxPlayerDist = 0f; float maxFrameJump = 0f; Vector3 prev = marker.transform.position; int samples = 0; float t = 0f; while (t < 2.5f && downed.IsDowned()) { Vector3 pos = marker.transform.position; maxPlayerDist = Mathf.Max(maxPlayerDist, Vector3.Distance(pos, ((Component)downed).transform.position)); maxFrameJump = Mathf.Max(maxFrameJump, Vector3.Distance(pos, prev)); prev = pos; samples++; t += Time.unscaledDeltaTime; yield return null; } bool noRagdolls = Object.FindObjectsOfType<Ragdoll>().Length == 0; bool corpseColliderOff = (Object)(object)((Character)downed).m_collider == (Object)null || !((Collider)((Character)downed).m_collider).enabled; Player me = Player.m_localPlayer; bool hoverRayHitsMarker = false; bool hoverBlockedByCorpse = false; bool hoverTextOk = false; Vector3 mpos = marker.transform.position + Vector3.up * 0.3f; Vector3 origin = mpos + new Vector3(1.8f, 1.2f, 0f); Vector3 val = mpos - origin; RaycastHit[] hits = Physics.RaycastAll(origin, ((Vector3)(ref val)).normalized, 6f, me.m_interactMask); Array.Sort(hits, (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance)); if (hits.Length != 0) { Collider first = ((RaycastHit)(ref hits[0])).collider; hoverRayHitsMarker = (Object)(object)((Component)first).GetComponentInParent<Revivable>() != (Object)null; hoverBlockedByCorpse = (Object)(object)((Component)first).GetComponentInParent<Player>() == (Object)(object)downed; Revivable inter = ((Component)first).GetComponentInParent<Revivable>(); if ((Object)(object)inter != (Object)null) { string hover = inter.GetHoverText(); hoverTextOk = !string.IsNullOrEmpty(hover) && hover.IndexOf("Revive", StringComparison.OrdinalIgnoreCase) >= 0; } } bool hudHidden = true; if ((Object)(object)EnemyHud.instance != (Object)null) { IDictionary huds = Traverse.Create((object)EnemyHud.instance).Field("m_huds").GetValue<IDictionary>(); hudHidden = huds == null || !huds.Contains(downed); } bool pass = samples > 5 && isMarkerFlag && green && hasInteractable && noTombScript && maxPlayerDist < 5f && maxFrameJump < 2f && noRagdolls && corpseColliderOff && hoverRayHitsMarker && !hoverBlockedByCorpse && hoverTextOk && hudHidden; Record("client_marker_sync", pass, $"samples={samples} markerFlag={isMarkerFlag} green={green} interactable={hasInteractable} " + $"noTombScript={noTombScript} maxPlayerDist={maxPlayerDist:F2} maxFrameJump={maxFrameJump:F2} noRagdolls={noRagdolls} " + $"corpseColliderOff={corpseColliderOff} hoverRayHitsMarker={hoverRayHitsMarker} " + $"hoverBlockedByCorpse={hoverBlockedByCorpse} hoverTextOk={hoverTextOk} hudHidden={hudHidden}"); } private IEnumerator RunManual() { yield return ((MonoBehaviour)this).StartCoroutine(AutoStart()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForPlayerInWorld()); Log("E2E: manual play mode (" + E2EConfig.Role + ") -- no tests, game is yours"); while (true) { yield return null; } } private IEnumerator RunDemo() { yield return ((MonoBehaviour)this).StartCoroutine(AutoStart()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForPlayerInWorld()); Player player = Player.m_localPlayer; if ((Object)(object)player == (Object)null) { Record("demo", pass: false, "no player"); yield break; } Log("E2E: local player ready (demo)"); ((MonoBehaviour)this).StartCoroutine(DespawnRavens()); yield return (object)new WaitForSecondsRealtime(3f); if ((Object)(object)GameCamera.instance != (Object)null) { GameCamera.instance.m_maxDistance = 9f; Traverse.Create((object)GameCamera.instance).Field("m_distance").SetValue((object)8f); } yield return (object)new WaitForSecondsRealtime(2f); Log("DEMO: downing"); GiveTestItem(player); ((Character)player).SetHealth(0f); float w = 0f; while (w < 5f && !player.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(3.5f); Log("DEMO: channeling revive"); w = 0f; while (w < 12f && player.IsDowned()) { FindMarkerInteractable(player)?.SimulateHold(); w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(3f); Log("DEMO: downing again, letting window run out"); ((Character)player).SetHealth(0f); w = 0f; while (w < 5f && !player.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } w = 0f; while (w < Plugin.ReviveWindow + 10f && !((Character)player).IsDead()) { w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(5f); Record("demo", pass: true, "sequence complete"); } private IEnumerator DespawnRavens() { while (true) { Raven[] array = Object.FindObjectsOfType<Raven>(); foreach (Raven raven in array) { if ((Object)(object)raven != (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(((Component)raven).gameObject); } } yield return (object)new WaitForSecondsRealtime(0.5f); } } private IEnumerator RunSingleProcess() { yield return ((MonoBehaviour)this).StartCoroutine(AutoStart()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForPlayerInWorld()); Player player = Player.m_localPlayer; if ((Object)(object)player == (Object)null) { Record("local_player", pass: false, "no local player"); yield break; } Log($"E2E: local player ready: {player.GetPlayerName()} hp={((Character)player).GetHealth()}/{((Character)player).GetMaxHealth()}"); yield return (object)new WaitForSecondsRealtime(2f); yield return ((MonoBehaviour)this).StartCoroutine(Test_LethalDamageDowns()); yield return ((MonoBehaviour)this).StartCoroutine(Test_DownedConstraints()); yield return ((MonoBehaviour)this).StartCoroutine(Test_MarkerColorGradient()); yield return ((MonoBehaviour)this).StartCoroutine(Test_HoldProgressAndUI()); yield return ((MonoBehaviour)this).StartCoroutine(Test_ReviveNoNetworkTick()); yield return ((MonoBehaviour)this).StartCoroutine(Test_ReviveRestores()); yield return ((MonoBehaviour)this).StartCoroutine(Test_PressModeRevives()); yield return ((MonoBehaviour)this).StartCoroutine(Test_DisconnectDeath()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForAlivePlayer()); yield return ((MonoBehaviour)this).StartCoroutine(Test_ExpiryKills()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForAlivePlayer()); yield return ((MonoBehaviour)this).StartCoroutine(Test_EmptyInventoryCrumbles()); yield return ((MonoBehaviour)this).StartCoroutine(WaitForAlivePlayer()); yield return ((MonoBehaviour)this).StartCoroutine(Test_GiveUp()); } private IEnumerator Test_GiveUp() { Player player = Player.m_localPlayer; if ((Object)(object)player == (Object)null || ((Character)player).IsDead()) { Record("give_up_kills", pass: false, "no alive player"); yield break; } ((Character)player).SetHealth(((Character)player).GetMaxHealth()); GiveTestItem(player); yield return null; ((Character)player).SetHealth(0f); float w = 0f; while (w < 5f && !player.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } if (!player.IsDowned()) { Record("give_up_kills", pass: false, "could not down"); yield break; } yield return (object)new WaitForSecondsRealtime(0.5f); w = 0f; float maxFrac = 0f; bool sawRed = false; while (w < 6f && !((Character)player).IsDead()) { ((Component)player).GetComponent<GiveUp>()?.SimulateHold(); maxFrac = Mathf.Max(maxFrac, GiveUp.LocalFraction); if (ProgressUI.Visible && ProgressUI.GivingUp) { sawRed = true; } w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(0.5f); bool dead = ((Character)player).IsDead(); bool notDowned = !player.IsDowned(); bool realTombstone = (Object)(object)FindRealGraveNear(((Component)player).transform.position, 999f) != (Object)null; bool fractionReset = GiveUp.LocalFraction <= 0.01f; Record("give_up_kills", dead && notDowned && sawRed && maxFrac > 0.5f && realTombstone && fractionReset, $"dead={dead} notDowned={notDowned} sawRed={sawRed} maxFrac={maxFrac:F2} " + $"realTombstone={realTombstone} fractionReset={fractionReset}"); } private IEnumerator Test_LethalDamageDowns() { Player player = Player.m_localPlayer; ((Character)player).SetHealth(0f); float waited = 0f; while (waited < 5f && !player.IsDowned()) { waited += Time.unscaledDeltaTime; yield return null; } float popVelY = MarkerPrefab.LastPopVelY; bool popped = popVelY > 3f; yield return (object)new WaitForSecondsRealtime(0.5f); bool downed = player.IsDowned(); bool notDead = !((Character)player).IsDead(); bool hasController = (Object)(object)((Component)player).GetComponent<DownedStateMachine>() != (Object)null; GameObject marker = player.FindDownedMarker(); bool markerExists = (Object)(object)marker != (Object)null; bool markerIsOurPrefab = (Object)(object)marker != (Object)null && (Object)(object)marker.GetComponent<ZNetView>() != (Object)null && ((Object)marker).name.StartsWith("RevivalRevived_DownedMarker") && (Object)(object)marker.GetComponent<TombStone>() == (Object)null; bool noRagdolls = Object.FindObjectsOfType<Ragdoll>().Length == 0; bool visualHidden = (Object)(object)((Character)player).m_visual != (Object)null && !((Character)player).m_visual.activeSelf; bool poofPlayed = PlayerDownedExtensions.LastPoofCount > 0; bool poofSmall = PlayerDownedExtensions.LastPoofSourceName.IndexOf("Greyling", StringComparison.OrdinalIgnoreCase) >= 0; Record("lethal_damage_downs", downed && notDead && hasController && markerExists && markerIsOurPrefab && noRagdolls && poofPlayed && poofSmall && popped, $"downed={downed} notDead={notDead} controller={hasController} marker={markerExists} " + $"ourPrefab={markerIsOurPrefab} noRagdolls={noRagdolls} visualHidden={visualHidden} " + $"poof={PlayerDownedExtensions.LastPoofCount} poofSrc={PlayerDownedExtensions.LastPoofSourceName} popVelY={popVelY:F1}"); } private IEnumerator Test_MarkerColorGradient() { Player player = Player.m_localPlayer; GameObject marker = player.FindDownedMarker(); DownedMarker dm = (((Object)(object)marker != (Object)null) ? marker.GetComponent<DownedMarker>() : null); if ((Object)(object)dm == (Object)null) { Record("marker_color_gradient", pass: false, "no DownedMarker"); yield break; } float blend0 = dm.CurrentBlend; DownedStateMachineView pstate = new DownedStateMachineView(((Character)player).m_nview); pstate.DownedTime -= Plugin.ReviveWindow * 0.5f; yield return null; yield return null; float blend1 = dm.CurrentBlend; bool startedGreen = blend0 < 0.25f; bool progressed = blend1 > blend0 + 0.25f; Record("marker_color_gradient", startedGreen && progressed, $"blend0={blend0:F2} blend1={blend1:F2}"); } private IEnumerator Test_DisconnectDeath() { Player player = Player.m_localPlayer; ((Character)player).SetHealth(((Character)player).GetMaxHealth()); GiveTestItem(player); yield return null; ((Character)player).SetHealth(0f); float w = 0f; while (w < 5f && !player.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(0.5f); GameObject m1 = player.FindDownedMarker(); if ((Object)(object)m1 == (Object)null) { Record("disconnect_kills_on_reconnect", pass: false, "no marker after down"); yield break; } DownedStateMachineView s = new DownedStateMachineView(((Character)player).m_nview) { Downed = false, Marker = ZDOID.None }; ((Character)player).SetHealth(((Character)player).GetMaxHealth()); ((Collider)((Character)player).m_collider).enabled = true; ((Character)player).m_body.isKinematic = false; if ((Object)(object)((Character)player).m_visual != (Object)null) { ((Character)player).m_visual.SetActive(true); } yield return null; ((Component)player).GetComponent<ReconnectDeathCheck>().RunReconnectCheck(); w = 0f; while (w < 8f && !((Character)player).IsDead()) { w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(0.5f); bool dead = ((Character)player).IsDead(); bool markerGone = (Object)(object)MarkerPrefab.FindFor(player.GetPlayerID()) == (Object)null; bool realTombstone = false; TombStone[] array = Object.FindObjectsOfType<TombStone>(); foreach (TombStone t in array) { ZNetView nv = ((Component)t).GetComponent<ZNetView>(); if ((Object)(object)nv != (Object)null && nv.IsValid() && !new DownedMarkerView(nv).IsMarker) { realTombstone = true; break; } } bool noRagdolls = Object.FindObjectsOfType<Ragdoll>().Length == 0; Record("disconnect_kills_on_reconnect", dead && markerGone && realTombstone && noRagdolls, $"dead={dead} markerGone={markerGone} realTombstone={realTombstone} noRagdolls={noRagdolls}"); yield return (object)new WaitForSecondsRealtime(0.5f); } private IEnumerator Test_DownedConstraints() { Player player = Player.m_localPlayer; if (!player.IsDowned()) { Record("downed_constraints", pass: false, "precondition: not downed"); yield break; } bool cannotMove = !((Character)player).CanMove(); bool kinematic = (Object)(object)((Character)player).m_body != (Object)null && ((Character)player).m_body.isKinematic; GameObject marker = player.FindDownedMarker(); bool interactableFound = false; bool hoverOk = false; bool green = false; bool stripped = false; bool noEmbers = false; bool nameOk = false; int disabledFx = 0; if ((Object)(object)marker != (Object)null) { TMP_Text worldText = marker.GetComponentInChildren<TMP_Text>(true); nameOk = (Object)(object)worldText != (Object)null && worldText.text == player.GetPlayerName(); Revivable interactable = marker.GetComponentInChildren<Revivable>(); interactableFound = (Object)(object)interactable != (Object)null; DownedMarker dm = marker.GetComponent<DownedMarker>(); green = (Object)(object)dm != (Object)null && dm.IsGreen(); disabledFx = MarkerPrefab.TemplateEffectsRemoved; stripped = (Object)(object)marker.GetComponent<TombStone>() == (Object)null && (Object)(object)marker.GetComponent<Container>() == (Object)null; noEmbers = disabledFx > 0 && marker.GetComponentsInChildren<ParticleSystem>(true).Length == 0; if ((Object)(object)interactable != (Object)null) { string hover = interactable.GetHoverText(); hoverOk = !string.IsNullOrEmpty(hover) && hover.IndexOf("Revive", StringComparison.OrdinalIgnoreCase) >= 0; Log("E2E[downed_constraints]: hover = \"" + hover.Replace("\n", " | ") + "\""); } } Record("downed_constraints", cannotMove && kinematic && interactableFound && hoverOk && green && stripped && noEmbers && nameOk, $"cannotMove={cannotMove} kinematic={kinematic} interactable={interactableFound} hoverOk={hoverOk} " + $"green={green} stripped={stripped} noEmbers={noEmbers} disabledFx={disabledFx} nameOk={nameOk}"); } private IEnumerator Test_HoldProgressAndUI() { Player player = Player.m_localPlayer; if (!player.IsDowned()) { Record("hold_progress_and_ui", pass: false, "precondition: not downed"); yield break; } Revivable interactable = FindMarkerInteractable(player); if ((Object)(object)interactable == (Object)null) { Record("hold_progress_and_ui", pass: false, "no marker interactable"); yield break; } float remainingBefore = player.GetDownedRemainingTime(); float t = 0f; float maxProg = 0f; float maxFill = 0f; float firstProgressAt = -1f; bool uiSeen = false; while (t < 1.5f) { interactable.SimulateHold(); float prog = player.GetReviveProgress(); if (prog > 0.05f && firstProgressAt < 0f) { firstProgressAt = t; } maxProg = Mathf.Max(maxProg, prog); if (ProgressUI.Visible) { uiSeen = true; maxFill = Mathf.Max(maxFill, ProgressUI.Fill); } t += Time.unscaledDeltaTime; yield return null; } bool responsive = firstProgressAt >= 0f && firstProgressAt < 0.6f; float remainingAfter = player.GetDownedRemainingTime(); bool windowPaused = remainingBefore - remainingAfter < 0.6f; bool partial = maxProg > 0.05f && maxProg < 0.9f; bool stillDowned = player.IsDowned(); t = 0f; while (t < 3f) { t += Time.unscaledDeltaTime; yield return null; } bool decayed = player.GetReviveProgress() <= 0.01f; bool uiHidden = !ProgressUI.Visible; Record("hold_progress_and_ui", partial && stillDowned && uiSeen && decayed && uiHidden && windowPaused && responsive, $"maxProg={maxProg:F2} partial={partial} stillDowned={stillDowned} uiSeen={uiSeen} " + $"maxFill={maxFill:F2} decayed={decayed} uiHidden={uiHidden} " + $"windowPaused={windowPaused} remBefore={remainingBefore:F1} remAfter={remainingAfter:F1} " + $"responsive={responsive} firstProgressAt={firstProgressAt:F2}"); } private IEnumerator Test_ReviveNoNetworkTick() { Player player = Player.m_localPlayer; if (!player.IsDowned()) { Record("revive_no_network_tick", pass: false, "precondition: not downed"); yield break; } Revivable interactable = FindMarkerInteractable(player); GameObject marker = player.FindDownedMarker(); ZNetView nview = (((Object)(object)marker != (Object)null) ? marker.GetComponent<ZNetView>() : null); if ((Object)(object)interactable == (Object)null || (Object)(object)nview == (Object)null || !nview.IsValid()) { Record("revive_no_network_tick", pass: false, "no marker/interactable"); yield break; } float t = 0f; while (t < 0.6f) { interactable.SimulateHold(); t += Time.unscaledDeltaTime; yield return null; } ReviveChannelDecayingProgressView anchor0 = new ReviveChannelDecayingProgressView(nview); float anchorTime0 = anchor0.AnchorTime; float anchorSeconds0 = anchor0.AnchorSeconds; float progAtBaseline = player.GetReviveProgress(); int frames = 0; t = 0f; while (t < 1.5f) { interactable.SimulateHold(); frames++; t += Time.unscaledDeltaTime; yield return null; } ReviveChannelDecayingProgressView anchor1 = new ReviveChannelDecayingProgressView(nview); float progAfter = player.GetReviveProgress(); bool progressRose = progAfter > progAtBaseline + 0.1f; bool anchorStable = Mathf.Abs(anchor1.AnchorTime - anchorTime0) < 0.01f && Mathf.Abs(anchor1.AnchorSeconds - anchorSeconds0) < 0.01f; bool stillDowned = player.IsDowned(); Record("revive_no_network_tick", progressRose && anchorStable && stillDowned, $"progressRose={progressRose} ({progAtBaseline:F2}->{progAfter:F2}) " + $"anchorStable={anchorStable} (t {anchorTime0:F2}->{anchor1.AnchorTime:F2}) frames={frames} stillDowned={stillDowned}"); } private IEnumerator Test_PressModeRevives() { Player player = Player.m_localPlayer; Plugin.ReviveModeCfg.Value = ReviveModeType.Press; ((Character)player).SetHealth(((Character)player).GetMaxHealth()); yield return null; ((Character)player).SetHealth(0f); float w = 0f; while (w < 5f && !player.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } if (!player.IsDowned()) { Plugin.ReviveModeCfg.Value = ReviveModeType.Hold; Record("press_mode_revives", pass: false, "could not down"); yield break; } yield return (object)new WaitForSecondsRealtime(0.5f); FindMarkerInteractable(player)?.SimulateHold(); w = 0f; while (w < 3f && player.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(0.5f); bool revived = !player.IsDowned() && !((Character)player).IsDead() && ((Character)player).GetHealth() > 0f; bool markerGone = (Object)(object)MarkerPrefab.FindFor(player.GetPlayerID()) == (Object)null; Plugin.ReviveModeCfg.Value = ReviveModeType.Hold; Record("press_mode_revives", revived && markerGone, $"revived={revived} hp={((Character)player).GetHealth():F0} markerGone={markerGone}"); } private IEnumerator Test_ReviveRestores() { Player player = Player.m_localPlayer; if (!player.IsDowned()) { Record("revive_restores", pass: false, "precondition: not downed"); yield break; } GameObject markerBefore = player.FindDownedMarker(); int crumblesBefore = DownedMarker.CrumbleEvents; player.ReviveFromDowned(0L); yield return (object)new WaitForSecondsRealtime(0.5f); bool canMove = false; float waitMove = 0f; while (waitMove < 3f) { canMove = ((Character)player).CanMove(); if (canMove) { break; } waitMove += Time.unscaledDeltaTime; yield return null; } bool notDowned = !player.IsDowned(); bool healthy = ((Character)player).GetHealth() > 0f; bool visualBack = (Object)(object)((Character)player).m_visual != (Object)null && ((Character)player).m_visual.activeSelf; bool collider = (Object)(object)((Character)player).m_collider != (Object)null && ((Collider)((Character)player).m_collider).enabled; bool notKinematic = (Object)(object)((Character)player).m_body != (Object)null && !((Character)player).m_body.isKinematic; yield return (object)new WaitForSecondsRealtime(0.5f); bool markerGone = (Object)(object)markerBefore == (Object)null || !Object.op_Implicit((Object)(object)markerBefore); bool crumbled = DownedMarker.CrumbleEvents > crumblesBefore; Record("revive_restores", notDowned && healthy && canMove && visualBack && collider && notKinematic && markerGone && crumbled, $"notDowned={notDowned} hp={((Character)player).GetHealth():F0} canMove={canMove} " + $"visualBack={visualBack} collider={collider} notKinematic={notKinematic} markerGone={markerGone} crumbled={crumbled}"); } private IEnumerator Test_ExpiryKills() { Player player = Player.m_localPlayer; if (((Character)player).IsDead()) { Record("expiry_kills_real_tombstone", pass: false, "already dead"); yield break; } ((Character)player).SetHealth(((Character)player).GetMaxHealth()); GiveTestItem(player); yield return null; ((Character)player).SetHealth(0f); float waited = 0f; while (waited < 5f && !player.IsDowned()) { waited += Time.unscaledDeltaTime; yield return null; } if (!player.IsDowned()) { Record("expiry_kills_real_tombstone", pass: false, "could not re-down"); yield break; } yield return (object)new WaitForSecondsRealtime(0.5f); GameObject markerBefore = player.FindDownedMarker(); Vector3 markerPos = (((Object)(object)markerBefore != (Object)null) ? markerBefore.transform.position : Vector3.zero); DownedStateMachineView s = new DownedStateMachineView(((Character)player).m_nview) { DownedTime = (float)ZNet.instance.GetTimeSeconds() - Plugin.ReviveWindow - 5f }; waited = 0f; while (waited < 10f && !((Character)player).IsDead()) { ((Character)player).SetHealth(0f); waited += Time.unscaledDeltaTime; yield return null; } bool dead = ((Character)player).IsDead(); bool notDowned = !player.IsDowned(); bool corpseInert = dead && (Object)(object)((Character)player).m_collider != (Object)null && !((Collider)((Character)player).m_collider).enabled; bool gapless = true; bool markerGone = (Object)(object)markerBefore == (Object)null || !Object.op_Implicit((Object)(object)markerBefore); float hw = 0f; while (hw < 9f && !markerGone) { bool visible = false; Renderer[] componentsInChildren = markerBefore.GetComponentsInChildren<Renderer>(); foreach (Renderer r in componentsInChildren) { if (r.enabled) { visible = true; break; } } if (!visible && (Object)(object)FindRealGraveNear(markerPos, 3f) == (Object)null) { gapless = false; } hw += Time.unscaledDeltaTime; yield return null; markerGone = (Object)(object)markerBefore == (Object)null || !Object.op_Implicit((Object)(object)markerBefore); } bool realTombstone = false; bool inPlace = false; bool noPop = false; bool embersOnGrave = false; TombStone grave = FindRealGraveNear(markerPos, 999f); if ((Object)(object)grave != (Object)null) { realTombstone = true; inPlace = markerPos != Vector3.zero && Vector3.Distance(((Component)grave).transform.position, markerPos) < 2f; Rigidbody rb = ((Component)grave).GetComponent<Rigidbody>(); noPop = (Object)(object)rb == (Object)null || rb.linearVelocity.y < 1f; embersOnGrave = ((Component)grave).GetComponentsInChildren<ParticleSystem>(false).Length != 0; } bool noRagdolls = Object.FindObjectsOfType<Ragdoll>().Length == 0; Record("expiry_kills_real_tombstone", dead && notDowned && corpseInert && markerGone && gapless && realTombstone && inPlace && noPop && embersOnGrave && noRagdolls, $"dead={dead} notDowned={notDowned} corpseInert={corpseInert} markerGone={markerGone} gapless={gapless} " + $"realTombstone={realTombstone} inPlace={inPlace} noPop={noPop} embersOnGrave={embersOnGrave} noRagdolls={noRagdolls}"); } private static TombStone? FindRealGraveNear(Vector3 pos, float maxDist) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) TombStone result = null; float num = maxDist; TombStone[] array = Object.FindObjectsOfType<TombStone>(); foreach (TombStone val in array) { ZNetView component = ((Component)val).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid() && (!((Object)(object)component != (Object)null) || !component.IsValid() || !new DownedMarkerView(component).IsMarker)) { float num2 = Vector3.Distance(((Component)val).transform.position, pos); if (num2 <= num) { num = num2; result = val; } } } return result; } private IEnumerator Test_EmptyInventoryCrumbles() { Player player = Player.m_localPlayer; if ((Object)(object)player == (Object)null || ((Character)player).IsDead()) { Record("empty_death_crumbles_marker", pass: false, "no alive player"); yield break; } ((Character)player).SetHealth(((Character)player).GetMaxHealth()); ((Humanoid)player).GetInventory().RemoveAll(); yield return null; ((Character)player).SetHealth(0f); float w = 0f; while (w < 5f && !player.IsDowned()) { w += Time.unscaledDeltaTime; yield return null; } if (!player.IsDowned()) { Record("empty_death_crumbles_marker", pass: false, "could not down"); yield break; } yield return (object)new WaitForSecondsRealtime(0.5f); GameObject marker = player.FindDownedMarker(); if ((Object)(object)marker == (Object)null) { Record("empty_death_crumbles_marker", pass: false, "no marker"); yield break; } _ = marker.transform.position; int gravesBefore = CountRealGraves(); DownedStateMachineView s = new DownedStateMachineView(((Character)player).m_nview) { DownedTime = (float)ZNet.instance.GetTimeSeconds() - Plugin.ReviveWindow - 5f }; w = 0f; while (w < 10f && !((Character)player).IsDead()) { ((Character)player).SetHealth(0f); w += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(1f); bool dead = ((Character)player).IsDead(); bool markerGone = (Object)(object)marker == (Object)null || !Object.op_Implicit((Object)(object)marker); bool crumbled = DownedMarker.LastCrumbleEffectCount > 0; bool noNewGrave = CountRealGraves() == gravesBefore; bool pendingCleared = !new GraveReplaceView(((Character)player).m_nview).Pending; Record("empty_death_crumbles_marker", dead && markerGone && crumbled && noNewGrave && pendingCleared, $"dead={dead} markerGone={markerGone} crumbleEffects={DownedMarker.LastCrumbleEffectCount} " + $"noNewGrave={noNewGrave} (before={gravesBefore}) pendingCleared={pendingCleared}"); } private static int CountRealGraves() { int num = 0; TombStone[] array = Object.FindObjectsOfType<TombStone>(); foreach (TombStone val in array) { ZNetView component = ((Component)val).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid() && (!((Object)(object)component != (Object)null) || !component.IsValid() || !new DownedMarkerView(component).IsMarker)) { num++; } } return num; } private static void GiveTestItem(Player player) { try { if (!((Object)(object)ObjectDB.instance == (Object)null)) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Wood"); if (!((Object)(object)itemPrefab == (Object)null)) { ((Humanoid)player).GetInventory().AddItem(itemPrefab, 1); } } } catch (Exception ex) { Log("E2E: GiveTestItem failed: " + ex.Message); } } private IEnumerator AutoStart() { Log("E2E: waiting for FejdStartup (main menu)..."); float waited = 0f; while ((Object)(object)FejdStartup.instance == (Object)null && (Object)(object)Game.instance == (Object)null) { waited += Time.unscaledDeltaTime; if (waited > 120f) { Log("E2E: FejdStartup never appeared"); yield break; } yield return null; } if ((Object)(object)Game.instance != (Object)null) { Log("E2E: Game already running"); yield break; } yield return (object)new WaitForSecondsRealtime(3f); if (_worldStartIssued) { yield break; } _worldStartIssued = true; Exception err = null; try { FejdStartup fejd = FejdStartup.instance; if (E2EConfig.IsHost) { PlayerProfile profile = GetOrCreateProfile(Environment.GetEnvironmentVariable("RR_E2E_PROFILE") ?? "e2e_host", Environment.GetEnvironmentVariable("RR_E2E_CHARNAME") ?? "E2EHost"); Game.SetProfile(profile.GetFilename(), profile.m_fileSource); World world = World.GetCreateWorld(E2EConfig.WorldName, (FileSource)2); ZNet.m_onlineBackend = (OnlineBackendType)3; ZNet.SetServer(true, true, false, world.m_name, "", world); ZNet.ResetServerHost(); Log($"E2E[host]: world '{world.m_name}', CustomSocket port {E2EConfig.Port}, loading scene"); } else if (E2EConfig.IsClient) { PlayerProfile profile2 = GetOrCreateProfile(Environment.GetEnvironmentVariable("RR_E2E_PROFILE") ?? "e2e_client", Environment.GetEnvironmentVariable("RR_E2E_CHARNAME") ?? "E2EClient"); Game.SetProfile(profile2.GetFilename(), profile2.m_fileSource); ZNet.m_onlineBackend = (OnlineBackendType)3; ZNet.SetServer(false, false, false, "", "", (World)null); ZNet.m_serverHost = E2EConfig.ServerHost; ZNet.m_serverHostPort = E2EConfig.Port; Log($"E2E[client]: connecting to {E2EConfig.ServerHost}:{E2EConfig.Port}, loading scene"); } else { PlayerProfile profile3 = GetOrCreateProfile("e2e_solo", "E2ESolo"); Game.SetProfile(profile3.GetFilename(), profile3.m_fileSource); string soloWorldName = Environment.GetEnvironmentVariable("RR_E2E_WORLD") ?? "e2e_world"; World world2 = World.GetCreateWorld(soloWorldName, (FileSource)2); ZNet.m_onlineBackend = (OnlineBackendType)0; ZNet.SetServer(true, false, false, world2.m_name, "", world2); ZNet.ResetServerHost(); Log("E2E[solo]: world '" + world2.m_name + "', loading scene"); } Traverse.Create((object)fejd).Field("m_startingWorld").SetValue((object)true); Traverse.Create((object)fejd).Method("LoadMainScene", Array.Empty<object>()).GetValue(); } catch (Exception ex) { Exception e = ex; err = e; } if (err != null) { Log("E2E: AutoStart error: " + err); } } private static PlayerProfile GetOrCreateProfile(string filename, string charName) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown foreach (PlayerProfile allPlayerProfile in SaveSystem.GetAllPlayerProfiles()) { if (allPlayerProfile.GetFilename() == filename) { allPlayerProfile.m_firstSpawn = false; allPlayerProfile.Save(); return allPlayerProfile; } } PlayerProfile val = new PlayerProfile(filename, (FileSource)2); val.SetName(charName); val.m_firstSpawn = false; val.Save(); return val; } private IEnumerator WaitForAlivePlayer() { float w = 0f; while (w < 30f) { Player p = Player.m_localPlayer; if ((Object)(object)p != (Object)null && (Object)(object)((Character)p).m_nview != (Object)null && ((Character)p).m_nview.IsValid() && !((Character)p).IsDead() && ((Character)p).GetHealth() > 0f) { yield return (object)new WaitForSecondsRealtime(0.5f); yield break; } w += Time.unscaledDeltaTime; yield return null; } Log("E2E: WaitForAlivePlayer timed out"); } private IEnumerator WaitForPlayerInWorld() { Log("E2E: waiting for player to spawn in world..."); float waited = 0f; while (true) { Player p = Player.m_localPlayer; if ((Object)(object)p != (Object)null && (Object)(object)((Component)p).GetComponent<ZNetView>() != (Object)null && ((Component)p).GetComponent<ZNetView>().IsValid() && (Object)(object)ZNetScene.instance != (Object)null) { yield return null; yield break; } waited += Time.unscaledDeltaTime; if (waited > 240f) { break; } yield return null; } Log("E2E: timed out waiting for player spawn"); } private static Revivable? FindInteractableViaHoverRay(Player downed, Player me) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_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_003c: 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) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) GameObject val = downed.FindDownedMarker(); if ((Object)(object)val == (Object)null) { return null; } Vector3 val2 = val.transform.position + Vector3.up * 0.3f; Vector3 val3 = val2 + new Vector3(1.8f, 1.2f, 0f); Vector3 val4 = val2 - val3; RaycastHit[] array = Physics.RaycastAll(val3, ((Vector3)(ref val4)).normalized, 6f, me.m_interactMask); if (array.Length == 0) { return null; } Array.Sort(array, (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance)); return ((Component)((RaycastHit)(ref array[0])).collider).GetComponentInParent<Revivable>(); } private static Revivable? FindMarkerInteractable(Player downed) { GameObject val = downed.FindDownedMarker(); return ((Object)(object)val != (Object)null) ? val.GetComponentInChildren<Revivable>() : null; } private static Player? FindDownedRemotePlayer(Player me) { foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer == (Object)null || (Object)(object)allPlayer == (Object)(object)me || !allPlayer.IsDowned()) { continue; } return allPlayer; } return null; } private void Record(string name, bool pass, string detail) { _results.Add((name, pass, detail)); Log("E2E_RESULT: " + (pass ? "PASS" : "FAIL") + " " + name + " -- " + detail); } private static void Log(string msg) { E2ELog.Write(msg); } private void Finish(bool ok, string reason) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("overall=" + (ok ? "PASS" : "FAIL") + " role=" + E2EConfig.Role + " reason=" + reason); foreach (var result in _results) { stringBuilder.AppendLine((result.pass ? "PASS" : "FAIL") + "\t" + result.name + "\t" + result.detail); } string text = stringBuilder.ToString(); try { File.WriteAllText(ResultPath, text); Log("E2E: results written to " + ResultPath); } catch (Exception ex) { Log("E2E: failed to write results: " + ex); } Log("E2E_SUMMARY_BEGIN\n" + text + "E2E_SUMMARY_END"); Log("E2E: DONE overall=" + (ok ? "PASS" : "FAIL") + " -- quitting"); ((MonoBehaviour)this).StartCoroutine(QuitSoon()); } private IEnumerator QuitSoon() { yield return (object)new WaitForSecondsRealtime(1f); Application.Quit(); yield return (object)new WaitForSecondsRealtime(2f); Process.GetCurrentProcess().Kill(); } } [HarmonyPatch(typeof(SteamUtils), "IsSteamRunningOnSteamDeck")] internal static class SteamStartupPatch { private static bool Prefix(ref bool __result) { __result = false; return false; } } } namespace ReviveAllies.Components { public static class DownedKeys { public const string RpcChannel = "RevivalRevived_Channel"; } public class DownedMarker : MonoBehaviour { private struct TintedMaterial { public Material Mat; public bool HasEmission; public Color OrigEmission; public float EmissionIntensity; public bool HasColor; public Color OrigColor; } public static readonly Color ReviveGreen = new Color(0.25f, 1f, 0.35f); private static EffectList? s_crumbleEffect; private static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor"); private static readonly int ColorId = Shader.PropertyToID("_Color"); private readonly List<TintedMaterial> m_tinted = new List<TintedMaterial>(); private readonly List<(Light light, Color orig)> m_lights = new List<(Light, Color)>(); private ZNetView? m_nview; private float m_replacedSince = -1f; private bool m_hiddenForReplace; private const float ReplaceHideTimeout = 4f; private const float ReplaceDestroyDelay = 5f; private const float ReplaceDestroyFailsafe = 10f; public static int LastCrumbleEffectCount { get; private set; } public static int CrumbleEvents { get; private set; } public int TintedLights => m_lights.Count; public int TintedMaterials => m_tinted.Count; public float CurrentBlend { get; private set; } public bool HiddenForReplace => m_hiddenForReplace; public static void MarkReplaced(GameObject? marker) { ZNetView val = Owned(marker); if (!((Object)(object)val == (Object)null)) { DownedMarkerView downedMarkerView = new DownedMarkerView(val); downedMarkerView.ReplacedByGrave = true; } } public static void Crumble(GameObject? marker) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) //IL_0043: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)marker == (Object)null)) { EffectList val = GraveCrumbleEffect(); if (val != null) { GameObject[] array = val.Create(marker.transform.position, marker.transform.rotation, (Transform)null, 1f, -1, default(ZDOID)); LastCrumbleEffectCount = ((array != null) ? array.Length : 0); CrumbleEvents++; } DestroyMarker(marker); } } public static void CrumbleLinked(ZNetView playerNview) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_0011: 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_003b: Unknown result type (might be due to invalid IL or missing references) ZDOID marker = new DownedStateMachineView(playerNview).Marker; if (!(marker == ZDOID.None)) { DownedStateMachineView downedStateMachineView = new DownedStateMachineView(playerNview); downedStateMachineView.Marker = ZDOID.None; Crumble(ZNetScene.instance.FindInstance(marker)); } } public static void DestroyMarker(GameObject? marker) { ZNetView val = Owned(marker); if (val != null) { val.Destroy(); } } private static ZNetView? Owned(GameObject? marker) { ZNetView val = (((Object)(object)marker != (Object)null) ? marker.GetComponent<ZNetView>() : null); if ((Object)(object)val == (Object)null || !val.IsValid()) { return null; } if (!val.IsOwner()) { val.ClaimOwnership(); } return val; } private static EffectList? GraveCrumbleEffect() { if (s_crumbleEffect != null) { return s_crumbleEffect; } GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? MarkerPrefab.FindTombstonePrefab(ZNetScene.instance) : null); TombStone val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<TombStone>() : null); if ((Object)(object)val2 != (Object)null && val2.m_removeEffect != null && val2.m_removeEffect.m_effectPrefabs.Length != 0) { s_crumbleEffect = val2.m_removeEffect; } else { Plugin.Logger.LogWarning((object)"DownedMarker: no grave crumble effect found"); } return s_crumbleEffect; } private void Awake() { m_nview = ((Component)this).GetComponent<ZNetView>(); CaptureAccents(); ApplyBlend(0f); } private void Start() { if ((Object)(object)m_nview == (Object)null || !m_nview.IsValid()) { return; } string ownerName = new DownedMarkerView(m_nview).OwnerName; if (!string.IsNullOrEmpty(ownerName)) { TMP_Text componentInChildren = ((Component)this).GetComponentInChildren<TMP_Text>(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = ownerName; } } } private void Update() { //IL_0051: 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_0057: 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_006c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)m_nview == (Object)null) && m_nview.IsValid()) { DownedMarkerView downedMarkerView = new DownedMarkerView(m_nview); if (downedMarkerView.ReplacedByGrave) { UpdateReplaced(); return; } ZDOID linkedPlayer = downedMarkerView.LinkedPlayer; ZDO val = ((linkedPlayer != ZDOID.None) ? ZDOMan.instance.GetZDO(linkedPlayer) : null); float num = ((val != null) ? new DownedStateMachineView(val).DownedTime : downedMarkerView.DownedTime); float num2 = (float)ZNet.instance.GetTimeSeconds() - num; ApplyBlend(Mathf.Clamp01(num2 / Plugin.ReviveWindow)); } } private void CaptureAccents() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: 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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) Light[] componentsInChildren = ((Component)this).GetComponentsInChildren<Light>(true); foreach (Light val in componentsInChildren) { m_lights.Add((val, val.color)); } Renderer[] componentsInChildren2 = ((Component)this).GetComponentsInChildren<Renderer>(true); foreach (Renderer val2 in componentsInChildren2) { Material[] materials = val2.materials; foreach (Material val3 in materials) { TintedMaterial item = new TintedMaterial { Mat = val3 }; if (val3.HasProperty(EmissionColorId)) { Color color = val3.GetColor(EmissionColorId); if (((Color)(ref color)).maxColorComponent > 0.05f) { item.HasEmission = true; item.OrigEmission = color; item.EmissionIntensity = ((Color)(ref color)).maxColorComponent; } } if (val3.HasProperty(ColorId)) { Color color2 = val3.GetColor(ColorId); if (color2.r > 0.4f && color2.r > color2.g * 1.5f && color2.r > color2.b * 1.5f) { item.HasColor = true; item.OrigColor = color2; } } if (item.HasEmission || item.HasColor) { m_tinted.Add(item); } } } } private void ApplyBlend(float blend) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) //IL_005e: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_009c: 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) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) CurrentBlend = blend; foreach (TintedMaterial item in m_tinted) { if (!((Object)(object)item.Mat == (Object)null)) { if (item.HasEmission) { Color val = ReviveGreen * item.EmissionIntensity; item.Mat.SetColor(EmissionColorId, Color.Lerp(val, item.OrigEmission, blend)); item.Mat.EnableKeyword("_EMISSION"); } if (item.HasColor) { item.Mat.SetColor(ColorId, Color.Lerp(ReviveGreen, item.OrigColor, blend)); } } } foreach (var (val2, val3) in m_lights) { if ((Object)(object)val2 != (Object)null) { val2.color = Color.Lerp(ReviveGreen, val3, blend); } } } public bool IsGreen() { return (TintedLights > 0 || TintedMaterials > 0) && CurrentBlend < 0.5f; } private void UpdateReplaced() { if (m_replacedSince < 0f) { m_replacedSince = Time.time; } float num = Time.time - m_replacedSince; if (!m_hiddenForReplace && (num > 4f || GraveNearby())) { HideLocally(); } if (m_nview.IsOwner()) { if (num > 5f) { m_nview.Destroy(); } } else if (num > 10f) { m_nview.ClaimOwnership(); m_nview.Destroy(); } } private bool GraveNearby() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might b