using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DHHFLastChanceMode.Modules.Config; using DHHFLastChanceMode.Modules.Gameplay.Core.Abilities; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Diagnostics; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Guards; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters.Adapters; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters.Interactions; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters.Pipeline; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters.Support; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Runtime; using DHHFLastChanceMode.Modules.Gameplay.LastChance.Spectate; using DHHFLastChanceMode.Modules.Gameplay.LastChance.UI; using DHHFLastChanceMode.Modules.Utilities; using DeathHeadHopper.Abilities; using DeathHeadHopper.DeathHead; using DeathHeadHopper.DeathHead.Handlers; using DeathHeadHopper.Helpers; using DeathHeadHopper.Managers; using DeathHeadHopper.UI; using DeathHeadHopperFix.Modules.Config; using DeathHeadHopperFix.Modules.Gameplay.Core.Abilities; using DeathHeadHopperFix.Modules.Gameplay.Spectate; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; using UnityEngine.AI; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DeathHeadHopperFix.Modules.Gameplay.LastChance.Runtime { internal static class LastChanceTimerController { internal static bool IsActive => DHHFLastChanceMode.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController.IsActive; internal static bool IsDirectionIndicatorUiVisible => DHHFLastChanceMode.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController.IsDirectionIndicatorUiVisible; internal static float GetDirectionIndicatorPenaltySecondsPreview() { return DHHFLastChanceMode.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController.GetDirectionIndicatorPenaltySecondsPreview(); } internal static bool IsDirectionIndicatorEnergySufficientPreview() { return DHHFLastChanceMode.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController.IsDirectionIndicatorEnergySufficientPreview(); } internal static void GetDirectionIndicatorEnergyDebugSnapshot(out bool visible, out float timerRemaining, out float penaltyPreview, out bool hasEnoughEnergy) { DHHFLastChanceMode.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController.GetDirectionIndicatorEnergyDebugSnapshot(out visible, out timerRemaining, out penaltyPreview, out hasEnoughEnergy); } internal static bool IsPlayerSurrenderedForData(PlayerAvatar? player) { return DHHFLastChanceMode.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController.IsPlayerSurrenderedForData(player); } } } namespace DHHFLastChanceMode { [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("AdrenSnyder.DHHFLastChanceMode", "DHHF LastChance Mode", "0.1.4")] public sealed class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class d__10 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__10(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if (!<>4__this._runtimeInitialized) { if (<>4__this.IsCoreLoaded()) { <>4__this.InitializeRuntime(); return false; } <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string CorePluginGuid = "AdrenSnyder.DeathHeadHopperFix"; internal const string PluginGuid = "AdrenSnyder.DHHFLastChanceMode"; internal const string PluginName = "DHHF LastChance Mode"; internal const string PluginVersion = "0.1.4"; private Harmony? _harmony; private bool _runtimeInitialized; private Coroutine? _deferredBootstrapRoutine; private static ManualLogSource? s_log; private void Awake() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown s_log = ((BaseUnityPlugin)this).Logger; _harmony = new Harmony("AdrenSnyder.DHHFLastChanceMode"); _deferredBootstrapRoutine = ((MonoBehaviour)this).StartCoroutine(DeferredBootstrap()); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; ConfigManager.HostControlledChanged -= OnHostControlledChanged; if (_deferredBootstrapRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_deferredBootstrapRoutine); _deferredBootstrapRoutine = null; } } [IteratorStateMachine(typeof(d__10))] private IEnumerator DeferredBootstrap() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__10(0) { <>4__this = this }; } private bool IsCoreLoaded() { if (Chainloader.PluginInfos.ContainsKey("AdrenSnyder.DeathHeadHopperFix")) { return true; } return false; } private void InitializeRuntime() { if (!_runtimeInitialized) { _runtimeInitialized = true; _deferredBootstrapRoutine = null; ConfigManager.Initialize(((BaseUnityPlugin)this).Config); ManualLogSource? obj = s_log; if (obj != null) { obj.LogInfo((object)"[LastChance] DHHF LastChance Mode loaded."); } AllPlayersDeadGuard.EnsureEnabled(); if (FeatureFlags.LastChangeMode) { LastChanceTimerController.PrewarmGlobalAssetsAtBoot(); } Harmony harmony = _harmony; if (harmony != null) { LastChanceHarmonyPatchRegistry.ApplyAll(harmony, s_log); SceneManager.sceneLoaded += OnSceneLoaded; ConfigManager.HostControlledChanged += OnHostControlledChanged; ReconcileConditionalMonsterPatches(); } } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { CompatibilityGate.EnsureCreated(); LastChanceRuntimeObjectRegistry.ResetForSceneChange(); bool flag = ShouldHandleRuntimeScene(); LastChanceTimerController.OnLevelLoaded(flag); if (flag) { CompatibilityGate.NotifyRuntimeSceneLoaded(); ConfigSyncManager.EnsureCreated(); ConfigSyncManager.RequestHostSnapshotBroadcast(); ReconcileConditionalMonsterPatches(); } } private void OnHostControlledChanged() { LastChanceTimerController.OnHostControlledConfigChanged(); if (FeatureFlags.LastChangeMode) { LastChanceTimerController.PrewarmGlobalAssetsAtBoot(); } ReconcileConditionalMonsterPatches(); } private static bool ShouldHandleRuntimeScene() { if ((Object)(object)RunManager.instance == (Object)null) { return false; } if (SemiFunc.RunIsLobbyMenu() || SemiFunc.RunIsLobby() || SemiFunc.RunIsShop() || SemiFunc.RunIsArena() || SemiFunc.RunIsTutorial() || SemiFunc.MenuLevel()) { return false; } return true; } private void ReconcileConditionalMonsterPatches() { Harmony harmony = _harmony; if (harmony != null) { bool enable = FeatureFlags.LastChangeMode && FeatureFlags.LastChanceMonstersSearchEnabled; LastChanceMonstersPatchLifecycle.ReconcilePipeline(enable, harmony); } } } } namespace DHHFLastChanceMode.Modules.Utilities { internal static class AudioAssetLoader { internal static string GetDefaultAssetsDirectory() { return BundleAssetLoader.GetPluginDirectory(); } internal static bool TryLoadAudioClip(string fileName, string? baseDirectory, out AudioClip? clip, out string resolvedPath) { clip = null; resolvedPath = string.Empty; if (string.IsNullOrWhiteSpace(fileName)) { return false; } if (BundleAssetLoader.TryLoadAudioClip(fileName, out AudioClip clip2, out string resolvedPath2) && (Object)(object)clip2 != (Object)null) { clip = clip2; resolvedPath = resolvedPath2; return true; } return false; } } internal static class BundleAssetLoader { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.BundleAssetLoader"); private static AssetBundle? s_bundle; private static Sprite[]? s_allSprites; private static AudioClip[]? s_allAudioClips; private static float s_lastLoadAttemptAt = -999f; private const float RetrySeconds = 3f; internal static string GetPluginDirectory() { try { string location = typeof(Plugin).Assembly.Location; if (!string.IsNullOrWhiteSpace(location)) { string directoryName = Path.GetDirectoryName(location); if (!string.IsNullOrWhiteSpace(directoryName)) { return directoryName; } } } catch { } return Paths.PluginPath; } private static void EnsureBundleLoaded() { if ((Object)(object)s_bundle != (Object)null) { return; } float unscaledTime = Time.unscaledTime; if (s_lastLoadAttemptAt > 0f && unscaledTime - s_lastLoadAttemptAt < 3f) { return; } s_lastLoadAttemptAt = unscaledTime; List list = BuildBundleCandidates(); for (int i = 0; i < list.Count; i++) { string text = list[i]; if (!File.Exists(text)) { continue; } try { s_bundle = AssetBundle.LoadFromFile(text); if ((Object)(object)s_bundle != (Object)null) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.Loaded", 120)) { Log.LogDebug((object)("[LastChance] Loaded asset bundle from: " + text)); } break; } } catch { } } } private static List BuildBundleCandidates() { List list = new List(); string pluginDirectory = GetPluginDirectory(); if (!string.IsNullOrWhiteSpace(pluginDirectory)) { list.Add(pluginDirectory); } if (!string.IsNullOrWhiteSpace(Paths.PluginPath)) { list.Add(Paths.PluginPath); list.Add(Path.Combine(Paths.PluginPath, "AdrenSnyder-DHHFLastChanceMode")); list.Add(Path.Combine(Paths.PluginPath, "DHHFLastChanceMode")); } string[] array = new string[4] { "DHHFLastChanceMode", "dhhflastchancemode", "DHHFLastChanceMode.bundle", "dhhflastchancemode.bundle" }; List list2 = new List(); for (int i = 0; i < list.Count; i++) { string text = list[i]; if (!string.IsNullOrWhiteSpace(text)) { for (int j = 0; j < array.Length; j++) { list2.Add(Path.Combine(text, array[j])); list2.Add(Path.Combine(text, "Assets", array[j])); } } } return list2.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); } internal static bool TryLoadSprite(string fileName, out Sprite? sprite, out string resolvedPath) { //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) sprite = null; resolvedPath = string.Empty; if (string.IsNullOrWhiteSpace(fileName)) { return false; } EnsureBundleLoaded(); if ((Object)(object)s_bundle == (Object)null) { return false; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); string[] array = new string[4] { fileNameWithoutExtension, fileName, "Assets/" + fileName, "assets/" + fileName }; foreach (string text in array) { try { sprite = s_bundle.LoadAsset(text); if ((Object)(object)sprite != (Object)null) { resolvedPath = text; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.SpriteHit." + fileName, 60)) { Log.LogDebug((object)("[LastChance] Sprite '" + fileName + "' resolved as Sprite: " + resolvedPath)); } return true; } Texture2D val = s_bundle.LoadAsset(text); if ((Object)(object)val != (Object)null) { sprite = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)sprite).name = Path.GetFileNameWithoutExtension(fileName); resolvedPath = text; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.SpriteHitTex." + fileName, 60)) { Log.LogDebug((object)("[LastChance] Sprite '" + fileName + "' resolved from Texture2D: " + resolvedPath)); } return true; } } catch { } } try { string[] allAssetNames = s_bundle.GetAllAssetNames(); foreach (string text2 in allAssetNames) { if (!text2.EndsWith("/" + fileName, StringComparison.OrdinalIgnoreCase) && !text2.EndsWith("/" + fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) && !string.Equals(text2, fileName, StringComparison.OrdinalIgnoreCase) && !string.Equals(text2, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { continue; } sprite = s_bundle.LoadAsset(text2); if ((Object)(object)sprite != (Object)null) { resolvedPath = text2; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.SpriteHitAsset." + fileName, 60)) { Log.LogDebug((object)("[LastChance] Sprite '" + fileName + "' resolved via asset name: " + resolvedPath)); } return true; } Texture2D val2 = s_bundle.LoadAsset(text2); if ((Object)(object)val2 != (Object)null) { sprite = Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f), 100f); ((Object)sprite).name = Path.GetFileNameWithoutExtension(fileName); resolvedPath = text2; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.SpriteHitAssetTex." + fileName, 60)) { Log.LogDebug((object)("[LastChance] Sprite '" + fileName + "' resolved via asset Texture2D: " + resolvedPath)); } return true; } } } catch { } string fileNameWithoutExtension2 = Path.GetFileNameWithoutExtension(fileName); Sprite[] allSprites = GetAllSprites(); foreach (Sprite val3 in allSprites) { if (!((Object)(object)val3 == (Object)null) && (string.Equals(((Object)val3).name, fileNameWithoutExtension2, StringComparison.OrdinalIgnoreCase) || string.Equals(((Object)val3).name, fileName, StringComparison.OrdinalIgnoreCase))) { sprite = val3; resolvedPath = ((Object)val3).name; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.SpriteHitByName." + fileName, 60)) { Log.LogDebug((object)("[LastChance] Sprite '" + fileName + "' resolved by object name: " + resolvedPath)); } return true; } } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.SpriteMiss." + fileName, 60)) { Log.LogWarning((object)("[LastChance] Sprite '" + fileName + "' not found in bundle.")); } return false; } internal static bool TryLoadAudioClip(string fileName, out AudioClip? clip, out string resolvedPath) { clip = null; resolvedPath = string.Empty; if (string.IsNullOrWhiteSpace(fileName)) { return false; } EnsureBundleLoaded(); if ((Object)(object)s_bundle == (Object)null) { return false; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); string[] array = new string[4] { fileNameWithoutExtension, fileName, "Assets/" + fileName, "assets/" + fileName }; foreach (string text in array) { try { clip = s_bundle.LoadAsset(text); if ((Object)(object)clip != (Object)null) { resolvedPath = text; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.AudioHit." + fileName, 60)) { Log.LogDebug((object)("[LastChance] Audio '" + fileName + "' resolved as AudioClip: " + resolvedPath)); } return true; } } catch { } } try { string[] allAssetNames = s_bundle.GetAllAssetNames(); foreach (string text2 in allAssetNames) { if (!text2.EndsWith("/" + fileName, StringComparison.OrdinalIgnoreCase) && !text2.EndsWith("/" + fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) && !string.Equals(text2, fileName, StringComparison.OrdinalIgnoreCase) && !string.Equals(text2, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { continue; } clip = s_bundle.LoadAsset(text2); if ((Object)(object)clip != (Object)null) { resolvedPath = text2; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.AudioHitAsset." + fileName, 60)) { Log.LogDebug((object)("[LastChance] Audio '" + fileName + "' resolved via asset name: " + resolvedPath)); } return true; } } } catch { } string fileNameWithoutExtension2 = Path.GetFileNameWithoutExtension(fileName); AudioClip[] allAudioClips = GetAllAudioClips(); foreach (AudioClip val in allAudioClips) { if (!((Object)(object)val == (Object)null) && (string.Equals(((Object)val).name, fileNameWithoutExtension2, StringComparison.OrdinalIgnoreCase) || string.Equals(((Object)val).name, fileName, StringComparison.OrdinalIgnoreCase))) { clip = val; resolvedPath = ((Object)val).name; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.AudioHitByName." + fileName, 60)) { Log.LogDebug((object)("[LastChance] Audio '" + fileName + "' resolved by object name: " + resolvedPath)); } return true; } } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Bundle.AudioMiss." + fileName, 60)) { Log.LogWarning((object)("[LastChance] Audio '" + fileName + "' not found in bundle.")); } return false; } private static Sprite[] GetAllSprites() { if (s_allSprites != null) { return s_allSprites; } if ((Object)(object)s_bundle == (Object)null) { s_allSprites = Array.Empty(); return s_allSprites; } try { s_allSprites = s_bundle.LoadAllAssets() ?? Array.Empty(); } catch { s_allSprites = Array.Empty(); } return s_allSprites; } private static AudioClip[] GetAllAudioClips() { if (s_allAudioClips != null) { return s_allAudioClips; } if ((Object)(object)s_bundle == (Object)null) { s_allAudioClips = Array.Empty(); return s_allAudioClips; } try { s_allAudioClips = s_bundle.LoadAllAssets() ?? Array.Empty(); } catch { s_allAudioClips = Array.Empty(); } return s_allAudioClips; } } internal static class CoreBatteryInterop { private const string BatteryJumpEnabledKey = "BatteryJumpEnabled"; internal static bool TryGetBatteryJumpEnabled(out bool value) { value = FeatureFlags.BatteryJumpEnabled; return true; } internal static bool TrySetBatteryJumpEnabled(bool value) { FeatureFlags.BatteryJumpEnabled = value; return true; } internal static void SetCoreHostRuntimeOverride(bool enabled) { ConfigManager.SetHostRuntimeOverride("BatteryJumpEnabled", enabled ? bool.TrueString : bool.FalseString); } internal static void ClearCoreHostRuntimeOverride() { ConfigManager.ClearHostRuntimeOverride("BatteryJumpEnabled"); } internal static void RequestCoreConfigSyncBroadcast() { ConfigSyncManager.RequestHostSnapshotBroadcast(); } } internal static class ImageAssetLoader { internal static string GetDefaultAssetsDirectory() { return BundleAssetLoader.GetPluginDirectory(); } internal static bool TryLoadTexture(string fileName, string? baseDirectory, out Texture2D? texture, out string resolvedPath) { texture = null; resolvedPath = string.Empty; if (string.IsNullOrWhiteSpace(fileName)) { return false; } if (BundleAssetLoader.TryLoadSprite(fileName, out Sprite sprite, out string resolvedPath2) && (Object)(object)sprite != (Object)null && (Object)(object)sprite.texture != (Object)null) { texture = sprite.texture; resolvedPath = resolvedPath2; return true; } return false; } internal static bool TryLoadSprite(string fileName, string? baseDirectory, out Sprite? sprite, out string resolvedPath, float pixelsPerUnit = 100f) { sprite = null; if (BundleAssetLoader.TryLoadSprite(fileName, out Sprite sprite2, out resolvedPath) && (Object)(object)sprite2 != (Object)null) { sprite = sprite2; return true; } return false; } } internal static class LogLimiter { public const int DefaultFrameInterval = 240; private static readonly Dictionary s_lastFrameByKey = new Dictionary(StringComparer.Ordinal); public static bool ShouldLog(string key, int frameInterval = 240) { if (string.IsNullOrEmpty(key) || frameInterval <= 0) { return true; } int frameCount = Time.frameCount; if (s_lastFrameByKey.TryGetValue(key, out var value) && frameCount - value < frameInterval) { return false; } s_lastFrameByKey[key] = frameCount; return true; } public static void Reset(string key) { if (!string.IsNullOrEmpty(key)) { s_lastFrameByKey.Remove(key); } } public static void Clear() { s_lastFrameByKey.Clear(); } } internal static class NetworkProtocol { internal const string ModId = "DHHFLastChanceMode"; internal const int ProtocolVersion = 1; internal const string RoomKeyPrefix = "DHHFLastChanceMode.Room."; internal static string BuildRoomKey(string localKey) { return string.IsNullOrWhiteSpace(localKey) ? "DHHFLastChanceMode.Room." : ("DHHFLastChanceMode.Room." + localKey.Trim()); } } internal readonly struct NetworkEnvelope { internal string ModId { get; } internal int ProtocolVersion { get; } internal string MessageType { get; } internal int MessageSeq { get; } internal object? Payload { get; } internal NetworkEnvelope(string modId, int protocolVersion, string messageType, int messageSeq, object? payload) { ModId = (string.IsNullOrWhiteSpace(modId) ? string.Empty : modId.Trim()); ProtocolVersion = protocolVersion; MessageType = (string.IsNullOrWhiteSpace(messageType) ? string.Empty : messageType.Trim()); MessageSeq = messageSeq; Payload = payload; } internal object?[] ToEventPayload() { return new object[5] { ModId, ProtocolVersion, MessageType, MessageSeq, Payload }; } internal bool IsExpectedSource() { return ModId == "DHHFLastChanceMode" && ProtocolVersion == 1 && !string.IsNullOrWhiteSpace(MessageType); } internal static bool TryParse(object? customData, out NetworkEnvelope envelope) { envelope = default(NetworkEnvelope); if (!(customData is object[] array) || array.Length < 5) { return false; } if (array[0] is string modId && array[1] is int protocolVersion && array[2] is string messageType) { object obj = array[3]; int messageSeq = default(int); int num; if (obj is int) { messageSeq = (int)obj; num = 1; } else { num = 0; } if (num != 0) { envelope = new NetworkEnvelope(modId, protocolVersion, messageType, messageSeq, array[4]); return true; } } return false; } } internal static class PhotonEventCodes { internal const byte ConfigSync = 79; internal const byte LastChanceSurrender = 80; internal const byte LastChanceTimerState = 81; internal const byte LastChanceDirectionPenaltyRequest = 82; internal const byte LastChanceUiState = 83; internal const byte ClientFixPresence = 84; internal const byte HostGateState = 85; internal const byte LastChancePlayerTruckHint = 86; internal const byte HostFixPresenceRequest = 87; } internal static class PlayerStateExtractionHelper { internal readonly struct PlayerStateSnapshot { internal int ActorNumber { get; } internal int SteamIdShort { get; } internal string Name { get; } internal Color Color { get; } internal bool IsAlive { get; } internal bool IsDead { get; } internal bool IsInTruck { get; } internal bool IsSurrendered { get; } internal int SourceOrder { get; } internal PlayerStateSnapshot(int actorNumber, int steamIdShort, string name, Color color, bool isAlive, bool isDead, bool isInTruck, bool isSurrendered, int sourceOrder) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) ActorNumber = actorNumber; SteamIdShort = steamIdShort; Name = name; Color = color; IsAlive = isAlive; IsDead = isDead; IsInTruck = isInTruck; IsSurrendered = isSurrendered; SourceOrder = sourceOrder; } } internal static List GetPlayersStateSnapshot() { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) List list = new List(); GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null || instance.PlayerList.Count == 0) { return list; } for (int i = 0; i < instance.PlayerList.Count; i++) { PlayerAvatar val = instance.PlayerList[i]; if (!((Object)(object)val == (Object)null)) { PhotonView photonView = val.photonView; int? obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? new int?(owner.ActorNumber) : null); } int? num = obj; int valueOrDefault = num.GetValueOrDefault(); int steamIdShort = GetSteamIdShort(val); string playerName = GetPlayerName(val); Color playerColor = GetPlayerColor(val); bool flag = IsDeadSet(val); bool flag2 = IsDisabled(val); bool flag3 = flag || flag2; bool isAlive = !flag3; bool isInTruck = IsPlayerInTruck(val, flag2); bool isSurrendered = LastChanceTimerController.IsPlayerSurrenderedForData(val); list.Add(new PlayerStateSnapshot(valueOrDefault, steamIdShort, playerName, playerColor, isAlive, flag3, isInTruck, isSurrendered, i)); } } list.Sort(CompareSnapshotOrder); return list; } internal static List GetPlayersStillInLastChance() { List playersStateSnapshot = GetPlayersStateSnapshot(); List list = new List(playersStateSnapshot.Count); for (int i = 0; i < playersStateSnapshot.Count; i++) { PlayerStateSnapshot item = playersStateSnapshot[i]; if (!item.IsSurrendered) { list.Add(item); } } return list; } private static int CompareSnapshotOrder(PlayerStateSnapshot left, PlayerStateSnapshot right) { if (left.ActorNumber > 0 && right.ActorNumber > 0) { return left.ActorNumber.CompareTo(right.ActorNumber); } return left.SourceOrder.CompareTo(right.SourceOrder); } private static string GetPlayerName(PlayerAvatar player) { if (!string.IsNullOrWhiteSpace(player.playerName)) { return player.playerName; } return "unknown"; } private static Color GetPlayerColor(PlayerAvatar player) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) PlayerAvatarVisuals playerAvatarVisuals = player.playerAvatarVisuals; if ((Object)(object)playerAvatarVisuals == (Object)null) { return Color.black; } return SemiFunc.PlayerGetColorMain(player); } private static bool IsDeadSet(PlayerAvatar player) { return player.deadSet; } private static bool IsDisabled(PlayerAvatar player) { return player.isDisabled; } private static bool IsPlayerInTruck(PlayerAvatar player, bool isDisabled) { if (!isDisabled) { RoomVolumeCheck roomVolumeCheck = player.RoomVolumeCheck; return (Object)(object)roomVolumeCheck != (Object)null && IsRoomVolumeInTruck(roomVolumeCheck); } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead == (Object)null) { return false; } RoomVolumeCheck roomVolumeCheck2 = playerDeathHead.roomVolumeCheck; if ((Object)(object)roomVolumeCheck2 != (Object)null) { return IsRoomVolumeInTruck(roomVolumeCheck2); } return playerDeathHead.inTruck; } private static bool IsRoomVolumeInTruck(RoomVolumeCheck roomVolumeCheck) { return roomVolumeCheck.inTruck; } private static int GetSteamIdShort(PlayerAvatar player) { return player.steamIDshort; } } internal static class PlayerTruckDistanceHelper { [Flags] internal enum DistanceQueryFields { None = 0, Height = 1, NavMeshDistance = 2, RoomPath = 4, All = 7 } internal readonly struct PlayerTruckDistance { internal PlayerAvatar PlayerAvatar { get; } internal float NavMeshDistance { get; } internal float HeightDelta { get; } internal int ShortestRoomPathToTruck { get; } internal int TotalMapRooms { get; } internal bool HasValidPath { get; } internal PlayerTruckDistance(PlayerAvatar playerAvatar, float navMeshDistance, float heightDelta, int shortestRoomPathToTruck, int totalMapRooms, bool hasValidPath) { PlayerAvatar = playerAvatar; NavMeshDistance = navMeshDistance; HeightDelta = heightDelta; ShortestRoomPathToTruck = shortestRoomPathToTruck; TotalMapRooms = totalMapRooms; HasValidPath = hasValidPath; } } private sealed class CachedPlayerDistance { internal float NavMeshDistance = -1f; internal float HeightDelta; internal int ShortestRoomPathToTruck = -1; internal int TotalMapRooms = -1; internal bool HasValidPath; internal int RoomHash; internal float HeightUpdatedAt = float.NegativeInfinity; internal int LevelStamp; internal Vector3 LastKnownWorldPosition; } private readonly struct RemotePlayerHint { internal int RoomHash { get; } internal float HeightDelta { get; } internal int LevelStamp { get; } internal float UpdatedAt { get; } internal RemotePlayerHint(int roomHash, float heightDelta, int levelStamp, float updatedAt) { RoomHash = roomHash; HeightDelta = heightDelta; LevelStamp = levelStamp; UpdatedAt = updatedAt; } } private struct ActivationProfileStats { internal int Calls; internal int PlayersProcessed; internal int NavRefreshCount; internal int RoomRefreshCount; internal int RemoteHintUsedCount; internal float TotalMs; internal float SetupMs; internal float LoopMs; internal float MaxCallMs; } private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.PlayerTruckDistance"); private const float HeightCacheTtlSeconds = 2f; private static LevelGenerator? s_cachedGraphLevelGenerator; private static int s_cachedGraphPointCount; private static Dictionary>? s_cachedRoomGraph; private static readonly Dictionary s_playerCache = new Dictionary(); private static readonly Dictionary s_remoteHints = new Dictionary(); private static LevelGenerator? s_cachedLevelGeneratorForPlayers; private static Vector3 s_cachedTruckPosition; private static bool s_hasCachedTruckPosition; private static int s_cachedLevelPointsCount; private static bool s_activationProfilingEnabled; private static ActivationProfileStats s_activationProfileStats; internal static void PrimeDistancesCache() { GetDistancesFromTruck(DistanceQueryFields.NavMeshDistance | DistanceQueryFields.RoomPath); } internal static PlayerTruckDistance[] GetDistancesFromTruck() { return GetDistancesFromTruck(DistanceQueryFields.All); } internal static PlayerTruckDistance[] GetDistancesFromTruck(DistanceQueryFields fields, ICollection? players = null, bool forceRefresh = false) { //IL_00e9: 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_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_049f: Unknown result type (might be due to invalid IL or missing references) //IL_04aa: Unknown result type (might be due to invalid IL or missing references) //IL_04ac: Unknown result type (might be due to invalid IL or missing references) //IL_045e: Unknown result type (might be due to invalid IL or missing references) //IL_0465: Unknown result type (might be due to invalid IL or missing references) try { bool debugLogging = FeatureFlags.DebugLogging; float num = (debugLogging ? Time.realtimeSinceStartup : 0f); float num2 = num; float num3 = num; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; if (fields == DistanceQueryFields.None) { return Array.Empty(); } LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null) { return Array.Empty(); } List allLevelPoints = GetAllLevelPoints(instance); if (!TryGetTruckTarget(instance, allLevelPoints, out Vector3 truckPosition, out LevelPoint truckPoint)) { return Array.Empty(); } GameDirector instance2 = GameDirector.instance; if (instance2?.PlayerList == null || instance2.PlayerList.Count == 0) { return Array.Empty(); } int num8 = allLevelPoints?.Count ?? 0; if (s_cachedLevelGeneratorForPlayers != instance || !s_hasCachedTruckPosition || Vector3.SqrMagnitude(s_cachedTruckPosition - truckPosition) > 0.0001f || s_cachedLevelPointsCount != num8) { s_playerCache.Clear(); s_remoteHints.Clear(); s_cachedLevelGeneratorForPlayers = instance; s_cachedTruckPosition = truckPosition; s_cachedLevelPointsCount = num8; s_hasCachedTruckPosition = true; } HashSet hashSet = null; if (players != null) { hashSet = new HashSet(); foreach (PlayerAvatar player in players) { if (!((Object)(object)player == (Object)null)) { hashSet.Add(GetPlayerKey(player)); } } if (hashSet.Count == 0) { return Array.Empty(); } } bool flag = (fields & DistanceQueryFields.RoomPath) != 0; bool flag2 = (fields & DistanceQueryFields.NavMeshDistance) != 0; bool flag3 = (fields & DistanceQueryFields.Height) != 0; Dictionary> dictionary = ((flag || flag2) ? GetOrBuildRoomGraph(instance, allLevelPoints) : null); int totalMapRooms = ((dictionary != null && dictionary.Count > 0) ? dictionary.Count : (-1)); int num9 = (((Object)(object)RunManager.instance != (Object)null) ? RunManager.instance.levelsCompleted : 0); if (debugLogging) { num2 = Time.realtimeSinceStartup; num3 = num2; } List list = new List(instance2.PlayerList.Count); foreach (PlayerAvatar player2 in instance2.PlayerList) { if ((Object)(object)player2 == (Object)null) { continue; } int playerKey = GetPlayerKey(player2); if (hashSet != null && !hashSet.Contains(playerKey)) { continue; } if (!s_playerCache.TryGetValue(playerKey, out CachedPlayerDistance value)) { value = new CachedPlayerDistance(); s_playerCache[playerKey] = value; } Vector3 val = (value.LastKnownWorldPosition = GetPlayerWorldPosition(player2)); PhotonView photonView = player2.photonView; int? obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? new int?(owner.ActorNumber) : null); } int? num10 = obj; int valueOrDefault = num10.GetValueOrDefault(); s_remoteHints.TryGetValue(valueOrDefault, out var value2); bool flag4 = SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.IsMultiplayer() && valueOrDefault > 0 && PhotonNetwork.LocalPlayer != null && valueOrDefault != PhotonNetwork.LocalPlayer.ActorNumber && value2.LevelStamp == num9; List list2 = null; int num11 = value.RoomHash; if (flag4) { num11 = value2.RoomHash; num6++; } else if (flag || flag2) { list2 = GetPlayerRooms(player2); num11 = ComputeRoomsHash(list2); } bool flag5 = num11 != value.RoomHash; bool flag6 = value.LevelStamp != num9; if (flag3) { float num12 = Time.unscaledTime - value.HeightUpdatedAt; if (forceRefresh || flag6 || num12 < 0f || num12 > 2f) { if (flag4 && Time.unscaledTime - value2.UpdatedAt <= 2f) { value.HeightDelta = value2.HeightDelta; value.HeightUpdatedAt = value2.UpdatedAt; } else { value.HeightDelta = val.y - truckPosition.y; value.HeightUpdatedAt = Time.unscaledTime; } } } if (flag2 && (forceRefresh || flag6 || flag5)) { float navMeshDistance = -1f; Vector3 navMeshPosition; bool flag7 = TryGetPlayerNavMeshPosition(player2, val, out navMeshPosition) && TryCalculatePathDistance(navMeshPosition, truckPosition, out navMeshDistance); value.NavMeshDistance = (flag7 ? navMeshDistance : (-1f)); value.HasValidPath = flag7; num4++; } if (flag && (forceRefresh || flag6 || flag5)) { if (list2 == null) { list2 = GetPlayerRooms(player2); } value.ShortestRoomPathToTruck = ResolveShortestRoomPathToTruck(list2 ?? new List(), truckPoint, dictionary); num5++; } if (flag || flag2) { value.TotalMapRooms = totalMapRooms; } value.RoomHash = num11; value.LevelStamp = num9; list.Add(new PlayerTruckDistance(player2, value.NavMeshDistance, value.HeightDelta, value.ShortestRoomPathToTruck, value.TotalMapRooms, value.HasValidPath)); num7++; } if (debugLogging && s_activationProfilingEnabled) { float realtimeSinceStartup = Time.realtimeSinceStartup; float num13 = (realtimeSinceStartup - num) * 1000f; float num14 = (num2 - num) * 1000f; float num15 = (realtimeSinceStartup - num3) * 1000f; s_activationProfileStats.Calls++; s_activationProfileStats.PlayersProcessed += num7; s_activationProfileStats.NavRefreshCount += num4; s_activationProfileStats.RoomRefreshCount += num5; s_activationProfileStats.RemoteHintUsedCount += num6; s_activationProfileStats.TotalMs += num13; s_activationProfileStats.SetupMs += num14; s_activationProfileStats.LoopMs += num15; s_activationProfileStats.MaxCallMs = Mathf.Max(s_activationProfileStats.MaxCallMs, num13); } return list.ToArray(); } catch (Exception ex) { LogRuntimeHotPathException("GetDistancesFromTruck", ex); return Array.Empty(); } } internal static void ApplyRemotePlayerHint(int actorNumber, int roomHash, float heightDelta, int levelStamp) { if (actorNumber > 0) { s_remoteHints[actorNumber] = new RemotePlayerHint(roomHash, heightDelta, levelStamp, Time.unscaledTime); } } internal static bool TryBuildLocalPlayerTruckHint(out int roomHash, out float heightDelta, out int levelStamp) { //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) roomHash = 0; heightDelta = 0f; levelStamp = (((Object)(object)RunManager.instance != (Object)null) ? RunManager.instance.levelsCompleted : 0); try { if (!PhotonNetwork.InRoom || PhotonNetwork.LocalPlayer == null) { return false; } LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null) { return false; } List allLevelPoints = GetAllLevelPoints(instance); if (!TryGetTruckTarget(instance, allLevelPoints, out Vector3 truckPosition, out LevelPoint _)) { return false; } GameDirector instance2 = GameDirector.instance; if (instance2?.PlayerList == null || instance2.PlayerList.Count == 0) { return false; } int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; PlayerAvatar val = null; for (int i = 0; i < instance2.PlayerList.Count; i++) { PlayerAvatar val2 = instance2.PlayerList[i]; if (!((Object)(object)val2 == (Object)null)) { PhotonView photonView = val2.photonView; int? obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? new int?(owner.ActorNumber) : null); } int? num = obj; if (num.GetValueOrDefault() == actorNumber) { val = val2; break; } } } if ((Object)(object)val == (Object)null) { return false; } List playerRooms = GetPlayerRooms(val); roomHash = ComputeRoomsHash(playerRooms); Vector3 playerWorldPosition = GetPlayerWorldPosition(val); heightDelta = playerWorldPosition.y - truckPosition.y; return true; } catch (Exception ex) { LogRuntimeHotPathException("TryBuildLocalPlayerTruckHint", ex); return false; } } private static void LogRuntimeHotPathException(string context, Exception ex) { if (FeatureFlags.DebugLogging) { string key = "LastChance.Runtime.PlayerTruckDistance." + context; if (LogLimiter.ShouldLog(key, 600)) { Log.LogWarning((object)("[LastChance] Runtime hot-path failed in " + context + ": " + ex.GetType().Name + ": " + ex.Message)); } } } internal static void BeginActivationProfiling() { s_activationProfileStats = default(ActivationProfileStats); s_activationProfilingEnabled = true; } internal static string EndActivationProfilingSummary() { s_activationProfilingEnabled = false; return $"calls={s_activationProfileStats.Calls} total={s_activationProfileStats.TotalMs:F1}ms setup={s_activationProfileStats.SetupMs:F1}ms loop={s_activationProfileStats.LoopMs:F1}ms maxCall={s_activationProfileStats.MaxCallMs:F1}ms players={s_activationProfileStats.PlayersProcessed} navRefresh={s_activationProfileStats.NavRefreshCount} roomRefresh={s_activationProfileStats.RoomRefreshCount} remoteHints={s_activationProfileStats.RemoteHintUsedCount}"; } private static int GetPlayerKey(PlayerAvatar player) { PhotonView photonView = player.photonView; int? obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? new int?(owner.ActorNumber) : null); } int? num = obj; int valueOrDefault = num.GetValueOrDefault(); if (valueOrDefault != 0) { return valueOrDefault; } return ((Object)player).GetInstanceID(); } private static int ComputeRoomsHash(List rooms) { int num = 17; for (int i = 0; i < rooms.Count; i++) { num = num * 31 + RuntimeHelpers.GetHashCode(rooms[i]); } return num; } private static Dictionary> GetOrBuildRoomGraph(LevelGenerator levelGenerator, List? allLevelPoints) { int num = allLevelPoints?.Count ?? 0; if (s_cachedRoomGraph != null && levelGenerator == s_cachedGraphLevelGenerator && s_cachedGraphPointCount == num) { return s_cachedRoomGraph; } Dictionary> result = BuildRoomGraph(allLevelPoints); s_cachedGraphLevelGenerator = levelGenerator; s_cachedGraphPointCount = num; s_cachedRoomGraph = result; return result; } private static bool TryGetTruckTarget(LevelGenerator levelGenerator, List? allLevelPoints, out Vector3 truckPosition, out LevelPoint? truckPoint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) truckPosition = Vector3.zero; truckPoint = null; LevelPoint levelPathTruck = levelGenerator.LevelPathTruck; if (TryGetLevelPointPosition(levelPathTruck, out truckPosition)) { truckPoint = levelPathTruck; return true; } if (allLevelPoints == null) { return false; } foreach (LevelPoint allLevelPoint in allLevelPoints) { if ((Object)(object)allLevelPoint == (Object)null || !TryIsTruckPoint(allLevelPoint) || !TryGetLevelPointPosition(allLevelPoint, out truckPosition)) { continue; } truckPoint = allLevelPoint; return true; } return false; } private static List? GetAllLevelPoints(LevelGenerator levelGenerator) { List levelPathPoints = levelGenerator.LevelPathPoints; if (levelPathPoints == null) { return null; } List list = new List(); foreach (LevelPoint item2 in levelPathPoints) { if (item2 != null) { LevelPoint item = item2; if (true) { list.Add(item); } } } return (list.Count > 0) ? list : null; } private static bool TryIsTruckPoint(LevelPoint point) { if (point.Truck) { return true; } return (Object)(object)point.Room != (Object)null && point.Room.Truck; } private static int ResolveShortestRoomPathToTruck(List playerRooms, LevelPoint? truckPoint, Dictionary>? roomGraph) { if ((Object)(object)truckPoint == (Object)null || roomGraph == null || roomGraph.Count == 0) { return -1; } RoomVolume levelPointRoom = GetLevelPointRoom(truckPoint); if ((Object)(object)levelPointRoom == (Object)null || !roomGraph.ContainsKey(levelPointRoom)) { return -1; } if (playerRooms.Count == 0) { return -1; } HashSet hashSet = new HashSet(); Queue<(RoomVolume, int)> queue = new Queue<(RoomVolume, int)>(); foreach (RoomVolume playerRoom in playerRooms) { if (!((Object)(object)playerRoom == (Object)null) && roomGraph.ContainsKey(playerRoom) && hashSet.Add(playerRoom)) { queue.Enqueue((playerRoom, 0)); } } while (queue.Count > 0) { var (val, num) = queue.Dequeue(); if (val == levelPointRoom) { return num; } if (!roomGraph.TryGetValue(val, out HashSet value)) { continue; } foreach (RoomVolume item in value) { if (!((Object)(object)item == (Object)null) && hashSet.Add(item)) { queue.Enqueue((item, num + 1)); } } } return -1; } private static List GetPlayerRooms(PlayerAvatar player) { List list = new List(); if ((Object)(object)player == (Object)null) { return list; } RoomVolumeCheck val = null; PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead != (Object)null) { val = playerDeathHead.roomVolumeCheck; } if ((Object)(object)val == (Object)null) { val = player.RoomVolumeCheck; } if ((Object)(object)val == (Object)null) { return list; } List currentRooms = val.CurrentRooms; if (currentRooms == null) { return list; } foreach (RoomVolume item2 in currentRooms) { if (item2 != null) { RoomVolume item = item2; if (true) { list.Add(item); } } } return list; } private static Dictionary> BuildRoomGraph(List? allLevelPoints) { Dictionary> dictionary = new Dictionary>(); if (allLevelPoints == null) { return dictionary; } foreach (LevelPoint allLevelPoint in allLevelPoints) { if ((Object)(object)allLevelPoint == (Object)null) { continue; } RoomVolume levelPointRoom = GetLevelPointRoom(allLevelPoint); if ((Object)(object)levelPointRoom == (Object)null) { continue; } if (!dictionary.ContainsKey(levelPointRoom)) { dictionary[levelPointRoom] = new HashSet(); } IEnumerable connectedPoints = GetConnectedPoints(allLevelPoint); if (connectedPoints == null) { continue; } foreach (LevelPoint item in connectedPoints) { if ((Object)(object)item == (Object)null) { continue; } RoomVolume levelPointRoom2 = GetLevelPointRoom(item); if (!((Object)(object)levelPointRoom2 == (Object)null)) { if (!dictionary.ContainsKey(levelPointRoom2)) { dictionary[levelPointRoom2] = new HashSet(); } if (levelPointRoom != levelPointRoom2) { dictionary[levelPointRoom].Add(levelPointRoom2); dictionary[levelPointRoom2].Add(levelPointRoom); } } } } return dictionary; } private static RoomVolume? GetLevelPointRoom(LevelPoint levelPoint) { return levelPoint.Room; } private static IEnumerable? GetConnectedPoints(LevelPoint levelPoint) { if ((Object)(object)levelPoint == (Object)null || levelPoint.ConnectedPoints == null) { return null; } List list = new List(); foreach (LevelPoint connectedPoint in levelPoint.ConnectedPoints) { if (connectedPoint != null) { LevelPoint item = connectedPoint; if (true) { list.Add(item); } } } return (list.Count > 0) ? list : null; } private static bool TryGetLevelPointPosition(LevelPoint? levelPoint, out Vector3 position) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_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) position = Vector3.zero; if ((Object)(object)levelPoint == (Object)null) { return false; } position = ((Component)levelPoint).transform.position; return true; } private static Vector3 GetPlayerWorldPosition(PlayerAvatar player) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return Vector3.zero; } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead != (Object)null) { return ((Component)playerDeathHead).transform.position; } if ((Object)(object)player.playerTransform != (Object)null) { return player.playerTransform.position; } return ((Component)player).transform.position; } private static bool TryGetPlayerNavMeshPosition(PlayerAvatar player, Vector3 worldPosition, out Vector3 navMeshPosition) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_0084: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) navMeshPosition = Vector3.zero; if ((Object)(object)player == (Object)null) { return false; } if (TrySamplePosition(worldPosition, 8f, out var navMeshPosition2)) { navMeshPosition = navMeshPosition2; return true; } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead != (Object)null) { Vector3 val = ((Component)playerDeathHead).transform.position; PhysGrabObject physGrabObject = playerDeathHead.physGrabObject; if ((Object)(object)physGrabObject != (Object)null) { val = physGrabObject.centerPoint; } if (TrySamplePosition(val, 12f, out navMeshPosition2) || TrySamplePosition(val - Vector3.up * 0.5f, 18f, out navMeshPosition2) || TrySamplePosition(val, 30f, out navMeshPosition2)) { navMeshPosition = navMeshPosition2; return true; } return false; } if (player.LastNavmeshPosition != Vector3.zero) { navMeshPosition = player.LastNavmeshPosition; return true; } return false; } private static bool TrySamplePosition(Vector3 worldPosition, float maxDistance, out Vector3 navMeshPosition) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //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) navMeshPosition = Vector3.zero; NavMeshHit val = default(NavMeshHit); if (!NavMesh.SamplePosition(worldPosition, ref val, maxDistance, -1)) { return false; } navMeshPosition = ((NavMeshHit)(ref val)).position; return true; } private static bool TryCalculatePathDistance(Vector3 from, Vector3 to, out float navMeshDistance) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_000e: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_006b: 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) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) navMeshDistance = 0f; NavMeshPath val = new NavMeshPath(); if (!NavMesh.CalculatePath(from, to, -1, val)) { return false; } Vector3[] corners = val.corners; if (corners == null || corners.Length == 0) { navMeshDistance = Vector3.Distance(from, to); return true; } Vector3 val2 = from; float num = 0f; Vector3[] array = corners; foreach (Vector3 val3 in array) { num += Vector3.Distance(val2, val3); val2 = val3; } navMeshDistance = num; return true; } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.UI { internal static class LastChanceMapIndicatorUI { internal static bool Show(Texture? texture, bool debugLogging) { return true; } internal static void SetYaw(float yawDegrees) { } internal static void Hide() { } } internal static class LastChanceTimerChangeEffectsModule { private static readonly Color PositiveDeltaColor = new Color(0.35f, 1f, 0.45f, 1f); private static readonly Color NegativeDeltaColor = new Color(1f, 0.35f, 0.35f, 1f); private static readonly Regex ColorTagRegex = new Regex("", RegexOptions.Compiled); private static TextMeshProUGUI? s_timerLabel; private static RectTransform? s_timerRect; private static TextMeshProUGUI? s_floatingLabel; private static RectTransform? s_floatingRect; private static float s_timerFontSize; private static bool s_initialized; private static string s_baseTimerText = string.Empty; private static string s_lastRenderedTimerText = string.Empty; private static Vector3 s_timerBaseScale = Vector3.one; private static float s_pulseRemainingSeconds; private static Color s_pulseColor = Color.white; private static float s_floatingRemainingSeconds; private static float s_floatingDropDistance; private static Color s_floatingBaseColor = Color.white; private static float PulseDurationSeconds => Mathf.Max(0.05f, InternalConfig.LastChanceTimerChangePulseDurationSeconds); internal static void Initialize(RectTransform? timerRect, TextMeshProUGUI? timerLabel, float timerFontSize) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) s_timerRect = timerRect; s_timerLabel = timerLabel; s_timerFontSize = Mathf.Max(1f, timerFontSize); s_timerBaseScale = (((Object)(object)timerRect != (Object)null) ? ((Transform)timerRect).localScale : Vector3.one); s_baseTimerText = string.Empty; s_lastRenderedTimerText = string.Empty; EnsureFloatingLabel(); ResetVisualState(); s_initialized = (Object)(object)s_timerRect != (Object)null && (Object)(object)s_timerLabel != (Object)null; } internal static void OnBaseTimerTextUpdated(string text) { s_baseTimerText = text ?? string.Empty; if (s_pulseRemainingSeconds <= 0f) { RenderTimerText(s_baseTimerText); } } internal static void NotifyLocalDelta(float deltaSeconds) { TriggerDeltaEffect(deltaSeconds, Mathf.Max(0f, InternalConfig.LastChanceTimerChangeLocalDeltaMinSeconds)); } internal static void NotifyNetworkDelta(float deltaSeconds) { TriggerDeltaEffect(deltaSeconds, Mathf.Max(0f, InternalConfig.LastChanceTimerChangeNetworkDeltaMinSeconds)); } internal static void Tick() { if (s_initialized) { float unscaledDeltaTime = Time.unscaledDeltaTime; TickPulse(unscaledDeltaTime); TickFloating(unscaledDeltaTime); } } internal static void SetVisible(bool visible) { if (!visible) { ResetVisualState(); } } internal static void Reset() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) s_initialized = false; s_timerLabel = null; s_timerRect = null; s_floatingLabel = null; s_floatingRect = null; s_timerFontSize = 0f; s_baseTimerText = string.Empty; s_lastRenderedTimerText = string.Empty; s_timerBaseScale = Vector3.one; s_pulseRemainingSeconds = 0f; s_floatingRemainingSeconds = 0f; s_floatingDropDistance = 0f; s_floatingBaseColor = Color.white; s_pulseColor = Color.white; } private static void TriggerDeltaEffect(float deltaSeconds, float minAbsSeconds) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_009e: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) if (s_initialized && !(Mathf.Abs(deltaSeconds) < minAbsSeconds)) { s_pulseRemainingSeconds = PulseDurationSeconds; s_pulseColor = ((deltaSeconds > 0f) ? PositiveDeltaColor : NegativeDeltaColor); s_floatingRemainingSeconds = Mathf.Max(0.05f, InternalConfig.LastChanceTimerChangeFloatingDurationSeconds); s_floatingDropDistance = s_timerFontSize * Mathf.Max(0.1f, InternalConfig.LastChanceTimerChangeFloatingDropFontMultiplier); s_floatingBaseColor = s_pulseColor; if ((Object)(object)s_floatingRect != (Object)null) { s_floatingRect.anchoredPosition = Vector2.zero; ((Transform)s_floatingRect).localScale = Vector3.one; ((Transform)s_floatingRect).SetAsLastSibling(); } if ((Object)(object)s_floatingLabel != (Object)null) { int num = Mathf.Max(1, Mathf.RoundToInt(Mathf.Abs(deltaSeconds))); string text = ((deltaSeconds >= 0f) ? "+" : "-"); ((TMP_Text)s_floatingLabel).text = text + num; ((Graphic)s_floatingLabel).color = s_floatingBaseColor; ((Behaviour)s_floatingLabel).enabled = true; ((Component)s_floatingLabel).gameObject.SetActive(true); } } } private static void TickPulse(float dt) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s_timerRect == (Object)null) { return; } if (s_pulseRemainingSeconds <= 0f) { ((Transform)s_timerRect).localScale = s_timerBaseScale; RenderTimerText(s_baseTimerText); return; } s_pulseRemainingSeconds = Mathf.Max(0f, s_pulseRemainingSeconds - dt); float num = Mathf.Max(0.05f, InternalConfig.LastChanceTimerChangePulseDurationSeconds); float num2 = Mathf.Max(0.01f, InternalConfig.LastChanceTimerChangePulseScaleBoost); float num3 = 1f - s_pulseRemainingSeconds / num; float num4 = 1f + Mathf.Sin(num3 * (float)Math.PI) * num2; ((Transform)s_timerRect).localScale = s_timerBaseScale * num4; if (num4 > 1.02f) { RenderTimerText(ReplaceFirstColorTag(s_baseTimerText, s_pulseColor)); } else { RenderTimerText(s_baseTimerText); } } private static void TickFloating(float dt) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s_floatingLabel == (Object)null || (Object)(object)s_floatingRect == (Object)null) { return; } if (s_floatingRemainingSeconds <= 0f) { Behaviour val = (Behaviour)(object)s_floatingLabel; if (val != null) { val.enabled = false; } ((Component)s_floatingLabel).gameObject.SetActive(false); } else { s_floatingRemainingSeconds = Mathf.Max(0f, s_floatingRemainingSeconds - dt); float num = Mathf.Max(0.05f, InternalConfig.LastChanceTimerChangeFloatingDurationSeconds); float num2 = 1f - s_floatingRemainingSeconds / num; float num3 = 0f - Mathf.Lerp(0f, s_floatingDropDistance, num2); s_floatingRect.anchoredPosition = new Vector2(0f, num3); Color color = s_floatingBaseColor; color.a = 1f - num2; ((Graphic)s_floatingLabel).color = color; } } private static void ResetVisualState() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) s_pulseRemainingSeconds = 0f; s_floatingRemainingSeconds = 0f; if ((Object)(object)s_timerRect != (Object)null) { ((Transform)s_timerRect).localScale = s_timerBaseScale; } if ((Object)(object)s_floatingLabel != (Object)null) { ((Behaviour)s_floatingLabel).enabled = false; ((Component)s_floatingLabel).gameObject.SetActive(false); } if ((Object)(object)s_floatingRect != (Object)null) { s_floatingRect.anchoredPosition = Vector2.zero; } RenderTimerText(s_baseTimerText); } private static void EnsureFloatingLabel() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)s_timerRect == (Object)null) && !((Object)(object)s_floatingLabel != (Object)null)) { GameObject val = new GameObject("LastChanceTimerDelta", new Type[1] { typeof(RectTransform) }); Transform parent = ((Transform)s_timerRect).parent; RectTransform val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null); val.transform.SetParent((Transform)(((Object)(object)val2 != (Object)null) ? ((object)val2) : ((object)s_timerRect)), false); s_floatingRect = val.GetComponent(); s_floatingRect.anchorMin = new Vector2(0.5f, 0.5f); s_floatingRect.anchorMax = new Vector2(0.5f, 0.5f); s_floatingRect.pivot = new Vector2(0.5f, 0.5f); s_floatingRect.anchoredPosition = Vector2.zero; s_floatingRect.sizeDelta = new Vector2(240f, 40f); ((Transform)s_floatingRect).SetAsLastSibling(); s_floatingLabel = val.AddComponent(); ((TMP_Text)s_floatingLabel).alignment = (TextAlignmentOptions)514; float fontSize = s_timerFontSize * Mathf.Max(1f, InternalConfig.LastChanceTimerChangeFloatingFontSizeMultiplier); ((TMP_Text)s_floatingLabel).fontSize = fontSize; ((TMP_Text)s_floatingLabel).enableAutoSizing = false; ((TMP_Text)s_floatingLabel).enableWordWrapping = false; ((TMP_Text)s_floatingLabel).richText = false; ((Graphic)s_floatingLabel).color = Color.white; ((Behaviour)s_floatingLabel).enabled = false; ((Component)s_floatingLabel).gameObject.SetActive(false); } } private static void RenderTimerText(string text) { if (!((Object)(object)s_timerLabel == (Object)null) && !string.Equals(s_lastRenderedTimerText, text, StringComparison.Ordinal)) { s_lastRenderedTimerText = text; ((TMP_Text)s_timerLabel).text = text; } } private static string ReplaceFirstColorTag(string text, Color color) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(text)) { return text; } string text2 = ""; Match match = ColorTagRegex.Match(text); if (!match.Success) { return text; } return text.Substring(0, match.Index) + text2 + text.Substring(match.Index + match.Length); } } internal static class LastChanceTimerUI { private sealed class PlayerIconSlot { internal RectTransform? Root; internal Image? BaseImage; internal Image? SurrenderImage; internal Image? SafeImage; } private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.TimerUI"); private const string SemibotWhiteFileName = "SemibotWhite.png"; private const string TruckWhiteFileName = "TruckWhite.png"; private const string SemibotSurrenderedFileName = "SemibotSurrendered.png"; private const string SemibotSafeFileName = "SemibotSafe.png"; private const int MaxPlayerIcons = 6; private const float TopPaddingFromCanvas = 8f; private const float DefaultTimerVerticalPosition = -8f; private const float SurrenderHintSpacing = 4f; private const float TimerFontSize = 14f; private const float SurrenderHintFontSize = 12f; private const float PlayerIconSize = 28f; private const float PlayerIconSpacing = 8f; private const float PlayerStatusOverlayScale = 0.75f; private const float TruckWidgetOffsetX = 165f; private const float TimerToSurrenderGap = 0.5f; private const float SurrenderToIconsGap = 4f; private const float TruckCounterOffsetY = -22f; private const float TruckCounterBadgeWidth = 74f; private const float TruckCounterBadgeHeight = 22f; private const float TruckCounterBadgeBorderThickness = 2f; private const float TruckCounterFontSize = 11f; private static readonly TextAlignmentOptions CenterAlignment = (TextAlignmentOptions)514; private static readonly TextAlignmentOptions TopAlignment = (TextAlignmentOptions)258; private static TextMeshProUGUI? s_label; private static RectTransform? s_rect; private static TextMeshProUGUI? s_hintLabel; private static RectTransform? s_hintRect; private static string s_defaultHintText = string.Empty; private static string s_lastTimerText = string.Empty; private static string s_lastHintText = string.Empty; private static bool s_isVisible; private static Transform? s_cachedUiParent; private static float s_nextVisibilityRefreshAt; private static RectTransform? s_playersRoot; private static readonly List s_playerSlots = new List(6); private static RectTransform? s_truckRoot; private static Image? s_truckIconImage; private static TextMeshProUGUI? s_truckCounterLabel; private static string s_lastTruckCounterText = string.Empty; private static Sprite? s_semibotWhiteSprite; private static Sprite? s_truckWhiteSprite; private static Sprite? s_semibotSurrenderedSprite; private static Sprite? s_semibotSafeSprite; private static bool s_assetLoadAttempted; private static float s_nextAssetRetryAt; private const float AssetRetryIntervalSeconds = 2f; private const float VisibilityRefreshIntervalSeconds = 0.5f; private const float AbilitySpotCacheRefreshIntervalSeconds = 0.5f; internal static void Show(string defaultHintText) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) s_defaultHintText = defaultHintText; if ((Object)(object)s_label != (Object)null) { ReparentToPreferredUiRoot(); EnsureSpritesLoaded(); RefreshVisibility(force: true); return; } Transform val = ResolvePreferredUiParent(); if (!((Object)(object)val == (Object)null)) { EnsureSpritesLoaded(); GameObject val2 = new GameObject("LastChanceTimer", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val, false); s_rect = val2.GetComponent(); s_rect.anchorMin = new Vector2(0.5f, 1f); s_rect.anchorMax = new Vector2(0.5f, 1f); s_rect.pivot = new Vector2(0.5f, 1f); s_rect.anchoredPosition = new Vector2(0f, -8f); s_rect.sizeDelta = new Vector2(700f, 120f); s_label = val2.AddComponent(); SetDefaults(s_label); LastChanceTimerChangeEffectsModule.Initialize(s_rect, s_label, 14f); SetEnabled(enabled: false); GameObject val3 = new GameObject("LastChanceSurrenderHint", new Type[1] { typeof(RectTransform) }); val3.transform.SetParent(val2.transform, false); s_hintRect = val3.GetComponent(); if ((Object)(object)s_hintRect != (Object)null) { s_hintRect.anchorMin = new Vector2(0.5f, 1f); s_hintRect.anchorMax = new Vector2(0.5f, 1f); s_hintRect.pivot = new Vector2(0.5f, 1f); s_hintRect.sizeDelta = new Vector2(700f, 26f); s_hintRect.anchoredPosition = new Vector2(0f, GetSurrenderOffsetY()); } s_hintLabel = val3.AddComponent(); SetHintDefaults(s_hintLabel); s_lastHintText = string.Empty; SetSurrenderHintText(s_defaultHintText); CreatePlayerSlots(val2.transform); CreateTruckWidget(val2.transform); KeepAtTopPosition(); RefreshVisibility(force: true); } } internal static void Prewarm(string defaultHintText) { Show(defaultHintText); Hide(); ResetSurrenderHint(); } internal static void PrewarmAssets() { EnsureSpritesLoaded(); } internal static void DestroyUi() { try { Component val = (Component)(object)s_label; if (val != null && (Object)(object)val != (Object)null) { GameObject gameObject = val.gameObject; if ((Object)(object)gameObject != (Object)null) { Object.Destroy((Object)(object)gameObject); } } } catch { } s_label = null; s_rect = null; s_hintLabel = null; s_hintRect = null; s_lastTimerText = string.Empty; s_lastHintText = string.Empty; s_isVisible = false; s_cachedUiParent = null; s_playersRoot = null; s_playerSlots.Clear(); s_truckRoot = null; s_truckIconImage = null; s_truckCounterLabel = null; s_lastTruckCounterText = string.Empty; AbilitySpotDiscoveryCache.Invalidate(); LastChanceTimerChangeEffectsModule.Reset(); } internal static void UpdateText(string text) { if (!((Object)(object)s_label == (Object)null)) { RefreshVisibility(force: false); LastChanceTimerChangeEffectsModule.OnBaseTimerTextUpdated(text); LastChanceTimerChangeEffectsModule.Tick(); if (!string.Equals(s_lastTimerText, text, StringComparison.Ordinal)) { s_lastTimerText = text; } } } internal static void NotifyTimerDelta(float deltaSeconds, bool isNetworkSync = false) { if (!((Object)(object)s_label == (Object)null)) { if (isNetworkSync) { LastChanceTimerChangeEffectsModule.NotifyNetworkDelta(deltaSeconds); } else { LastChanceTimerChangeEffectsModule.NotifyLocalDelta(deltaSeconds); } } } internal static void UpdatePlayerStates(IReadOnlyList snapshots, int requiredOnTruck) { //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s_label == (Object)null || (Object)(object)s_playersRoot == (Object)null) { return; } EnsureSpritesLoaded(); int num = Mathf.Min(6, snapshots?.Count ?? 0); float num2 = ((num <= 0) ? 0f : ((float)num * 28f + (float)(num - 1) * 8f)); float num3 = -0.5f * num2 + 14f; int num4 = 0; if (snapshots != null) { for (int i = 0; i < snapshots.Count; i++) { if (!snapshots[i].IsSurrendered && snapshots[i].IsInTruck) { num4++; } } } UpdateTruckCounter(num4, Mathf.Max(1, requiredOnTruck)); for (int j = 0; j < s_playerSlots.Count; j++) { PlayerIconSlot playerIconSlot = s_playerSlots[j]; if (!((Object)(object)playerIconSlot.Root == (Object)null)) { if (j >= num || snapshots == null) { ((Component)playerIconSlot.Root).gameObject.SetActive(false); continue; } PlayerStateExtractionHelper.PlayerStateSnapshot playerStateSnapshot = snapshots[j]; ((Component)playerIconSlot.Root).gameObject.SetActive(true); playerIconSlot.Root.anchoredPosition = new Vector2(num3 + (float)j * 36f, 0f); SetImage(playerIconSlot.BaseImage, s_semibotWhiteSprite, playerStateSnapshot.Color, enabled: true); SetImage(playerIconSlot.SafeImage, s_semibotSafeSprite, Color.white, !playerStateSnapshot.IsSurrendered && playerStateSnapshot.IsInTruck); SetImage(playerIconSlot.SurrenderImage, s_semibotSurrenderedSprite, Color.white, playerStateSnapshot.IsSurrendered); } } } internal static void Hide() { if (!((Object)(object)s_label == (Object)null)) { SetEnabled(enabled: false); LastChanceTimerChangeEffectsModule.SetVisible(visible: false); } } internal static void SetSurrenderHintText(string text) { if (!((Object)(object)s_hintLabel == (Object)null) && !string.Equals(s_lastHintText, text, StringComparison.Ordinal)) { s_lastHintText = text; ((TMP_Text)s_hintLabel).text = text; } } internal static void ResetSurrenderHint() { SetSurrenderHintText(s_defaultHintText); } private static void CreatePlayerSlots(Transform parent) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Expected O, but got Unknown //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Expected O, but got Unknown //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("LastChancePlayersRow", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); s_playersRoot = val.GetComponent(); if ((Object)(object)s_playersRoot != (Object)null) { s_playersRoot.anchorMin = new Vector2(0.5f, 1f); s_playersRoot.anchorMax = new Vector2(0.5f, 1f); s_playersRoot.pivot = new Vector2(0.5f, 1f); s_playersRoot.anchoredPosition = new Vector2(0f, GetPlayerIconsOffsetY()); s_playersRoot.sizeDelta = new Vector2(500f, 36f); } s_playerSlots.Clear(); for (int i = 0; i < 6; i++) { GameObject val2 = new GameObject($"PlayerSlot.{i}", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform component = val2.GetComponent(); component.anchorMin = new Vector2(0.5f, 1f); component.anchorMax = new Vector2(0.5f, 1f); component.pivot = new Vector2(0.5f, 1f); component.sizeDelta = new Vector2(28f, 28f); Image baseImage = AddImageComponent(val2); GameObject val3 = new GameObject("Safe", new Type[1] { typeof(RectTransform) }); val3.transform.SetParent(val2.transform, false); RectTransform component2 = val3.GetComponent(); component2.anchorMin = new Vector2(0.5f, 0.5f); component2.anchorMax = new Vector2(0.5f, 0.5f); component2.pivot = new Vector2(0.5f, 0.5f); component2.anchoredPosition = Vector2.zero; component2.sizeDelta = Vector2.one * 21f; Image safeImage = AddImageComponent(val3); GameObject val4 = new GameObject("Surrendered", new Type[1] { typeof(RectTransform) }); val4.transform.SetParent(val2.transform, false); RectTransform component3 = val4.GetComponent(); component3.anchorMin = new Vector2(0.5f, 0.5f); component3.anchorMax = new Vector2(0.5f, 0.5f); component3.pivot = new Vector2(0.5f, 0.5f); component3.anchoredPosition = Vector2.zero; component3.sizeDelta = Vector2.one * 21f; Image surrenderImage = AddImageComponent(val4); val2.SetActive(false); s_playerSlots.Add(new PlayerIconSlot { Root = component, BaseImage = baseImage, SafeImage = safeImage, SurrenderImage = surrenderImage }); } } private static void CreateTruckWidget(Transform parent) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0169: 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_0074: 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_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("LastChanceTruckCounter", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); s_truckRoot = val.GetComponent(); if ((Object)(object)s_truckRoot != (Object)null) { s_truckRoot.anchorMin = new Vector2(0.5f, 1f); s_truckRoot.anchorMax = new Vector2(0.5f, 1f); s_truckRoot.pivot = new Vector2(0.5f, 1f); s_truckRoot.anchoredPosition = new Vector2(165f, -1f); s_truckRoot.sizeDelta = new Vector2(64f, 56f); } GameObject val2 = new GameObject("TruckIcon", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform component = val2.GetComponent(); component.anchorMin = new Vector2(0.5f, 1f); component.anchorMax = new Vector2(0.5f, 1f); component.pivot = new Vector2(0.5f, 1f); component.anchoredPosition = new Vector2(0f, 0f); component.sizeDelta = new Vector2(26f, 26f); s_truckIconImage = AddImageComponent(val2); CreateTruckCounterBadge(val.transform); SetTruckCounterText("00 / 00"); } private static void UpdateTruckCounter(int onTruck, int required) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s_truckIconImage != (Object)null) { SetImage(s_truckIconImage, s_truckWhiteSprite, Color.white, enabled: true); } SetTruckCounterText($"{Mathf.Clamp(onTruck, 0, 99):00} / {Mathf.Clamp(required, 0, 99):00}"); } private static void SetTruckCounterText(string text) { if (!((Object)(object)s_truckCounterLabel == (Object)null) && !string.Equals(s_lastTruckCounterText, text, StringComparison.Ordinal)) { s_lastTruckCounterText = text; ((TMP_Text)s_truckCounterLabel).text = text; ((Behaviour)s_truckCounterLabel).enabled = true; ((Component)s_truckCounterLabel).gameObject.SetActive(true); } } private static void CreateTruckCounterBadge(Transform parent) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_003f: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Expected O, but got Unknown //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("TruckCounterBadgeOuter", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0.5f, 1f); component.anchorMax = new Vector2(0.5f, 1f); component.pivot = new Vector2(0.5f, 1f); component.anchoredPosition = new Vector2(0f, -22f); component.sizeDelta = new Vector2(74f, 22f); Image image = AddImageComponent(val); SetImage(image, null, new Color(1f, 0.84f, 0.12f, 1f), enabled: true); GameObject val2 = new GameObject("TruckCounterBadgeInner", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform component2 = val2.GetComponent(); component2.anchorMin = new Vector2(0.5f, 0.5f); component2.anchorMax = new Vector2(0.5f, 0.5f); component2.pivot = new Vector2(0.5f, 0.5f); component2.anchoredPosition = Vector2.zero; component2.sizeDelta = new Vector2(Mathf.Max(1f, 70f), Mathf.Max(1f, 18f)); Image image2 = AddImageComponent(val2); SetImage(image2, null, new Color(0f, 0f, 0f, 0.55f), enabled: true); GameObject val3 = new GameObject("TruckCounterText", new Type[1] { typeof(RectTransform) }); val3.transform.SetParent(val2.transform, false); RectTransform component3 = val3.GetComponent(); component3.anchorMin = new Vector2(0f, 0f); component3.anchorMax = new Vector2(1f, 1f); component3.pivot = new Vector2(0.5f, 0.5f); component3.anchoredPosition = Vector2.zero; component3.sizeDelta = Vector2.zero; s_truckCounterLabel = val3.AddComponent(); SetHintDefaults(s_truckCounterLabel); ((TMP_Text)s_truckCounterLabel).fontSize = 11f; ((Graphic)s_truckCounterLabel).color = new Color(1f, 0.92f, 0.3f, 1f); } private static Image? AddImageComponent(GameObject go) { return go.AddComponent(); } private static void SetImage(Image? image, Sprite? sprite, Color color, bool enabled) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)image == (Object)null)) { image.sprite = sprite; ((Graphic)image).color = color; image.preserveAspect = true; ((Behaviour)image).enabled = enabled; ((Component)image).gameObject.SetActive(enabled); } } private static void EnsureSpritesLoaded() { if (HasAllSprites()) { return; } float unscaledTime = Time.unscaledTime; if (!s_assetLoadAttempted || !(unscaledTime < s_nextAssetRetryAt)) { s_assetLoadAttempted = true; s_nextAssetRetryAt = unscaledTime + 2f; string defaultAssetsDirectory = ImageAssetLoader.GetDefaultAssetsDirectory(); string resolvedPath; if ((Object)(object)s_semibotWhiteSprite == (Object)null) { ImageAssetLoader.TryLoadSprite("SemibotWhite.png", defaultAssetsDirectory, out s_semibotWhiteSprite, out resolvedPath); } if ((Object)(object)s_truckWhiteSprite == (Object)null) { ImageAssetLoader.TryLoadSprite("TruckWhite.png", defaultAssetsDirectory, out s_truckWhiteSprite, out resolvedPath); } if ((Object)(object)s_semibotSurrenderedSprite == (Object)null) { ImageAssetLoader.TryLoadSprite("SemibotSurrendered.png", defaultAssetsDirectory, out s_semibotSurrenderedSprite, out resolvedPath); } if ((Object)(object)s_semibotSafeSprite == (Object)null) { ImageAssetLoader.TryLoadSprite("SemibotSafe.png", defaultAssetsDirectory, out s_semibotSafeSprite, out resolvedPath); } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.UI.Sprites", 30)) { Log.LogDebug((object)($"[LastChance] UI sprites status: semibot={(Object)(object)s_semibotWhiteSprite != (Object)null} truck={(Object)(object)s_truckWhiteSprite != (Object)null} " + $"surrendered={(Object)(object)s_semibotSurrenderedSprite != (Object)null} safe={(Object)(object)s_semibotSafeSprite != (Object)null}")); } } } private static bool HasAllSprites() { return (Object)(object)s_semibotWhiteSprite != (Object)null && (Object)(object)s_truckWhiteSprite != (Object)null && (Object)(object)s_semibotSurrenderedSprite != (Object)null && (Object)(object)s_semibotSafeSprite != (Object)null; } private static void RefreshVisibility(bool force) { if (force || !(Time.unscaledTime < s_nextVisibilityRefreshAt)) { s_nextVisibilityRefreshAt = Time.unscaledTime + 0.5f; KeepAtTopPosition(); UpdateSurrenderHintPosition(); SetEnabled(ShouldBeVisibleNow()); } } private static void KeepAtTopPosition() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)s_rect == (Object)null)) { Vector2 anchoredPosition = s_rect.anchoredPosition; if (!(Mathf.Abs(anchoredPosition.y - -8f) <= 0.01f)) { s_rect.anchoredPosition = new Vector2(anchoredPosition.x, -8f); } } } private static void UpdateSurrenderHintPosition() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s_rect == (Object)null || (Object)(object)s_hintRect == (Object)null) { return; } float surrenderOffsetY = GetSurrenderOffsetY(); Vector2 anchoredPosition = s_hintRect.anchoredPosition; if (Mathf.Abs(anchoredPosition.y - surrenderOffsetY) > 0.01f) { s_hintRect.anchoredPosition = new Vector2(anchoredPosition.x, surrenderOffsetY); } if ((Object)(object)s_playersRoot != (Object)null) { Vector2 anchoredPosition2 = s_playersRoot.anchoredPosition; float playerIconsOffsetY = GetPlayerIconsOffsetY(); if (Mathf.Abs(anchoredPosition2.y - playerIconsOffsetY) > 0.01f) { s_playersRoot.anchoredPosition = new Vector2(anchoredPosition2.x, playerIconsOffsetY); } } } private static float GetSurrenderOffsetY() { return -14.5f; } private static float GetPlayerIconsOffsetY() { return GetSurrenderOffsetY() - 16f; } private static bool ShouldBeVisibleNow() { if (!LastChanceTimerController.IsActive) { return false; } if ((Object)(object)GetPreferredUiParentCached() == (Object)null) { return false; } if ((Object)(object)MenuManager.instance != (Object)null && (Object)(object)MenuManager.instance.currentMenuPage != (Object)null) { return false; } return true; } private static void ReparentToPreferredUiRoot() { Component val = (Component)(object)s_label; if (val != null) { Transform preferredUiParentCached = GetPreferredUiParentCached(); if (!((Object)(object)preferredUiParentCached == (Object)null) && (Object)(object)val.transform.parent != (Object)(object)preferredUiParentCached) { val.transform.SetParent(preferredUiParentCached, false); } } } private static Transform? ResolvePreferredUiParent() { if ((Object)(object)DHHUIManager.instance != (Object)null) { GameObject gameHUD = DHHUIManager.instance.gameHUD; if ((Object)(object)gameHUD != (Object)null) { return gameHUD.transform; } } return ((Object)(object)HUDCanvas.instance != (Object)null) ? ((Component)HUDCanvas.instance).transform : null; } private static Transform? GetPreferredUiParentCached() { if ((Object)(object)s_cachedUiParent != (Object)null) { return s_cachedUiParent; } s_cachedUiParent = ResolvePreferredUiParent(); return s_cachedUiParent; } private static void SetDefaults(TextMeshProUGUI label) { //IL_0002: 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) ((Graphic)label).color = Color.white; ((TMP_Text)label).fontSize = 14f; ((TMP_Text)label).enableAutoSizing = false; ((TMP_Text)label).enableWordWrapping = false; ((TMP_Text)label).richText = true; ((TMP_Text)label).alignment = TopAlignment; } private static void SetHintDefaults(TextMeshProUGUI label) { //IL_0002: 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) ((Graphic)label).color = Color.white; ((TMP_Text)label).fontSize = 12f; ((TMP_Text)label).enableAutoSizing = false; ((TMP_Text)label).enableWordWrapping = false; ((TMP_Text)label).richText = true; ((TMP_Text)label).alignment = CenterAlignment; } private static void SetEnabled(bool enabled) { if (!((Object)(object)s_label == (Object)null) && s_isVisible != enabled) { s_isVisible = enabled; ((Behaviour)s_label).enabled = enabled; ((Component)s_label).gameObject.SetActive(enabled); if ((Object)(object)s_hintLabel != (Object)null) { ((Behaviour)s_hintLabel).enabled = enabled; ((Component)s_hintLabel).gameObject.SetActive(enabled); } LastChanceTimerChangeEffectsModule.SetVisible(enabled); } } private static void TryApplyAbilityCostTextStyle(TextMeshProUGUI? label) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)label == (Object)null) { return; } AbilitySpot[] abilitySpotsCached = GetAbilitySpotsCached(); if (abilitySpotsCached == null || abilitySpotsCached.Length == 0) { return; } for (int i = 0; i < abilitySpotsCached.Length; i++) { TextMeshProUGUI energyCost = abilitySpotsCached[i].energyCost; if (energyCost != null) { ((TMP_Text)label).font = ((TMP_Text)energyCost).font; ((TMP_Text)label).fontSharedMaterial = ((TMP_Text)energyCost).fontSharedMaterial; ((Graphic)label).color = Color.black; break; } } } private static AbilitySpot[] GetAbilitySpotsCached() { return AbilitySpotDiscoveryCache.GetCached(0.5f); } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Spectate { internal static class LastChanceSpectateHelper { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.Spectate"); private const string ForceSpectateLogKey = "LastChance.ForceDeathHeadSpectate"; private const string DebugStateLogKey = "LastChance.SpectateState"; private static bool s_forceComplete; private static DeathHeadController? s_cachedController; private static string? s_lastSpectateDebugMessage; internal static bool AllPlayersDisabled() { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null || instance.PlayerList.Count == 0) { return false; } foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null || player.isDisabled) { continue; } return false; } return true; } internal static void ForceDeathHeadSpectateIfPossible() { if (!FeatureFlags.LastChangeMode || s_forceComplete || !LogLimiter.ShouldLog("LastChance.ForceDeathHeadSpectate", 30) || !DHHFunc.LocalDeathHeadActive()) { return; } PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance != (Object)null && !DHHFunc.IsDeathHeadSpectatable(instance)) { return; } DeathHeadController val = TryGetLocalDeathHeadController(); if ((Object)(object)val == (Object)null) { return; } s_cachedController = val; if (IsSpectated(val)) { s_forceComplete = true; return; } SpectateCamera instance2 = SpectateCamera.instance; if ((Object)(object)instance2 != (Object)null && (Object)(object)PlayerAvatar.instance != (Object)null) { instance2.player = PlayerAvatar.instance; } val.SetSpectated(true); val.UpdateSpectated(); if (IsSpectated(val)) { s_forceComplete = true; } } internal static void ResetForceState() { s_forceComplete = false; } private static bool IsSpectated(DeathHeadController controller) { return controller.spectated; } internal static bool IsDeathHeadSpectated() { DeathHeadController val = s_cachedController ?? TryGetLocalDeathHeadController(); if ((Object)(object)val == (Object)null) { return false; } s_cachedController = val; return IsSpectated(val); } internal static bool IsManualSwitchInputDown() { return SemiFunc.InputDown((InputKey)1) || SemiFunc.InputDown((InputKey)23) || SemiFunc.InputDown((InputKey)24); } internal static void EnsureSpectatePlayerLocal(SpectateCamera spectate) { if (!((Object)(object)spectate == (Object)null)) { PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance != (Object)null) { spectate.player = instance; } } } internal static void DebugLogState(SpectateCamera? spectate) { if (FeatureFlags.DebugLogging) { PlayerAvatar instance = PlayerAvatar.instance; PlayerAvatar val = (((Object)(object)spectate != (Object)null) ? spectate.player : null); bool flag = (Object)(object)val != (Object)null && (Object)(object)instance != (Object)null && val == instance; bool? flag2 = null; bool? flag3 = null; bool? flag4 = null; flag2 = DHHFunc.LocalDeathHeadActive(); if ((Object)(object)instance != (Object)null) { flag3 = DHHFunc.IsDeathHeadSpectatable(instance); } DeathHeadController val2 = s_cachedController ?? TryGetLocalDeathHeadController(); if ((Object)(object)val2 != (Object)null) { s_cachedController = val2; flag4 = val2.spectated; } string arg = (((Object)(object)val != (Object)null) ? ((object)val).GetType().Name : "null"); string arg2 = (((Object)(object)instance != (Object)null) ? ((object)instance).GetType().Name : "null"); string text = $"[LastChance] SpectateState: spectatePlayer={arg} local={arg2} isSpectateLocal={flag} " + $"DHH.LocalActive={flag2} DHH.Spectatable={flag3} DHH.Spectated={flag4}"; if (!string.Equals(s_lastSpectateDebugMessage, text, StringComparison.Ordinal)) { s_lastSpectateDebugMessage = text; Log.LogDebug((object)text); } } } internal static bool ShouldForceLocalDeathHeadSpectate() { if (FeatureFlags.SpectateDeadPlayers) { string text = (FeatureFlags.SpectateDeadPlayersMode ?? string.Empty).Trim(); if (text.Equals("Always", StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } private static DeathHeadController? TryGetLocalDeathHeadController() { PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerDeathHead == (Object)null) { return null; } return DHHFunc.GetLocalDeathHeadController(); } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Runtime { internal static class LastChanceHarmonyPatchRegistry { private static readonly Type[] PatchTypes = new Type[53] { typeof(CompatibilityGateStartHookPatch), typeof(StatsManagerSaveFileDeleteLastChancePatch), typeof(MenuPageSavesOnDeleteGameLastChancePatch), typeof(AllPlayersDeadGuard.RunManagerUpdateAllPlayersDeadPatch), typeof(AllPlayersDeadGuard.RunManagerChangeLevelPatch), typeof(LastChanceHeadEyesOverrideBypassModule), typeof(LastChanceHeadPupilVisualModule), typeof(RunManagerUpdateLastChanceTimerPatch), typeof(AbilitySpotAwakeRegistryPatch), typeof(LastChanceEnemyAwakeRegistryPatch), typeof(LastChanceEnemyAnimalAwakeRegistryPatch), typeof(LastChancePlayerVoiceChatAwakeRegistryPatch), typeof(LastChancePlayerVoiceChatDestroyRegistryPatch), typeof(LastChanceMonstersVoiceEnemyOnlyModule), typeof(LastChanceMonstersBodyPositionProxyModule), typeof(LastChanceMonstersVisionModule), typeof(LastChanceMonstersVisionAnchorProxyModule), typeof(LastChanceMonstersHurtColliderHeadProxyModule), typeof(LastChanceMonstersOnScreenCameraModule), typeof(LastChanceMonstersOnScreenSafeLookupPatch), typeof(LastChanceMonstersTriggerAttackModule), typeof(LastChanceMonstersVisualCouplingDecoupleModule.EnemyHeartHuggerPlayersInGasLogicPatch), typeof(LastChanceMonstersVisualCouplingDecoupleModule.EnemyHeartHuggerGasCheckerUpdatePatch), typeof(LastChanceMonstersVisualCouplingDecoupleModule.PlayerAvatarUpgradeTumbleWingsVisualsActivePatch), typeof(LastChanceMonstersPlayerVisionCheckModule.SemiFuncPlayerVisionCheckPatch), typeof(LastChanceMonstersPlayerVisionCheckModule.SemiFuncPlayerVisionCheckPositionPatch), typeof(LastChanceMonstersNoiseAggroModule), typeof(LastChanceMonstersHeadPlayerProxyColliderModule), typeof(LastChanceMonstersGasGuiderHeadProxyModule), typeof(LastChanceMonstersAnimalWreakHavocHeadRoomProxyModule), typeof(LastChanceMonstersThinManStandModule), typeof(LastChanceMonstersSharedPlayerSearchModule), typeof(LastChanceMonstersEffectiveTargetPointModule), typeof(LastChanceMonstersChaseNavmeshProxyModule), typeof(LastChanceMonstersDeathTimerBonusModule), typeof(LastChanceMonstersGasCaptureModule), typeof(LastChanceMonstersDisabledOverrideModule), typeof(LastChanceMonstersAnimalHeadVisionFallbackModule), typeof(LastChanceMonstersSharedChaseTargetPointModule), typeof(LastChanceMonstersGasVictimPositionModule), typeof(LastChanceMonstersCarryTargetPositionModule), typeof(LastChanceMonstersHiddenDestinationModule), typeof(LastChanceMonstersCarryProxyModule), typeof(LastChanceMonstersPathBlockingModule), typeof(LastChanceMonstersSpinnyLockBridgeModule), typeof(LastChanceMonstersHeadGrabberReleaseHeadModule), typeof(LastChanceMonstersBeamerHeadAimModule), typeof(LastChanceMonstersUpscreamHeadAimModule), typeof(LastChanceMonstersValuableThrowerHeadAimModule), typeof(LastChanceMonstersBirthdayBoyBalloonHeadProxyModule), typeof(LastChanceMonstersCameraForceLockModule), typeof(LastChanceMonstersSlowMouthHeadOnlyModule), typeof(LastChanceMonstersBehaviorDebugProbeModule) }; internal static void ApplyAll(Harmony harmony, ManualLogSource? log) { if (harmony == null) { return; } int num = 0; for (int i = 0; i < PatchTypes.Length; i++) { Type type = PatchTypes[i]; try { num += harmony.CreateClassProcessor(type).Patch()?.Count ?? 0; } catch (Exception ex) { if (log != null) { log.LogWarning((object)("[LastChance] Patch registry failed for " + type.FullName + ": " + ex.GetType().Name)); } } } if (FeatureFlags.DebugLogging && log != null) { log.LogInfo((object)$"[LastChance] Explicit patch registry applied. types={PatchTypes.Length} methods={num}."); } } } [HarmonyPatch(typeof(PlayerEyes), "Override")] internal static class LastChanceHeadEyesOverrideBypassModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.Eyes"); internal static void ResetRuntimeState() { } [HarmonyPrefix] private static bool Prefix(PlayerEyes __instance, GameObject _obj) { if ((Object)(object)__instance == (Object)null) { DebugLog("Override.Skip.NoEyes", "eyesNull=true"); return true; } PlayerAvatar playerAvatar = __instance.playerAvatar; if (!LastChancePupilGate.TryGetEligibleHead(playerAvatar, out PlayerDeathHead head, out string reason)) { DebugLog("Override.Skip.Gate", $"reason={reason} playerId={GetPlayerId(playerAvatar)}"); return true; } if ((Object)(object)head == (Object)null) { DebugLog("Override.Skip.NoHeadAfterGate", $"playerId={GetPlayerId(playerAvatar)}"); return true; } if (head.spectated) { DebugLog("Override.Pass.Spectated", $"playerId={GetPlayerId(playerAvatar)}"); return true; } if ((Object)(object)_obj == (Object)(object)((Component)head).gameObject) { DebugLog("Override.Block.HeadSelf", $"playerId={GetPlayerId(playerAvatar)}"); return false; } DebugLog("Override.Pass.OtherObj", string.Format("playerId={0} obj={1}", GetPlayerId(playerAvatar), ((Object)(object)_obj != (Object)null) ? ((Object)_obj).name : "null")); return true; } private static void DebugLog(string reason, string detail) { if (FeatureFlags.DebugLogging && InternalDebugFlags.DebugLastChanceEyesFlow && LogLimiter.ShouldLog("LastChance.Eyes.Override." + reason, 90)) { Log.LogInfo((object)("[LastChance][EyesOverride][" + reason + "] " + detail)); } } private static int GetPlayerId(PlayerAvatar? player) { if ((Object)(object)player == (Object)null) { return -1; } return ((Object)(object)player.photonView != (Object)null) ? player.photonView.ViewID : ((Object)player).GetInstanceID(); } } [HarmonyPatch(typeof(PlayerDeathHead), "Update")] internal static class LastChanceHeadPupilVisualModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.Eyes"); private static readonly Dictionary LastEyeColorByHeadId = new Dictionary(); internal static void ResetRuntimeState() { LastEyeColorByHeadId.Clear(); } [HarmonyPostfix] private static void Postfix(PlayerDeathHead __instance) { if (!((Object)(object)__instance == (Object)null)) { PlayerAvatar playerAvatar = __instance.playerAvatar; if (!LastChancePupilGate.TryGetEligibleHead(playerAvatar, out PlayerDeathHead _, out string reason)) { DebugLog("Skip.Gate", $"reason={reason} playerId={GetPlayerId(playerAvatar)}"); return; } ForcePupilsVisible(__instance); ForcePupilOverlayVisible(__instance); ForceEyeLookPipeline(__instance); } } private static void ForcePupilsVisible(PlayerDeathHead head) { //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_009a: 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_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) Transform pupilScaleTransformRight = head.pupilScaleTransformRight; Transform pupilScaleTransformLeft = head.pupilScaleTransformLeft; if ((Object)(object)pupilScaleTransformRight == (Object)null || (Object)(object)pupilScaleTransformLeft == (Object)null) { DebugLog("Pupil.Visible.MissingTransforms", $"headId={((Object)head).GetInstanceID()}"); return; } Vector3 pupilScaleDefault = head.pupilScaleDefault; if (!((Component)pupilScaleTransformRight).gameObject.activeSelf) { ((Component)pupilScaleTransformRight).gameObject.SetActive(true); } if (!((Component)pupilScaleTransformLeft).gameObject.activeSelf) { ((Component)pupilScaleTransformLeft).gameObject.SetActive(true); } pupilScaleTransformRight.localScale = pupilScaleDefault; pupilScaleTransformLeft.localScale = pupilScaleDefault; DebugLog("Pupil.Visible.Forced", $"headId={((Object)head).GetInstanceID()} leftActive={((Component)pupilScaleTransformLeft).gameObject.activeSelf} rightActive={((Component)pupilScaleTransformRight).gameObject.activeSelf} leftScale={pupilScaleTransformLeft.localScale} rightScale={pupilScaleTransformRight.localScale}"); } private static void ForcePupilOverlayVisible(PlayerDeathHead head) { //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) Material pupilMaterial = head.pupilMaterial; if ((Object)(object)pupilMaterial == (Object)null) { DebugLog("Pupil.Overlay.MissingMaterial", $"headId={((Object)head).GetInstanceID()}"); return; } int eyeMaterialAmount = head.eyeMaterialAmount; int eyeMaterialColor = head.eyeMaterialColor; pupilMaterial.SetFloat(eyeMaterialAmount, 1f); Material eyeMaterial = head.eyeMaterial; int instanceID = ((Object)head).GetInstanceID(); if ((Object)(object)eyeMaterial != (Object)null) { Color color = eyeMaterial.GetColor(eyeMaterialColor); if (!LastEyeColorByHeadId.TryGetValue(instanceID, out var value) || !ApproximatelyEqual(value, color)) { LastEyeColorByHeadId[instanceID] = color; Color oppositeColor = GetOppositeColor(color); pupilMaterial.SetColor(eyeMaterialColor, oppositeColor); DebugLog("Pupil.Color.SyncedOnEyeChange", $"headId={instanceID} eyeColor={color} pupilColor={oppositeColor}"); } } float @float = pupilMaterial.GetFloat(eyeMaterialAmount); Color color2 = pupilMaterial.GetColor(eyeMaterialColor); DebugLog("Pupil.Overlay.Forced", $"headId={instanceID} amountPropertyId={eyeMaterialAmount} amount={@float:F3} colorPropertyId={eyeMaterialColor} color={color2}"); MeshRenderer[] pupilRenderers = head.pupilRenderers; if (pupilRenderers == null || pupilRenderers.Length == 0) { DebugLog("Pupil.Renderers.Missing", $"headId={((Object)head).GetInstanceID()}"); return; } for (int i = 0; i < pupilRenderers.Length; i++) { MeshRenderer val = pupilRenderers[i]; if ((Object)(object)val == (Object)null) { DebugLog("Pupil.Renderers.Null", $"headId={((Object)head).GetInstanceID()} idx={i}"); continue; } DebugLog("Pupil.Renderers.State", $"headId={((Object)head).GetInstanceID()} idx={i} rendererEnabled={((Renderer)val).enabled} activeSelf={((Component)val).gameObject.activeSelf} activeInHierarchy={((Component)val).gameObject.activeInHierarchy}"); } } private static void ForceEyeLookPipeline(PlayerDeathHead head) { PlayerEyes playerEyes = head.playerEyes; if ((Object)(object)playerEyes == (Object)null) { DebugLog("Eyes.MissingPlayerEyes", $"headId={((Object)head).GetInstanceID()}"); } else if (!((Behaviour)playerEyes).enabled) { ((Behaviour)playerEyes).enabled = true; DebugLog("Eyes.Enabled.Forced", $"headId={((Object)head).GetInstanceID()}"); } else { DebugLog("Eyes.Enabled.Already", $"headId={((Object)head).GetInstanceID()}"); } } private static void DebugLog(string reason, string detail) { if (FeatureFlags.DebugLogging && InternalDebugFlags.DebugLastChanceEyesFlow && LogLimiter.ShouldLog("LastChance.Eyes." + reason, 90)) { Log.LogInfo((object)("[LastChance][Eyes][" + reason + "] " + detail)); } } private static int GetPlayerId(PlayerAvatar? player) { if ((Object)(object)player == (Object)null) { return -1; } return ((Object)(object)player.photonView != (Object)null) ? player.photonView.ViewID : ((Object)player).GetInstanceID(); } private static Color GetOppositeColor(Color source) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) return new Color(1f - source.r, 1f - source.g, 1f - source.b, source.a); } private static bool ApproximatelyEqual(Color a, Color b) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) return Mathf.Abs(a.r - b.r) < 0.001f && Mathf.Abs(a.g - b.g) < 0.001f && Mathf.Abs(a.b - b.b) < 0.001f && Mathf.Abs(a.a - b.a) < 0.001f; } } internal static class LastChancePupilGate { internal static bool IsEnabled() { return FeatureFlags.LastChancePupilVisualsEnabled && LastChanceRuntimeOrchestrator.IsRuntimeActive; } internal static bool TryGetEligibleHead(PlayerAvatar? player, out PlayerDeathHead? head, out string reason) { head = null; if ((Object)(object)player == (Object)null) { reason = "NoPlayer"; return false; } if (!IsEnabled()) { reason = "GateDisabled"; return false; } if (!LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player)) { reason = "NoHeadProxy"; return false; } head = player.playerDeathHead; if ((Object)(object)head == (Object)null) { reason = "NoHead"; return false; } if (!head.triggered) { reason = "NotTriggered"; return false; } reason = "Allowed"; return true; } } internal static class LastChanceRuntimeObjectRegistry { private static readonly List AbilitySpots = new List(); private static readonly HashSet AbilitySpotIds = new HashSet(); private static readonly List Enemies = new List(); private static readonly HashSet EnemyIds = new HashSet(); private static readonly List EnemyAnimals = new List(); private static readonly HashSet EnemyAnimalIds = new HashSet(); private static readonly List PlayerVoiceChats = new List(); private static readonly HashSet PlayerVoiceChatIds = new HashSet(); internal static void RegisterAbilitySpot(AbilitySpot? spot) { Register(AbilitySpots, AbilitySpotIds, spot); } internal static void UnregisterAbilitySpot(AbilitySpot? spot) { Unregister(AbilitySpots, AbilitySpotIds, spot); } internal static AbilitySpot[] GetAbilitySpotsSnapshot() { return Snapshot(AbilitySpots, AbilitySpotIds); } internal static void RegisterEnemy(Enemy? enemy) { Register(Enemies, EnemyIds, enemy); } internal static void UnregisterEnemy(Enemy? enemy) { Unregister(Enemies, EnemyIds, enemy); } internal static Enemy[] GetEnemiesSnapshot() { return Snapshot(Enemies, EnemyIds); } internal static void RegisterEnemyAnimal(EnemyAnimal? enemyAnimal) { Register(EnemyAnimals, EnemyAnimalIds, enemyAnimal); } internal static void UnregisterEnemyAnimal(EnemyAnimal? enemyAnimal) { Unregister(EnemyAnimals, EnemyAnimalIds, enemyAnimal); } internal static EnemyAnimal[] GetEnemyAnimalsSnapshot() { return Snapshot(EnemyAnimals, EnemyAnimalIds); } internal static void RegisterPlayerVoiceChat(PlayerVoiceChat? voiceChat) { Register(PlayerVoiceChats, PlayerVoiceChatIds, voiceChat); } internal static void UnregisterPlayerVoiceChat(PlayerVoiceChat? voiceChat) { Unregister(PlayerVoiceChats, PlayerVoiceChatIds, voiceChat); } internal static PlayerVoiceChat[] GetPlayerVoiceChatsSnapshot() { return Snapshot(PlayerVoiceChats, PlayerVoiceChatIds); } internal static void ClearAbilitySpots() { AbilitySpots.Clear(); AbilitySpotIds.Clear(); } internal static void ClearEnemies() { Enemies.Clear(); EnemyIds.Clear(); EnemyAnimals.Clear(); EnemyAnimalIds.Clear(); } internal static void ClearVoiceChats() { PlayerVoiceChats.Clear(); PlayerVoiceChatIds.Clear(); } internal static void ClearAll() { ClearAbilitySpots(); ClearEnemies(); ClearVoiceChats(); } internal static void ResetForRoomExit() { ClearAll(); } internal static void ResetForSceneChange() { ClearAll(); RepopulateFromKnownManagers(); } internal static void ResetForRuntimeDeactivated() { ClearAll(); RepopulateFromKnownManagers(); } private static void RepopulateFromKnownManagers() { DHHAbilityManager instance = DHHAbilityManager.instance; if ((Object)(object)instance != (Object)null) { AbilitySpot[] abilitySpots = instance.abilitySpots; if (abilitySpots != null) { for (int i = 0; i < abilitySpots.Length; i++) { RegisterAbilitySpot(abilitySpots[i]); } } } List list = EnemyDirector.instance?.enemiesSpawned; if (list != null) { EnemyAnimal enemyAnimal = default(EnemyAnimal); for (int j = 0; j < list.Count; j++) { Enemy val = list[j]?.Enemy; RegisterEnemy(val); if ((Object)(object)val != (Object)null && ((Component)val).TryGetComponent(ref enemyAnimal)) { RegisterEnemyAnimal(enemyAnimal); } } } List list2 = RunManager.instance?.voiceChats; if (list2 != null) { for (int k = 0; k < list2.Count; k++) { RegisterPlayerVoiceChat(list2[k]); } } } private static void Register(List items, HashSet ids, T? item) where T : Object { if (!((Object)(object)item == (Object)null)) { int instanceID = ((Object)item).GetInstanceID(); if (ids.Add(instanceID)) { items.Add(item); } } } private static void Unregister(List items, HashSet ids, T? item) where T : Object { if ((Object)(object)item == (Object)null) { return; } int instanceID = ((Object)item).GetInstanceID(); if (!ids.Remove(instanceID)) { return; } for (int num = items.Count - 1; num >= 0; num--) { T val = items[num]; if ((Object)(object)val == (Object)null || ((Object)val).GetInstanceID() == instanceID) { items.RemoveAt(num); } } } private static T[] Snapshot(List items, HashSet ids) where T : Object { for (int num = items.Count - 1; num >= 0; num--) { T val = items[num]; if (!((Object)(object)val != (Object)null)) { items.RemoveAt(num); } } ids.Clear(); for (int i = 0; i < items.Count; i++) { ids.Add(((Object)items[i]).GetInstanceID()); } return items.ToArray(); } } internal enum LastChanceRuntimeState { Inactive, Arming, Active, Cooldown, Teardown } internal static class LastChanceRuntimeOrchestrator { private static readonly Dictionary s_behaviorCooldownUntil = new Dictionary(); private static LastChanceRuntimeState s_state = LastChanceRuntimeState.Inactive; private static string s_lastTransitionReason = "init"; private static float s_lastTransitionAt; internal static LastChanceRuntimeState State => s_state; internal static bool IsRuntimeActive => s_state == LastChanceRuntimeState.Active; internal static string LastTransitionReason => s_lastTransitionReason; internal static float LastTransitionAt => s_lastTransitionAt; internal static void EnterActiveRuntime() { s_state = LastChanceRuntimeState.Active; s_lastTransitionReason = "enter-active"; s_lastTransitionAt = Time.realtimeSinceStartup; } internal static void ExitRuntime(string reason) { s_state = LastChanceRuntimeState.Teardown; s_lastTransitionReason = (string.IsNullOrWhiteSpace(reason) ? "exit-runtime" : reason.Trim()); s_lastTransitionAt = Time.realtimeSinceStartup; s_behaviorCooldownUntil.Clear(); s_state = LastChanceRuntimeState.Inactive; } internal static void OnLevelTransition() { s_state = LastChanceRuntimeState.Teardown; s_lastTransitionReason = "level-transition"; s_lastTransitionAt = Time.realtimeSinceStartup; s_behaviorCooldownUntil.Clear(); s_state = LastChanceRuntimeState.Inactive; } internal static bool CanApplyMonsterBehavior(string kind, int sourceId) { if (!IsRuntimeActive) { return false; } string key = BuildKey(kind, sourceId); if (!s_behaviorCooldownUntil.TryGetValue(key, out var value)) { return true; } return Time.unscaledTime >= value; } internal static void SetMonsterBehaviorCooldown(string kind, int sourceId, float cooldownSeconds) { string key = BuildKey(kind, sourceId); if (cooldownSeconds <= 0f) { s_behaviorCooldownUntil.Remove(key); } else { s_behaviorCooldownUntil[key] = Time.unscaledTime + cooldownSeconds; } } private static string BuildKey(string kind, int sourceId) { string arg = (string.IsNullOrWhiteSpace(kind) ? "unknown" : kind.Trim()); return $"{arg}:{sourceId}"; } } internal sealed class LastChanceSurrenderNetwork : MonoBehaviourPunCallbacks, IOnEventCallback { private static LastChanceSurrenderNetwork? s_instance; private static int s_messageSeq; private static float s_lastTruckHintSentAt; private static int s_lastTruckHintRoomHash; private static int s_lastTruckHintLevelStamp = -1; private const float TruckHintBroadcastIntervalSeconds = 0.5f; private const string LastChanceSurrenderMessageType = "LastChanceSurrender"; private const string LastChanceTimerStateMessageType = "LastChanceTimerState"; private const string LastChanceDirectionPenaltyRequestMessageType = "LastChanceDirectionPenaltyRequest"; private const string LastChanceUiStateMessageType = "LastChanceUiState"; private const string LastChancePlayerTruckHintMessageType = "LastChancePlayerTruckHint"; private const string LastChanceSurrenderSnapshotMessageType = "LastChanceSurrenderSnapshot"; internal static void EnsureCreated() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (!((Object)(object)s_instance != (Object)null)) { GameObject val = new GameObject("DHHFix.LastChanceSurrender"); Object.DontDestroyOnLoad((Object)(object)val); s_instance = val.AddComponent(); } } internal static void NotifyLocalSurrender(int actorNumber) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom && actorNumber > 0) { EnsureCreated(); RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)80, (object)CreateEnvelope("LastChanceSurrender", actorNumber).ToEventPayload(), val, SendOptions.SendReliable); } } internal static void NotifyTimerState(bool active, float secondsRemaining, double hostSentAt) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { EnsureCreated(); RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)81, (object)CreateEnvelope("LastChanceTimerState", new object[3] { active, secondsRemaining, hostSentAt }).ToEventPayload(), val, SendOptions.SendReliable); } } internal static void NotifySurrenderSnapshot(object[] surrenderedActorsPayload) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { EnsureCreated(); RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)80, (object)CreateEnvelope("LastChanceSurrenderSnapshot", surrenderedActorsPayload ?? Array.Empty()).ToEventPayload(), val, SendOptions.SendReliable); } } internal static void NotifyDirectionPenaltyRequest() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { EnsureCreated(); RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)2 }; PhotonNetwork.RaiseEvent((byte)82, (object)CreateEnvelope("LastChanceDirectionPenaltyRequest", null).ToEventPayload(), val, SendOptions.SendReliable); } } internal static void NotifyUiState(int requiredOnTruck, object[] playerStatesPayload) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom) { EnsureCreated(); RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)83, (object)CreateEnvelope("LastChanceUiState", new object[2] { requiredOnTruck, playerStatesPayload }).ToEventPayload(), val, SendOptions.SendReliable); } } internal static void TryBroadcastLocalPlayerTruckHint() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.InRoom || SemiFunc.IsMasterClient() || PhotonNetwork.LocalPlayer == null) { return; } EnsureCreated(); if (PlayerTruckDistanceHelper.TryBuildLocalPlayerTruckHint(out var roomHash, out var heightDelta, out var levelStamp)) { bool flag = roomHash != s_lastTruckHintRoomHash || levelStamp != s_lastTruckHintLevelStamp; bool flag2 = Time.unscaledTime - s_lastTruckHintSentAt >= 0.5f; if (flag || flag2) { RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)2 }; PhotonNetwork.RaiseEvent((byte)86, (object)CreateEnvelope("LastChancePlayerTruckHint", new object[4] { PhotonNetwork.LocalPlayer.ActorNumber, roomHash, heightDelta, levelStamp }).ToEventPayload(), val, SendOptions.SendUnreliable); s_lastTruckHintSentAt = Time.unscaledTime; s_lastTruckHintRoomHash = roomHash; s_lastTruckHintLevelStamp = levelStamp; } } } public override void OnEnable() { ((MonoBehaviourPunCallbacks)this).OnEnable(); PhotonNetwork.AddCallbackTarget((object)this); } public override void OnDisable() { ((MonoBehaviourPunCallbacks)this).OnDisable(); PhotonNetwork.RemoveCallbackTarget((object)this); } public override void OnMasterClientSwitched(Player newMasterClient) { ((MonoBehaviourPunCallbacks)this).OnMasterClientSwitched(newMasterClient); LastChanceTimerController.SuppressForCurrentRoom("[LastChance] Master client switched; disabling LastChance and related runtime features for room safety."); } public override void OnJoinedRoom() { ((MonoBehaviourPunCallbacks)this).OnJoinedRoom(); LastChanceTimerController.ClearRoomSuppression(); if (PhotonNetwork.IsMasterClient) { LastChanceTimerController.ForceBroadcastRuntimeSnapshotForSync(); } } public override void OnPlayerEnteredRoom(Player newPlayer) { ((MonoBehaviourPunCallbacks)this).OnPlayerEnteredRoom(newPlayer); if (PhotonNetwork.IsMasterClient && newPlayer != null) { LastChanceTimerController.ForceBroadcastRuntimeSnapshotForSync(); } } public override void OnLeftRoom() { ((MonoBehaviourPunCallbacks)this).OnLeftRoom(); LastChanceTimerController.ClearRoomSuppression(); } public void OnEvent(EventData photonEvent) { if (photonEvent.Code == 81) { Player masterClient = PhotonNetwork.MasterClient; int num = ((masterClient != null) ? masterClient.ActorNumber : (-1)); if (num <= 0 || photonEvent.Sender != num || !NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope) || !envelope.IsExpectedSource() || !string.Equals(envelope.MessageType, "LastChanceTimerState", StringComparison.Ordinal) || !(envelope.Payload is object[] array) || array.Length < 3) { return; } object obj = array[0]; if (!(obj is bool)) { return; } bool active = (bool)obj; obj = array[1]; if (!(obj is float)) { return; } float secondsRemaining = (float)obj; obj = array[2]; if (obj is double) { double hostSentAt = (double)obj; if (true) { LastChanceTimerController.ApplyNetworkTimerState(active, secondsRemaining, hostSentAt); } } } else if (photonEvent.Code == 82) { Player masterClient2 = PhotonNetwork.MasterClient; int num2 = ((masterClient2 != null) ? masterClient2.ActorNumber : (-1)); if (num2 > 0 && PhotonNetwork.IsMasterClient && photonEvent.Sender > 0 && photonEvent.Sender != num2 && NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope2) && envelope2.IsExpectedSource() && string.Equals(envelope2.MessageType, "LastChanceDirectionPenaltyRequest", StringComparison.Ordinal)) { LastChanceTimerController.HandleDirectionPenaltyRequest(photonEvent.Sender); } } else if (photonEvent.Code == 83) { Player masterClient3 = PhotonNetwork.MasterClient; int num3 = ((masterClient3 != null) ? masterClient3.ActorNumber : (-1)); if (num3 <= 0 || photonEvent.Sender != num3 || !NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope3) || !envelope3.IsExpectedSource() || !string.Equals(envelope3.MessageType, "LastChanceUiState", StringComparison.Ordinal) || !(envelope3.Payload is object[] array2) || array2.Length < 2) { return; } object obj = array2[0]; if (obj is int) { int requiredOnTruck = (int)obj; if (array2[1] is object[] statesPayload) { LastChanceTimerController.ApplyNetworkUiState(requiredOnTruck, statesPayload, photonEvent.Sender); } } } else if (photonEvent.Code == 86) { Player masterClient4 = PhotonNetwork.MasterClient; int num4 = ((masterClient4 != null) ? masterClient4.ActorNumber : (-1)); if (num4 <= 0 || !PhotonNetwork.IsMasterClient || photonEvent.Sender <= 0 || !NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope4) || !envelope4.IsExpectedSource() || !string.Equals(envelope4.MessageType, "LastChancePlayerTruckHint", StringComparison.Ordinal) || !(envelope4.Payload is object[] array3) || array3.Length < 4) { return; } object obj = array3[0]; if (!(obj is int)) { return; } int num5 = (int)obj; if (num5 != photonEvent.Sender) { return; } obj = array3[1]; if (!(obj is int)) { return; } int roomHash = (int)obj; obj = array3[2]; if (!(obj is float)) { return; } float heightDelta = (float)obj; obj = array3[3]; if (obj is int) { int levelStamp = (int)obj; if (true) { PlayerTruckDistanceHelper.ApplyRemotePlayerHint(num5, roomHash, heightDelta, levelStamp); } } } else { if (photonEvent.Code != 80) { return; } Player masterClient5 = PhotonNetwork.MasterClient; int num6 = ((masterClient5 != null) ? masterClient5.ActorNumber : (-1)); if (num6 <= 0 || !NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope5) || !envelope5.IsExpectedSource() || (!string.Equals(envelope5.MessageType, "LastChanceSurrender", StringComparison.Ordinal) && !string.Equals(envelope5.MessageType, "LastChanceSurrenderSnapshot", StringComparison.Ordinal))) { return; } if (string.Equals(envelope5.MessageType, "LastChanceSurrenderSnapshot", StringComparison.Ordinal)) { if (photonEvent.Sender == num6) { LastChanceTimerController.ApplyRemoteSurrenderSnapshot((envelope5.Payload as object[]) ?? Array.Empty()); } return; } int num7 = 0; if (envelope5.Payload is int num8) { num7 = num8; } else if (envelope5.Payload is object[] array4 && array4.Length != 0 && array4[0] is int num9) { num7 = num9; } if (num7 <= 0) { return; } if (PhotonNetwork.IsMasterClient) { if (photonEvent.Sender > 0 && photonEvent.Sender != num6 && num7 == photonEvent.Sender) { LastChanceTimerController.RegisterRemoteSurrender(num7); } } else if (photonEvent.Sender == num6) { LastChanceTimerController.RegisterRemoteSurrender(num7); } } } private static NetworkEnvelope CreateEnvelope(string messageType, object? payload) { return new NetworkEnvelope("DHHFLastChanceMode", 1, messageType, ++s_messageSeq, payload); } } [HarmonyPatch(typeof(RunManager), "Update")] internal static class RunManagerUpdateLastChanceTimerPatch { [HarmonyPostfix] private static void Postfix() { LastChanceTimerController.Tick(); } } internal static class LastChanceTimerController { private enum LastChanceIndicatorMode { None, Direction } private enum IndicatorKind { Direction = 1 } private enum TimerChangeReason { CountdownTick, MonsterKillBonus, DirectionPenalty, NetworkSync } private readonly struct NetworkUiPlayerState { internal bool IsInTruck { get; } internal bool IsSurrendered { get; } internal NetworkUiPlayerState(bool isInTruck, bool isSurrendered) { IsInTruck = isInTruck; IsSurrendered = isSurrendered; } } private readonly struct DynamicTimerInputs { internal int RequiredPlayers { get; } internal int LevelNumber { get; } internal int AliveSearchMonsters { get; } internal float TotalDistanceMeters { get; } internal int PlayersBelowTruckThreshold { get; } internal float TotalBelowTruckMeters { get; } internal int TotalShortestRoomPathSteps { get; } internal DynamicTimerInputs(int requiredPlayers, int levelNumber, int aliveSearchMonsters, float totalDistanceMeters, int playersBelowTruckThreshold, float totalBelowTruckMeters, int totalShortestRoomPathSteps) { RequiredPlayers = requiredPlayers; LevelNumber = levelNumber; AliveSearchMonsters = aliveSearchMonsters; TotalDistanceMeters = totalDistanceMeters; PlayersBelowTruckThreshold = playersBelowTruckThreshold; TotalBelowTruckMeters = totalBelowTruckMeters; TotalShortestRoomPathSteps = totalShortestRoomPathSteps; } } private readonly struct DynamicTimerProfileSnapshot { internal float TotalMs { get; } internal float MonstersMs { get; } internal float RecordsMs { get; } internal float SelectMs { get; } internal float HeightRefreshMs { get; } internal float AggregateMs { get; } internal int RecordsCount { get; } internal int SelectedCount { get; } internal int RequiredPlayers { get; } internal int LevelNumber { get; } internal int AliveMonsters { get; } internal DynamicTimerProfileSnapshot(float totalMs, float monstersMs, float recordsMs, float selectMs, float heightRefreshMs, float aggregateMs, int recordsCount, int selectedCount, int requiredPlayers, int levelNumber, int aliveMonsters) { TotalMs = totalMs; MonstersMs = monstersMs; RecordsMs = recordsMs; SelectMs = selectMs; HeightRefreshMs = heightRefreshMs; AggregateMs = aggregateMs; RecordsCount = recordsCount; SelectedCount = selectedCount; RequiredPlayers = requiredPlayers; LevelNumber = levelNumber; AliveMonsters = aliveMonsters; } } private readonly struct ActivationStartPhaseProfileSnapshot { internal float TotalMs { get; } internal float SetActiveMs { get; } internal float InitialTimerMs { get; } internal float CaptureCurrencyMs { get; } internal float EnsureNetworkMs { get; } internal float ShowUiMs { get; } internal float ClearStateMs { get; } internal float BroadcastMs { get; } internal float DebugExtrasMs { get; } internal ActivationStartPhaseProfileSnapshot(float totalMs, float setActiveMs, float initialTimerMs, float captureCurrencyMs, float ensureNetworkMs, float showUiMs, float clearStateMs, float broadcastMs, float debugExtrasMs) { TotalMs = totalMs; SetActiveMs = setActiveMs; InitialTimerMs = initialTimerMs; CaptureCurrencyMs = captureCurrencyMs; EnsureNetworkMs = ensureNetworkMs; ShowUiMs = showUiMs; ClearStateMs = clearStateMs; BroadcastMs = broadcastMs; DebugExtrasMs = debugExtrasMs; } } private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.Runtime"); private const string LogKey = "LastChance.Timer"; private const string TimerSecondAudioFileName = "TimerSecond.mp3"; private const string TimerWarningAudioPrimaryFileName = "TimerWarning.mp3"; private static float s_timerRemaining; private static bool s_active; private static int s_baseCurrency; private static bool s_currencyCaptured; private static readonly Color TimerColor = new Color(1f, 0.85f, 0.1f, 1f); private static readonly Color FlashColor = new Color(1f, 0.2f, 0.2f, 1f); private static readonly InputKey SurrenderInputKey = (InputKey)12; private const string SurrenderHintPromptFormat = "Back to truck, hop hop! (Surrender [{0}])"; private const string SurrenderCountdownFormat = "Surrender in {0}s"; private const string SurrenderedHintText = "Surrendered <3"; private const string LocalSurrenderedHintText = "You surrendered <3"; private const string IndicatorLogKey = "LastChance.Indicator"; private const string IndicatorCooldownLogKey = "LastChance.Indicator.Cooldown"; private static readonly Vector2 DirectionLineScrollSpeed = new Vector2(4f, 0f); private const float DirectionLineHeightOffset = 0.2f; private const float DirectionPathRefreshSeconds = 0.4f; private const float DirectionPathMovementThresholdSqr = 0.64f; private const float DirectionIndicatorHoldSeconds = 1f; private const float DirectionIndicatorMinimumTimerSeconds = 30f; private static readonly HashSet LastChanceSurrenderedPlayers = new HashSet(); private static float s_surrenderHoldTimer; private static bool s_localSurrendered; private static bool s_surrenderDistanceLogged; private static float s_directionCooldownUntil; private static float s_directionActiveUntil; private static bool s_directionActive; private static float s_directionHoldTimer; private static bool s_indicatorNoneLoggedThisCycle; private static GameObject? s_indicatorDirectionObject; private static LineRenderer? s_indicatorDirectionLine; private static Material? s_indicatorDirectionMaterial; private static float s_indicatorNextPathRefreshAt; private static Vector3 s_lastDirectionPathFrom; private static Vector3 s_lastDirectionPathTo; private static bool s_hasLastDirectionPathSample; private static AudioSource? s_timerSecondAudioSource; private static AudioClip? s_timerSecondAudioClip; private static bool s_timerSecondAudioLoadAttempted; private static float s_nextTimerSecondAudioRetryAt; private static int s_lastTimerSecondAudioPlayed = -1; private static AudioSource? s_timerWarningAudioSource; private static AudioClip? s_timerWarningAudioClip; private static bool s_timerWarningAudioLoadAttempted; private static float s_nextTimerWarningAudioRetryAt; private static int s_lastTimerWarningAudioPlayed = -1; private static float s_previousTimerWarningCheckSeconds = float.NaN; private static int s_lastNetworkTimerBroadcastSecond = -1; private static bool s_timerSyncedFromHost; private static bool s_hasNetworkUiState; private static int s_networkUiRequiredOnTruck; private static readonly Dictionary s_networkUiStatesByActor = new Dictionary(); private static readonly Dictionary s_nextDirectionPenaltyAllowedAtByActor = new Dictionary(); private static float s_lastUiStateBroadcastAt; private static int s_lastUiStateHash; private static bool s_suppressedForRoom; private static bool s_suppressedLogEmitted; private static bool s_lastChanceBatteryOverrideApplied; private static bool s_lastChancePreviousBatteryJumpEnabled; private static DynamicTimerInputs s_cachedDynamicTimerInputs; private static bool s_hasCachedDynamicTimerInputs; private static string? s_lastTruckStateDebugMessage; private const float UiStateBroadcastIntervalSeconds = 0.2f; private const float DirectionPenaltyRequestCooldownSeconds = 0.2f; private const float TimerDriftHardSnapSeconds = 1.25f; private const float TimerDriftLerpFactor = 0.35f; private static bool s_activationProfilePending; private static float s_activationProfileStartedAt; private static DynamicTimerProfileSnapshot s_lastDynamicTimerProfile; private static bool s_hasDynamicTimerProfile; private static ActivationStartPhaseProfileSnapshot s_lastActivationStartPhaseProfile; private static bool s_hasActivationStartPhaseProfile; private static bool s_assetsPrewarmedForSession; private static float s_cachedDirectionPenaltySeconds; private static bool s_hasCachedDirectionPenaltySeconds; private const float AssetAudioRetryIntervalSeconds = 2f; private static float SurrenderHoldDuration => Mathf.Clamp((float)FeatureFlags.LastChanceSurrenderSeconds, 2f, 10f); internal static bool IsActive => s_active; internal static bool IsSuppressedForRoom => s_suppressedForRoom; internal static bool IsDirectionIndicatorUiVisible => s_active && AllPlayersDeadGuard.AllPlayersDisabled() && GetIndicatorMode() == LastChanceIndicatorMode.Direction; internal static void OnHostControlledConfigChanged() { ClearCachedDynamicTimerInputs(); LastChanceMonstersNoiseAggroModule.ResetRuntimeState(); LastChanceMonstersSearchModule.ResetRuntimeState(); LastChanceMonstersVoiceEnemyOnlyModule.ResetRuntimeState(); LastChanceMonstersCameraForceLockModule.ResetRuntimeState(); LastChanceMonstersPlayerVisionCheckModule.ResetRuntimeState(); LastChanceMonstersAnimalHeadVisionFallbackModule.ResetRuntimeState(); LastChanceMonstersCarryProxyModule.ResetRuntimeState(); LastChanceMonstersOnScreenCameraModule.ResetRuntimeState(); LastChanceMonstersThinManStandModule.ResetRuntimeState(); LastChanceHeadPupilVisualModule.ResetRuntimeState(); LastChanceHeadEyesOverrideBypassModule.ResetRuntimeState(); } internal static float GetDirectionIndicatorPenaltySecondsPreview() { if (!IsDirectionIndicatorUiVisible) { return 0f; } int runPlayerCount = GetRunPlayerCount(); if (runPlayerCount <= 0) { return 0f; } return GetOrComputeDirectionPenaltySeconds(); } internal static bool IsDirectionIndicatorEnergySufficientPreview() { if (!IsDirectionIndicatorUiVisible) { return false; } float directionIndicatorPenaltySecondsPreview = GetDirectionIndicatorPenaltySecondsPreview(); if (directionIndicatorPenaltySecondsPreview <= 0f) { return false; } return HasEnoughTimerForDirectionPenalty(directionIndicatorPenaltySecondsPreview); } internal static void GetDirectionIndicatorEnergyDebugSnapshot(out bool visible, out float timerRemaining, out float penaltyPreview, out bool hasEnoughEnergy) { visible = IsDirectionIndicatorUiVisible; timerRemaining = s_timerRemaining; penaltyPreview = (visible ? GetDirectionIndicatorPenaltySecondsPreview() : 0f); hasEnoughEnergy = visible && penaltyPreview > 0f && timerRemaining >= penaltyPreview; } internal static void OnLevelLoaded(bool shouldPrewarmAssets) { LastChanceRuntimeOrchestrator.OnLevelTransition(); s_suppressedForRoom = false; s_suppressedLogEmitted = false; ClearSurrenderState(); ClearCachedDynamicTimerInputs(); ClearLastChanceHostRuntimeOverrides(); ClearActivationProfileState(); s_assetsPrewarmedForSession = false; LastChanceTimerUI.DestroyUi(); if (shouldPrewarmAssets) { PrewarmGlobalAssetsAtBoot(); } if (!s_active) { LastChanceTimerUI.Hide(); ResetLastChanceRuntimeModules(allowVanillaAllPlayersDead: false, allowAutoDelete: false); return; } SetLastChanceActive(active: false); s_currencyCaptured = false; s_timerRemaining = 0f; s_timerSyncedFromHost = false; LastChanceTimerUI.Hide(); ResetLastChanceRuntimeModules(allowVanillaAllPlayersDead: false, allowAutoDelete: false); } internal static void Tick() { if (!FeatureFlags.LastChangeMode) { ResetState(); return; } if (!CompatibilityGate.IsFeatureUsable(ModFeatureGate.LastChanceCluster)) { ResetState(); return; } if (s_suppressedForRoom) { ForceStopRuntimeState(); return; } if (!IsValidRunContext()) { ResetState(); return; } if (SemiFunc.IsMultiplayer()) { LastChanceSurrenderNetwork.TryBroadcastLocalPlayerTruckHint(); } bool flag = AllPlayersDeadGuard.AllPlayersDisabled(); if (!flag) { PrewarmLastChanceAssets(); if (!SemiFunc.IsMultiplayer() || SemiFunc.IsMasterClient()) { PlayerTruckDistanceHelper.PrimeDistancesCache(); } ResetState(); return; } int runPlayerCount = GetRunPlayerCount(); if (runPlayerCount <= 0) { ResetState(); return; } if (!s_active) { BeginActivationProfile(); StartTimer(runPlayerCount); EmitActivationProfileSummary(); } UpdateTimer(); UpdateSurrenderInput(flag); UpdateIndicators(runPlayerCount, flag); UpdatePlayersStatusUi(runPlayerCount); if ((!SemiFunc.IsMultiplayer() || SemiFunc.IsMasterClient()) && !CheckSurrenderFailure(runPlayerCount)) { DebugTruckState(flag); if (AllHeadsInTruck()) { HandleSuccess(); } else if (s_timerRemaining <= 0f && (!SemiFunc.IsMultiplayer() || SemiFunc.IsMasterClient() || s_timerSyncedFromHost)) { HandleTimeout(); } } } private static void StartTimer(int maxPlayers) { bool flag = FeatureFlags.DebugLogging && s_activationProfilePending; float num = (flag ? Time.realtimeSinceStartup : 0f); float num2 = num; float num3 = num; float num4 = num; float num5 = num; float num6 = num; float num7 = num; float num8 = num; SetLastChanceActive(active: true); if (flag) { num2 = Time.realtimeSinceStartup; } if (SemiFunc.IsMultiplayer() && !SemiFunc.IsMasterClient()) { s_timerRemaining = Mathf.Max(30f, GetConfiguredSeconds()); s_timerSyncedFromHost = false; } else { s_timerRemaining = GetInitialTimerSeconds(maxPlayers); s_timerSyncedFromHost = true; } if (flag) { num3 = Time.realtimeSinceStartup; } s_lastTimerSecondAudioPlayed = -1; s_lastTimerWarningAudioPlayed = -1; s_previousTimerWarningCheckSeconds = s_timerRemaining; s_lastNetworkTimerBroadcastSecond = -1; s_currencyCaptured = false; s_indicatorNoneLoggedThisCycle = false; CaptureBaseCurrency(); if (flag) { num4 = Time.realtimeSinceStartup; } LastChanceSurrenderNetwork.EnsureCreated(); if (flag) { num5 = Time.realtimeSinceStartup; } LastChanceTimerUI.Show(GetSurrenderHintPrompt()); if (flag) { num6 = Time.realtimeSinceStartup; } s_surrenderDistanceLogged = false; s_hasNetworkUiState = false; s_networkUiRequiredOnTruck = 0; s_networkUiStatesByActor.Clear(); s_nextDirectionPenaltyAllowedAtByActor.Clear(); s_lastUiStateBroadcastAt = 0f; s_lastUiStateHash = 0; CacheDirectionPenaltySeconds(); if (flag) { num7 = Time.realtimeSinceStartup; } BroadcastTimerStateIfHost(force: true); if (flag) { num8 = Time.realtimeSinceStartup; } if (FeatureFlags.DebugLogging) { LastChanceTruckDistanceLogger.LogDistances(); } Log.LogInfo((object)$"[LastChance] Runtime activated. Timer started: {s_timerRemaining:F1}s."); if (flag) { float realtimeSinceStartup = Time.realtimeSinceStartup; s_lastActivationStartPhaseProfile = new ActivationStartPhaseProfileSnapshot((realtimeSinceStartup - num) * 1000f, (num2 - num) * 1000f, (num3 - num2) * 1000f, (num4 - num3) * 1000f, (num5 - num4) * 1000f, (num6 - num5) * 1000f, (num7 - num6) * 1000f, (num8 - num7) * 1000f, (realtimeSinceStartup - num8) * 1000f); s_hasActivationStartPhaseProfile = true; } } private static void UpdateTimer() { ApplyTimerDelta(0f - Time.deltaTime, TimerChangeReason.CountdownTick, broadcastIfHost: true, forceBroadcastIfHost: false); TryPlayLastChanceTimerSecondTick(); } private static void HandleTimeout() { FailLastChance("[LastChance] Timer expired; resuming vanilla all-dead flow."); } private static void HandleSuccess() { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Timer", 30)) { Log.LogDebug((object)"[LastChance] All heads in truck; sending to shop."); } LastChanceTimerUI.Hide(); SetLastChanceActive(active: false); ResetLastChanceRuntimeModules(allowVanillaAllPlayersDead: false, allowAutoDelete: false); s_timerSyncedFromHost = false; StopTimerSecondAudio(); BroadcastTimerStateIfHost(force: true); if (SemiFunc.IsMasterClientOrSingleplayer()) { RunManager instance = RunManager.instance; if (!((Object)(object)instance == (Object)null) && !IsRestarting(instance)) { CaptureBaseCurrency(); int num = Mathf.Max(0, FeatureFlags.LastChanceConsolationMoney); int num2 = s_baseCurrency + num; SemiFunc.StatSetRunCurrency(num2); NormalizeDirectorsBeforeShopReturn(); instance.previousRunLevel = instance.levelCurrent; TryLogShopReturnSnapshot(instance, num2, "before-change-level"); instance.ChangeLevel(false, false, (ChangeLevelType)5); } } } private static void NormalizeDirectorsBeforeShopReturn() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)RoundDirector.instance != (Object)null) { RoundDirector.instance.allExtractionPointsCompleted = false; RoundDirector.instance.extractionPointActive = false; RoundDirector.instance.extractionPointCurrent = null; } if ((Object)(object)EnemyDirector.instance != (Object)null) { EnemyDirector.instance.extractionsDoneState = (ExtractionsDoneState)0; } } catch (Exception ex) { LogRuntimeHotPathException("NormalizeDirectorsBeforeShopReturn", ex); } } private static void TryLogShopReturnSnapshot(RunManager runMgr, int targetCurrency, string phase) { if (!FeatureFlags.DebugLogging || !LogLimiter.ShouldLog("LastChance.ShopReturn", 15)) { return; } try { string text = (((Object)(object)runMgr.levelCurrent != (Object)null) ? ((Object)runMgr.levelCurrent).name : ""); string text2 = (((Object)(object)runMgr.previousRunLevel != (Object)null) ? ((Object)runMgr.previousRunLevel).name : ""); bool flag = (Object)(object)RoundDirector.instance != (Object)null && RoundDirector.instance.allExtractionPointsCompleted; Log.LogDebug((object)("[LastChance] ShopReturn snapshot phase=" + phase + " levelCurrent=" + text + " previousRunLevel=" + text2 + " " + $"runCurrency={SemiFunc.StatGetRunCurrency()} targetCurrency={targetCurrency} " + $"allExtractionPointsCompleted={flag}.")); } catch (Exception ex) { LogRuntimeHotPathException("TryLogShopReturnSnapshot", ex); } } private static void CaptureBaseCurrency() { if (!s_currencyCaptured) { s_baseCurrency = SemiFunc.StatGetRunCurrency(); s_currencyCaptured = true; } } private static void ResetState() { if (HasRuntimeStateToReset()) { ClearSurrenderState(); ClearCachedDynamicTimerInputs(); ClearActivationProfileState(); if (s_active) { SetLastChanceActive(active: false); s_currencyCaptured = false; s_timerRemaining = 0f; s_timerSyncedFromHost = false; s_hasNetworkUiState = false; s_networkUiRequiredOnTruck = 0; s_networkUiStatesByActor.Clear(); s_nextDirectionPenaltyAllowedAtByActor.Clear(); s_lastUiStateBroadcastAt = 0f; s_lastUiStateHash = 0; StopTimerSecondAudio(); LastChanceTimerUI.Hide(); ResetLastChanceRuntimeModules(allowVanillaAllPlayersDead: false, allowAutoDelete: false); BroadcastTimerStateIfHost(force: true); } } } internal static void SuppressForCurrentRoom(string reason) { s_suppressedForRoom = true; ForceStopRuntimeState(); if (FeatureFlags.DebugLogging && !s_suppressedLogEmitted && LogLimiter.ShouldLog("LastChance.Suppress", 10)) { s_suppressedLogEmitted = true; Log.LogDebug((object)reason); } } internal static void ClearRoomSuppression() { s_suppressedForRoom = false; s_suppressedLogEmitted = false; } private static void ForceStopRuntimeState() { if (HasRuntimeStateToReset()) { ClearSurrenderState(); ClearCachedDynamicTimerInputs(); SetLastChanceActive(active: false); s_currencyCaptured = false; s_timerRemaining = 0f; s_timerSyncedFromHost = false; s_hasNetworkUiState = false; s_networkUiRequiredOnTruck = 0; s_networkUiStatesByActor.Clear(); s_nextDirectionPenaltyAllowedAtByActor.Clear(); s_lastUiStateBroadcastAt = 0f; s_lastUiStateHash = 0; StopTimerSecondAudio(); LastChanceTimerUI.Hide(); ResetLastChanceRuntimeModules(allowVanillaAllPlayersDead: true, allowAutoDelete: true); BroadcastTimerStateIfHost(force: true); } } private static bool HasRuntimeStateToReset() { if (s_active) { return true; } if (s_currencyCaptured || s_timerRemaining > 0f) { return true; } if (LastChanceSurrenderedPlayers.Count > 0 || s_surrenderHoldTimer > 0f || s_localSurrendered) { return true; } if (s_directionActive || s_directionActiveUntil > 0f || s_directionCooldownUntil > 0f) { return true; } return false; } private static bool IsValidRunContext() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)RunManager.instance)) { return false; } if (SemiFunc.RunIsArena() || SemiFunc.RunIsLobby() || SemiFunc.RunIsShop() || SemiFunc.RunIsLobbyMenu() || SemiFunc.RunIsTutorial()) { return false; } if ((Object)(object)GameDirector.instance == (Object)null) { return false; } RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { return false; } return IsRunStarted(instance) && (int)GameDirector.instance.currentState == 2; } private static bool AllHeadsInTruck() { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null || instance.PlayerList.Count == 0) { return false; } int num = 0; int num2 = 0; foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } num++; if (IsPlayerSurrendered(player)) { continue; } if (!IsPlayerDisabled(player)) { RoomVolumeCheck roomVolumeCheck = GetRoomVolumeCheck(player); if (!((Object)(object)roomVolumeCheck == (Object)null) && IsRoomVolumeInTruck(roomVolumeCheck)) { continue; } return false; } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead == (Object)null) { return false; } bool? deathHeadInTruckStatus = GetDeathHeadInTruckStatus(playerDeathHead); if (deathHeadInTruckStatus.HasValue && deathHeadInTruckStatus.Value) { num2++; } } int maxPlayers = num; int lastChanceNeededPlayers = GetLastChanceNeededPlayers(maxPlayers); if (lastChanceNeededPlayers <= 0) { return false; } return num2 >= lastChanceNeededPlayers; } private static int GetLastChanceNeededPlayers(int maxPlayers) { if (maxPlayers <= 0) { return 0; } int num = Math.Max(0, Math.Min(FeatureFlags.LastChanceMissingPlayers, Math.Max(0, maxPlayers - 1))); int val = maxPlayers - num; return Math.Max(1, val); } private static int GetLastChanceCanSurrender(int maxPlayers) { if (maxPlayers <= 0) { return 0; } int lastChanceNeededPlayers = GetLastChanceNeededPlayers(maxPlayers); return Math.Max(0, maxPlayers - lastChanceNeededPlayers); } private static int GetRunPlayerCount() { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return 0; } int num = 0; foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player != (Object)null) { num++; } } return num; } private static bool CheckSurrenderFailure(int maxPlayers) { if (maxPlayers <= 0) { return false; } int count = LastChanceSurrenderedPlayers.Count; int lastChanceCanSurrender = GetLastChanceCanSurrender(maxPlayers); if (count <= lastChanceCanSurrender) { return false; } FailLastChance($"[LastChance] Too many surrendered ({count}) > allowed ({lastChanceCanSurrender}); resuming vanilla all-dead flow."); return true; } private static void FailLastChance(string reason) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Timer", 30)) { Log.LogDebug((object)reason); } LastChanceTimerUI.Hide(); s_timerRemaining = 0f; SetLastChanceActive(active: false); s_timerSyncedFromHost = false; StopTimerSecondAudio(); BroadcastTimerStateIfHost(force: true); ResetLastChanceRuntimeModules(allowVanillaAllPlayersDead: true, allowAutoDelete: true); ClearIndicatorsState(); if (SemiFunc.IsMasterClientOrSingleplayer()) { RunManager instance = RunManager.instance; if (!((Object)(object)instance == (Object)null) && !IsRestarting(instance)) { instance.ChangeLevel(false, true, (ChangeLevelType)0); } } } internal static void ApplyNetworkTimerState(bool active, float secondsRemaining, double hostSentAt) { if (!SemiFunc.IsMultiplayer() || SemiFunc.IsMasterClient()) { return; } SetLastChanceActive(active); s_timerSyncedFromHost = active; float num = s_timerRemaining; float num2 = ComputeAuthoritativeRemaining(secondsRemaining, hostSentAt); if (!active || !s_active) { s_timerRemaining = Mathf.Max(0f, num2); if (!active) { s_previousTimerWarningCheckSeconds = float.NaN; } } else { float num3 = Mathf.Abs(s_timerRemaining - num2); if (num3 >= 1.25f) { SetTimerRemainingAndRefreshUi(num2, TimerChangeReason.NetworkSync, broadcastIfHost: false, forceBroadcastIfHost: false); } else { SetTimerRemainingAndRefreshUi(Mathf.Lerp(s_timerRemaining, num2, 0.35f), TimerChangeReason.NetworkSync, broadcastIfHost: false, forceBroadcastIfHost: false); } } s_lastNetworkTimerBroadcastSecond = Mathf.CeilToInt(s_timerRemaining); if (active) { LastChanceTimerUI.Show(GetSurrenderHintPrompt()); LastChanceTimerUI.UpdateText(FormatTimerText(s_timerRemaining)); float deltaSeconds = s_timerRemaining - num; LastChanceTimerUI.NotifyTimerDelta(deltaSeconds, isNetworkSync: true); } else { LastChanceTimerUI.Hide(); StopTimerSecondAudio(); } } private static void BroadcastTimerStateIfHost(bool force) { if (SemiFunc.IsMultiplayer() && SemiFunc.IsMasterClient()) { int num = Mathf.CeilToInt(s_timerRemaining); if (force || num != s_lastNetworkTimerBroadcastSecond) { s_lastNetworkTimerBroadcastSecond = num; LastChanceSurrenderNetwork.NotifyTimerState(s_active, s_timerRemaining, PhotonNetwork.Time); } } } private static void UpdateSurrenderInput(bool allDead) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!s_active || !allDead) { ResetLocalSurrenderAttempt(); return; } if (s_localSurrendered) { LastChanceTimerUI.SetSurrenderHintText("Surrendered <3"); return; } if (!SemiFunc.InputHold(SurrenderInputKey)) { ResetLocalSurrenderAttempt(); return; } if (s_surrenderHoldTimer <= 0f) { TryLogTruckDistancesForSurrender(); } s_surrenderHoldTimer += Time.deltaTime; float num = SurrenderHoldDuration - s_surrenderHoldTimer; if (num > 0f) { int num2 = Mathf.CeilToInt(num); LastChanceTimerUI.SetSurrenderHintText($"Surrender in {num2}s"); } else { HandleLocalSurrender(); } } private static void HandleLocalSurrender() { if (!s_localSurrendered) { int localActorNumber = GetLocalActorNumber(); if (RegisterSurrenderedActor(localActorNumber, broadcast: true)) { s_localSurrendered = true; s_surrenderHoldTimer = SurrenderHoldDuration; LastChanceTimerUI.SetSurrenderHintText("You surrendered <3"); } } } private static void ResetLocalSurrenderAttempt() { if (s_surrenderHoldTimer > 0f && !s_localSurrendered) { s_surrenderHoldTimer = 0f; LastChanceTimerUI.ResetSurrenderHint(); } s_surrenderDistanceLogged = false; } private static void TryLogTruckDistancesForSurrender() { if (FeatureFlags.LastChangeMode && FeatureFlags.DebugLogging && !s_surrenderDistanceLogged) { LastChanceTruckDistanceLogger.LogDistances(); s_surrenderDistanceLogged = true; } } private static string GetSurrenderHintPrompt() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return $"Back to truck, hop hop! (Surrender [{SurrenderInputKey}])"; } private static int GetLocalActorNumber() { PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance?.photonView != (Object)null) { Player owner = instance.photonView.Owner; if (owner != null) { return owner.ActorNumber; } } Player localPlayer = PhotonNetwork.LocalPlayer; return (localPlayer != null) ? localPlayer.ActorNumber : 0; } private static int GetPlayerActorNumber(PlayerAvatar player) { if ((Object)(object)player?.photonView != (Object)null) { Player owner = player.photonView.Owner; if (owner != null) { return owner.ActorNumber; } } return 0; } private static bool IsPlayerSurrendered(PlayerAvatar player) { int playerActorNumber = GetPlayerActorNumber(player); if (playerActorNumber <= 0) { return false; } return LastChanceSurrenderedPlayers.Contains(playerActorNumber); } private static bool RegisterSurrenderedActor(int actorNumber, bool broadcast) { if (actorNumber <= 0) { return false; } bool flag = LastChanceSurrenderedPlayers.Add(actorNumber); if (flag && broadcast) { LastChanceSurrenderNetwork.NotifyLocalSurrender(actorNumber); } return true; } internal static void RegisterRemoteSurrender(int actorNumber) { RegisterSurrenderedActor(actorNumber, broadcast: false); } internal static void ApplyRemoteSurrenderSnapshot(object[] payload) { if (!SemiFunc.IsMultiplayer() || SemiFunc.IsMasterClient()) { return; } LastChanceSurrenderedPlayers.Clear(); if (payload == null || payload.Length == 0) { return; } for (int i = 0; i < payload.Length; i++) { if (payload[i] is int num && num > 0) { LastChanceSurrenderedPlayers.Add(num); } } } private static void UpdatePlayersStatusUi(int maxPlayers) { if (!s_active) { return; } if (SemiFunc.IsMultiplayer() && !SemiFunc.IsMasterClient()) { if (s_hasNetworkUiState) { List playersStateSnapshot = PlayerStateExtractionHelper.GetPlayersStateSnapshot(); List snapshots = BuildUiSnapshotsFromNetwork(playersStateSnapshot); LastChanceTimerUI.UpdatePlayerStates(snapshots, Mathf.Max(1, s_networkUiRequiredOnTruck)); } } else { List playersStateSnapshot2 = PlayerStateExtractionHelper.GetPlayersStateSnapshot(); int lastChanceNeededPlayers = GetLastChanceNeededPlayers(maxPlayers); LastChanceTimerUI.UpdatePlayerStates(playersStateSnapshot2, lastChanceNeededPlayers); TryBroadcastUiStateIfHost(playersStateSnapshot2, lastChanceNeededPlayers, force: false); } } internal static bool IsPlayerSurrenderedForData(PlayerAvatar? player) { return (Object)(object)player != (Object)null && IsPlayerSurrendered(player); } private static void ClearSurrenderState() { LastChanceSurrenderedPlayers.Clear(); s_surrenderHoldTimer = 0f; s_localSurrendered = false; s_hasNetworkUiState = false; s_networkUiRequiredOnTruck = 0; s_networkUiStatesByActor.Clear(); s_lastUiStateBroadcastAt = 0f; s_lastUiStateHash = 0; StopTimerSecondAudio(); LastChanceTimerUI.ResetSurrenderHint(); ClearIndicatorsState(); } internal static void ApplyNetworkUiState(int requiredOnTruck, object[] statesPayload, int senderActorNumber) { if (!SemiFunc.IsMultiplayer()) { return; } Player masterClient = PhotonNetwork.MasterClient; int num = ((masterClient != null) ? masterClient.ActorNumber : (-1)); if (num <= 0 || senderActorNumber != num) { return; } s_networkUiStatesByActor.Clear(); bool isSurrendered = default(bool); for (int i = 0; i < statesPayload.Length; i++) { if (statesPayload[i] is object[] array && array.Length >= 3 && array[0] is int num2 && num2 > 0 && array[1] is bool isInTruck) { object obj = array[2]; int num3; if (obj is bool) { isSurrendered = (bool)obj; num3 = 1; } else { num3 = 0; } if (num3 != 0) { s_networkUiStatesByActor[num2] = new NetworkUiPlayerState(isInTruck, isSurrendered); } } } s_networkUiRequiredOnTruck = Mathf.Max(1, requiredOnTruck); s_hasNetworkUiState = true; } private static List BuildUiSnapshotsFromNetwork(List localSnapshots) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (localSnapshots == null || localSnapshots.Count == 0) { return localSnapshots ?? new List(0); } List list = new List(localSnapshots.Count); for (int i = 0; i < localSnapshots.Count; i++) { PlayerStateExtractionHelper.PlayerStateSnapshot item = localSnapshots[i]; if (item.ActorNumber > 0 && s_networkUiStatesByActor.TryGetValue(item.ActorNumber, out var value)) { list.Add(new PlayerStateExtractionHelper.PlayerStateSnapshot(item.ActorNumber, item.SteamIdShort, item.Name, item.Color, item.IsAlive, item.IsDead, value.IsInTruck, value.IsSurrendered, item.SourceOrder)); } else { list.Add(item); } } return list; } private static void TryBroadcastUiStateIfHost(List snapshots, int requiredOnTruck, bool force) { if (SemiFunc.IsMultiplayer() && SemiFunc.IsMasterClient() && (force || !(Time.time < s_lastUiStateBroadcastAt + 0.2f))) { int num = ComputeUiStateHash(snapshots, requiredOnTruck); if (force || num != s_lastUiStateHash || !(s_lastUiStateBroadcastAt > 0f)) { object[] playerStatesPayload = BuildUiStatePayload(snapshots); LastChanceSurrenderNetwork.NotifyUiState(Mathf.Max(1, requiredOnTruck), playerStatesPayload); s_lastUiStateHash = num; s_lastUiStateBroadcastAt = Time.time; } } } internal static void ForceBroadcastRuntimeSnapshotForSync() { if (SemiFunc.IsMultiplayer() && SemiFunc.IsMasterClient()) { BroadcastTimerStateIfHost(force: true); int runPlayerCount = GetRunPlayerCount(); if (s_active && runPlayerCount > 0) { List playersStateSnapshot = PlayerStateExtractionHelper.GetPlayersStateSnapshot(); int lastChanceNeededPlayers = GetLastChanceNeededPlayers(runPlayerCount); TryBroadcastUiStateIfHost(playersStateSnapshot, lastChanceNeededPlayers, force: true); } else { LastChanceSurrenderNetwork.NotifyUiState(1, Array.Empty()); s_lastUiStateBroadcastAt = Time.time; s_lastUiStateHash = 0; } LastChanceSurrenderNetwork.NotifySurrenderSnapshot(BuildSurrenderedActorsPayload()); } } private static object[] BuildUiStatePayload(List snapshots) { if (snapshots == null || snapshots.Count == 0) { return Array.Empty(); } List list = new List(snapshots.Count); for (int i = 0; i < snapshots.Count; i++) { PlayerStateExtractionHelper.PlayerStateSnapshot playerStateSnapshot = snapshots[i]; if (playerStateSnapshot.ActorNumber > 0) { list.Add(new object[3] { playerStateSnapshot.ActorNumber, playerStateSnapshot.IsInTruck, playerStateSnapshot.IsSurrendered }); } } return list.ToArray(); } private static int ComputeUiStateHash(List snapshots, int requiredOnTruck) { int num = requiredOnTruck * 397; if (snapshots == null) { return num; } for (int i = 0; i < snapshots.Count; i++) { PlayerStateExtractionHelper.PlayerStateSnapshot playerStateSnapshot = snapshots[i]; num = num * 31 + playerStateSnapshot.ActorNumber; num = num * 31 + (playerStateSnapshot.IsInTruck ? 1 : 0); num = num * 31 + (playerStateSnapshot.IsSurrendered ? 1 : 0); } return num; } private static void TryPlayLastChanceTimerSecondTick() { if (s_active) { int num = Mathf.CeilToInt(s_timerRemaining); if (num <= 10 && num > 0 && num != s_lastTimerSecondAudioPlayed && TryEnsureTimerSecondAudioReady() && !((Object)(object)s_timerSecondAudioSource == (Object)null) && !((Object)(object)s_timerSecondAudioClip == (Object)null)) { s_timerSecondAudioSource.PlayOneShot(s_timerSecondAudioClip); s_lastTimerSecondAudioPlayed = num; } } } private static void TryPlayLastChanceTimerWarnings(float previousSeconds, float currentSeconds) { if (s_active) { bool flag = previousSeconds > 60f && currentSeconds <= 60f; bool flag2 = previousSeconds > 30f && currentSeconds <= 30f; if ((flag || flag2) && TryEnsureTimerWarningAudioReady() && !((Object)(object)s_timerWarningAudioSource == (Object)null) && !((Object)(object)s_timerWarningAudioClip == (Object)null)) { s_timerWarningAudioSource.pitch = (flag2 ? 1.5f : 1f); s_timerWarningAudioSource.PlayOneShot(s_timerWarningAudioClip); s_lastTimerWarningAudioPlayed = (flag2 ? 30 : 60); } } } private static bool TryEnsureTimerSecondAudioReady() { //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Expected O, but got Unknown if ((Object)(object)s_timerSecondAudioClip == (Object)null) { float unscaledTime = Time.unscaledTime; if (s_timerSecondAudioLoadAttempted && !(unscaledTime >= s_nextTimerSecondAudioRetryAt)) { return false; } s_timerSecondAudioLoadAttempted = true; s_nextTimerSecondAudioRetryAt = unscaledTime + 2f; if (!AudioAssetLoader.TryLoadAudioClip("TimerSecond.mp3", AudioAssetLoader.GetDefaultAssetsDirectory(), out AudioClip clip, out string resolvedPath) || (Object)(object)clip == (Object)null) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.TimerSecond.LoadFail", 30)) { string defaultAssetsDirectory = AudioAssetLoader.GetDefaultAssetsDirectory(); Log.LogWarning((object)("[LastChance] Failed to load timer tick audio. file=TimerSecond.mp3 baseDir=" + defaultAssetsDirectory)); } return false; } s_timerSecondAudioClip = clip; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.TimerSecond.Loaded", 30)) { Log.LogDebug((object)("[LastChance] Loaded timer tick audio from: " + resolvedPath)); } } if ((Object)(object)s_timerSecondAudioClip == (Object)null) { return false; } if ((Object)(object)s_timerSecondAudioSource == (Object)null) { GameObject val = new GameObject("DHHFix.LastChanceTimerSecondAudio"); Object.DontDestroyOnLoad((Object)(object)val); AudioSource val2 = val.AddComponent(); val2.playOnAwake = false; val2.loop = false; val2.spatialBlend = 0f; val2.volume = 1f; s_timerSecondAudioSource = val2; } return (Object)(object)s_timerSecondAudioSource != (Object)null; } private static bool TryEnsureTimerWarningAudioReady() { //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Expected O, but got Unknown if ((Object)(object)s_timerWarningAudioClip == (Object)null) { float unscaledTime = Time.unscaledTime; if (s_timerWarningAudioLoadAttempted && !(unscaledTime >= s_nextTimerWarningAudioRetryAt)) { return false; } s_timerWarningAudioLoadAttempted = true; s_nextTimerWarningAudioRetryAt = unscaledTime + 2f; if (!TryLoadTimerWarningClip(out AudioClip clip, out string resolvedPath) || (Object)(object)clip == (Object)null) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.TimerWarning.LoadFail", 30)) { string defaultAssetsDirectory = AudioAssetLoader.GetDefaultAssetsDirectory(); Log.LogWarning((object)("[LastChance] Failed to load timer warning audio. files=TimerWarning.mp3 baseDir=" + defaultAssetsDirectory)); } return false; } s_timerWarningAudioClip = clip; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.TimerWarning.Loaded", 30)) { Log.LogDebug((object)("[LastChance] Loaded timer warning audio from: " + resolvedPath)); } } if ((Object)(object)s_timerWarningAudioClip == (Object)null) { return false; } if ((Object)(object)s_timerWarningAudioSource == (Object)null) { GameObject val = new GameObject("DHHFix.LastChanceTimerWarningAudio"); Object.DontDestroyOnLoad((Object)(object)val); AudioSource val2 = val.AddComponent(); val2.playOnAwake = false; val2.loop = false; val2.spatialBlend = 0f; val2.volume = 1f; val2.pitch = 1f; s_timerWarningAudioSource = val2; } return (Object)(object)s_timerWarningAudioSource != (Object)null; } private static bool TryLoadTimerWarningClip(out AudioClip? clip, out string resolvedPath) { clip = null; resolvedPath = string.Empty; return AudioAssetLoader.TryLoadAudioClip("TimerWarning.mp3", AudioAssetLoader.GetDefaultAssetsDirectory(), out clip, out resolvedPath); } private static void StopTimerSecondAudio() { s_lastTimerSecondAudioPlayed = -1; if ((Object)(object)s_timerSecondAudioSource != (Object)null) { s_timerSecondAudioSource.Stop(); } s_lastTimerWarningAudioPlayed = -1; s_previousTimerWarningCheckSeconds = float.NaN; if ((Object)(object)s_timerWarningAudioSource != (Object)null) { s_timerWarningAudioSource.Stop(); s_timerWarningAudioSource.pitch = 1f; } } private static void PrewarmLastChanceAssets() { if (!s_assetsPrewarmedForSession) { LastChanceTimerUI.PrewarmAssets(); TryEnsureTimerSecondAudioReady(); TryEnsureTimerWarningAudioReady(); s_assetsPrewarmedForSession = true; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Prewarm", 30)) { Log.LogDebug((object)"[LastChance] Prewarmed UI sprites and timer audio assets."); } } } internal static void PrewarmGlobalAssetsAtBoot() { LastChanceTimerUI.PrewarmAssets(); TryEnsureTimerSecondAudioReady(); TryEnsureTimerWarningAudioReady(); s_assetsPrewarmedForSession = true; } private static void UpdateIndicators(int maxPlayers, bool allDead) { LastChanceIndicatorMode indicatorMode = GetIndicatorMode(); if (!s_active || !allDead) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Indicator.Blocked", 5)) { string text = FeatureFlags.LastChanceIndicators ?? string.Empty; Log.LogDebug((object)$"[LastChance] Indicator blocked: active={s_active} allDead={allDead} modeRaw='{text}' modeParsed={indicatorMode}"); } ClearActiveIndicatorVisuals(); AbilityModule.RefreshDirectionSlotVisuals(); } else if (indicatorMode == LastChanceIndicatorMode.None) { if (!s_indicatorNoneLoggedThisCycle && FeatureFlags.DebugLogging) { string arg = FeatureFlags.LastChanceIndicators ?? string.Empty; Log.LogDebug((object)$"[LastChance] Indicator disabled for this cycle: modeRaw='{arg}' modeParsed={indicatorMode}"); s_indicatorNoneLoggedThisCycle = true; } ClearActiveIndicatorVisuals(); AbilityModule.RefreshDirectionSlotVisuals(); } else { bool enabled = indicatorMode == LastChanceIndicatorMode.Direction; UpdateSingleIndicator(IndicatorKind.Direction, enabled); AbilityModule.RefreshDirectionSlotVisuals(); } } private static LastChanceIndicatorMode GetIndicatorMode() { string text = (FeatureFlags.LastChanceIndicators ?? string.Empty).Trim(); if (text.Equals("Direction", StringComparison.OrdinalIgnoreCase)) { return LastChanceIndicatorMode.Direction; } if (text.Equals("Indicator", StringComparison.OrdinalIgnoreCase)) { return LastChanceIndicatorMode.Direction; } return LastChanceIndicatorMode.None; } private static void UpdateSingleIndicator(IndicatorKind kind, bool enabled) { if (!enabled) { ResetIndicatorHold(); DeactivateIndicator(kind); return; } if (IsIndicatorActive(kind)) { if (Time.time >= GetIndicatorActiveUntil(kind)) { DeactivateIndicator(kind); } else { TickActiveIndicator(kind); } } if (IsIndicatorActive(kind) || Time.time < GetIndicatorCooldownUntil(kind)) { ResetIndicatorHold(); } else if (kind == IndicatorKind.Direction && !IsDirectionIndicatorEnergySufficientPreview()) { ResetIndicatorHold(); } } private static void ResetIndicatorHold() { s_directionHoldTimer = 0f; AbilityModule.SetDirectionSlotActivationProgress(0f); } internal static void OnDirectionAbilityInputDown() { if (!IsDirectionIndicatorUiVisible) { ResetIndicatorHold(); } } internal static void OnDirectionAbilityInputHold() { if (!IsDirectionIndicatorUiVisible) { ResetIndicatorHold(); return; } if (IsIndicatorActive(IndicatorKind.Direction) || Time.time < GetIndicatorCooldownUntil(IndicatorKind.Direction)) { ResetIndicatorHold(); return; } if (!IsDirectionIndicatorEnergySufficientPreview()) { ResetIndicatorHold(); return; } float num = 1f; s_directionHoldTimer = Mathf.Min(num, s_directionHoldTimer + Time.deltaTime); AbilityModule.SetDirectionSlotActivationProgress(Mathf.Clamp01(s_directionHoldTimer / num)); if (!(s_directionHoldTimer < num)) { ResetIndicatorHold(); int runPlayerCount = GetRunPlayerCount(); if (runPlayerCount > 0) { TriggerIndicator(IndicatorKind.Direction, runPlayerCount); } } } internal static void OnDirectionAbilityInputUp() { ResetIndicatorHold(); } internal static void OnDirectionAbilityInputCancel() { ResetIndicatorHold(); } private static void TriggerIndicator(IndicatorKind kind, int maxPlayers) { float num = Mathf.Clamp(FeatureFlags.LastChanceIndicatorDirectionDurationSeconds, 0.5f, 20f); float num2 = Mathf.Clamp(FeatureFlags.LastChanceIndicatorDirectionCooldownSeconds, 1f, 60f); float num3 = Time.time + num; SetIndicatorActive(kind, value: true); SetIndicatorActiveUntil(kind, num3); SetIndicatorCooldownUntil(kind, num3 + num2); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog(string.Format("{0}.Start.{1}", "LastChance.Indicator.Cooldown", kind), 2)) { Log.LogDebug((object)$"[LastChance] Indicator cooldown started: kind={kind} duration={num:F1}s cooldown={num2:F1}s"); } ApplyIndicatorPenalty(kind, maxPlayers); TickActiveIndicator(kind); if (kind == IndicatorKind.Direction) { float cooldownSeconds = Mathf.Max(0f, GetIndicatorCooldownUntil(kind) - Time.time); AbilityModule.TriggerDirectionSlotCooldown(cooldownSeconds); } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.Indicator", 3)) { float num4 = Mathf.Max(0f, GetIndicatorCooldownUntil(kind) - Time.time); Log.LogDebug((object)$"[LastChance] Indicator triggered: mode={kind} active={num:F1}s cooldown={num4:F1}s timer={s_timerRemaining:F1}s"); } } private static void ApplyIndicatorPenalty(IndicatorKind kind, int maxPlayers) { if (SemiFunc.IsMultiplayer() && !SemiFunc.IsMasterClient()) { LastChanceSurrenderNetwork.NotifyDirectionPenaltyRequest(); } else { ApplyIndicatorPenaltyHost(maxPlayers); } } internal static void HandleDirectionPenaltyRequest(int senderActorNumber) { if (!SemiFunc.IsMultiplayer() || !SemiFunc.IsMasterClient() || !s_active || !AllPlayersDeadGuard.AllPlayersDisabled() || senderActorNumber <= 0) { return; } float unscaledTime = Time.unscaledTime; if (!s_nextDirectionPenaltyAllowedAtByActor.TryGetValue(senderActorNumber, out var value) || !(unscaledTime < value)) { s_nextDirectionPenaltyAllowedAtByActor[senderActorNumber] = unscaledTime + 0.2f; int runPlayerCount = GetRunPlayerCount(); if (runPlayerCount > 0) { ApplyIndicatorPenaltyHost(runPlayerCount); } } } internal static void TryApplyMonsterDeathTimerBonusHost() { if (SemiFunc.IsMasterClientOrSingleplayer() && FeatureFlags.LastChangeMode && s_active && AllPlayersDeadGuard.AllPlayersDisabled()) { int num = Mathf.Clamp(FeatureFlags.LastChanceTimerBonusPerMonsterDeathSeconds, 0, 10); if (num > 0) { ApplyTimerDelta(num, TimerChangeReason.MonsterKillBonus, broadcastIfHost: true, forceBroadcastIfHost: true); } } } private static void ApplyIndicatorPenaltyHost(int maxPlayers) { float orComputeDirectionPenaltySeconds = GetOrComputeDirectionPenaltySeconds(); if (!(orComputeDirectionPenaltySeconds <= 0f) && HasEnoughTimerForDirectionPenalty(orComputeDirectionPenaltySeconds)) { ApplyTimerDelta(0f - orComputeDirectionPenaltySeconds, TimerChangeReason.DirectionPenalty, broadcastIfHost: true, forceBroadcastIfHost: true); } } private static bool HasEnoughTimerForDirectionPenalty(float penaltySeconds) { float num = Mathf.Max(0f, penaltySeconds); if (num <= 0f) { return false; } if (s_timerRemaining <= 30f) { return false; } return s_timerRemaining >= num; } private static void ApplyTimerDelta(float deltaSeconds, TimerChangeReason reason, bool broadcastIfHost, bool forceBroadcastIfHost) { if (!(Mathf.Abs(deltaSeconds) <= Mathf.Epsilon)) { SetTimerRemainingAndRefreshUi(s_timerRemaining + deltaSeconds, reason, broadcastIfHost, forceBroadcastIfHost); if (reason == TimerChangeReason.MonsterKillBonus || reason == TimerChangeReason.DirectionPenalty) { LastChanceTimerUI.NotifyTimerDelta(deltaSeconds); } } } private static void SetTimerRemainingAndRefreshUi(float nextSeconds, TimerChangeReason reason, bool broadcastIfHost, bool forceBroadcastIfHost) { float num = s_timerRemaining; s_timerRemaining = Mathf.Max(0f, nextSeconds); if (broadcastIfHost) { BroadcastTimerStateIfHost(forceBroadcastIfHost); } LastChanceTimerUI.UpdateText(FormatTimerText(s_timerRemaining)); float previousSeconds = (float.IsNaN(s_previousTimerWarningCheckSeconds) ? num : s_previousTimerWarningCheckSeconds); TryPlayLastChanceTimerWarnings(previousSeconds, s_timerRemaining); s_previousTimerWarningCheckSeconds = s_timerRemaining; } private static float CalculateIndicatorPenaltySeconds() { float num = Mathf.Max(0f, FeatureFlags.LastChanceIndicatorDirectionPenaltyMaxSeconds); float num2 = Mathf.Max(0f, FeatureFlags.LastChanceIndicatorDirectionPenaltyMinSeconds); if (num2 > num) { float num3 = num; num = num2; num2 = num3; } if (!FeatureFlags.LastChanceDynamicTimerEnabled) { return Mathf.Round(num); } int currentLevelNumber = GetCurrentLevelNumber(); int num4 = Mathf.Max(2, FeatureFlags.LastChanceDynamicMaxMinutesAtLevel); float num5 = Mathf.Clamp01(((float)Mathf.Max(1, currentLevelNumber) - 1f) / ((float)num4 - 1f)); return Mathf.Round(Mathf.Lerp(num, num2, num5)); } private static float GetOrComputeDirectionPenaltySeconds() { if (s_hasCachedDirectionPenaltySeconds) { return s_cachedDirectionPenaltySeconds; } CacheDirectionPenaltySeconds(); return s_cachedDirectionPenaltySeconds; } private static void CacheDirectionPenaltySeconds() { s_cachedDirectionPenaltySeconds = CalculateIndicatorPenaltySeconds(); s_hasCachedDirectionPenaltySeconds = true; } private static void ClearDirectionPenaltyCache() { s_cachedDirectionPenaltySeconds = 0f; s_hasCachedDirectionPenaltySeconds = false; } private static void TickActiveIndicator(IndicatorKind kind) { EnsureDirectionLine(); AnimateDirectionLineMaterial(); UpdateDirectionPath(Time.time >= s_indicatorNextPathRefreshAt); } private static void DeactivateIndicator(IndicatorKind kind) { SetIndicatorActive(kind, value: false); if ((Object)(object)s_indicatorDirectionLine != (Object)null) { s_indicatorDirectionLine.positionCount = 0; ((Renderer)s_indicatorDirectionLine).enabled = false; } } private static bool IsIndicatorActive(IndicatorKind kind) { return s_directionActive; } private static void SetIndicatorActive(IndicatorKind kind, bool value) { s_directionActive = value; } private static float GetIndicatorActiveUntil(IndicatorKind kind) { return s_directionActiveUntil; } private static void SetIndicatorActiveUntil(IndicatorKind kind, float value) { s_directionActiveUntil = value; } private static float GetIndicatorCooldownUntil(IndicatorKind kind) { return s_directionCooldownUntil; } private static void SetIndicatorCooldownUntil(IndicatorKind kind, float value) { s_directionCooldownUntil = value; } private static void EnsureDirectionLine() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown if ((Object)(object)s_indicatorDirectionLine != (Object)null) { ((Renderer)s_indicatorDirectionLine).enabled = true; return; } s_indicatorDirectionObject = new GameObject("DHHFix.LastChanceDirectionIndicator"); Object.DontDestroyOnLoad((Object)(object)s_indicatorDirectionObject); s_indicatorDirectionLine = s_indicatorDirectionObject.AddComponent(); s_indicatorDirectionLine.useWorldSpace = true; ((Renderer)s_indicatorDirectionLine).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)s_indicatorDirectionLine).receiveShadows = false; s_indicatorDirectionLine.textureMode = (LineTextureMode)1; s_indicatorDirectionLine.alignment = (LineAlignment)0; s_indicatorDirectionLine.widthCurve = AnimationCurve.EaseInOut(0f, 0.09f, 1f, 0.05f); s_indicatorDirectionLine.positionCount = 0; ((Renderer)s_indicatorDirectionLine).enabled = true; if (!TryApplyPhysGrabBeamMaterial(s_indicatorDirectionLine)) { ((Renderer)s_indicatorDirectionLine).material = new Material(Shader.Find("Sprites/Default")); s_indicatorDirectionMaterial = ((Renderer)s_indicatorDirectionLine).material; } ConfigureDirectionLineFromPhysGrabBeam(); s_indicatorNextPathRefreshAt = 0f; } private static bool TryApplyPhysGrabBeamMaterial(LineRenderer lineRenderer) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (!TryGetPhysGrabBeamSource(out LineRenderer source)) { return false; } if ((Object)(object)source == (Object)null) { return false; } ((Renderer)lineRenderer).material = ((Renderer)source).material; lineRenderer.textureMode = source.textureMode; s_indicatorDirectionMaterial = ((Renderer)lineRenderer).material; return true; } private static void ConfigureDirectionLineFromPhysGrabBeam() { //IL_003c: 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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)s_indicatorDirectionLine == (Object)null) && TryGetPhysGrabBeamSource(out LineRenderer source) && !((Object)(object)source == (Object)null)) { s_indicatorDirectionLine.alignment = source.alignment; s_indicatorDirectionLine.textureMode = source.textureMode; s_indicatorDirectionLine.widthMultiplier = source.widthMultiplier; s_indicatorDirectionLine.widthCurve = source.widthCurve; s_indicatorDirectionLine.colorGradient = source.colorGradient; s_indicatorDirectionLine.startColor = source.startColor; s_indicatorDirectionLine.endColor = source.endColor; s_indicatorDirectionLine.numCornerVertices = source.numCornerVertices; s_indicatorDirectionLine.numCapVertices = source.numCapVertices; s_indicatorDirectionLine.generateLightingData = source.generateLightingData; ((Renderer)s_indicatorDirectionLine).material = ((Renderer)source).material; s_indicatorDirectionMaterial = ((Renderer)s_indicatorDirectionLine).material; } } private static void AnimateDirectionLineMaterial() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)s_indicatorDirectionMaterial == (Object)null)) { s_indicatorDirectionMaterial.mainTextureScale = Vector2.one; s_indicatorDirectionMaterial.mainTextureOffset = Time.time * DirectionLineScrollSpeed; } } private static bool TryGetPhysGrabBeamSource(out LineRenderer? source) { source = null; PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null) { return false; } PhysGrabber component = ((Component)instance).GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component.physGrabBeam == (Object)null) { return false; } source = component.physGrabBeam.GetComponent(); if ((Object)(object)source == (Object)null || (Object)(object)((Renderer)source).material == (Object)null) { return false; } return true; } private static void UpdateDirectionPath(bool force) { //IL_0063: 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) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) if (!force) { return; } s_indicatorNextPathRefreshAt = Time.time + 0.4f; if ((Object)(object)s_indicatorDirectionLine == (Object)null) { return; } if (!TryBuildPathToTruck(out List points, out Vector3 navFrom, out Vector3 navTo)) { s_indicatorDirectionLine.positionCount = 0; return; } if (s_hasLastDirectionPathSample) { Vector3 val = navFrom - s_lastDirectionPathFrom; if (((Vector3)(ref val)).sqrMagnitude <= 0.64f) { val = navTo - s_lastDirectionPathTo; if (((Vector3)(ref val)).sqrMagnitude <= 0.64f) { return; } } } s_hasLastDirectionPathSample = true; s_lastDirectionPathFrom = navFrom; s_lastDirectionPathTo = navTo; s_indicatorDirectionLine.positionCount = points.Count; for (int i = 0; i < points.Count; i++) { s_indicatorDirectionLine.SetPosition(i, points[points.Count - 1 - i]); } } private static bool TryBuildPathToTruck(out List points, out Vector3 navFrom, out Vector3 navTo) { //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_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: 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_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) points = new List(2); navFrom = Vector3.zero; navTo = Vector3.zero; PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null) { return false; } Vector3 localHeadOrPlayerPosition = GetLocalHeadOrPlayerPosition(instance); if (!TryGetTruckPosition(out var truckPosition)) { return false; } Vector3 item = localHeadOrPlayerPosition + Vector3.up * 0.2f; Vector3 item2 = truckPosition + Vector3.up * 0.2f; if (!TrySampleNavMeshPosition(localHeadOrPlayerPosition, 12f, out var sampledPosition)) { sampledPosition = localHeadOrPlayerPosition; } if (!TrySampleNavMeshPosition(truckPosition, 8f, out var sampledPosition2)) { sampledPosition2 = truckPosition; } navFrom = sampledPosition; navTo = sampledPosition2; if (!TryCalculateNavMeshPathCorners(sampledPosition, sampledPosition2, out Vector3[] corners) || corners.Length == 0) { points.Add(item); points.Add(item2); return true; } points = new List(corners.Length + 1) { item }; for (int i = 0; i < corners.Length; i++) { points.Add(corners[i] + Vector3.up * 0.2f); } return points.Count >= 2; } private static Vector3 GetLocalHeadOrPlayerPosition(PlayerAvatar avatar) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)avatar.playerDeathHead != (Object)null) { return ((Component)avatar.playerDeathHead).transform.position; } if ((Object)(object)avatar.playerTransform != (Object)null) { return avatar.playerTransform.position; } return ((Component)avatar).transform.position; } private static bool TryGetTruckPosition(out Vector3 truckPosition) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) truckPosition = Vector3.zero; try { LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null) { return false; } if ((Object)(object)instance.LevelPathTruck != (Object)null) { LevelPoint levelPathTruck = instance.LevelPathTruck; if (TryGetTransformPosition(levelPathTruck, out truckPosition)) { return true; } } if (instance.LevelPathPoints == null) { return false; } foreach (LevelPoint levelPathPoint in instance.LevelPathPoints) { if ((Object)(object)levelPathPoint == (Object)null || !levelPathPoint.Truck || !TryGetTransformPosition(levelPathPoint, out truckPosition)) { continue; } return true; } } catch (Exception ex) { LogRuntimeHotPathException("TryGetTruckPosition", ex); } return false; } private static bool TryGetTransformPosition(object obj, out Vector3 position) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; try { if (obj == null) { return false; } Component val = (Component)((obj is Component) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { position = val.transform.position; return true; } } catch (Exception ex) { LogRuntimeHotPathException("TryGetTransformPosition", ex); } return false; } private static bool TrySampleNavMeshPosition(Vector3 source, float maxDistance, out Vector3 sampledPosition) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) sampledPosition = Vector3.zero; try { NavMeshHit val = default(NavMeshHit); if (NavMesh.SamplePosition(source, ref val, maxDistance, -1)) { sampledPosition = ((NavMeshHit)(ref val)).position; return true; } } catch (Exception ex) { LogRuntimeHotPathException("TrySampleNavMeshPosition", ex); } return false; } private static bool TryCalculateNavMeshPathCorners(Vector3 from, Vector3 to, out Vector3[] corners) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //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) corners = Array.Empty(); try { NavMeshPath val = new NavMeshPath(); if (!NavMesh.CalculatePath(from, to, -1, val)) { return false; } if (val.corners.Length != 0) { corners = val.corners; return true; } } catch (Exception ex) { LogRuntimeHotPathException("TryCalculateNavMeshPathCorners", ex); } return false; } private static void LogRuntimeHotPathException(string context, Exception ex) { if (FeatureFlags.DebugLogging) { string key = "LastChance.Runtime.TimerController." + context; if (LogLimiter.ShouldLog(key, 600)) { Log.LogWarning((object)("[LastChance] Runtime hot-path failed in " + context + ": " + ex.GetType().Name + ": " + ex.Message)); } } } private static void ClearActiveIndicatorVisuals() { DeactivateIndicator(IndicatorKind.Direction); AbilityModule.RefreshDirectionSlotVisuals(); } private static void ClearIndicatorsState() { s_indicatorNoneLoggedThisCycle = false; s_directionHoldTimer = 0f; s_directionCooldownUntil = 0f; s_directionActiveUntil = 0f; s_directionActive = false; s_indicatorNextPathRefreshAt = 0f; s_hasLastDirectionPathSample = false; AbilityModule.SetDirectionSlotActivationProgress(0f); ClearActiveIndicatorVisuals(); } private static void DebugTruckState(bool allDead) { if (!FeatureFlags.DebugLogging || !FeatureFlags.LastChangeMode || !allDead) { return; } GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null || instance.PlayerList.Count == 0) { return; } string text = "[LastChance] TruckState:"; bool flag = (Object)(object)RoundDirector.instance != (Object)null && RoundDirector.instance.allExtractionPointsCompleted; text += $" extractionDone={flag}"; foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null) { text += " [null player]"; continue; } string playerName = GetPlayerName(player); if (!IsPlayerDisabled(player)) { RoomVolumeCheck roomVolumeCheck = GetRoomVolumeCheck(player); bool flag2 = (Object)(object)roomVolumeCheck != (Object)null && IsRoomVolumeInTruck(roomVolumeCheck); text += $" {playerName}(alive,inTruck={flag2})"; } else { PlayerDeathHead playerDeathHead = player.playerDeathHead; RoomVolumeCheck val = (((Object)(object)playerDeathHead != (Object)null) ? GetDeathHeadRoomVolumeCheck(playerDeathHead) : null); bool flag3 = (Object)(object)val != (Object)null && IsRoomVolumeInTruck(val); bool flag4 = (Object)(object)playerDeathHead != (Object)null && playerDeathHead.inTruck; text += $" {playerName}(deadHead,roomInTruck={flag3},inTruck={flag4})"; } } if (!string.Equals(s_lastTruckStateDebugMessage, text, StringComparison.Ordinal)) { s_lastTruckStateDebugMessage = text; Log.LogDebug((object)text); } } private static string GetPlayerName(PlayerAvatar player) { if (!string.IsNullOrWhiteSpace(player.playerName)) { return player.playerName; } return ((object)player).GetType().Name; } private static bool IsRunStarted(RunManager runMgr) { return runMgr.runStarted; } private static bool IsRestarting(RunManager runMgr) { return runMgr.restarting; } private static bool IsPlayerDisabled(PlayerAvatar player) { return player.isDisabled; } private static RoomVolumeCheck? GetRoomVolumeCheck(PlayerAvatar player) { return player.RoomVolumeCheck; } private static bool IsRoomVolumeInTruck(RoomVolumeCheck roomVolumeCheck) { return roomVolumeCheck.inTruck; } private static bool? GetDeathHeadInTruckStatus(PlayerDeathHead deathHead) { if ((Object)(object)deathHead == (Object)null) { return null; } RoomVolumeCheck deathHeadRoomVolumeCheck = GetDeathHeadRoomVolumeCheck(deathHead); if ((Object)(object)deathHeadRoomVolumeCheck != (Object)null) { return IsRoomVolumeInTruck(deathHeadRoomVolumeCheck); } return deathHead.inTruck; } private static RoomVolumeCheck? GetDeathHeadRoomVolumeCheck(PlayerDeathHead deathHead) { return deathHead.roomVolumeCheck; } private static float GetConfiguredSeconds() { int num = Mathf.Clamp(FeatureFlags.LastChanceTimerSeconds, 30, 600); int num2 = Mathf.RoundToInt((float)num / 30f) * 30; return Mathf.Clamp(num2, 30, 600); } private static float GetInitialTimerSeconds(int maxPlayers) { float configuredSeconds = GetConfiguredSeconds(); float dynamicTimerCapSeconds = GetDynamicTimerCapSeconds(); if (!FeatureFlags.LastChanceDynamicTimerEnabled) { ClearCachedDynamicTimerInputs(); return Mathf.Clamp(configuredSeconds, 30f, dynamicTimerCapSeconds); } DynamicTimerInputs inputs = CollectDynamicTimerInputs(maxPlayers); CacheDynamicTimerInputs(inputs); float num = CalculateRawAddedSeconds(inputs); float num2 = configuredSeconds + num; float levelFloorSeconds = GetLevelFloorSeconds(inputs.LevelNumber, dynamicTimerCapSeconds); float num3 = Mathf.Clamp(Mathf.Max(num2, levelFloorSeconds), 30f, dynamicTimerCapSeconds); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.DynamicTimer", 30)) { Log.LogDebug((object)($"[LastChance] DynamicTimer: base={configuredSeconds:F1}s level={inputs.LevelNumber} required={inputs.RequiredPlayers} " + $"totalDistance={inputs.TotalDistanceMeters:F1}m belowPlayers={inputs.PlayersBelowTruckThreshold} belowMeters={inputs.TotalBelowTruckMeters:F2} " + $"aliveMonsters={inputs.AliveSearchMonsters} totalRoomSteps={inputs.TotalShortestRoomPathSteps} rawAdd={num:F1}s " + $"dynamic={num2:F1}s levelFloor={levelFloorSeconds:F1}s final={num3:F1}s hardCap={dynamicTimerCapSeconds:F1}s")); } return num3; } private static DynamicTimerInputs GetDynamicTimerInputsForRuntime(int maxPlayers) { if (s_hasCachedDynamicTimerInputs) { return s_cachedDynamicTimerInputs; } DynamicTimerInputs dynamicTimerInputs = CollectDynamicTimerInputs(maxPlayers); CacheDynamicTimerInputs(dynamicTimerInputs); return dynamicTimerInputs; } private static void CacheDynamicTimerInputs(DynamicTimerInputs inputs) { s_cachedDynamicTimerInputs = inputs; s_hasCachedDynamicTimerInputs = true; } private static void ClearCachedDynamicTimerInputs() { s_cachedDynamicTimerInputs = default(DynamicTimerInputs); s_hasCachedDynamicTimerInputs = false; } private static void CacheDynamicTimerProfile(DynamicTimerProfileSnapshot profile) { s_lastDynamicTimerProfile = profile; s_hasDynamicTimerProfile = true; } private static void BeginActivationProfile() { if (FeatureFlags.DebugLogging && !s_activationProfilePending) { s_activationProfilePending = true; s_activationProfileStartedAt = Time.realtimeSinceStartup; s_hasDynamicTimerProfile = false; s_hasActivationStartPhaseProfile = false; PlayerTruckDistanceHelper.BeginActivationProfiling(); } } private static void EmitActivationProfileSummary() { if (s_activationProfilePending && FeatureFlags.DebugLogging) { s_activationProfilePending = false; float num = (Time.realtimeSinceStartup - s_activationProfileStartedAt) * 1000f; string text = PlayerTruckDistanceHelper.EndActivationProfilingSummary(); string text2 = (s_hasDynamicTimerProfile ? $"dynamic=total={s_lastDynamicTimerProfile.TotalMs:F1}ms monsters={s_lastDynamicTimerProfile.MonstersMs:F1}ms records={s_lastDynamicTimerProfile.RecordsMs:F1}ms select={s_lastDynamicTimerProfile.SelectMs:F1}ms heightRefresh={s_lastDynamicTimerProfile.HeightRefreshMs:F1}ms aggregate={s_lastDynamicTimerProfile.AggregateMs:F1}ms records={s_lastDynamicTimerProfile.RecordsCount} selected={s_lastDynamicTimerProfile.SelectedCount} required={s_lastDynamicTimerProfile.RequiredPlayers} level={s_lastDynamicTimerProfile.LevelNumber} aliveMonsters={s_lastDynamicTimerProfile.AliveMonsters}" : "dynamic=not-collected"); string text3 = (s_hasActivationStartPhaseProfile ? $"start=total={s_lastActivationStartPhaseProfile.TotalMs:F1}ms setActive={s_lastActivationStartPhaseProfile.SetActiveMs:F1}ms initialTimer={s_lastActivationStartPhaseProfile.InitialTimerMs:F1}ms captureCurrency={s_lastActivationStartPhaseProfile.CaptureCurrencyMs:F1}ms ensureNetwork={s_lastActivationStartPhaseProfile.EnsureNetworkMs:F1}ms showUi={s_lastActivationStartPhaseProfile.ShowUiMs:F1}ms clearState={s_lastActivationStartPhaseProfile.ClearStateMs:F1}ms broadcast={s_lastActivationStartPhaseProfile.BroadcastMs:F1}ms debugExtras={s_lastActivationStartPhaseProfile.DebugExtrasMs:F1}ms" : "start=not-collected"); Log.LogDebug((object)$"[LastChance] ActivationProfile: window={num:F1}ms {text3} {text2} helper={text}"); } } private static void ClearActivationProfileState() { s_activationProfilePending = false; s_activationProfileStartedAt = 0f; s_hasDynamicTimerProfile = false; s_lastDynamicTimerProfile = default(DynamicTimerProfileSnapshot); s_hasActivationStartPhaseProfile = false; s_lastActivationStartPhaseProfile = default(ActivationStartPhaseProfileSnapshot); } private static float GetDynamicTimerCapSeconds() { int num = Mathf.Clamp(FeatureFlags.LastChanceDynamicMaxMinutes, 5, 20); return (float)num * 60f; } private static DynamicTimerInputs CollectDynamicTimerInputs(int maxPlayers) { bool debugLogging = FeatureFlags.DebugLogging; float num = (debugLogging ? Time.realtimeSinceStartup : 0f); float num2 = num; float num3 = num; float num4 = num; float num5 = num; int requiredPlayers = Mathf.Max(1, GetLastChanceNeededPlayers(maxPlayers)); int currentLevelNumber = GetCurrentLevelNumber(); int aliveSearchMonsterCount = LastChanceMonstersSearchModule.GetAliveSearchMonsterCount(); if (debugLogging) { num2 = Time.realtimeSinceStartup; } PlayerTruckDistanceHelper.PlayerTruckDistance[] distancesFromTruck = PlayerTruckDistanceHelper.GetDistancesFromTruck(PlayerTruckDistanceHelper.DistanceQueryFields.All); if (debugLogging) { num3 = Time.realtimeSinceStartup; } if (distancesFromTruck.Length == 0) { if (debugLogging) { float totalMs = (Time.realtimeSinceStartup - num) * 1000f; float monstersMs = (num2 - num) * 1000f; float recordsMs = (num3 - num2) * 1000f; CacheDynamicTimerProfile(new DynamicTimerProfileSnapshot(totalMs, monstersMs, recordsMs, 0f, 0f, 0f, 0, 0, requiredPlayers, currentLevelNumber, aliveSearchMonsterCount)); } return new DynamicTimerInputs(requiredPlayers, currentLevelNumber, aliveSearchMonsterCount, 0f, 0, 0f, 0); } List list = SelectRequiredPlayers(distancesFromTruck, requiredPlayers); if (debugLogging) { num4 = Time.realtimeSinceStartup; } List list2 = new List(list.Count); for (int i = 0; i < list.Count; i++) { PlayerAvatar playerAvatar = list[i].PlayerAvatar; if ((Object)(object)playerAvatar != (Object)null) { list2.Add(playerAvatar); } } if (list2.Count > 0) { PlayerTruckDistanceHelper.PlayerTruckDistance[] distancesFromTruck2 = PlayerTruckDistanceHelper.GetDistancesFromTruck(PlayerTruckDistanceHelper.DistanceQueryFields.Height, list2); if (distancesFromTruck2.Length != 0) { list = SelectRequiredPlayers(distancesFromTruck2, requiredPlayers); } } if (debugLogging) { num5 = Time.realtimeSinceStartup; } float num6 = Mathf.Min(0f, FeatureFlags.LastChanceBelowTruckThresholdMeters); float num7 = 0f; int num8 = 0; float num9 = 0f; int num10 = 0; for (int j = 0; j < list.Count; j++) { PlayerTruckDistanceHelper.PlayerTruckDistance playerTruckDistance = list[j]; if (playerTruckDistance.HasValidPath && playerTruckDistance.NavMeshDistance >= 0f) { num7 += playerTruckDistance.NavMeshDistance; } if (playerTruckDistance.ShortestRoomPathToTruck >= 0) { num10 += playerTruckDistance.ShortestRoomPathToTruck; } if (playerTruckDistance.HeightDelta <= num6) { num8++; num9 += Mathf.Max(0f, num6 - playerTruckDistance.HeightDelta); } } if (debugLogging) { float realtimeSinceStartup = Time.realtimeSinceStartup; float totalMs2 = (realtimeSinceStartup - num) * 1000f; float monstersMs2 = (num2 - num) * 1000f; float recordsMs2 = (num3 - num2) * 1000f; float selectMs = (num4 - num3) * 1000f; float heightRefreshMs = (num5 - num4) * 1000f; float aggregateMs = (realtimeSinceStartup - num5) * 1000f; CacheDynamicTimerProfile(new DynamicTimerProfileSnapshot(totalMs2, monstersMs2, recordsMs2, selectMs, heightRefreshMs, aggregateMs, distancesFromTruck.Length, list.Count, requiredPlayers, currentLevelNumber, aliveSearchMonsterCount)); } return new DynamicTimerInputs(requiredPlayers, currentLevelNumber, aliveSearchMonsterCount, num7, num8, num9, num10); } private static List SelectRequiredPlayers(PlayerTruckDistanceHelper.PlayerTruckDistance[] records, int requiredPlayers) { List list = new List(records.Length); for (int i = 0; i < records.Length; i++) { list.Add(records[i]); } list.Sort((PlayerTruckDistanceHelper.PlayerTruckDistance left, PlayerTruckDistanceHelper.PlayerTruckDistance right) => ScoreTimerDifficulty(right).CompareTo(ScoreTimerDifficulty(left))); int num = Mathf.Clamp(requiredPlayers, 1, list.Count); if (num >= list.Count) { return list; } List list2 = new List(num); for (int j = 0; j < num; j++) { list2.Add(list[j]); } return list2; } private static float ScoreTimerDifficulty(PlayerTruckDistanceHelper.PlayerTruckDistance record) { if (record.HasValidPath && record.NavMeshDistance >= 0f) { return record.NavMeshDistance; } if (record.ShortestRoomPathToTruck >= 0) { return (float)record.ShortestRoomPathToTruck * 15f; } return 99999f; } private static int GetCurrentLevelNumber() { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { return 1; } try { return Mathf.Max(1, instance.levelsCompleted + 1); } catch { return 1; } } private static float CalculateRawAddedSeconds(DynamicTimerInputs inputs) { float num = 0f; num += (float)inputs.RequiredPlayers * FeatureFlags.LastChanceTimerPerRequiredPlayerSeconds; num += inputs.TotalDistanceMeters * FeatureFlags.LastChanceTimerPerFarthestMeterSeconds; num += (float)inputs.PlayersBelowTruckThreshold * FeatureFlags.LastChanceTimerPerBelowTruckPlayerSeconds; num += inputs.TotalBelowTruckMeters * FeatureFlags.LastChanceTimerPerBelowTruckMeterSeconds; num += (float)inputs.TotalShortestRoomPathSteps * FeatureFlags.LastChanceTimerPerRoomStepSeconds; float num2 = (float)inputs.AliveSearchMonsters * Mathf.Max(0f, FeatureFlags.LastChanceTimerPerMonsterSeconds); if (FeatureFlags.LastChanceMonstersSearchEnabled) { num2 *= 1.2f; } num += num2; num *= CalculateLevelContributionMultiplier(inputs); return Mathf.Max(0f, num); } private static float CalculateLevelContributionMultiplier(DynamicTimerInputs inputs) { int num = Mathf.Max(2, FeatureFlags.LastChanceDynamicMaxMinutesAtLevel); int num2 = Mathf.Min(Mathf.Max(1, inputs.LevelNumber), num); float num3 = ((float)num2 - 1f) / ((float)num - 1f); float num4 = Mathf.Lerp(0.55f, 1f, num3); float num5 = Mathf.Max(1f, (float)inputs.RequiredPlayers * 14f); float num6 = Mathf.Clamp01((float)inputs.TotalShortestRoomPathSteps / num5); float num7 = Mathf.Clamp01((float)inputs.AliveSearchMonsters / 10f); float num8 = Mathf.Max(0f, FeatureFlags.LastChanceLevelContextRoomWeight); float num9 = Mathf.Max(0f, FeatureFlags.LastChanceLevelContextMonsterWeight); float num10 = 1f + num6 * num8 + num7 * num9; float num11 = Mathf.Lerp(1f, num10, num3); return Mathf.Max(0.1f, num4 * num11); } private static float GetLevelFloorSeconds(int levelNumber, float maxSeconds) { int num = Mathf.Max(2, FeatureFlags.LastChanceDynamicMaxMinutesAtLevel); int num2 = Mathf.Min(Mathf.Max(1, levelNumber), num); float num3 = ((float)num2 - 1f) / ((float)num - 1f); float configuredSeconds = GetConfiguredSeconds(); float num4 = Mathf.Lerp(configuredSeconds, maxSeconds, num3); return Mathf.Clamp(num4, 30f, maxSeconds); } private static string FormatTimerText(float secondsRemaining) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.CeilToInt(secondsRemaining); int num2 = num / 60; int num3 = num % 60; Color val = ((num <= 30) ? FlashColor : TimerColor); string arg = ColorUtility.ToHtmlStringRGB(val); return $"LAST CHANCE {num2:0}:{num3:00}"; } private static void SetLastChanceActive(bool active) { s_active = active; if (active) { LastChanceRuntimeOrchestrator.EnterActiveRuntime(); ApplyLastChanceHostRuntimeOverrides(); return; } LastChanceRuntimeOrchestrator.ExitRuntime("lastchance-deactivated"); LastChanceRuntimeObjectRegistry.ResetForRuntimeDeactivated(); ClearDirectionPenaltyCache(); ClearLastChanceHostRuntimeOverrides(); LastChanceMonstersNoiseAggroModule.ResetRuntimeState(); LastChanceMonstersSearchModule.ResetRuntimeState(); LastChanceMonstersVoiceEnemyOnlyModule.ResetRuntimeState(); LastChanceMonstersCameraForceLockModule.ResetRuntimeState(); LastChanceMonstersPlayerVisionCheckModule.ResetRuntimeState(); LastChanceMonstersAnimalHeadVisionFallbackModule.ResetRuntimeState(); LastChanceMonstersCarryProxyModule.ResetRuntimeState(); LastChanceMonstersOnScreenCameraModule.ResetRuntimeState(); LastChanceMonstersThinManStandModule.ResetRuntimeState(); LastChanceHeadPupilVisualModule.ResetRuntimeState(); LastChanceHeadEyesOverrideBypassModule.ResetRuntimeState(); } private static void ResetLastChanceRuntimeModules(bool allowVanillaAllPlayersDead, bool allowAutoDelete) { LastChanceMonstersNoiseAggroModule.ResetRuntimeState(); LastChanceMonstersSearchModule.ResetRuntimeState(); LastChanceMonstersVoiceEnemyOnlyModule.ResetRuntimeState(); LastChanceMonstersCameraForceLockModule.ResetRuntimeState(); LastChanceMonstersPlayerVisionCheckModule.ResetRuntimeState(); LastChanceMonstersAnimalHeadVisionFallbackModule.ResetRuntimeState(); LastChanceMonstersCarryProxyModule.ResetRuntimeState(); LastChanceMonstersOnScreenCameraModule.ResetRuntimeState(); LastChanceMonstersThinManStandModule.ResetRuntimeState(); LastChanceHeadPupilVisualModule.ResetRuntimeState(); LastChanceHeadEyesOverrideBypassModule.ResetRuntimeState(); LastChanceSpectateHelper.ResetForceState(); if (allowAutoDelete) { LastChanceSaveDeleteState.AllowAutoDelete(); } else { LastChanceSaveDeleteState.ResetAutoDeleteBlock(); } if (allowVanillaAllPlayersDead) { AllPlayersDeadGuard.AllowVanillaAllPlayersDead(); } else { AllPlayersDeadGuard.ResetVanillaAllPlayersDead(); } } private static void ApplyLastChanceHostRuntimeOverrides() { if (LastChanceRuntimeOrchestrator.IsRuntimeActive && SemiFunc.IsMasterClientOrSingleplayer() && !s_lastChanceBatteryOverrideApplied && CoreBatteryInterop.TryGetBatteryJumpEnabled(out var value) && value) { s_lastChancePreviousBatteryJumpEnabled = value; if (CoreBatteryInterop.TrySetBatteryJumpEnabled(value: false)) { CoreBatteryInterop.SetCoreHostRuntimeOverride(enabled: false); CoreBatteryInterop.RequestCoreConfigSyncBroadcast(); s_lastChanceBatteryOverrideApplied = true; } } } private static void ClearLastChanceHostRuntimeOverrides() { if (s_lastChanceBatteryOverrideApplied) { CoreBatteryInterop.TrySetBatteryJumpEnabled(s_lastChancePreviousBatteryJumpEnabled); CoreBatteryInterop.ClearCoreHostRuntimeOverride(); CoreBatteryInterop.RequestCoreConfigSyncBroadcast(); s_lastChanceBatteryOverrideApplied = false; s_lastChancePreviousBatteryJumpEnabled = false; } } private static object[] BuildSurrenderedActorsPayload() { if (LastChanceSurrenderedPlayers.Count == 0) { return Array.Empty(); } object[] array = new object[LastChanceSurrenderedPlayers.Count]; int num = 0; foreach (int lastChanceSurrenderedPlayer in LastChanceSurrenderedPlayers) { array[num++] = lastChanceSurrenderedPlayer; } return array; } private static float ComputeAuthoritativeRemaining(float secondsRemaining, double hostSentAt) { if (!SemiFunc.IsMultiplayer()) { return Mathf.Max(0f, secondsRemaining); } double num = PhotonNetwork.Time - hostSentAt; if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.0) { num = 0.0; } return Mathf.Max(0f, secondsRemaining - (float)num); } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters { internal static class LastChanceMonstersPatchLifecycle { private static bool s_pipelineApplied; internal static void ReconcilePipeline(bool enable, Harmony harmony) { if (enable) { ApplyPipeline(harmony); } else { UnapplyPipeline(); } } internal static void ApplyPipeline(Harmony harmony) { if (!s_pipelineApplied) { LastChanceMonstersSearchModule.Apply(harmony); LastChanceMonstersNoiseAggroModule.Apply(harmony); LastChanceMonstersPlayerVisionCheckModule.Apply(); LastChanceMonstersCameraForceLockModule.Apply(); s_pipelineApplied = true; } } internal static void UnapplyPipeline() { if (s_pipelineApplied) { LastChanceMonstersCameraForceLockModule.Unapply(); LastChanceMonstersPlayerVisionCheckModule.Unapply(); LastChanceMonstersNoiseAggroModule.Unapply(); LastChanceMonstersSearchModule.Unapply(); LastChanceMonstersAnimalHeadVisionFallbackModule.ResetRuntimeState(); LastChanceMonstersCarryProxyModule.ResetRuntimeState(); LastChanceMonstersOnScreenCameraModule.ResetRuntimeState(); LastChanceMonstersThinManStandModule.ResetRuntimeState(); s_pipelineApplied = false; } } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters.Support { internal static class LastChanceMonstersDisabledGateHelper { internal static bool ShouldTreatDisabledAsActive(PlayerAvatar? player) { return (Object)(object)player != (Object)null && LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player); } } internal static class LastChanceMonstersLockBridgeCore { internal enum LockStabilizeKind { None, Force, Snap, Emergency } internal readonly struct LockStabilizeResult { internal LockStabilizeKind Kind { get; } internal float Distance { get; } internal float OffLockTimer { get; } internal float ForceMagnitude { get; } internal bool Applied => Kind != LockStabilizeKind.None; internal LockStabilizeResult(LockStabilizeKind kind, float distance, float offLockTimer, float forceMagnitude) { Kind = kind; Distance = distance; OffLockTimer = offLockTimer; ForceMagnitude = forceMagnitude; } } internal readonly struct HeadCoupleResult { internal bool HardSnap { get; } internal float Distance { get; } internal float ForceMagnitude { get; } internal HeadCoupleResult(bool hardSnap, float distance, float forceMagnitude) { HardSnap = hardSnap; Distance = distance; ForceMagnitude = forceMagnitude; } } internal static bool IsHeadProxyRuntimeApplicable(PlayerAvatar? player) { return (Object)(object)player != (Object)null && LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled() && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player); } internal static LockStabilizeResult StabilizeTargetAtLockPoint(Rigidbody targetRb, Transform lockPoint, float offLockPointTimer, bool fixedUpdate, bool isPrimaryLockState) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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_0106: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: 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_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016a: 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_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) if (!fixedUpdate || !isPrimaryLockState || (Object)(object)targetRb == (Object)null || (Object)(object)lockPoint == (Object)null) { return new LockStabilizeResult(LockStabilizeKind.None, 0f, offLockPointTimer, 0f); } float num = Vector3.Distance(targetRb.position, lockPoint.position); if (num <= 0.9f) { return new LockStabilizeResult(LockStabilizeKind.None, num, offLockPointTimer, 0f); } if (offLockPointTimer > 1.2f) { Vector3 position = Vector3.Lerp(targetRb.position, lockPoint.position, 0.65f); targetRb.position = position; if (!targetRb.isKinematic) { targetRb.velocity = Vector3.Lerp(targetRb.velocity, Vector3.zero, 0.7f); } return new LockStabilizeResult(LockStabilizeKind.Emergency, num, offLockPointTimer, 0f); } if (num > 1.6f) { Vector3 position2 = Vector3.Lerp(targetRb.position, lockPoint.position, 0.35f); targetRb.position = position2; if (!targetRb.isKinematic) { targetRb.velocity = Vector3.Lerp(targetRb.velocity, Vector3.zero, 0.5f); } return new LockStabilizeResult(LockStabilizeKind.Snap, num, offLockPointTimer, 0f); } if (targetRb.isKinematic) { targetRb.position = Vector3.Lerp(targetRb.position, lockPoint.position, 0.2f); return new LockStabilizeResult(LockStabilizeKind.Snap, num, offLockPointTimer, 0f); } Vector3 val = SemiFunc.PhysFollowPosition(targetRb.position, lockPoint.position, targetRb.velocity, 7f); targetRb.AddForce(val, (ForceMode)5); return new LockStabilizeResult(LockStabilizeKind.Force, num, offLockPointTimer, ((Vector3)(ref val)).magnitude); } internal static HeadCoupleResult CoupleHeadToTarget(PhysGrabObject headPhys, Rigidbody headRb, Rigidbody targetRb, bool fixedUpdate) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) float num = Vector3.Distance(headRb.position, targetRb.position); if (num > 2.5f) { headRb.position = targetRb.position; headRb.rotation = targetRb.rotation; if (!headRb.isKinematic) { headRb.velocity = (targetRb.isKinematic ? Vector3.zero : targetRb.velocity); headRb.angularVelocity = (targetRb.isKinematic ? Vector3.zero : targetRb.angularVelocity); } return new HeadCoupleResult(hardSnap: true, num, 0f); } headPhys.OverrideZeroGravity(0.1f); if (headRb.isKinematic) { headRb.position = Vector3.Lerp(headRb.position, targetRb.position, 0.3f); headRb.rotation = Quaternion.Slerp(headRb.rotation, targetRb.rotation, 0.3f); return new HeadCoupleResult(hardSnap: false, num, 0f); } Vector3 val = SemiFunc.PhysFollowPosition(headRb.position, targetRb.position, headRb.velocity, 5f); Vector3 val2 = targetRb.position - headRb.position; if (((Vector3)(ref val2)).sqrMagnitude > 0.0001f) { Vector3 val3 = SemiFunc.PhysFollowDirection(((Component)headRb).transform, ((Vector3)(ref val2)).normalized, headRb, 0.5f); headRb.AddTorque(val3 / Mathf.Max(headRb.mass, 0.0001f), (ForceMode)0); } headRb.AddForce(val, (ForceMode)(fixedUpdate ? 5 : 0)); Vector3 val4 = (targetRb.isKinematic ? Vector3.zero : targetRb.velocity); Vector3 val5 = (targetRb.isKinematic ? Vector3.zero : targetRb.angularVelocity); headRb.velocity = Vector3.Lerp(headRb.velocity, val4, 0.35f); headRb.angularVelocity = Vector3.Lerp(headRb.angularVelocity, val5, 0.35f); return new HeadCoupleResult(hardSnap: false, num, ((Vector3)(ref val)).magnitude); } } internal static class LastChanceMonstersTargetingOrchestrator { internal static Vector3 ResolveEffectiveTransformTargetPoint(Transform? transform) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) return LastChanceMonstersTargetProxyHelper.ResolveEffectiveTransformTargetPosition(transform); } internal static void ExtendPlayersWithinRangeLastChanceAware(List list, float range, Vector3 position, bool doRaycastCheck, LayerMask layerMask) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return; } List list2 = GameDirector.instance?.PlayerList; if (list2 == null || list2.Count == 0) { return; } for (int i = 0; i < list2.Count; i++) { PlayerAvatar val = list2[i]; if (!((Object)(object)val == (Object)null) && !list.Contains(val) && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(val, out var center)) { float num = Vector3.Distance(position, center); if (!(num > range) && (!doRaycastCheck || !IsWallBlocking(position, center, num, layerMask))) { list.Add(val); } } } } internal static PlayerAvatar? ResolveNearestPlayerWithinRangeLastChanceAware(PlayerAvatar? currentNearest, float range, Vector3 position, bool doRaycastCheck, LayerMask layerMask) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return currentNearest; } List list = SemiFunc.PlayerGetAllPlayerAvatarWithinRange(range, position, doRaycastCheck, layerMask) ?? new List(); ExtendPlayersWithinRangeLastChanceAware(list, range, position, doRaycastCheck, layerMask); if ((Object)(object)currentNearest != (Object)null && !list.Contains(currentNearest)) { list.Add(currentNearest); } if (list.Count == 0) { return currentNearest; } float num = range; PlayerAvatar result = currentNearest; for (int i = 0; i < list.Count; i++) { PlayerAvatar val = list[i]; if (!((Object)(object)val == (Object)null)) { Vector3 val2 = ResolveDistancePoint(val); float num2 = Vector3.Distance(position, val2); if (num2 < num) { num = num2; result = val; } } } return result; } internal static List GetAllPlayersWithinRangeLastChanceAware(float range, Vector3 position, bool doRaycastCheck, LayerMask layerMask) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) List list = SemiFunc.PlayerGetAllPlayerAvatarWithinRange(range, position, doRaycastCheck, layerMask) ?? new List(); ExtendPlayersWithinRangeLastChanceAware(list, range, position, doRaycastCheck, layerMask); return list; } internal static PlayerAvatar? GetNearestPlayerWithinRangeLastChanceAware(float range, Vector3 position, bool doRaycastCheck, LayerMask layerMask) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) return ResolveNearestPlayerWithinRangeLastChanceAware(null, range, position, doRaycastCheck, layerMask); } private static bool IsWallBlocking(Vector3 origin, Vector3 target, float distance, LayerMask layerMask) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) Vector3 val = target - origin; RaycastHit[] array = Physics.RaycastAll(origin, val, distance, LayerMask.op_Implicit(layerMask), (QueryTriggerInteraction)1); for (int i = 0; i < array.Length; i++) { Collider collider = ((RaycastHit)(ref array[i])).collider; Transform val2 = ((collider != null) ? ((Component)collider).transform : null); if ((Object)(object)val2 != (Object)null && ((Component)val2).CompareTag("Wall")) { return true; } } return false; } private static Vector3 ResolveDistancePoint(PlayerAvatar player) { //IL_000e: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(player, out var center)) { return center; } Transform val = player.PlayerVisionTarget?.VisionTransform; if ((Object)(object)val != (Object)null) { return val.position; } return ((Component)player).transform.position; } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters.Pipeline { [HarmonyPatch(typeof(EnemyAnimal), "Update")] internal static class LastChanceMonstersAnimalHeadVisionFallbackModule { private const float ProbeInterval = 0.2f; private static readonly Dictionary NextProbeAtByAnimal = new Dictionary(); internal static void ResetRuntimeState() { NextProbeAtByAnimal.Clear(); } [HarmonyPostfix] private static void UpdatePostfix(EnemyAnimal __instance) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } int instanceID = ((Object)__instance).GetInstanceID(); if (!LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled() || !IsStateEligible(__instance.currentState)) { NextProbeAtByAnimal.Remove(instanceID); return; } float unscaledTime = Time.unscaledTime; if (NextProbeAtByAnimal.TryGetValue(instanceID, out var value) && unscaledTime < value) { return; } NextProbeAtByAnimal[instanceID] = unscaledTime + 0.2f; Enemy enemy = __instance.enemy; EnemyVision val = enemy?.Vision; Transform val2 = val?.VisionTransform; if ((Object)(object)enemy == (Object)null || (Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return; } List list = GameDirector.instance?.PlayerList; if (list == null || list.Count == 0) { return; } LayerMask visionMask = enemy.VisionMask; for (int i = 0; i < list.Count; i++) { PlayerAvatar val3 = list[i]; if ((Object)(object)val3 == (Object)null || !LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val3) || !LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(val3, out var point)) { continue; } Vector3 val4 = point - val2.position; float magnitude = ((Vector3)(ref val4)).magnitude; if (!(magnitude > val.VisionDistance) && !(magnitude <= 0.001f)) { Vector3 val5 = val4 / magnitude; bool flag = magnitude <= val.VisionDistanceClose; if ((Vector3.Dot(val2.forward, val5) >= val.VisionDotStanding || flag) && LastChanceMonstersTargetProxyHelper.IsLineOfSightToHead(val2, point, visionMask, val3)) { val.onVisionTriggeredPlayer = val3; val.onVisionTriggeredID = (((Object)(object)val3.photonView != (Object)null) ? val3.photonView.ViewID : ((Object)val3).GetInstanceID()); LastChanceMonstersTargetProxyHelper.EnsureVisionTriggered(val, val3, flag); __instance.OnVision(); break; } } } } private static bool IsStateEligible(State state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 return (int)state == 1 || (int)state == 2 || (int)state == 3 || (int)state == 7; } } internal static class LastChanceMonstersAnimalOnVisionGuardModule { } [HarmonyPatch(typeof(SemiFunc), "LevelPointInTargetRoomGet")] internal static class LastChanceMonstersAnimalWreakHavocHeadRoomProxyModule { private static int s_animalWreakHavocDepth; [HarmonyPatch(typeof(EnemyAnimal), "StateWreakHavoc")] [HarmonyPrefix] private static void EnemyAnimalStateWreakHavocPrefix() { s_animalWreakHavocDepth++; } [HarmonyPatch(typeof(EnemyAnimal), "StateWreakHavoc")] [HarmonyPostfix] private static void EnemyAnimalStateWreakHavocPostfix() { if (s_animalWreakHavocDepth > 0) { s_animalWreakHavocDepth--; } } [HarmonyPrefix] private static bool Prefix(RoomVolumeCheck _target, float _minDistance, float _maxDistance, LevelPoint ignorePoint, ref LevelPoint __result) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) __result = null; if ((Object)(object)_target == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled()) { return true; } if (s_animalWreakHavocDepth <= 0) { return true; } PlayerAvatar val = ResolveTargetPlayer(_target); if ((Object)(object)val == (Object)null || !LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val)) { return true; } if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(val, out var center)) { return true; } List list = LevelGenerator.Instance?.LevelPathPoints; if (list == null || list.Count == 0) { return true; } HashSet hashSet = CollectHeadRooms(center); if (hashSet.Count == 0 && _target.CurrentRooms != null) { for (int i = 0; i < _target.CurrentRooms.Count; i++) { RoomVolume val2 = _target.CurrentRooms[i]; if ((Object)(object)val2 != (Object)null) { hashSet.Add(val2); } } } if (hashSet.Count == 0) { return true; } List list2 = new List(); for (int j = 0; j < list.Count; j++) { LevelPoint val3 = list[j]; if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3 == (Object)(object)ignorePoint) && !((Object)(object)val3.Room == (Object)null) && hashSet.Contains(val3.Room)) { float num = Vector3.Distance(((Component)val3).transform.position, center); if (!(num < _minDistance) && !(num > _maxDistance)) { list2.Add(val3); } } } if (list2.Count == 0) { for (int k = 0; k < list.Count; k++) { LevelPoint val4 = list[k]; if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val4 == (Object)(object)ignorePoint) && !((Object)(object)val4.Room == (Object)null) && !val4.Room.Truck) { float num2 = Vector3.Distance(((Component)val4).transform.position, center); if (!(num2 < _minDistance) && !(num2 > _maxDistance)) { list2.Add(val4); } } } } if (list2.Count == 0) { return true; } __result = list2[Random.Range(0, list2.Count)]; return false; } private static PlayerAvatar? ResolveTargetPlayer(RoomVolumeCheck target) { if ((Object)(object)target == (Object)null) { return null; } PlayerAvatar componentInParent = ((Component)target).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return componentInParent; } return target.player; } private static HashSet CollectHeadRooms(Vector3 headCenter) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) HashSet hashSet = new HashSet(); Collider[] array = Physics.OverlapSphere(headCenter, 1.1f, LayerMask.GetMask(new string[1] { "RoomVolume" }), (QueryTriggerInteraction)2); foreach (Collider val in array) { if (!((Object)(object)val == (Object)null)) { RoomVolume val2 = ((Component)val).GetComponent() ?? ((Component)val).GetComponentInParent(); if ((Object)(object)val2 != (Object)null) { hashSet.Add(val2); } } } return hashSet; } } [HarmonyPatch] internal static class LastChanceMonstersBehaviorDebugProbeModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.MonstersDebugProbe"); private static readonly Dictionary HeadmanEnemyStateById = new Dictionary(); private static readonly Dictionary HeadmanTargetDisabledById = new Dictionary(); private static readonly Dictionary SlowMouthStateById = new Dictionary(); private static bool ShouldDebug() { return InternalDebugFlags.DebugLastChanceHeadmanSlowMouthFlow && LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled(); } [HarmonyPatch(typeof(EnemyHeadUp), "Update")] [HarmonyPostfix] private static void EnemyHeadUpUpdatePostfix(EnemyHeadUp __instance) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.enemy == (Object)null)) { Enemy enemy = __instance.enemy; int instanceID = ((Object)enemy).GetInstanceID(); PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; bool flag = (Object)(object)targetPlayerAvatar != (Object)null && targetPlayerAvatar.isDisabled; EnemyState value; bool flag2 = !HeadmanEnemyStateById.TryGetValue(instanceID, out value) || value != enemy.CurrentState; bool value2; bool flag3 = !HeadmanTargetDisabledById.TryGetValue(instanceID, out value2) || value2 != flag; bool flag4 = LogLimiter.ShouldLog($"Headman.Heartbeat.{instanceID}"); if (flag2 || flag3 || flag4) { HeadmanEnemyStateById[instanceID] = enemy.CurrentState; HeadmanTargetDisabledById[instanceID] = flag; bool flag5 = LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(targetPlayerAvatar); bool flag6 = (Object)(object)targetPlayerAvatar != (Object)null; float num = (((Object)(object)enemy.StateChase != (Object)null) ? enemy.StateChase.VisionTimer : (-1f)); float num2 = ((flag6 && (Object)(object)targetPlayerAvatar.PlayerVisionTarget != (Object)null && (Object)(object)targetPlayerAvatar.PlayerVisionTarget.VisionTransform != (Object)null) ? targetPlayerAvatar.PlayerVisionTarget.VisionTransform.position.y : (-1f)); bool isOverrideScopeActive = LastChanceMonstersDisabledOverrideModule.IsOverrideScopeActive; Log.LogInfo((object)($"[HeadmanDebug] enemy={((Object)((Component)enemy).gameObject).name} id={instanceID} componentId={((Object)__instance).GetInstanceID()} state={enemy.CurrentState} hasTarget={flag6} " + $"targetDisabled={flag} treatDisabledAsActive={flag5} " + $"disabledOverrideScopeActive={isOverrideScopeActive} visionTimer={num:F2} targetY={num2:F2} " + BuildPlayerAnchorSummary(targetPlayerAvatar, null))); } } } [HarmonyPatch(typeof(EnemySlowMouth), "UpdateState")] [HarmonyPrefix] private static void EnemySlowMouthUpdateStatePrefix(EnemySlowMouth __instance, State newState) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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) if (ShouldDebug() && !((Object)(object)__instance == (Object)null)) { State currentState = __instance.currentState; if (currentState != newState || LogLimiter.ShouldLog($"SlowMouth.UpdateState.Same.{((Object)__instance).GetInstanceID()}")) { Log.LogInfo((object)($"[SlowMouthDebug] UpdateState request enemy={((Object)((Component)__instance).gameObject).name} id={((Object)__instance).GetInstanceID()} from={currentState} to={newState} " + $"targetDisabled={(Object)(object)__instance.playerTarget != (Object)null && __instance.playerTarget.isDisabled} " + $"targetActive={LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(__instance.playerTarget)} " + $"disabledOverrideScopeActive={LastChanceMonstersDisabledOverrideModule.IsOverrideScopeActive} " + BuildPlayerAnchorSummary(__instance.playerTarget, __instance.currentTarget))); } } } [HarmonyPatch(typeof(EnemySlowMouth), "UpdateStateRPC")] [HarmonyPostfix] private static void EnemySlowMouthUpdateStateRpcPostfix(EnemySlowMouth __instance, State newState) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null)) { Log.LogInfo((object)$"[SlowMouthDebug] UpdateStateRPC applied enemy={((Object)((Component)__instance).gameObject).name} id={((Object)__instance).GetInstanceID()} state={newState}"); } } [HarmonyPatch(typeof(EnemySlowMouth), "Update")] [HarmonyPostfix] private static void EnemySlowMouthUpdatePostfix(EnemySlowMouth __instance) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null)) { int instanceID = ((Object)__instance).GetInstanceID(); State currentState = __instance.currentState; State value; bool flag = !SlowMouthStateById.TryGetValue(instanceID, out value) || value != currentState; bool flag2 = LogLimiter.ShouldLog($"SlowMouth.Heartbeat.{instanceID}"); if (flag || flag2) { SlowMouthStateById[instanceID] = currentState; PlayerAvatar playerTarget = __instance.playerTarget; bool flag3 = (Object)(object)playerTarget != (Object)null && playerTarget.isDisabled; bool flag4 = LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(playerTarget); Log.LogInfo((object)($"[SlowMouthDebug] enemy={((Object)((Component)__instance).gameObject).name} id={instanceID} state={currentState} hasTarget={(Object)(object)playerTarget != (Object)null} " + $"targetDisabled={flag3} targetActive={flag4} " + $"disabledOverrideScopeActive={LastChanceMonstersDisabledOverrideModule.IsOverrideScopeActive} " + BuildPlayerAnchorSummary(playerTarget, __instance.currentTarget))); } } } [HarmonyPatch(typeof(EnemySlowMouthAttaching), "Update")] [HarmonyPostfix] private static void EnemySlowMouthAttachingUpdatePostfix(EnemySlowMouthAttaching __instance) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && LogLimiter.ShouldLog($"SlowMouthAttaching.Update.{((Object)__instance).GetInstanceID()}", 120)) { PlayerAvatar targetPlayerAvatar = __instance.targetPlayerAvatar; bool flag = (Object)(object)targetPlayerAvatar != (Object)null && targetPlayerAvatar.isDisabled; Log.LogInfo((object)($"[SlowMouthDebug] Attaching.Update active={__instance.isActive} hasTarget={(Object)(object)targetPlayerAvatar != (Object)null} " + $"targetDisabled={flag} enemyState={__instance.enemySlowMouth.currentState} " + $"disabledOverrideScopeActive={LastChanceMonstersDisabledOverrideModule.IsOverrideScopeActive} " + BuildPlayerAnchorSummary(targetPlayerAvatar, __instance.targetTransform))); } } [HarmonyPatch(typeof(EnemyHeadController), "VisionTriggered")] [HarmonyPrefix] private static void EnemyHeadControllerVisionTriggeredPrefix(EnemyHeadController __instance) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { Enemy enemy = __instance.Enemy; if (LogLimiter.ShouldLog($"Headman.VisionTriggered.Prefix.{((Object)enemy).GetInstanceID()}", 20)) { Log.LogInfo((object)($"[HeadmanDebug] VisionTriggered.Prefix enemy={((Object)((Component)enemy).gameObject).name} id={((Object)enemy).GetInstanceID()} " + $"state={enemy.CurrentState} targetViewId={enemy.TargetPlayerViewID} " + BuildPlayerAnchorSummary(enemy.TargetPlayerAvatar, enemy.TargetPlayerAvatar?.PlayerVisionTarget?.VisionTransform))); } } } [HarmonyPatch(typeof(EnemyHeadController), "VisionTriggered")] [HarmonyPostfix] private static void EnemyHeadControllerVisionTriggeredPostfix(EnemyHeadController __instance) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { Enemy enemy = __instance.Enemy; if (LogLimiter.ShouldLog($"Headman.VisionTriggered.{((Object)enemy).GetInstanceID()}", 20)) { PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; Log.LogInfo((object)($"[HeadmanDebug] VisionTriggered enemy={((Object)((Component)enemy).gameObject).name} id={((Object)enemy).GetInstanceID()} " + $"state={enemy.CurrentState} targetViewId={(((Object)(object)targetPlayerAvatar?.photonView != (Object)null) ? targetPlayerAvatar.photonView.ViewID : (-1))} " + BuildPlayerAnchorSummary(targetPlayerAvatar, ((Object)(object)targetPlayerAvatar?.PlayerVisionTarget != (Object)null) ? targetPlayerAvatar.PlayerVisionTarget.VisionTransform : null))); } } } [HarmonyPatch(typeof(EnemyStateChaseBegin), "Update")] [HarmonyPrefix] private static void EnemyStateChaseBeginUpdatePrefix(EnemyStateChaseBegin __instance) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { Enemy enemy = __instance.Enemy; if (LogLimiter.ShouldLog($"Headman.ChaseBegin.Update.Prefix.{((Object)enemy).GetInstanceID()}", 15)) { PlayerAvatar val = __instance.TargetPlayer ?? enemy.TargetPlayerAvatar; Log.LogInfo((object)($"[HeadmanDebug] ChaseBegin.Update.Prefix enemy={((Object)((Component)enemy).gameObject).name} id={((Object)enemy).GetInstanceID()} " + $"enemyState={enemy.CurrentState} active={__instance.Active} stateTimer={__instance.StateTimer:F2} " + $"master={enemy.MasterClient} targetViewId={(((Object)(object)val?.photonView != (Object)null) ? val.photonView.ViewID : (-1))} " + BuildPlayerAnchorSummary(val, val?.PlayerVisionTarget?.VisionTransform))); } } } [HarmonyPatch(typeof(EnemyStateChaseBegin), "Update")] [HarmonyPostfix] private static void EnemyStateChaseBeginUpdatePostfix(EnemyStateChaseBegin __instance) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { Enemy enemy = __instance.Enemy; if (LogLimiter.ShouldLog($"Headman.ChaseBegin.Update.Postfix.{((Object)enemy).GetInstanceID()}", 15)) { PlayerAvatar val = __instance.TargetPlayer ?? enemy.TargetPlayerAvatar; Log.LogInfo((object)($"[HeadmanDebug] ChaseBegin.Update.Postfix enemy={((Object)((Component)enemy).gameObject).name} id={((Object)enemy).GetInstanceID()} " + $"enemyState={enemy.CurrentState} active={__instance.Active} stateTimer={__instance.StateTimer:F2} " + $"master={enemy.MasterClient} targetViewId={(((Object)(object)val?.photonView != (Object)null) ? val.photonView.ViewID : (-1))} " + BuildPlayerAnchorSummary(val, val?.PlayerVisionTarget?.VisionTransform))); } } } [HarmonyPatch(typeof(EnemyStateChase), "Update")] [HarmonyPrefix] private static void EnemyStateChaseUpdatePrefix(EnemyStateChase __instance) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { Enemy enemy = __instance.Enemy; if (LogLimiter.ShouldLog($"Headman.Chase.Update.Prefix.{((Object)enemy).GetInstanceID()}", 15)) { PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; Log.LogInfo((object)($"[HeadmanDebug] Chase.Update.Prefix enemy={((Object)((Component)enemy).gameObject).name} id={((Object)enemy).GetInstanceID()} " + $"enemyState={enemy.CurrentState} active={__instance.Active} visionTimer={__instance.VisionTimer:F2} " + $"stateTimer={__instance.StateTimer:F2} canReach={__instance.ChaseCanReach} master={enemy.MasterClient} " + $"targetViewId={(((Object)(object)targetPlayerAvatar?.photonView != (Object)null) ? targetPlayerAvatar.photonView.ViewID : (-1))} " + BuildPlayerAnchorSummary(targetPlayerAvatar, targetPlayerAvatar?.PlayerVisionTarget?.VisionTransform))); } } } [HarmonyPatch(typeof(EnemyStateChase), "Update")] [HarmonyPostfix] private static void EnemyStateChaseUpdatePostfix(EnemyStateChase __instance) { if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { LogRoamingWriter("EnemyStateChase.Update", __instance.Enemy, $"visionTimer={__instance.VisionTimer:F2} stateTimer={__instance.StateTimer:F2} canReach={__instance.ChaseCanReach}"); } } [HarmonyPatch(typeof(EnemyHeadController), "OnStunnedEnd")] [HarmonyPrefix] private static void EnemyHeadControllerOnStunnedEndPrefix(EnemyHeadController __instance) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { Enemy enemy = __instance.Enemy; if (LogLimiter.ShouldLog($"Headman.StunnedEnd.{((Object)enemy).GetInstanceID()}", 20)) { Log.LogInfo((object)($"[HeadmanDebug] OnStunnedEnd enemy={((Object)((Component)enemy).gameObject).name} id={((Object)enemy).GetInstanceID()} " + $"stateBefore={enemy.CurrentState} -> Roaming")); } } } [HarmonyPatch(typeof(EnemyHeadController), "OnStunnedEnd")] [HarmonyPostfix] private static void EnemyHeadControllerOnStunnedEndPostfix(EnemyHeadController __instance) { if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { LogRoamingWriter("EnemyHeadController.OnStunnedEnd", __instance.Enemy, "postfix"); } } [HarmonyPatch(typeof(EnemyStateStunned), "Update")] [HarmonyPrefix] private static void EnemyStateStunnedUpdatePrefix(EnemyStateStunned __instance) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.enemy == (Object)null)) { Enemy enemy = __instance.enemy; if (!((Object)(object)((Component)enemy).GetComponentInChildren() == (Object)null) && LogLimiter.ShouldLog($"Headman.Stunned.Update.Prefix.{((Object)enemy).GetInstanceID()}", 15)) { PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; Log.LogInfo((object)($"[HeadmanDebug] Stunned.Update.Prefix enemy={((Object)((Component)enemy).gameObject).name} id={((Object)enemy).GetInstanceID()} " + $"enemyState={enemy.CurrentState} stunTimer={__instance.stunTimer:F2} overrideDisableTimer={__instance.overrideDisableTimer:F2} " + $"active={__instance.active} targetViewId={(((Object)(object)targetPlayerAvatar?.photonView != (Object)null) ? targetPlayerAvatar.photonView.ViewID : (-1))} " + BuildPlayerAnchorSummary(targetPlayerAvatar, targetPlayerAvatar?.PlayerVisionTarget?.VisionTransform))); } } } [HarmonyPatch(typeof(EnemyStateStunned), "Update")] [HarmonyPostfix] private static void EnemyStateStunnedUpdatePostfix(EnemyStateStunned __instance) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.enemy == (Object)null)) { Enemy enemy = __instance.enemy; if (!((Object)(object)((Component)enemy).GetComponentInChildren() == (Object)null) && LogLimiter.ShouldLog($"Headman.Stunned.Update.Postfix.{((Object)enemy).GetInstanceID()}", 15)) { PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; Log.LogInfo((object)($"[HeadmanDebug] Stunned.Update.Postfix enemy={((Object)((Component)enemy).gameObject).name} id={((Object)enemy).GetInstanceID()} " + $"enemyState={enemy.CurrentState} stunTimer={__instance.stunTimer:F2} overrideDisableTimer={__instance.overrideDisableTimer:F2} " + $"active={__instance.active} targetViewId={(((Object)(object)targetPlayerAvatar?.photonView != (Object)null) ? targetPlayerAvatar.photonView.ViewID : (-1))} " + BuildPlayerAnchorSummary(targetPlayerAvatar, targetPlayerAvatar?.PlayerVisionTarget?.VisionTransform))); LogRoamingWriter("EnemyStateStunned.Update", enemy, $"stunTimer={__instance.stunTimer:F2} overrideDisableTimer={__instance.overrideDisableTimer:F2} active={__instance.active}"); } } } [HarmonyPatch(typeof(EnemyStateSneak), "Update")] [HarmonyPostfix] private static void EnemyStateSneakUpdatePostfix(EnemyStateSneak __instance) { if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { PlayerAvatar val = __instance.TargetPlayer ?? __instance.Enemy.TargetPlayerAvatar; LogRoamingWriter("EnemyStateSneak.Update", __instance.Enemy, $"stateTimer={__instance.StateTimer:F2} active={__instance.Active} targetViewId={(((Object)(object)val?.photonView != (Object)null) ? val.photonView.ViewID : (-1))}"); } } [HarmonyPatch(typeof(EnemyStateChaseEnd), "Update")] [HarmonyPostfix] private static void EnemyStateChaseEndUpdatePostfix(EnemyStateChaseEnd __instance) { if (ShouldDebug() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.Enemy == (Object)null)) { LogRoamingWriter("EnemyStateChaseEnd.Update", __instance.Enemy, $"stateTimer={__instance.StateTimer:F2} active={__instance.Active}"); } } [HarmonyPatch(typeof(Enemy), "PlayerRemoved")] [HarmonyPostfix] private static void EnemyPlayerRemovedPostfix(Enemy __instance, int photonID) { if (ShouldDebug() && !((Object)(object)__instance == (Object)null)) { LogRoamingWriter("Enemy.PlayerRemoved", __instance, $"removedPhotonId={photonID}"); } } [HarmonyPatch(typeof(EnemySlowMouth), "OnSpawn")] [HarmonyPostfix] private static void EnemySlowMouthOnSpawnPostfix(EnemySlowMouth __instance) { if (ShouldDebug() && !((Object)(object)__instance == (Object)null)) { Log.LogInfo((object)($"[SlowMouthDebug] OnSpawn enemy={((Object)((Component)__instance).gameObject).name} id={((Object)__instance).GetInstanceID()} " + BuildPlayerAnchorSummary(__instance.playerTarget, __instance.currentTarget))); } } private static string BuildPlayerAnchorSummary(PlayerAvatar? target, Transform? currentTarget) { //IL_003b: 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_006e: 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) if ((Object)(object)target == (Object)null) { return "anchorSummary=target-null"; } float num = -1f; if ((Object)(object)currentTarget != (Object)null && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(target, out var point)) { num = Vector3.Distance(currentTarget.position, point); } float num2 = -1f; if ((Object)(object)currentTarget != (Object)null && (Object)(object)target.localCamera != (Object)null) { num2 = Vector3.Distance(currentTarget.position, ((Component)target.localCamera).transform.position); } return $"anchorSummary=currentTargetToHead={num:F2} currentTargetToCamera={num2:F2}"; } private static void LogRoamingWriter(string source, Enemy? enemy, string details) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if (!((Object)(object)enemy == (Object)null) && !((Object)(object)((Component)enemy).GetComponentInChildren() == (Object)null) && (int)enemy.CurrentState == 2) { int instanceID = ((Object)enemy).GetInstanceID(); if (LogLimiter.ShouldLog($"Headman.RoamingWriter.{source}.{instanceID}", 5)) { PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; int num = (((Object)(object)targetPlayerAvatar?.photonView != (Object)null) ? targetPlayerAvatar.photonView.ViewID : (-1)); bool flag = (Object)(object)targetPlayerAvatar != (Object)null && targetPlayerAvatar.isDisabled; bool flag2 = LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(targetPlayerAvatar); float num2 = (((Object)(object)enemy.StateChase != (Object)null) ? enemy.StateChase.VisionTimer : (-1f)); Log.LogInfo((object)($"[HeadmanRoamingWriter] source={source} enemy={((Object)((Component)enemy).gameObject).name} id={instanceID} " + $"targetViewId={num} targetDisabled={flag} targetEligible={flag2} " + $"visionTimer={num2:F2} details={details}")); } } } } [HarmonyPatch] internal static class LastChanceMonstersCameraForceLockModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.CeilingEye"); internal static void Apply() { ResetRuntimeState(); } internal static void Unapply() { ResetRuntimeState(); } [HarmonyPatch(typeof(CameraAim), "AimTargetSoftSet", new Type[] { typeof(Vector3), typeof(float), typeof(float), typeof(float), typeof(GameObject), typeof(int) })] [HarmonyPrefix] private static bool AimTargetSoftSetPrefix(Vector3 position, GameObject obj) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return HandleCameraAimRequest(position, obj); } [HarmonyPatch(typeof(CameraAim), "AimTargetSet", new Type[] { typeof(Vector3), typeof(float), typeof(float), typeof(GameObject), typeof(int) })] [HarmonyPrefix] private static bool AimTargetSetPrefix(Vector3 position, GameObject obj) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return HandleCameraAimRequest(position, obj); } private static bool HandleCameraAimRequest(Vector3 position, GameObject? source) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!IsSupportedCameraSource(source)) { return true; } if (!IsLastChanceCameraContextActive()) { return true; } if (!ShouldApplyCameraForce(source)) { return false; } TryForceSpectateAimTo(position, source); return true; } private static bool IsSupportedCameraSource(GameObject? source) { if ((Object)(object)source == (Object)null) { return false; } return (Object)(object)source.GetComponentInParent() != (Object)null || (Object)(object)source.GetComponentInParent() != (Object)null || (Object)(object)source.GetComponentInParent() != (Object)null || (Object)(object)source.GetComponentInParent() != (Object)null || (Object)(object)source.GetComponentInParent() != (Object)null || (Object)(object)source.GetComponentInParent() != (Object)null || (Object)(object)source.GetComponentInParent() != (Object)null; } private static bool IsLastChanceCameraContextActive() { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return false; } return LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(PlayerAvatar.instance); } private static bool ShouldApplyCameraForce(GameObject? source) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return false; } PlayerAvatar instance = PlayerAvatar.instance; if (!LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(instance)) { return false; } float unscaledTime = Time.unscaledTime; int key = (((Object)(object)source != (Object)null) ? ((Object)source).GetInstanceID() : 0); if (!LastChanceMonstersCeilingEyeLockCoordinator.CanForceCamera(instance, unscaledTime, out string reason)) { DebugDecision(source, key, reason, unscaledTime, decision: false); return false; } bool lastChanceMonstersForceCameraOnLock = InternalConfig.LastChanceMonstersForceCameraOnLock; DebugDecision(source, key, lastChanceMonstersForceCameraOnLock ? "AllowForceCamera" : "ForceCameraDisabledByConfig", unscaledTime, lastChanceMonstersForceCameraOnLock); return lastChanceMonstersForceCameraOnLock; } private static void DebugDecision(GameObject? source, int key, string reason, float now, bool decision) { if (InternalDebugFlags.DebugLastChanceCeilingEyeFlow && LogLimiter.ShouldLog($"CeilingEye.CameraForce.{reason}.{key}", 90)) { string text = (((Object)(object)source != (Object)null) ? ((Object)source).name : "null-source"); Log.LogInfo((object)($"[CeilingEye][CameraForce][{reason}] source='{text}' key={key} decision={decision} " + $"now={now:F2} cfgForce={InternalConfig.LastChanceMonstersForceCameraOnLock}")); } } private static void TryForceSpectateAimTo(Vector3 targetPosition, GameObject? source) { //IL_0056: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0088: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) SpectateCamera instance = SpectateCamera.instance; if ((Object)(object)instance == (Object)null) { return; } PlayerAvatar instance2 = PlayerAvatar.instance; PlayerAvatar player = instance.player; if ((Object)(object)instance2 == (Object)null || (Object)(object)player == (Object)null || instance2 != player) { return; } Transform transform = ((Component)instance).transform; Vector3 val = targetPosition - transform.position; if (!(((Vector3)(ref val)).sqrMagnitude <= 0.0001f)) { float num = Mathf.Atan2(val.x, val.z) * 57.29578f; Vector2 val2 = new Vector2(val.x, val.z); float magnitude = ((Vector2)(ref val2)).magnitude; float num2 = (0f - Mathf.Atan2(val.y, Mathf.Max(0.0001f, magnitude))) * 57.29578f; instance.normalAimHorizontal = num; instance.normalAimVertical = Mathf.Clamp(num2, -80f, 80f); if (InternalDebugFlags.DebugLastChanceCeilingEyeFlow && LogLimiter.ShouldLog("CeilingEye.SpectateBridge", 90)) { Log.LogInfo((object)string.Format("[CeilingEye][SpectateBridge] source='{0}' yaw={1:F1} pitch={2:F1} target={3}", ((Object)(object)source != (Object)null) ? ((Object)source).name : "null-source", num, num2, targetPosition)); } } } internal static void ResetRuntimeState() { LastChanceMonstersCeilingEyeLockCoordinator.ResetRuntimeState(); } } internal static class LastChanceMonstersCeilingEyeLockCoordinator { private sealed class LockState { internal float LockStartAt = -1f; internal float LastSeenAt = -1f; internal float CooldownUntil = -1f; internal float LastTouchAt = -1f; } private static readonly Dictionary s_stateByPlayer = new Dictionary(); private static float s_nextCleanupAt; internal static void ResetRuntimeState() { s_stateByPlayer.Clear(); s_nextCleanupAt = 0f; } internal static bool EvaluateVisionLock(PlayerAvatar? player, bool seen, float now, out string reason) { reason = "NoPlayer"; int playerKey = GetPlayerKey(player); if (playerKey <= 0) { return false; } CleanupOldStates(now); LockState orCreateState = GetOrCreateState(playerKey); orCreateState.LastTouchAt = now; if (orCreateState.CooldownUntil > now) { reason = "CooldownActive"; return false; } float num = Mathf.Max(0.05f, InternalConfig.LastChanceMonstersCameraLockKeepAliveGraceSeconds); if (!seen) { if (orCreateState.LastSeenAt < 0f || now - orCreateState.LastSeenAt > num) { orCreateState.LockStartAt = -1f; } reason = "NotSeen"; return false; } if (orCreateState.LockStartAt < 0f || orCreateState.LastSeenAt < 0f || now - orCreateState.LastSeenAt > num) { orCreateState.LockStartAt = now; } orCreateState.LastSeenAt = now; float num2 = Mathf.Max(0.1f, InternalConfig.LastChanceMonstersCameraLockMaxSeconds); if (now - orCreateState.LockStartAt >= num2) { float num3 = Mathf.Max(0.1f, InternalConfig.LastChanceMonstersCameraLockCooldownSeconds); orCreateState.CooldownUntil = now + num3; orCreateState.LockStartAt = -1f; orCreateState.LastSeenAt = -1f; reason = "ReachedMaxLock_SetCooldown"; return false; } reason = "SeenAndAllowed"; return true; } internal static bool CanForceCamera(PlayerAvatar? player, float now, out string reason) { reason = "NoPlayer"; int playerKey = GetPlayerKey(player); if (playerKey <= 0) { return false; } CleanupOldStates(now); if (!s_stateByPlayer.TryGetValue(playerKey, out LockState value)) { reason = "NoVisionState"; return false; } value.LastTouchAt = now; if (value.CooldownUntil > now) { reason = "CooldownActive"; return false; } float num = Mathf.Max(0.05f, InternalConfig.LastChanceMonstersCameraLockKeepAliveGraceSeconds); if (value.LastSeenAt < 0f || now - value.LastSeenAt > num) { reason = "NoRecentVision"; return false; } if (value.LockStartAt < 0f) { value.LockStartAt = now; } float num2 = Mathf.Max(0.1f, InternalConfig.LastChanceMonstersCameraLockMaxSeconds); if (now - value.LockStartAt >= num2) { float num3 = Mathf.Max(0.1f, InternalConfig.LastChanceMonstersCameraLockCooldownSeconds); value.CooldownUntil = now + num3; value.LockStartAt = -1f; value.LastSeenAt = -1f; reason = "ReachedMaxLock_SetCooldown"; return false; } reason = "Allowed"; return true; } private static LockState GetOrCreateState(int key) { if (s_stateByPlayer.TryGetValue(key, out LockState value)) { return value; } value = new LockState(); s_stateByPlayer[key] = value; return value; } private static int GetPlayerKey(PlayerAvatar? player) { if ((Object)(object)player == (Object)null) { return 0; } return ((Object)(object)player.photonView != (Object)null) ? player.photonView.ViewID : ((Object)player).GetInstanceID(); } private static void CleanupOldStates(float now) { if (now < s_nextCleanupAt) { return; } s_nextCleanupAt = now + 5f; if (s_stateByPlayer.Count == 0) { return; } List list = new List(); foreach (KeyValuePair item in s_stateByPlayer) { LockState value = item.Value; if (value == null) { list.Add(item.Key); continue; } float num = Mathf.Max(value.LastTouchAt, value.CooldownUntil); if (num < 0f || now - num > 30f) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { s_stateByPlayer.Remove(list[i]); } } } [HarmonyPatch(typeof(EnemyStateChase), "Update")] internal static class LastChanceMonstersChaseNavmeshProxyModule { [HarmonyPrefix] private static void Prefix(EnemyStateChase __instance) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return; } Enemy enemy = __instance.Enemy; if (!((Object)(object)enemy == (Object)null) && (int)enemy.CurrentState == 4) { PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; if (!((Object)(object)targetPlayerAvatar == (Object)null) && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(targetPlayerAvatar, out var center)) { targetPlayerAvatar.LastNavmeshPosition = center; targetPlayerAvatar.LastNavMeshPositionTimer = 0f; } } } } [HarmonyPatch(typeof(EnemyHealth), "DeathImpulseRPC")] internal static class LastChanceMonstersDeathTimerBonusModule { [HarmonyPostfix] private static void Postfix(EnemyHealth __instance) { if (!((Object)(object)__instance == (Object)null) && LastChanceRuntimeOrchestrator.IsRuntimeActive) { LastChanceTimerController.TryApplyMonsterDeathTimerBonusHost(); } } } [HarmonyPatch] internal static class LastChanceMonstersDisabledOverrideModule { private readonly struct DisabledOverrideScope : IDisposable { private readonly PlayerAvatar[] _players; private readonly bool[] _originalDisabled; private readonly int _count; internal DisabledOverrideScope(PlayerAvatar[] players, bool[] originalDisabled, int count) { _players = players; _originalDisabled = originalDisabled; _count = count; } internal static DisabledOverrideScope Enter() { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return default(DisabledOverrideScope); } List list = GameDirector.instance?.PlayerList; if (list == null || list.Count == 0) { return default(DisabledOverrideScope); } List list2 = new List(list.Count); List list3 = new List(list.Count); for (int i = 0; i < list.Count; i++) { PlayerAvatar val = list[i]; if (!((Object)(object)val == (Object)null) && val.isDisabled && LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(val)) { list2.Add(val); list3.Add(val.isDisabled); val.isDisabled = false; } } if (list2.Count == 0) { return default(DisabledOverrideScope); } Interlocked.Increment(ref s_activeOverrideScopes); return new DisabledOverrideScope(list2.ToArray(), list3.ToArray(), list2.Count); } public void Dispose() { if (_players == null || _originalDisabled == null || _count <= 0) { return; } for (int i = 0; i < _count; i++) { PlayerAvatar val = _players[i]; if (!((Object)(object)val == (Object)null)) { val.isDisabled = _originalDisabled[i]; } } Interlocked.Decrement(ref s_activeOverrideScopes); } } private static int s_activeOverrideScopes; internal static bool IsOverrideScopeActive => Volatile.Read(ref s_activeOverrideScopes) > 0; private static DisabledOverrideScope EnterScope() { return DisabledOverrideScope.Enter(); } private static Exception? ExitScope(DisabledOverrideScope __state, Exception? __exception) { __state.Dispose(); return __exception; } [HarmonyPatch(typeof(EnemyBangDirector), "StateAttackPlayer")] [HarmonyPrefix] private static void EnemyBangDirectorStateAttackPlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyBangDirector), "StateAttackPlayer")] [HarmonyFinalizer] private static Exception? EnemyBangDirectorStateAttackPlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyBirthdayBoy), "Update")] [HarmonyPrefix] private static void EnemyBirthdayBoyUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyBirthdayBoy), "Update")] [HarmonyFinalizer] private static Exception? EnemyBirthdayBoyUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyBombThrower), "StateGotoPlayer")] [HarmonyPrefix] private static void EnemyBombThrowerStateGotoPlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyBombThrower), "StateGotoPlayer")] [HarmonyFinalizer] private static Exception? EnemyBombThrowerStateGotoPlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyBombThrower), "StateBackAwayPlayer")] [HarmonyPrefix] private static void EnemyBombThrowerStateBackAwayPlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyBombThrower), "StateBackAwayPlayer")] [HarmonyFinalizer] private static Exception? EnemyBombThrowerStateBackAwayPlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyBombThrower), "StateBackAwayHead")] [HarmonyPrefix] private static void EnemyBombThrowerStateBackAwayHeadPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyBombThrower), "StateBackAwayHead")] [HarmonyFinalizer] private static Exception? EnemyBombThrowerStateBackAwayHeadFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyBombThrowerHead), "StateSpawn", new Type[] { typeof(bool) })] [HarmonyPrefix] private static void EnemyBombThrowerHeadStateSpawnPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyBombThrowerHead), "StateSpawn", new Type[] { typeof(bool) })] [HarmonyFinalizer] private static Exception? EnemyBombThrowerHeadStateSpawnFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyBombThrowerHead), "StateActive", new Type[] { typeof(bool) })] [HarmonyPrefix] private static void EnemyBombThrowerHeadStateActivePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyBombThrowerHead), "StateActive", new Type[] { typeof(bool) })] [HarmonyFinalizer] private static Exception? EnemyBombThrowerHeadStateActiveFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyBombThrowerHead), "EyeLogic")] [HarmonyPrefix] private static void EnemyBombThrowerHeadEyeLogicPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyBombThrowerHead), "EyeLogic")] [HarmonyFinalizer] private static Exception? EnemyBombThrowerHeadEyeLogicFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyDuck), "StateGoToPlayer")] [HarmonyPrefix] private static void EnemyDuckStateGoToPlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyDuck), "StateGoToPlayer")] [HarmonyFinalizer] private static Exception? EnemyDuckStateGoToPlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyDuck), "StateGoToPlayerUnder")] [HarmonyPrefix] private static void EnemyDuckStateGoToPlayerUnderPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyDuck), "StateGoToPlayerUnder")] [HarmonyFinalizer] private static Exception? EnemyDuckStateGoToPlayerUnderFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyDuck), "StateGoToPlayerOver")] [HarmonyPrefix] private static void EnemyDuckStateGoToPlayerOverPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyDuck), "StateGoToPlayerOver")] [HarmonyFinalizer] private static Exception? EnemyDuckStateGoToPlayerOverFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyDuck), "HeadLookAtLogic")] [HarmonyPrefix] private static void EnemyDuckHeadLookAtLogicPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyDuck), "HeadLookAtLogic")] [HarmonyFinalizer] private static Exception? EnemyDuckHeadLookAtLogicFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyDuck), "ChaseStop")] [HarmonyPrefix] private static void EnemyDuckChaseStopPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyDuck), "ChaseStop")] [HarmonyFinalizer] private static Exception? EnemyDuckChaseStopFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyElsa), "Update")] [HarmonyPrefix] private static void EnemyElsaUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyElsa), "Update")] [HarmonyFinalizer] private static Exception? EnemyElsaUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyElsa), "StateGoToPlayerSmall")] [HarmonyPrefix] private static void EnemyElsaStateGoToPlayerSmallPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyElsa), "StateGoToPlayerSmall")] [HarmonyFinalizer] private static Exception? EnemyElsaStateGoToPlayerSmallFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyElsa), "StateGoToPlayerUnderSmall")] [HarmonyPrefix] private static void EnemyElsaStateGoToPlayerUnderSmallPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyElsa), "StateGoToPlayerUnderSmall")] [HarmonyFinalizer] private static Exception? EnemyElsaStateGoToPlayerUnderSmallFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyElsa), "StateLookUnderBig")] [HarmonyPrefix] private static void EnemyElsaStateLookUnderBigPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyElsa), "StateLookUnderBig")] [HarmonyFinalizer] private static Exception? EnemyElsaStateLookUnderBigFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyElsa), "ChaseStop")] [HarmonyPrefix] private static void EnemyElsaChaseStopPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyElsa), "ChaseStop")] [HarmonyFinalizer] private static Exception? EnemyElsaChaseStopFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyGnomeDirector), "StateAttackPlayer")] [HarmonyPrefix] private static void EnemyGnomeDirectorStateAttackPlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyGnomeDirector), "StateAttackPlayer")] [HarmonyFinalizer] private static Exception? EnemyGnomeDirectorStateAttackPlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHeadGrabber), "GotoLogic")] [HarmonyPrefix] private static void EnemyHeadGrabberGotoLogicPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHeadGrabber), "GotoLogic")] [HarmonyFinalizer] private static Exception? EnemyHeadGrabberGotoLogicFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHeadGrabber), "GotoOverLogic")] [HarmonyPrefix] private static void EnemyHeadGrabberGotoOverLogicPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHeadGrabber), "GotoOverLogic")] [HarmonyFinalizer] private static Exception? EnemyHeadGrabberGotoOverLogicFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHeadGrabber), "GetClosestDeathHead", new Type[] { typeof(float) })] [HarmonyPrefix] private static void EnemyHeadGrabberGetClosestDeathHeadPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHeadGrabber), "GetClosestDeathHead", new Type[] { typeof(float) })] [HarmonyFinalizer] private static Exception? EnemyHeadGrabberGetClosestDeathHeadFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHeadGrabber), "NearbyHeadLogic")] [HarmonyPrefix] private static void EnemyHeadGrabberNearbyHeadLogicPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHeadGrabber), "NearbyHeadLogic")] [HarmonyFinalizer] private static Exception? EnemyHeadGrabberNearbyHeadLogicFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerGoTo")] [HarmonyPrefix] private static void EnemyHiddenStatePlayerGoToPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerGoTo")] [HarmonyFinalizer] private static Exception? EnemyHiddenStatePlayerGoToFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerPickup")] [HarmonyPrefix] private static void EnemyHiddenStatePlayerPickupPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerPickup")] [HarmonyFinalizer] private static Exception? EnemyHiddenStatePlayerPickupFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerMove")] [HarmonyPrefix] private static void EnemyHiddenStatePlayerMovePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerMove")] [HarmonyFinalizer] private static Exception? EnemyHiddenStatePlayerMoveFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerRelease")] [HarmonyPrefix] private static void EnemyHiddenStatePlayerReleasePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerRelease")] [HarmonyFinalizer] private static Exception? EnemyHiddenStatePlayerReleaseFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyShadow), "Update")] [HarmonyPrefix] private static void EnemyShadowUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyShadow), "Update")] [HarmonyFinalizer] private static Exception? EnemyShadowUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyShadow), "StateChooseTarget")] [HarmonyPrefix] private static void EnemyShadowStateChooseTargetPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyShadow), "StateChooseTarget")] [HarmonyFinalizer] private static Exception? EnemyShadowStateChooseTargetFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouth), "DetatchLogic")] [HarmonyPrefix] private static void EnemySlowMouthDetatchLogicPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouth), "DetatchLogic")] [HarmonyFinalizer] private static Exception? EnemySlowMouthDetatchLogicFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouth), "StateAttached", new Type[] { typeof(bool) })] [HarmonyPrefix] private static void EnemySlowMouthStateAttachedPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouth), "StateAttached", new Type[] { typeof(bool) })] [HarmonyFinalizer] private static Exception? EnemySlowMouthStateAttachedFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouth), "StateIdlePuke", new Type[] { typeof(bool) })] [HarmonyPrefix] private static void EnemySlowMouthStateIdlePukePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouth), "StateIdlePuke", new Type[] { typeof(bool) })] [HarmonyFinalizer] private static Exception? EnemySlowMouthStateIdlePukeFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouth), "StateGoToPlayerOver", new Type[] { typeof(bool) })] [HarmonyPrefix] private static void EnemySlowMouthStateGoToPlayerOverPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouth), "StateGoToPlayerOver", new Type[] { typeof(bool) })] [HarmonyFinalizer] private static Exception? EnemySlowMouthStateGoToPlayerOverFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouth), "StateGoToPlayerUnder", new Type[] { typeof(bool) })] [HarmonyPrefix] private static void EnemySlowMouthStateGoToPlayerUnderPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouth), "StateGoToPlayerUnder", new Type[] { typeof(bool) })] [HarmonyFinalizer] private static Exception? EnemySlowMouthStateGoToPlayerUnderFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouth), "TargettingPlayer")] [HarmonyPrefix] private static void EnemySlowMouthTargettingPlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouth), "TargettingPlayer")] [HarmonyFinalizer] private static Exception? EnemySlowMouthTargettingPlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyTricycle), "StateStateBeforeAttack")] [HarmonyPrefix] private static void EnemyTricycleStateStateBeforeAttackPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyTricycle), "StateStateBeforeAttack")] [HarmonyFinalizer] private static Exception? EnemyTricycleStateStateBeforeAttackFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyTricycle), "StateAttackDive")] [HarmonyPrefix] private static void EnemyTricycleStateAttackDivePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyTricycle), "StateAttackDive")] [HarmonyFinalizer] private static Exception? EnemyTricycleStateAttackDiveFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyTricycle), "FixedUpdateAttackDive")] [HarmonyPrefix] private static void EnemyTricycleFixedUpdateAttackDivePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyTricycle), "FixedUpdateAttackDive")] [HarmonyFinalizer] private static Exception? EnemyTricycleFixedUpdateAttackDiveFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyValuableThrower), "TargetFailsafe")] [HarmonyPrefix] private static void EnemyValuableThrowerTargetFailsafePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyValuableThrower), "TargetFailsafe")] [HarmonyFinalizer] private static Exception? EnemyValuableThrowerTargetFailsafeFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyCeilingEye), "StateHasTarget")] [HarmonyPrefix] private static void EnemyCeilingEyeStateHasTargetPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyCeilingEye), "StateHasTarget")] [HarmonyFinalizer] private static Exception? EnemyCeilingEyeStateHasTargetFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyCeilingEye), "TargetFailSafe")] [HarmonyPrefix] private static void EnemyCeilingEyeTargetFailSafePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyCeilingEye), "TargetFailSafe")] [HarmonyFinalizer] private static Exception? EnemyCeilingEyeTargetFailSafeFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySpinny), "Update")] [HarmonyPrefix] private static void EnemySpinnyUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySpinny), "Update")] [HarmonyFinalizer] private static Exception? EnemySpinnyUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyOogly), "StatePlayerSpotted")] [HarmonyPrefix] private static void EnemyOoglyStatePlayerSpottedPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyOogly), "StatePlayerSpotted")] [HarmonyFinalizer] private static Exception? EnemyOoglyStatePlayerSpottedFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyOogly), "StateWrestlePlayer")] [HarmonyPrefix] private static void EnemyOoglyStateWrestlePlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyOogly), "StateWrestlePlayer")] [HarmonyFinalizer] private static Exception? EnemyOoglyStateWrestlePlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyRunner), "StateAttackPlayer")] [HarmonyPrefix] private static void EnemyRunnerStateAttackPlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyRunner), "StateAttackPlayer")] [HarmonyFinalizer] private static Exception? EnemyRunnerStateAttackPlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyRunner), "Update")] [HarmonyPrefix] private static void EnemyRunnerUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyRunner), "Update")] [HarmonyFinalizer] private static Exception? EnemyRunnerUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyRunner), "StateAttackPlayerOver")] [HarmonyPrefix] private static void EnemyRunnerStateAttackPlayerOverPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyRunner), "StateAttackPlayerOver")] [HarmonyFinalizer] private static Exception? EnemyRunnerStateAttackPlayerOverFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyRobe), "StateLookUnderAttack")] [HarmonyPrefix] private static void EnemyRobeStateLookUnderAttackPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyRobe), "StateLookUnderAttack")] [HarmonyFinalizer] private static Exception? EnemyRobeStateLookUnderAttackFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyTick), "Update")] [HarmonyPrefix] private static void EnemyTickUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyTick), "Update")] [HarmonyFinalizer] private static Exception? EnemyTickUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowWalker), "StateLookUnderAttack")] [HarmonyPrefix] private static void EnemySlowWalkerStateLookUnderAttackPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowWalker), "StateLookUnderAttack")] [HarmonyFinalizer] private static Exception? EnemySlowWalkerStateLookUnderAttackFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowWalker), "Update")] [HarmonyPrefix] private static void EnemySlowWalkerUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowWalker), "Update")] [HarmonyFinalizer] private static Exception? EnemySlowWalkerUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHeadUp), "Update")] [HarmonyPrefix] private static void EnemyHeadUpUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHeadUp), "Update")] [HarmonyFinalizer] private static Exception? EnemyHeadUpUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyStateChaseBegin), "Update")] [HarmonyPrefix] private static void EnemyStateChaseBeginUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyStateChaseBegin), "Update")] [HarmonyFinalizer] private static Exception? EnemyStateChaseBeginUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyStateChase), "Update")] [HarmonyPrefix] private static void EnemyStateChaseUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyStateChase), "Update")] [HarmonyFinalizer] private static Exception? EnemyStateChaseUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyStateChaseEnd), "Update")] [HarmonyPrefix] private static void EnemyStateChaseEndUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyStateChaseEnd), "Update")] [HarmonyFinalizer] private static Exception? EnemyStateChaseEndUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyStateSneak), "Update")] [HarmonyPrefix] private static void EnemyStateSneakUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyStateSneak), "Update")] [HarmonyFinalizer] private static Exception? EnemyStateSneakUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyStateStunned), "Update")] [HarmonyPrefix] private static void EnemyStateStunnedUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyStateStunned), "Update")] [HarmonyFinalizer] private static Exception? EnemyStateStunnedUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHeadController), "VisionTriggered")] [HarmonyPrefix] private static void EnemyHeadControllerVisionTriggeredPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHeadController), "VisionTriggered")] [HarmonyFinalizer] private static Exception? EnemyHeadControllerVisionTriggeredFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyHeadController), "OnStunnedEnd")] [HarmonyPrefix] private static void EnemyHeadControllerOnStunnedEndPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyHeadController), "OnStunnedEnd")] [HarmonyFinalizer] private static Exception? EnemyHeadControllerOnStunnedEndFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyTriggerAttack), "OnTriggerStay")] [HarmonyPrefix] private static void EnemyTriggerAttackOnTriggerStayPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyTriggerAttack), "OnTriggerStay")] [HarmonyFinalizer] private static Exception? EnemyTriggerAttackOnTriggerStayFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(Enemy), "OnPhotonSerializeView")] [HarmonyPrefix] private static void EnemyOnPhotonSerializeViewPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(Enemy), "OnPhotonSerializeView")] [HarmonyFinalizer] private static Exception? EnemyOnPhotonSerializeViewFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouthAttaching), "Update")] [HarmonyPrefix] private static void EnemySlowMouthAttachingUpdatePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouthAttaching), "Update")] [HarmonyFinalizer] private static Exception? EnemySlowMouthAttachingUpdateFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouthAttaching), "AttachToPlayer")] [HarmonyPrefix] private static void EnemySlowMouthAttachingAttachToPlayerPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouthAttaching), "AttachToPlayer")] [HarmonyFinalizer] private static Exception? EnemySlowMouthAttachingAttachToPlayerFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemySlowMouthAttached), "OnDisable")] [HarmonyPrefix] private static void EnemySlowMouthAttachedOnDisablePrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemySlowMouthAttached), "OnDisable")] [HarmonyFinalizer] private static Exception? EnemySlowMouthAttachedOnDisableFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } [HarmonyPatch(typeof(EnemyThinMan), "StateStand")] [HarmonyPrefix] private static void EnemyThinManStateStandPrefix(out DisabledOverrideScope __state) { __state = EnterScope(); } [HarmonyPatch(typeof(EnemyThinMan), "StateStand")] [HarmonyFinalizer] private static Exception? EnemyThinManStateStandFinalizer(DisabledOverrideScope __state, Exception? __exception) { return ExitScope(__state, __exception); } } [HarmonyPatch(typeof(GameDirector), "Update")] internal static class LastChanceMonstersHeadPlayerProxyColliderModule { [HarmonyPostfix] private static void UpdatePostfix(GameDirector __instance) { if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } internal sealed class LastChanceHeadPlayerProxyColliderRuntime : MonoBehaviour { private sealed class ProxyEntry { internal PlayerAvatar? Player; internal GameObject? ProxyObject; internal SphereCollider? Collider; internal PlayerTrigger? Trigger; } private readonly Dictionary _entries = new Dictionary(); private const float ProxyRadius = 0.35f; private void LateUpdate() { //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { DisableAll(); return; } List list = GameDirector.instance?.PlayerList; if (list == null || list.Count == 0) { DisableAll(); return; } HashSet hashSet = new HashSet(); for (int i = 0; i < list.Count; i++) { PlayerAvatar val = list[i]; if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)val).GetInstanceID(); hashSet.Add(instanceID); Vector3 center; bool flag = LastChanceMonstersTargetProxyHelper.TryGetHeadCenter(val, out center); bool flag2 = LastChanceMonstersTargetProxyHelper.IsDisabled(val) && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val) && flag; ProxyEntry orCreateEntry = GetOrCreateEntry(instanceID, val); if (!flag2) { SetActive(orCreateEntry, enabled: false); } else if (!((Object)(object)orCreateEntry.ProxyObject == (Object)null)) { orCreateEntry.ProxyObject.transform.position = center; orCreateEntry.ProxyObject.transform.rotation = Quaternion.identity; orCreateEntry.ProxyObject.transform.localScale = Vector3.one; SetActive(orCreateEntry, enabled: true); } } } DisableMissing(hashSet); } private ProxyEntry GetOrCreateEntry(int id, PlayerAvatar player) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown if (_entries.TryGetValue(id, out ProxyEntry value)) { value.Player = player; return value; } ProxyEntry proxyEntry = new ProxyEntry { Player = player }; GameObject val = new GameObject("LastChance_HeadPlayerProxyCollider"); val.transform.SetParent(((Component)this).transform, true); val.tag = "Player"; int num = LayerMask.NameToLayer("Player"); if (num >= 0) { val.layer = num; } SphereCollider val2 = val.AddComponent(); ((Collider)val2).isTrigger = true; val2.radius = 0.35f; PlayerTrigger val3 = val.AddComponent(); val3.PlayerAvatar = player; val.SetActive(false); proxyEntry.ProxyObject = val; proxyEntry.Collider = val2; proxyEntry.Trigger = val3; _entries[id] = proxyEntry; return proxyEntry; } private void DisableAll() { foreach (ProxyEntry value in _entries.Values) { SetActive(value, enabled: false); } } private void DisableMissing(HashSet seen) { foreach (KeyValuePair entry in _entries) { if (!seen.Contains(entry.Key)) { SetActive(entry.Value, enabled: false); } } } private static void SetActive(ProxyEntry entry, bool enabled) { if (!((Object)(object)entry.ProxyObject == (Object)null) && entry.ProxyObject.activeSelf != enabled) { entry.ProxyObject.SetActive(enabled); } } private void OnDestroy() { foreach (ProxyEntry value in _entries.Values) { if ((Object)(object)value.ProxyObject != (Object)null) { Object.Destroy((Object)(object)value.ProxyObject); } } _entries.Clear(); } } [HarmonyPatch(typeof(HurtCollider), "Update")] internal static class LastChanceMonstersHurtColliderHeadProxyModule { [HarmonyPostfix] private static void UpdatePostfix(HurtCollider __instance) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() || !((Behaviour)__instance).isActiveAndEnabled || !__instance.playerLogic || __instance.playerDamageCooldown <= 0f) { return; } Collider[] array = CollectOverlaps(__instance, __instance.LayerMask); if (array == null || array.Length == 0) { return; } HashSet hashSet = new HashSet(); foreach (Collider val in array) { if ((Object)(object)val == (Object)null) { continue; } PlayerAvatar val2 = ResolvePlayer(val); if ((Object)(object)val2 == (Object)null && LastChanceMonstersTargetProxyHelper.TryGetPlayerFromDeathHeadCollider(val, out PlayerAvatar player)) { val2 = player; } if (!((Object)(object)val2 == (Object)null) && LastChanceMonstersTargetProxyHelper.IsDisabled(val2) && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val2)) { int item = (((Object)(object)val2.photonView != (Object)null) ? val2.photonView.ViewID : ((Object)val2).GetInstanceID()); if (hashSet.Add(item)) { __instance.PlayerHurt(val2); } } } } private static Collider[]? CollectOverlaps(HurtCollider instance, LayerMask mask) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: 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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) if (instance.ColliderIsBox) { BoxCollider boxCollider = instance.BoxCollider; if ((Object)(object)boxCollider == (Object)null) { return null; } Vector3 val = ((Component)instance).transform.TransformPoint(boxCollider.center); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(((Component)instance).transform.lossyScale.x * boxCollider.size.x, ((Component)instance).transform.lossyScale.y * boxCollider.size.y, ((Component)instance).transform.lossyScale.z * boxCollider.size.z); return Physics.OverlapBox(val, val2 * 0.5f, ((Component)instance).transform.rotation, LayerMask.op_Implicit(mask), (QueryTriggerInteraction)2); } SphereCollider sphereCollider = instance.SphereCollider; if ((Object)(object)sphereCollider == (Object)null) { return null; } Bounds bounds = ((Collider)sphereCollider).bounds; Vector3 center = ((Bounds)(ref bounds)).center; float num = ((Component)instance).transform.lossyScale.x * sphereCollider.radius; return Physics.OverlapSphere(center, num, LayerMask.op_Implicit(mask), (QueryTriggerInteraction)2); } private static PlayerAvatar? ResolvePlayer(Collider collider) { PlayerAvatar componentInParent = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return componentInParent; } PlayerController componentInParent2 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent2?.playerAvatarScript != (Object)null) { return componentInParent2.playerAvatarScript; } return (((Component)collider).GetComponent() ?? ((Component)collider).GetComponentInParent())?.PlayerAvatar; } } [HarmonyPatch(typeof(EnemyOnScreen), "Awake")] internal static class LastChanceMonstersOnScreenCameraModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.ThinMan"); private static readonly Dictionary s_lastBoolStateByKey = new Dictionary(); internal static void ResetRuntimeState() { s_lastBoolStateByKey.Clear(); } [HarmonyPostfix] private static void AwakePostfix(EnemyOnScreen __instance) { if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); DebugLog("OnScreen.AwakeAttach", "enemy=" + ((Object)((Component)__instance).gameObject).name + " attachedRuntimeSync=True"); } } internal static void DebugLog(string reason, string detail) { if (InternalDebugFlags.DebugLastChanceThinManFlow && LogLimiter.ShouldLog("ThinMan." + reason, 300)) { Log.LogInfo((object)("[ThinMan][" + reason + "] " + detail)); } } internal static void DebugLogOnBoolTransition(string reason, string key, bool value, string detail) { if (InternalDebugFlags.DebugLastChanceThinManFlow) { string text = reason + "." + key; if (!s_lastBoolStateByKey.TryGetValue(text, out var value2) || value2 != value || LogLimiter.ShouldLog("ThinMan." + text + ".Heartbeat", 600)) { s_lastBoolStateByKey[text] = value; Log.LogInfo((object)("[ThinMan][" + reason + "] " + detail)); } } } } internal sealed class OnScreenCameraSyncRuntime : MonoBehaviour { private EnemyOnScreen? _onScreen; private bool _lastSyncedOnScreenLocal; private bool _lastSyncedCulledLocal; private bool _hasSyncSnapshot; private void Awake() { _onScreen = ((Component)this).GetComponent(); } private void LateUpdate() { if (!((Object)(object)_onScreen == (Object)null) && LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { SyncLocalHeadProxyOnScreenState(); } } private void SyncLocalHeadProxyOnScreenState() { if ((Object)(object)_onScreen == (Object)null || !GameManager.Multiplayer()) { return; } PlayerAvatar localPlayerAvatar = GetLocalPlayerAvatar(); if ((Object)(object)localPlayerAvatar?.photonView == (Object)null || localPlayerAvatar.photonView.ViewID < 0) { return; } if (!LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(localPlayerAvatar)) { _hasSyncSnapshot = false; LastChanceMonstersOnScreenCameraModule.DebugLog("Sync.Skip.NoHeadProxy", $"enemy={((Object)((Component)_onScreen).gameObject).name} player={localPlayerAvatar.photonView.ViewID}"); return; } bool onScreenLocal = _onScreen.OnScreenLocal; bool culledLocal = _onScreen.CulledLocal; if (!_hasSyncSnapshot || onScreenLocal != _lastSyncedOnScreenLocal || culledLocal != _lastSyncedCulledLocal) { _lastSyncedOnScreenLocal = onScreenLocal; _lastSyncedCulledLocal = culledLocal; _hasSyncSnapshot = true; _onScreen.OnScreenPlayerUpdate(onScreenLocal, culledLocal); LastChanceMonstersOnScreenCameraModule.DebugLogOnBoolTransition("Sync.PlayerUpdate", $"{((Object)_onScreen).GetInstanceID()}.{localPlayerAvatar.photonView.ViewID}.OnScreen", onScreenLocal, $"enemy={((Object)((Component)_onScreen).gameObject).name} player={localPlayerAvatar.photonView.ViewID} onScreenLocal={onScreenLocal} culledLocal={culledLocal}"); LastChanceMonstersOnScreenCameraModule.DebugLogOnBoolTransition("Sync.PlayerUpdate", $"{((Object)_onScreen).GetInstanceID()}.{localPlayerAvatar.photonView.ViewID}.Culled", culledLocal, $"enemy={((Object)((Component)_onScreen).gameObject).name} player={localPlayerAvatar.photonView.ViewID} onScreenLocal={onScreenLocal} culledLocal={culledLocal}"); } } private static PlayerAvatar? GetLocalPlayerAvatar() { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return null; } foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player?.photonView != (Object)null && player.photonView.IsMine) { return player; } } return null; } } [HarmonyPatch(typeof(EnemyOnScreen), "GetOnScreen")] internal static class LastChanceMonstersOnScreenSafeLookupPatch { [HarmonyPrefix] private static bool Prefix(EnemyOnScreen __instance, PlayerAvatar _playerAvatar, ref bool __result) { if ((Object)(object)__instance == (Object)null || (Object)(object)_playerAvatar == (Object)null) { __result = false; return false; } if (!GameManager.Multiplayer()) { __result = __instance.OnScreenLocal; LastChanceMonstersOnScreenCameraModule.DebugLog("GetOnScreen.Singleplayer", string.Format("enemy={0} player={1} result={2}", ((Object)((Component)__instance).gameObject).name, ((Object)(object)_playerAvatar.photonView != (Object)null) ? _playerAvatar.photonView.ViewID.ToString() : "n/a", __result)); return false; } if (LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(_playerAvatar) && (Object)(object)_playerAvatar.photonView != (Object)null && _playerAvatar.photonView.IsMine) { __result = __instance.OnScreenLocal; LastChanceMonstersOnScreenCameraModule.DebugLog("GetOnScreen.HeadProxyLocal", $"enemy={((Object)((Component)__instance).gameObject).name} player={_playerAvatar.photonView.ViewID} result={__result}"); return false; } int num = (((Object)(object)_playerAvatar.photonView != (Object)null) ? _playerAvatar.photonView.ViewID : (-1)); if (num < 0) { __result = false; return false; } if (!__instance.OnScreenPlayer.ContainsKey(num)) { __instance.OnScreenPlayer[num] = false; __result = false; LastChanceMonstersOnScreenCameraModule.DebugLog("GetOnScreen.DictMiss", $"enemy={((Object)((Component)__instance).gameObject).name} player={num} result={__result}"); return false; } __result = __instance.OnScreenPlayer[num]; LastChanceMonstersOnScreenCameraModule.DebugLogOnBoolTransition("GetOnScreen.DictHit", $"{((Object)__instance).GetInstanceID()}.{num}", __result, $"enemy={((Object)((Component)__instance).gameObject).name} player={num} result={__result}"); return false; } } internal static class LastChanceMonstersPlayerVisionCheckModule { [HarmonyPatch(typeof(SemiFunc), "PlayerVisionCheck", new Type[] { typeof(Vector3), typeof(float), typeof(PlayerAvatar), typeof(bool) })] internal static class SemiFuncPlayerVisionCheckPatch { [HarmonyPrefix] private static bool Prefix(Vector3 _position, float _range, PlayerAvatar _player, bool _previouslySeen, ref bool __result) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(_player, out var point)) { return true; } __result = PlayerVisionCheckPositionLastChanceAware(_position, point, _range, _player, _previouslySeen); return false; } } [HarmonyPatch(typeof(SemiFunc), "PlayerVisionCheckPosition", new Type[] { typeof(Vector3), typeof(Vector3), typeof(float), typeof(PlayerAvatar), typeof(bool) })] internal static class SemiFuncPlayerVisionCheckPositionPatch { [HarmonyPrefix] private static bool Prefix(Vector3 _startPosition, Vector3 _endPosition, float _range, PlayerAvatar _player, bool _previouslySeen, ref bool __result) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(_player, out var point)) { return true; } __result = PlayerVisionCheckPositionLastChanceAware(_startPosition, point, _range, _player, _previouslySeen); return false; } } private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.CeilingEye"); internal static void ResetRuntimeState() { LastChanceMonstersCeilingEyeLockCoordinator.ResetRuntimeState(); } internal static void Apply() { } internal static void Unapply() { ResetRuntimeState(); } internal static bool PlayerVisionCheckPositionLastChanceAware(Vector3 startPosition, Vector3 endPosition, float range, PlayerAvatar player, bool previouslySeen) { //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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return SemiFunc.PlayerVisionCheckPosition(startPosition, endPosition, range, player, previouslySeen); } if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(player, out var point)) { return SemiFunc.PlayerVisionCheckPosition(startPosition, endPosition, range, player, previouslySeen); } endPosition = point; float unscaledTime = Time.unscaledTime; bool seen = HeadProxyVisionCheckPosition(startPosition, endPosition, range, player); string reason; bool flag = LastChanceMonstersCeilingEyeLockCoordinator.EvaluateVisionLock(player, seen, unscaledTime, out reason); DebugVision(reason, startPosition, endPosition, player, unscaledTime, flag); return flag; } private static bool HeadProxyVisionCheckPosition(Vector3 startPosition, Vector3 endPosition, float range, PlayerAvatar player) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_0050: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[3] { endPosition, endPosition + Vector3.up * 0.2f, endPosition + Vector3.up * 0.45f }; for (int i = 0; i < array.Length; i++) { if (HeadProxyVisionCheckPositionSingle(startPosition, array[i], range, player)) { return true; } } return false; } private static bool HeadProxyVisionCheckPositionSingle(Vector3 startPosition, Vector3 endPosition, float range, PlayerAvatar player) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) Vector3 val = endPosition - startPosition; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude > range) { return false; } if (magnitude <= 0.001f) { return true; } RaycastHit[] array = Physics.RaycastAll(startPosition, ((Vector3)(ref val)).normalized, magnitude, -1, (QueryTriggerInteraction)1); for (int i = 0; i < array.Length; i++) { Transform transform = ((RaycastHit)(ref array[i])).transform; if ((Object)(object)transform == (Object)null || ((Component)transform).CompareTag("Enemy")) { continue; } PlayerDeathHead componentInParent = ((Component)transform).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)player != (Object)null && (Object)(object)componentInParent == (Object)(object)player.playerDeathHead) { continue; } PlayerAvatar componentInParent2 = ((Component)transform).GetComponentInParent(); if ((!((Object)(object)componentInParent2 != (Object)null) || !((Object)(object)componentInParent2 == (Object)(object)player)) && !((Object)(object)((Component)transform).GetComponentInParent() != (Object)null)) { float num = Vector3.Distance(((RaycastHit)(ref array[i])).point, endPosition); if (!(num <= 0.35f)) { return false; } } } return true; } private static void DebugVision(string reason, Vector3 startPosition, Vector3 endPosition, PlayerAvatar player, float now, bool decision) { //IL_0088: 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) if (InternalDebugFlags.DebugLastChanceCeilingEyeFlow) { int num = (((Object)(object)player != (Object)null && (Object)(object)player.photonView != (Object)null) ? player.photonView.ViewID : ((player != null) ? ((Object)player).GetInstanceID() : 0)); if (LogLimiter.ShouldLog($"CeilingEye.Vision.{reason}.{num}", 90)) { Log.LogInfo((object)($"[CeilingEye][Vision][{reason}] playerId={num} decision={decision} " + $"start={startPosition} end={endPosition} now={now:F2}")); } } } } internal static class LastChanceMonstersSearchModule { [HarmonyPatch(typeof(Enemy), "SetChaseTarget", new Type[] { typeof(PlayerAvatar) })] internal static class EnemySetChaseTargetPatch { [HarmonyPrefix] private static bool Prefix(Enemy __instance, PlayerAvatar playerAvatar) { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Invalid comparison between Unknown and I4 //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } if (!LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(playerAvatar)) { return true; } if (LastChanceMonstersTargetProxyHelper.IsDebugNoVision(playerAvatar) || __instance.DisableChaseTimer > 0f || !__instance.HasVision) { return false; } if (__instance.Vision.DisableTimer > 0f) { return false; } __instance.Vision.VisionTrigger(playerAvatar.photonView.ViewID, playerAvatar, false, false); if (!__instance.HasStateChase) { return false; } if (!__instance.CheckChase() || (int)__instance.CurrentState == 5) { __instance.CurrentState = (EnemyState)3; __instance.TargetPlayerViewID = playerAvatar.photonView.ViewID; __instance.TargetPlayerAvatar = playerAvatar; } return false; } } [HarmonyPatch(typeof(EnemyStateSneak), "Update")] internal static class EnemyStateSneakUpdatePatch { [HarmonyPrefix] private static bool Prefix(EnemyStateSneak __instance) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } ExecuteSneakUpdate(__instance); return false; } } private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.MonstersSearch"); private static bool s_typedPatchesApplied; internal static void ResetRuntimeState() { } internal static void Apply(Harmony harmony) { if (!s_typedPatchesApplied && harmony != null) { harmony.CreateClassProcessor(typeof(EnemySetChaseTargetPatch)).Patch(); harmony.CreateClassProcessor(typeof(EnemyStateSneakUpdatePatch)).Patch(); s_typedPatchesApplied = true; if (FeatureFlags.DebugLogging) { Log.LogInfo((object)"[LastChance] MonstersSearch typed patches applied."); } } } internal static void Unapply() { ResetRuntimeState(); } internal static int GetAliveSearchMonsterCount() { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return 0; } EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null || instance.enemiesSpawned == null) { return 0; } int num = 0; foreach (EnemyParent item in instance.enemiesSpawned) { if (!((Object)(object)item == (Object)null) && item.Spawned && !item.forceLeave) { num++; } } return num; } private static void ExecuteSneakUpdate(EnemyStateSneak state) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) if ((int)state.Enemy.CurrentState != 8) { if (state.Active) { state.Active = false; } return; } if (!state.Active) { state.TargetPlayer = PlayerController.instance.playerAvatarScript; if (GameManager.instance.gameMode == 1) { foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((!player.isDisabled || LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(player)) && player.photonView.ViewID == state.Enemy.TargetPlayerViewID) { state.TargetPlayer = player; break; } } } state.StateTimer = Random.Range(state.StateTimeMin, state.StateTimeMax); state.Active = true; } if (state.Enemy.MasterClient) { state.Enemy.NavMeshAgent.UpdateAgent(state.Speed, state.Acceleration); if ((Object)(object)state.TargetPlayer != (Object)null) { Vector3 destination = LastChanceMonstersTargetingOrchestrator.ResolveEffectiveTransformTargetPoint(((Component)state.TargetPlayer).transform); state.Enemy.NavMeshAgent.SetDestination(destination); } if (state.Enemy.HasRigidbody) { state.Enemy.Rigidbody.IdleSet(0.1f); } if ((Object)(object)state.Enemy.TargetPlayerAvatar != (Object)null && state.Enemy.Vision.VisionsTriggered[state.Enemy.TargetPlayerAvatar.photonView.ViewID] >= state.Enemy.Vision.VisionsToTrigger) { state.StateTimer = Random.Range(state.StateTimeMin, state.StateTimeMax); } state.StateTimer -= Time.deltaTime; if (state.StateTimer <= 0f) { state.Enemy.CurrentState = (EnemyState)2; } } } } internal static class LastChanceMonstersSetChaseTargetDisabledBridgeModule { } [HarmonyPatch] internal static class LastChanceMonstersSharedChaseTargetPointModule { private sealed class HeadmanDeathHeadWindowState { internal float FocusStartAt = -1f; internal float CooldownUntil = -1f; internal float LastTouchAt = -1f; } [HarmonyPatch(typeof(EnemyStateChase), "Update")] internal static class EnemyStateChaseUpdatePatch { [HarmonyPrefix] private static bool Prefix(EnemyStateChase __instance) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } ExecuteChaseUpdate(__instance); return false; } } [HarmonyPatch(typeof(EnemyStateChaseBegin), "Update")] internal static class EnemyStateChaseBeginUpdatePatch { [HarmonyPrefix] private static bool Prefix(EnemyStateChaseBegin __instance) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } ExecuteChaseBeginUpdate(__instance); return false; } } [HarmonyPatch(typeof(EnemyHeadController), "VisionTriggered")] internal static class EnemyHeadControllerVisionTriggeredPatch { [HarmonyPostfix] private static void Postfix(EnemyHeadController __instance) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() || (Object)(object)__instance == (Object)null) { return; } Enemy enemy = __instance.Enemy; if ((Object)(object)enemy == (Object)null || (int)enemy.CurrentState != 3) { return; } PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; if (!ShouldTreatDisabledAsActiveForEnemy(enemy, targetPlayerAvatar, out string reason)) { DebugChaseProbe(enemy, "VisionTriggered.Skip", targetPlayerAvatar, "target not eligible reason=" + reason); return; } if ((Object)(object)enemy.StateChase != (Object)null) { enemy.StateChase.ChaseCanReach = true; enemy.StateChase.VisionTimer = Mathf.Max(enemy.StateChase.VisionTimer, 0.5f); } enemy.CurrentState = (EnemyState)4; DebugChaseTransition(enemy, "VisionTriggered->Chase.Force", targetPlayerAvatar, "LastChance target eligible"); } } [HarmonyPatch(typeof(EnemyHeadController), "OnStunnedEnd")] internal static class EnemyHeadControllerOnStunnedEndPatch { [HarmonyPrefix] private static bool Prefix(EnemyHeadController __instance) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() || (Object)(object)__instance == (Object)null) { return true; } Enemy enemy = __instance.Enemy; if ((Object)(object)enemy == (Object)null) { return true; } PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; if (!ShouldTreatDisabledAsActiveForEnemy(enemy, targetPlayerAvatar, out string reason)) { DebugChaseProbe(enemy, "OnStunnedEnd.Skip", targetPlayerAvatar, "target not eligible reason=" + reason); return true; } if ((Object)(object)enemy.StateChase != (Object)null) { enemy.StateChase.ChaseCanReach = true; enemy.StateChase.VisionTimer = Mathf.Max(enemy.StateChase.VisionTimer, 0.5f); } enemy.CurrentState = (EnemyState)3; DebugChaseTransition(enemy, "OnStunnedEnd->ChaseBegin.Override", targetPlayerAvatar, "prevent forced roaming for eligible LastChance target"); return false; } } [HarmonyPatch(typeof(EnemyStateStunned), "Update")] internal static class EnemyStateStunnedUpdatePatch { [HarmonyPostfix] private static void Postfix(EnemyStateStunned __instance) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 //IL_00fd: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() || (Object)(object)__instance == (Object)null || (Object)(object)__instance.enemy == (Object)null) { return; } Enemy enemy = __instance.enemy; if ((Object)(object)((Component)enemy).GetComponentInChildren() == (Object)null) { return; } PlayerAvatar targetPlayerAvatar = enemy.TargetPlayerAvatar; if (!ShouldTreatDisabledAsActiveForEnemy(enemy, targetPlayerAvatar, out string reason)) { DebugChaseProbe(enemy, "Stunned.Update.Skip", targetPlayerAvatar, "target not eligible reason=" + reason); } else if ((int)enemy.CurrentState == 2 && !(__instance.stunTimer > 0f) && !(__instance.overrideDisableTimer > 0f)) { if ((Object)(object)enemy.StateChase != (Object)null) { enemy.StateChase.ChaseCanReach = true; enemy.StateChase.VisionTimer = Mathf.Max(enemy.StateChase.VisionTimer, 0.5f); } enemy.CurrentState = (EnemyState)3; DebugChaseTransition(enemy, "Stunned.Update->ChaseBegin.Override", targetPlayerAvatar, "prevent post-stun roaming reset for eligible LastChance target"); } } } private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.HeadmanChase"); private static readonly Dictionary HeadmanWindowByEnemyId = new Dictionary(); private static float s_nextHeadmanWindowCleanupAt; private static void ExecuteChaseUpdate(EnemyStateChase state) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_03f3: Unknown result type (might be due to invalid IL or missing references) //IL_0404: Unknown result type (might be due to invalid IL or missing references) //IL_040a: Unknown result type (might be due to invalid IL or missing references) //IL_03dd: Unknown result type (might be due to invalid IL or missing references) //IL_0429: Unknown result type (might be due to invalid IL or missing references) //IL_046d: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Unknown result type (might be due to invalid IL or missing references) //IL_0455: Unknown result type (might be due to invalid IL or missing references) //IL_038c: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Unknown result type (might be due to invalid IL or missing references) //IL_039e: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_0344: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) //IL_0565: Unknown result type (might be due to invalid IL or missing references) //IL_05c6: Unknown result type (might be due to invalid IL or missing references) //IL_062f: Unknown result type (might be due to invalid IL or missing references) if (!state.Enemy.MasterClient) { DebugChaseProbe(state.Enemy, "Chase.Return.NotMaster", state.Enemy.TargetPlayerAvatar, "skipping custom chase update on non-master"); return; } if ((int)state.Enemy.CurrentState != 4) { if (state.Active) { state.Active = false; DebugChaseProbe(state.Enemy, "Chase.ResetActive", state.Enemy.TargetPlayerAvatar, "CurrentState != Chase"); } return; } PlayerAvatar targetPlayerAvatar = state.Enemy.TargetPlayerAvatar; if ((Object)(object)targetPlayerAvatar == (Object)null) { DebugChaseTransition(state.Enemy, "Chase->Roaming.NoTarget", null, "TargetPlayerAvatar is null"); state.Enemy.CurrentState = (EnemyState)2; return; } if (!ShouldTreatDisabledAsActiveForEnemy(state.Enemy, targetPlayerAvatar, out string reason)) { DebugChaseTransition(state.Enemy, "Chase->Roaming.TargetWindowClosed", targetPlayerAvatar, "reason=" + reason); state.Enemy.Vision.VisionsTriggered[targetPlayerAvatar.photonView.ViewID] = 0; state.Enemy.CurrentState = (EnemyState)2; return; } DebugChaseHeartbeat(state.Enemy, "Chase.Heartbeat", targetPlayerAvatar, $"visionTimer={state.VisionTimer:F2} stateTimer={state.StateTimer:F2} canReach={state.ChaseCanReach}"); if (!state.Active) { targetPlayerAvatar.LastNavMeshPositionTimer = 0f; state.ChasePosition = ResolveTargetPosition(targetPlayerAvatar); state.VisionTimer = state.VisionTime; state.ChaseCanReachSet = false; state.SawPlayerHide = false; state.CantReachTime = 0f; state.StateTimer = Random.Range(state.StateTimeMin, state.StateTimeMax); state.Active = true; } state.Enemy.SetChaseTimer(); state.Enemy.NavMeshAgent.UpdateAgent(state.Speed, state.Acceleration); if (state.Enemy.Vision.VisionTriggered[targetPlayerAvatar.photonView.ViewID]) { state.VisionTimer = state.VisionTime; } else if (state.VisionTimer > 0f) { state.VisionTimer -= Time.deltaTime; } Vector3 val = ResolveTargetPosition(targetPlayerAvatar); if (state.VisionTimer > 0f) { if (state.ChaseOnlyOnNavmesh || targetPlayerAvatar.LastNavMeshPositionTimer <= 0.25f) { state.Enemy.NavMeshAgent.Enable(); state.Enemy.NavMeshAgent.SetDestination(targetPlayerAvatar.LastNavmeshPosition); if (state.ChaseCanReachSet) { Vector3 point = state.Enemy.NavMeshAgent.GetPoint(); state.ChaseCanReach = Vector3.Distance(point, val) <= 0.5f; if (targetPlayerAvatar.isCrawling && !state.ChaseCanReach && SemiFunc.EnemyLookUnderCondition(state.Enemy, state.StateTimer, 5f, targetPlayerAvatar)) { state.SawPlayerHidePosition = val; state.SawPlayerNavmeshPosition = targetPlayerAvatar.LastNavmeshPosition; state.SawPlayerHide = true; } state.ChasePosition = point; } state.ChaseCanReachSet = true; } else { state.Enemy.NavMeshAgent.Disable(0.1f); ((Component)state).transform.position = Vector3.MoveTowards(((Component)state).transform.position, val, state.Speed * Time.deltaTime); } } else { if (state.SawPlayerHide) { DebugChaseProbe(state.Enemy, "Chase->LookUnder.SawPlayerHide", targetPlayerAvatar, "vision timer expired while saw player hide"); state.Enemy.CurrentState = (EnemyState)10; return; } state.Enemy.NavMeshAgent.SetDestination(state.ChasePosition); if (Vector3.Distance(((Component)state).transform.position, state.ChasePosition) < 1f) { LevelPoint levelPointAhead = state.Enemy.GetLevelPointAhead(state.ChasePosition); if (Object.op_Implicit((Object)(object)levelPointAhead)) { state.Enemy.NavMeshAgent.SetDestination(((Component)levelPointAhead).transform.position); } state.ChasePosition = state.Enemy.NavMeshAgent.GetDestination(); } state.ChaseCanReach = true; state.ChaseCanReachSet = false; } if (state.ChaseCanReach && state.Enemy.Vision.VisionsTriggered[targetPlayerAvatar.photonView.ViewID] >= state.VisionsToReset) { state.StateTimer = Random.Range(state.StateTimeMin, state.StateTimeMax); } if (!state.ChaseCanReach) { state.CantReachTime += Time.deltaTime; if (state.CantReachTime > 2f) { state.Enemy.Vision.VisionsTriggered[targetPlayerAvatar.photonView.ViewID] = 0; DebugChaseProbe(state.Enemy, "Chase->ChaseSlow.CantReach", targetPlayerAvatar, $"cantReachTime={state.CantReachTime:F2}"); state.Enemy.CurrentState = (EnemyState)5; return; } } else { state.CantReachTime = 0f; } state.StateTimer -= Time.deltaTime; if (state.StateTimer <= 0f) { DebugChaseProbe(state.Enemy, "Chase->ChaseSlow.TimerElapsed", targetPlayerAvatar, "state timer elapsed"); state.Enemy.CurrentState = (EnemyState)5; } if (targetPlayerAvatar.isDisabled && !ShouldTreatDisabledAsActiveForEnemy(state.Enemy, targetPlayerAvatar, out string _)) { DebugChaseTransition(state.Enemy, "Chase->Roaming.DisabledTarget", targetPlayerAvatar, "target is disabled and not eligible"); state.Enemy.Vision.VisionsTriggered[targetPlayerAvatar.photonView.ViewID] = 0; state.Enemy.CurrentState = (EnemyState)2; } } private static void ExecuteChaseBeginUpdate(EnemyStateChaseBegin state) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_048b: Unknown result type (might be due to invalid IL or missing references) //IL_04a7: Unknown result type (might be due to invalid IL or missing references) //IL_04b6: Unknown result type (might be due to invalid IL or missing references) //IL_0508: Unknown result type (might be due to invalid IL or missing references) //IL_0448: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) if ((int)state.Enemy.CurrentState != 3) { if (state.Active) { DebugChaseTransition(state.Enemy, "ChaseBegin.ResetActive", state.TargetPlayer, "CurrentState != ChaseBegin"); state.Active = false; } else { DebugChaseProbe(state.Enemy, "ChaseBegin.Return.NotInState", state.TargetPlayer, $"currentState={state.Enemy.CurrentState}"); } return; } string reason; if (!state.Active) { if (state.Enemy.MasterClient) { state.Enemy.StateChase.ChaseCanReach = true; state.Enemy.NavMeshAgent.ResetPath(); state.StateTimer = Random.Range(state.StateTimeMin, state.StateTimeMax); } state.TargetPlayer = PlayerController.instance.playerAvatarScript; foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((!player.isDisabled || ShouldTreatDisabledAsActiveForEnemy(state.Enemy, player, out reason)) && player.photonView.ViewID == state.Enemy.TargetPlayerViewID) { state.TargetPlayer = player; break; } } foreach (PlayerAvatar player2 in GameDirector.instance.PlayerList) { if ((player2.isDisabled && !ShouldTreatDisabledAsActiveForEnemy(state.Enemy, player2, out reason)) || !player2.isLocal) { continue; } if (GameManager.instance.gameMode != 0 && !((Object)(object)state.TargetPlayer == (Object)(object)player2) && !state.Enemy.PlayerRoom.SameLocal && !state.Enemy.OnScreen.OnScreenLocal) { state.LocalEffect = false; GameDirector.instance.CameraImpact.ShakeDistance(5f, 5f, 10f, ((Component)state).transform.position, 0.25f); GameDirector.instance.CameraShake.ShakeDistance(3f, 5f, 10f, ((Component)state).transform.position, 0.5f); break; } state.LocalEffect = true; GameDirector.instance.CameraImpact.Shake(5f, 0.25f); GameDirector.instance.CameraShake.Shake(3f, 0.5f); if (state.Stinger) { CameraGlitch.Instance.PlayShort(); AudioScare.instance.PlayImpact(); } break; } state.Active = true; DebugChaseProbe(state.Enemy, "ChaseBegin.ActiveInit", state.TargetPlayer, $"targetViewId={state.Enemy.TargetPlayerViewID}"); } state.Enemy.SetChaseTimer(); if (!state.Enemy.MasterClient) { DebugChaseProbe(state.Enemy, "ChaseBegin.Return.NotMaster", state.TargetPlayer, "skipping chase begin update on non-master"); return; } DebugChaseHeartbeat(state.Enemy, "ChaseBegin.Heartbeat", state.TargetPlayer, $"active={state.Active} stateTimer={state.StateTimer:F2} targetViewId={state.Enemy.TargetPlayerViewID}"); PlayerAvatar val = state.TargetPlayer; if ((Object)(object)val == (Object)null) { PlayerAvatar targetPlayerAvatar = state.Enemy.TargetPlayerAvatar; if (!((Object)(object)targetPlayerAvatar != (Object)null) || (targetPlayerAvatar.isDisabled && !ShouldTreatDisabledAsActiveForEnemy(state.Enemy, targetPlayerAvatar, out reason))) { DebugChaseTransition(state.Enemy, "ChaseBegin->Roaming.NoUsableTarget", targetPlayerAvatar, "TargetPlayer null and fallback invalid"); state.Enemy.CurrentState = (EnemyState)2; return; } val = targetPlayerAvatar; state.TargetPlayer = targetPlayerAvatar; DebugChaseTransition(state.Enemy, "ChaseBegin.TargetFallback", targetPlayerAvatar, "using Enemy.TargetPlayerAvatar fallback"); } state.Enemy.NavMeshAgent.UpdateAgent(0f, 5f); state.Enemy.NavMeshAgent.Stop(0.1f); ((Component)state).transform.LookAt(ResolveTargetPosition(val)); ((Component)state).transform.localEulerAngles = new Vector3(0f, ((Component)state).transform.localEulerAngles.y, 0f); state.StateTimer -= Time.deltaTime; if (state.StateTimer <= 0f) { DebugChaseTransition(state.Enemy, "ChaseBegin->Chase.TimerElapsed", val, "transitioning to chase"); state.Enemy.CurrentState = (EnemyState)4; } else { DebugChaseProbe(state.Enemy, "ChaseBegin.WaitingTimer", val, $"stateTimer={state.StateTimer:F2}"); } } private static bool ShouldTreatDisabledAsActiveForEnemy(Enemy? enemy, PlayerAvatar? target, out string reason) { reason = "DisabledGateFalse"; if (!LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(target)) { return false; } if ((Object)(object)enemy == (Object)null || !IsHeadmanEnemy(enemy)) { reason = "Allowed.NonHeadman"; return true; } return EvaluateHeadmanDeathHeadWindow(enemy, out reason); } private static bool IsHeadmanEnemy(Enemy enemy) { return (Object)(object)((Component)enemy).GetComponentInChildren() != (Object)null; } private static bool EvaluateHeadmanDeathHeadWindow(Enemy enemy, out string reason) { float unscaledTime = Time.unscaledTime; CleanupHeadmanWindowState(unscaledTime); int instanceID = ((Object)enemy).GetInstanceID(); if (!HeadmanWindowByEnemyId.TryGetValue(instanceID, out HeadmanDeathHeadWindowState value)) { value = new HeadmanDeathHeadWindowState(); HeadmanWindowByEnemyId[instanceID] = value; } value.LastTouchAt = unscaledTime; if (value.CooldownUntil > unscaledTime) { reason = "Blocked.Cooldown"; return false; } if (value.FocusStartAt < 0f) { value.FocusStartAt = unscaledTime; } float num = Mathf.Max(0.1f, InternalConfig.LastChanceMonstersHeadmanDeathHeadFocusMaxSeconds); if (unscaledTime - value.FocusStartAt >= num) { float num2 = Mathf.Max(0.1f, InternalConfig.LastChanceMonstersHeadmanDeathHeadFocusCooldownSeconds); value.CooldownUntil = unscaledTime + num2; value.FocusStartAt = -1f; reason = "Blocked.StartCooldown"; return false; } reason = "Allowed.FocusWindow"; return true; } private static void CleanupHeadmanWindowState(float now) { if (now < s_nextHeadmanWindowCleanupAt) { return; } s_nextHeadmanWindowCleanupAt = now + 5f; if (HeadmanWindowByEnemyId.Count == 0) { return; } List list = new List(); foreach (KeyValuePair item in HeadmanWindowByEnemyId) { HeadmanDeathHeadWindowState value = item.Value; if (value == null) { list.Add(item.Key); continue; } float num = Mathf.Max(value.LastTouchAt, value.CooldownUntil); if (num < 0f || now - num > 30f) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { HeadmanWindowByEnemyId.Remove(list[i]); } } private static Vector3 ResolveTargetPosition(PlayerAvatar targetPlayer) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) return LastChanceMonstersTargetingOrchestrator.ResolveEffectiveTransformTargetPoint(((Component)targetPlayer).transform); } private static void DebugChaseTransition(Enemy? enemy, string reason, PlayerAvatar? target, string details) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) if (InternalDebugFlags.DebugLastChanceHeadmanSlowMouthFlow && LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() && !((Object)(object)enemy == (Object)null)) { int instanceID = ((Object)enemy).GetInstanceID(); if (LogLimiter.ShouldLog($"HeadmanChase.{reason}.{instanceID}", 20)) { int num = (((Object)(object)target != (Object)null && (Object)(object)target.photonView != (Object)null) ? target.photonView.ViewID : (-1)); bool flag = (Object)(object)target != (Object)null && target.isDisabled; bool flag2 = LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(target); string text = (((Object)(object)target == (Object)null) ? "target=n/a" : $"target='{((Object)((Component)target).gameObject).name}' targetViewId={num}"); Log.LogInfo((object)($"[HeadmanChase] enemy='{((Object)((Component)enemy).gameObject).name}' enemyId={instanceID} state={enemy.CurrentState} reason={reason} " + $"{text} targetDisabled={flag} targetEligible={flag2} details={details}")); } } } private static void DebugChaseHeartbeat(Enemy? enemy, string reason, PlayerAvatar? target, string details) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (InternalDebugFlags.DebugLastChanceHeadmanSlowMouthFlow && LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() && !((Object)(object)enemy == (Object)null)) { int instanceID = ((Object)enemy).GetInstanceID(); if (LogLimiter.ShouldLog($"HeadmanChase.{reason}.{instanceID}", 10)) { int num = (((Object)(object)target != (Object)null && (Object)(object)target.photonView != (Object)null) ? target.photonView.ViewID : (-1)); bool flag = (Object)(object)target != (Object)null && target.isDisabled; bool flag2 = LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(target); Log.LogInfo((object)($"[HeadmanChase] enemy='{((Object)((Component)enemy).gameObject).name}' enemyId={instanceID} state={enemy.CurrentState} reason={reason} " + $"targetViewId={num} targetDisabled={flag} targetEligible={flag2} {details}")); } } } private static void DebugChaseProbe(Enemy? enemy, string reason, PlayerAvatar? target, string details) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) if (InternalDebugFlags.DebugLastChanceHeadmanSlowMouthFlow && LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() && !((Object)(object)enemy == (Object)null)) { int instanceID = ((Object)enemy).GetInstanceID(); if (LogLimiter.ShouldLog($"HeadmanChase.Probe.{reason}.{instanceID}", 8)) { int num = (((Object)(object)target != (Object)null && (Object)(object)target.photonView != (Object)null) ? target.photonView.ViewID : (-1)); bool flag = (Object)(object)target != (Object)null && target.isDisabled; bool flag2 = LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(target); Log.LogInfo((object)($"[HeadmanChaseProbe] enemy='{((Object)((Component)enemy).gameObject).name}' enemyId={instanceID} state={enemy.CurrentState} reason={reason} " + $"targetViewId={num} targetDisabled={flag} targetEligible={flag2} details={details}")); } } } } [HarmonyPatch(typeof(SemiFunc), "PlayerGetAllPlayerAvatarWithinRange", new Type[] { typeof(float), typeof(Vector3), typeof(bool), typeof(LayerMask) })] internal static class LastChanceMonstersSharedPlayerSearchModule { [HarmonyPostfix] private static void Postfix(float range, Vector3 position, bool doRaycastCheck, LayerMask layerMask, ref List __result) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (__result == null) { __result = new List(); } LastChanceMonstersTargetingOrchestrator.ExtendPlayersWithinRangeLastChanceAware(__result, range, position, doRaycastCheck, layerMask); } } [HarmonyPatch(typeof(SemiFunc), "PlayerGetNearestPlayerAvatarWithinRange", new Type[] { typeof(float), typeof(Vector3), typeof(bool), typeof(LayerMask) })] internal static class LastChanceMonstersEffectiveTargetPointModule { [HarmonyPostfix] private static void Postfix(float range, Vector3 position, bool doRaycastCheck, LayerMask layerMask, ref PlayerAvatar? __result) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) __result = LastChanceMonstersTargetingOrchestrator.ResolveNearestPlayerWithinRangeLastChanceAware(__result, range, position, doRaycastCheck, layerMask); } } [HarmonyPatch] internal static class LastChanceMonstersSlowMouthHeadOnlyModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.SlowMouthHeadTarget"); [HarmonyPatch(typeof(EnemySlowMouth), "UpdatePlayerTargetRPC")] [HarmonyPostfix] private static void EnemySlowMouthUpdatePlayerTargetRpcPostfix(EnemySlowMouth __instance) { if (ShouldApplyTargetFix(__instance)) { ApplyHeadTarget(__instance, "UpdatePlayerTargetRPC"); } } [HarmonyPatch(typeof(EnemySlowMouthAttaching), "SetTarget")] [HarmonyPostfix] private static void EnemySlowMouthAttachingSetTargetPostfix(EnemySlowMouthAttaching __instance, PlayerAvatar _playerAvatar) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)_playerAvatar == (Object)null) && ShouldApplyTargetFix(__instance.enemySlowMouth) && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(_playerAvatar)) { Transform val = (((Object)(object)_playerAvatar.localCamera != (Object)null) ? ((Component)_playerAvatar.localCamera).transform : null); if ((Object)(object)val != (Object)null) { __instance.targetTransform = val; DebugTargetApply(__instance.enemySlowMouth, "Attaching.SetTarget.Camera"); } } } private static bool ShouldApplyTargetFix(EnemySlowMouth? slowMouth) { if ((Object)(object)slowMouth == (Object)null) { return false; } if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return false; } PlayerAvatar player = ResolveBehaviorTarget(slowMouth) ?? PlayerAvatar.instance; return LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player); } private static PlayerAvatar? ResolveBehaviorTarget(EnemySlowMouth slowMouth) { if ((Object)(object)slowMouth.playerTarget != (Object)null) { return slowMouth.playerTarget; } Enemy enemy = slowMouth.enemy; if ((Object)(object)enemy != (Object)null && (Object)(object)enemy.TargetPlayerAvatar != (Object)null) { return enemy.TargetPlayerAvatar; } return null; } private static void ApplyHeadTarget(EnemySlowMouth slowMouth, string source) { PlayerAvatar player = ResolveBehaviorTarget(slowMouth) ?? PlayerAvatar.instance; if (LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player) && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTransform(player, out Transform transform) && (Object)(object)transform != (Object)null) { slowMouth.currentTarget = transform; DebugTargetApply(slowMouth, source); } } private static void DebugTargetApply(EnemySlowMouth? slowMouth, string source) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)slowMouth == (Object)null || !InternalDebugFlags.DebugLastChanceHeadmanSlowMouthFlow || !LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return; } int instanceID = ((Object)slowMouth).GetInstanceID(); if (LogLimiter.ShouldLog($"SlowMouthHeadTarget.Apply.{source}.{instanceID}", 30)) { PlayerAvatar val = ResolveBehaviorTarget(slowMouth) ?? PlayerAvatar.instance; float num = -1f; float num2 = -1f; if ((Object)(object)slowMouth.currentTarget != (Object)null && (Object)(object)val != (Object)null && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(val, out var point)) { num = Vector3.Distance(slowMouth.currentTarget.position, point); } if ((Object)(object)slowMouth.currentTarget != (Object)null && (Object)(object)val != (Object)null && (Object)(object)val.localCamera != (Object)null) { num2 = Vector3.Distance(slowMouth.currentTarget.position, ((Component)val.localCamera).transform.position); } Log.LogInfo((object)($"[SlowMouthHeadTarget] source={source} enemyId={instanceID} state={slowMouth.currentState} " + $"targetDisabled={(Object)(object)val != (Object)null && val.isDisabled} headProxyActive={LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val)} " + $"currentTargetToHead={num:F2} currentTargetToCamera={num2:F2}")); } } } [HarmonyPatch(typeof(EnemyThinMan), "StateStand")] internal static class LastChanceMonstersThinManStandModule { private static readonly Dictionary s_lastTransitionFrameByEnemy = new Dictionary(); internal static void ResetRuntimeState() { s_lastTransitionFrameByEnemy.Clear(); } [HarmonyPostfix] private static void Postfix(EnemyThinMan __instance) { if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled() || __instance.playerTarget != null) { return; } EnemyOnScreen val = __instance.enemy?.OnScreen; if ((Object)(object)val == (Object)null) { return; } List list = GameDirector.instance?.PlayerList; if (list == null) { return; } for (int i = 0; i < list.Count; i++) { PlayerAvatar val2 = list[i]; if ((Object)(object)val2 == (Object)null || (LastChanceMonstersTargetProxyHelper.IsDisabled(val2) && !LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val2)) || LastChanceMonstersTargetProxyHelper.IsDebugNoVision(val2) || !val.GetOnScreen(val2)) { continue; } __instance.SetTarget(val2); __instance.UpdateState((State)1); if (InternalDebugFlags.DebugLastChanceThinManFlow) { int instanceID = ((Object)__instance).GetInstanceID(); int frameCount = Time.frameCount; if (!s_lastTransitionFrameByEnemy.TryGetValue(instanceID, out var value) || frameCount - value >= 120) { s_lastTransitionFrameByEnemy[instanceID] = frameCount; LastChanceMonstersOnScreenCameraModule.DebugLog("StateStand.ProxyAcquire", $"enemy={((Object)((Component)__instance).gameObject).name} player={(((Object)(object)val2.photonView != (Object)null) ? val2.photonView.ViewID : (-1))} fromDisabled={LastChanceMonstersTargetProxyHelper.IsDisabled(val2)} headProxy={LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val2)}"); } } break; } } } [HarmonyPatch(typeof(EnemyTriggerAttack), "OnTriggerStay")] internal static class LastChanceMonstersTriggerAttackModule { [HarmonyPrefix] private static bool OnTriggerStayPrefix(EnemyTriggerAttack __instance, Collider other) { //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Invalid comparison between Unknown and I4 //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Invalid comparison between Unknown and I4 //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Invalid comparison between Unknown and I4 //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)other == (Object)null) { return true; } if (!LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled()) { return true; } if ((Object)(object)((Component)other).GetComponent() != (Object)null) { return true; } if (!LastChanceMonstersTargetProxyHelper.TryGetPlayerFromDeathHeadCollider(other, out PlayerAvatar player) || (Object)(object)player == (Object)null) { return true; } if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(player, out var center)) { return true; } if (!LevelGenerator.Instance.Generated) { return false; } float triggerCheckTimer = __instance.TriggerCheckTimer; if (triggerCheckTimer > 0f) { return false; } __instance.TriggerCheckTimerSet = true; Enemy enemy = __instance.Enemy; if ((Object)(object)enemy == (Object)null) { return false; } if ((Object)(object)((Component)enemy).GetComponent() != (Object)null) { __instance.Attack = true; return false; } if ((int)enemy.CurrentState != 4 && (int)enemy.CurrentState != 10) { return false; } EnemyStateLookUnder stateLookUnder = enemy.StateLookUnder; EnemyStateChase stateChase = enemy.StateChase; EnemyVision vision = enemy.Vision; bool flag = (int)enemy.CurrentState == 10 && (Object)(object)stateLookUnder != (Object)null && stateLookUnder.WaitDone; bool flag2 = (Object)(object)stateChase != (Object)null && stateChase.ChaseCanReach; int viewID = player.photonView.ViewID; bool value = default(bool); bool flag3 = (Object)(object)vision != (Object)null && vision.VisionTriggered.TryGetValue(viewID, out value) && value; if (!flag3) { bool near = (Object)(object)vision != (Object)null && Vector3.Distance(__instance.VisionTransform.position, center) <= vision.VisionDistanceClose; if ((Object)(object)vision != (Object)null) { LastChanceMonstersTargetProxyHelper.EnsureVisionTriggered(vision, player, near); } flag3 = true; } if (!flag3 && !flag) { return false; } bool flag4 = flag2 && !flag; bool flag5 = !flag2 || flag; if (!LastChanceMonstersTargetProxyHelper.IsLineOfSightToHead(__instance.VisionTransform, center, __instance.VisionMask, player)) { if (!flag5) { flag4 = false; } } else if (flag5) { flag4 = true; } if (flag4) { __instance.Attack = true; } return false; } } [HarmonyPatch] internal static class LastChanceMonstersVisionAnchorProxyModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.VisionAnchor"); [HarmonyPatch(typeof(PlayerVisionTarget), "Update")] [HarmonyPostfix] private static void PlayerVisionTargetUpdatePostfix(PlayerVisionTarget __instance) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() || (Object)(object)__instance == (Object)null) { return; } PlayerAvatar playerAvatar = __instance.PlayerAvatar; if (LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(playerAvatar)) { Transform visionTransform = __instance.VisionTransform; if (!((Object)(object)visionTransform == (Object)null) && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTransform(playerAvatar, out Transform transform) && !((Object)(object)transform == (Object)null)) { float delta = Vector3.Distance(visionTransform.position, transform.position); visionTransform.position = transform.position; visionTransform.rotation = transform.rotation; DebugAnchorApply(playerAvatar, "PlayerVisionTarget.Update", delta); } } } [HarmonyPatch(typeof(SemiFunc), "PlayerGetFaceEyeTransform", new Type[] { typeof(PlayerAvatar) })] [HarmonyPostfix] private static void PlayerGetFaceEyeTransformPostfix(PlayerAvatar _player, ref Transform __result) { //IL_0047: 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) if (LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(_player) && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTransform(_player, out Transform transform) && (Object)(object)transform != (Object)null) { float delta = (((Object)(object)__result != (Object)null) ? Vector3.Distance(__result.position, transform.position) : (-1f)); __result = transform; DebugAnchorApply(_player, "SemiFunc.PlayerGetFaceEyeTransform", delta); } } private static void DebugAnchorApply(PlayerAvatar? player, string source, float delta) { if (InternalDebugFlags.DebugLastChanceHeadmanSlowMouthFlow && !((Object)(object)player == (Object)null)) { int num = (((Object)(object)player.photonView != (Object)null) ? player.photonView.ViewID : ((Object)player).GetInstanceID()); if (LogLimiter.ShouldLog($"VisionAnchor.{source}.{num}", 120)) { bool flag = LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player); Log.LogInfo((object)($"[VisionAnchor] source={source} playerViewId={num} isLocal={player.isLocal} " + $"headProxyActive={flag} delta={delta:F3}")); } } } } [HarmonyPatch(typeof(EnemyVision), "Awake")] internal static class LastChanceMonstersVisionModule { [HarmonyPostfix] private static void AwakePostfix(EnemyVision __instance) { if (!((Object)(object)__instance == (Object)null) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } internal sealed class LastChanceEnemyVisionHeadProxyRuntime : MonoBehaviour { private EnemyVision? _vision; private Enemy? _enemy; private float _tick; private void Awake() { _vision = ((Component)this).GetComponent(); _enemy = ((Component)this).GetComponent(); float num = Mathf.Max(0.01f, InternalConfig.LastChanceMonstersVisionProxyTickSeconds); _tick = Random.Range(0f, num); } private void Update() { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) if (!InternalConfig.LastChanceMonstersVisionProxyEnabled || (Object)(object)_vision == (Object)null || (Object)(object)_enemy == (Object)null || (Object)(object)((Component)_enemy).GetComponent() != (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled()) { return; } _tick -= Time.deltaTime; if (_tick > 0f) { return; } _tick = Mathf.Max(0.01f, InternalConfig.LastChanceMonstersVisionProxyTickSeconds); List list = GameDirector.instance?.PlayerList; if (list == null || list.Count == 0) { return; } Transform visionTransform = _vision.VisionTransform; if ((Object)(object)visionTransform == (Object)null) { return; } LayerMask visionMask = _enemy.VisionMask; for (int i = 0; i < list.Count; i++) { PlayerAvatar player = list[i]; if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(player, out var center)) { continue; } Vector3 val = center - visionTransform.position; float magnitude = ((Vector3)(ref val)).magnitude; if (!(magnitude > _vision.VisionDistance)) { float num = Vector3.Dot(visionTransform.forward, ((Vector3)(ref val)).normalized); bool flag = magnitude <= _vision.VisionDistanceClose; if ((num >= _vision.VisionDotStanding || flag) && LastChanceMonstersTargetProxyHelper.IsLineOfSightToHead(visionTransform, center, visionMask, player)) { LastChanceMonstersTargetProxyHelper.EnsureVisionTriggered(_vision, player, flag); } } } } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters.Interactions { [HarmonyPatch] internal static class LastChanceMonstersBeamerHeadAimModule { [HarmonyPatch(typeof(EnemyBeamer), "StateAttackStart")] [HarmonyPostfix] private static void StateAttackStartPostfix(EnemyBeamer __instance) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) if (TryGetAimContext(__instance, out Enemy enemy, out PlayerAvatar _, out Vector3 targetPoint)) { Vector3 val = (((Object)(object)enemy.CenterTransform != (Object)null) ? enemy.CenterTransform.position : ((Component)enemy).transform.position); Vector3 val2 = targetPoint - val; if (!(((Vector3)(ref val2)).sqrMagnitude <= 0.0001f)) { Quaternion val3 = Quaternion.LookRotation(val2); val3 = (__instance.aimHorizontalTarget = Quaternion.Euler(0f, ((Quaternion)(ref val3)).eulerAngles.y, 0f)); } } } [HarmonyPatch(typeof(EnemyBeamer), "VerticalAimLogic")] [HarmonyPostfix] private static void VerticalAimLogicPostfix(EnemyBeamer __instance) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_009f: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) if (!TryGetAimContext(__instance, out Enemy _, out PlayerAvatar _, out Vector3 targetPoint)) { return; } Transform laserRayTransform = __instance.laserRayTransform; if ((Object)(object)laserRayTransform == (Object)null) { return; } Transform aimVerticalTransform = __instance.aimVerticalTransform; Vector3 val = targetPoint - laserRayTransform.position; if (!(((Vector3)(ref val)).sqrMagnitude <= 0.0001f)) { Quaternion rotation = Quaternion.LookRotation(val); Quaternion rotation2 = laserRayTransform.rotation; laserRayTransform.rotation = rotation; Quaternion localRotation = laserRayTransform.localRotation; localRotation = Quaternion.Euler(laserRayTransform.eulerAngles.x, 0f, 0f); laserRayTransform.rotation = rotation2; __instance.aimVerticalTarget = localRotation; laserRayTransform.localRotation = localRotation; if ((Object)(object)aimVerticalTransform != (Object)null) { aimVerticalTransform.localRotation = localRotation; } } } private static bool TryGetAimContext(EnemyBeamer beamer, out Enemy enemy, out PlayerAvatar player, out Vector3 targetPoint) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) enemy = null; player = null; targetPoint = default(Vector3); if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return false; } Enemy enemy2 = beamer.enemy; PlayerAvatar playerTarget = beamer.playerTarget; if ((Object)(object)enemy2 == (Object)null || (Object)(object)playerTarget == (Object)null) { return false; } enemy = enemy2; player = playerTarget; if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(player, out targetPoint)) { return false; } return true; } } [HarmonyPatch] internal static class LastChanceMonstersBirthdayBoyBalloonHeadProxyModule { [HarmonyPatch(typeof(BirthdayBoyBalloon), "Update")] [HarmonyPostfix] private static void UpdatePostfix(BirthdayBoyBalloon __instance) { if (!((Object)(object)__instance == (Object)null) && !__instance.popped && LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled() && TryGetOverlappingHeadPopper(__instance, out PlayerAvatar popper) && !((Object)(object)popper == (Object)null)) { __instance.popped = true; __instance.popper = popper; } } [HarmonyPatch(typeof(BirthdayBoyBalloon), "OnTriggerEnter")] [HarmonyPrefix] private static bool OnTriggerEnterPrefix(BirthdayBoyBalloon __instance, Collider _other) { if ((Object)(object)__instance == (Object)null || (Object)(object)_other == (Object)null || __instance.popped) { return true; } if (!LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled()) { return true; } PlayerDeathHead componentInParent = ((Component)_other).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { return true; } PlayerAvatar playerAvatar = componentInParent.playerAvatar; if ((Object)(object)playerAvatar == (Object)null || !LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(playerAvatar)) { return true; } __instance.popped = true; __instance.popper = playerAvatar; return false; } private static bool TryGetOverlappingHeadPopper(BirthdayBoyBalloon balloon, out PlayerAvatar? popper) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) popper = null; List list = SemiFunc.PlayerGetList(); if (list == null) { return false; } float num = float.MaxValue; foreach (PlayerAvatar item in list) { if ((Object)(object)item == (Object)null || !LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(item)) { continue; } PlayerDeathHead playerDeathHead = item.playerDeathHead; if (!((Object)(object)playerDeathHead == (Object)null) && IsHeadOverlappingBalloon(balloon, playerDeathHead)) { Vector3 val = ((Component)playerDeathHead).transform.position - ((Component)balloon).transform.position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; popper = item; } } } return (Object)(object)popper != (Object)null; } private static bool IsHeadOverlappingBalloon(BirthdayBoyBalloon balloon, PlayerDeathHead head) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) if (!TryGetBalloonCollider(balloon, 1, out Collider collider) && !TryGetBalloonCollider(balloon, 2, out Collider _)) { return false; } Collider[] array = head.colliders; if (array == null || array.Length == 0) { array = ((Component)head).GetComponentsInChildren(); } Vector3 val2 = default(Vector3); float num = default(float); float num2 = default(float); foreach (Collider val in array) { if (IsColliderValid(val)) { if ((Object)(object)collider != (Object)null && Physics.ComputePenetration(collider, ((Component)collider).transform.position, ((Component)collider).transform.rotation, val, ((Component)val).transform.position, ((Component)val).transform.rotation, ref val2, ref num) && num > 0f) { return true; } if (TryGetBalloonCollider(balloon, 2, out Collider collider3) && (Object)(object)collider3 != (Object)null && Physics.ComputePenetration(collider3, ((Component)collider3).transform.position, ((Component)collider3).transform.rotation, val, ((Component)val).transform.position, ((Component)val).transform.rotation, ref val2, ref num2) && num2 > 0f) { return true; } } } return false; } private static bool TryGetBalloonCollider(BirthdayBoyBalloon balloon, int index, out Collider? collider) { collider = ((index == 1) ? balloon.collider1 : balloon.collider2); return IsColliderValid(collider); } private static bool IsColliderValid(Collider? collider) { return (Object)(object)collider != (Object)null && collider.enabled && ((Component)collider).gameObject.activeInHierarchy; } } [HarmonyPatch] internal static class LastChanceMonstersCarryTargetPositionModule { [HarmonyPatch(typeof(EnemyHidden), "StatePlayerGoTo")] internal static class EnemyHiddenStatePlayerGoToPatch { [HarmonyPrefix] private static bool Prefix(EnemyHidden __instance) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } ExecuteStatePlayerGoTo(__instance); return false; } } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerMove")] internal static class EnemyHiddenStatePlayerMovePatch { [HarmonyPrefix] private static bool Prefix(EnemyHidden __instance) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } ExecuteStatePlayerMove(__instance); return false; } } [HarmonyPatch(typeof(EnemyHidden), "StatePlayerRelease")] internal static class EnemyHiddenStatePlayerReleasePatch { [HarmonyPrefix] private static bool Prefix(EnemyHidden __instance) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } ExecuteStatePlayerRelease(__instance); return false; } } private static void ExecuteStatePlayerGoTo(EnemyHidden hidden) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) if (hidden.stateImpulse) { hidden.stateImpulse = false; hidden.stateTimer = 2f; hidden.agentSet = true; } hidden.stateTimer -= Time.deltaTime; PlayerAvatar playerTarget = hidden.playerTarget; if ((Object)(object)playerTarget == (Object)null || (playerTarget.isDisabled && !LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(playerTarget)) || hidden.stateTimer <= 0f) { hidden.UpdateState((State)10); return; } Vector3 effectivePlayerTargetPosition = GetEffectivePlayerTargetPosition(playerTarget); SemiFunc.EnemyCartJump(hidden.enemy); if (hidden.enemy.Jump.jumping) { hidden.enemy.NavMeshAgent.Disable(0.5f); ((Component)hidden).transform.position = Vector3.MoveTowards(((Component)hidden).transform.position, effectivePlayerTargetPosition, 5f * Time.deltaTime); hidden.agentSet = true; } else if (!hidden.enemy.NavMeshAgent.IsDisabled()) { if (!hidden.agentSet && hidden.enemy.NavMeshAgent.HasPath() && Vector3.Distance(((Component)hidden.enemy.Rigidbody).transform.position + Vector3.down * 0.75f, hidden.enemy.NavMeshAgent.GetDestination()) < 0.25f) { hidden.enemy.Jump.StuckTrigger(((Component)hidden.enemy.Rigidbody).transform.position - effectivePlayerTargetPosition); } hidden.enemy.NavMeshAgent.SetDestination(effectivePlayerTargetPosition); hidden.enemy.NavMeshAgent.OverrideAgent(5f, 10f, 0.25f); hidden.agentSet = false; } if (Vector3.Distance(((Component)hidden.enemy.Rigidbody).transform.position, effectivePlayerTargetPosition) < 1.5f) { SemiFunc.EnemyCartJumpReset(hidden.enemy); hidden.UpdateState((State)6); } } private static void ExecuteStatePlayerMove(EnemyHidden hidden) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) if (hidden.stateImpulse) { hidden.stateTimer = 5f; hidden.maxMoveTimer = 10f; bool flag = false; LevelPoint val = SemiFunc.LevelPointGetPlayerDistance(((Component)hidden).transform.position, 50f, 999f, false); if (!Object.op_Implicit((Object)(object)val)) { val = SemiFunc.LevelPointGetFurthestFromPlayer(((Component)hidden).transform.position, 5f); } NavMeshHit val2 = default(NavMeshHit); if (Object.op_Implicit((Object)(object)val) && NavMesh.SamplePosition(((Component)val).transform.position + Random.insideUnitSphere * 3f, ref val2, 5f, -1) && Physics.Raycast(((NavMeshHit)(ref val2)).position, Vector3.down, 5f, LayerMask.GetMask(new string[1] { "Default" }))) { hidden.agentDestination = ((NavMeshHit)(ref val2)).position; flag = true; } if (!flag) { hidden.stateTimer = 0f; } hidden.stateImpulse = false; } if (hidden.enemy.Rigidbody.notMovingTimer > 2f) { hidden.stateTimer -= Time.deltaTime; } PlayerAvatar playerTarget = hidden.playerTarget; if ((Object)(object)playerTarget == (Object)null || (playerTarget.isDisabled && !LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(playerTarget))) { hidden.UpdateState((State)10); return; } Vector3 effectivePlayerTargetPosition = GetEffectivePlayerTargetPosition(playerTarget); SemiFunc.EnemyCartJump(hidden.enemy); hidden.enemy.NavMeshAgent.SetDestination(hidden.agentDestination); hidden.enemy.NavMeshAgent.OverrideAgent(5f, 10f, 0.25f); hidden.enemy.Jump.GapJumpOverride(0.1f, 20f, 20f); hidden.maxMoveTimer -= Time.deltaTime; float num = Vector3.Distance(((Component)hidden.enemy.Rigidbody).transform.position, effectivePlayerTargetPosition); if (!hidden.enemy.NavMeshAgent.HasPath() || Vector3.Distance(((Component)hidden).transform.position, hidden.agentDestination) < 1f || num > 5f || hidden.stateTimer <= 0f || hidden.maxMoveTimer <= 0f) { SemiFunc.EnemyCartJumpReset(hidden.enemy); hidden.UpdateState((State)8); } } private static void ExecuteStatePlayerRelease(EnemyHidden hidden) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (hidden.stateImpulse) { hidden.stateImpulse = false; hidden.stateTimer = 2f; } hidden.stateTimer -= Time.deltaTime; PlayerAvatar playerTarget = hidden.playerTarget; if ((Object)(object)playerTarget == (Object)null || (playerTarget.isDisabled && !LastChanceMonstersDisabledGateHelper.ShouldTreatDisabledAsActive(playerTarget))) { hidden.UpdateState((State)10); return; } Vector3 effectivePlayerTargetPosition = GetEffectivePlayerTargetPosition(playerTarget); if (hidden.stateTimer <= 0f || Vector3.Distance(((Component)hidden.enemy.Rigidbody).transform.position, effectivePlayerTargetPosition) > 5f) { hidden.UpdateState((State)9); } } internal static Vector3 GetEffectivePlayerTargetPosition(PlayerAvatar? player) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) return LastChanceMonstersTargetProxyHelper.ResolveEffectivePlayerTargetPosition(player); } } [HarmonyPatch(typeof(EnemyHeartHuggerGasChecker), "Update")] internal static class LastChanceMonstersGasCaptureModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.HeartHugger"); [HarmonyPostfix] private static void Postfix(EnemyHeartHuggerGasChecker __instance) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_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_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled() || __instance.checkTimer < 0.95f) { return; } EnemyHeartHugger enemyHeartHugger = __instance.enemyHeartHugger; if ((Object)(object)enemyHeartHugger == (Object)null) { return; } IList playersColliding = __instance.playersColliding; if (playersColliding == null) { return; } EnemyVision ownerVision = enemyHeartHugger.enemy?.Vision; Vector3 prevCheckPos = __instance.prevCheckPos; Vector3 position = ((Component)__instance).transform.position; Vector3 val = position - prevCheckPos; Vector3 normalized = ((Vector3)(ref val)).normalized; float magnitude = ((Vector3)(ref val)).magnitude; float num = Mathf.Max(((Component)__instance).transform.localScale.z * 0.5f, 0.2f); if (magnitude <= 0.01f) { DebugLog("Gas.NoTravel", $"overlap fallback radius={num:0.00}"); Collider[] array = Physics.OverlapSphere(position, num, -1, (QueryTriggerInteraction)1); int num2 = 0; foreach (Collider val2 in array) { if (!((Object)(object)val2 == (Object)null) && ProcessCandidateCollider(__instance, enemyHeartHugger, playersColliding, ownerVision, val2, position, num)) { num2++; } } DebugLog("Gas.NoTravel.Result", $"overlap={array.Length} processed={num2}"); return; } RaycastHit[] array2 = Physics.SphereCastAll(prevCheckPos, num, normalized, magnitude, LayerMask.GetMask(new string[2] { "Player", "PhysGrabObject" }), (QueryTriggerInteraction)1); for (int j = 0; j < array2.Length; j++) { Collider collider = ((RaycastHit)(ref array2[j])).collider; if (!((Object)(object)collider == (Object)null)) { ProcessCandidateCollider(__instance, enemyHeartHugger, playersColliding, ownerVision, collider, ((RaycastHit)(ref array2[j])).point, num); } } } private static bool ProcessCandidateCollider(EnemyHeartHuggerGasChecker checkerInstance, EnemyHeartHugger owner, IList playersColliding, EnemyVision? ownerVision, Collider collider, Vector3 hitPoint, float radius) { //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) if (!TryResolvePlayer(collider, out PlayerAvatar player) || (Object)(object)player == (Object)null) { return false; } if (!LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player)) { DebugLog("Gas.Skip.NotHeadProxy", $"player={GetPlayerId(player)}"); return false; } if (owner.PlayerIsOnCooldown(player)) { DebugLog("Gas.Skip.Cooldown", $"player={GetPlayerId(player)}"); return false; } if (owner.PlayerInGasCheck(player)) { DebugLog("Gas.Skip.AlreadyInGas", $"player={GetPlayerId(player)}"); return false; } Transform val = ownerVision?.VisionTransform; if ((Object)(object)val != (Object)null && (Object)(object)player.PlayerVisionTarget?.VisionTransform != (Object)null) { Vector3 position = player.PlayerVisionTarget.VisionTransform.position; Vector3 val2 = position - val.position; if (Physics.Raycast(val.position, val2, ((Vector3)(ref val2)).magnitude, LayerMask.GetMask(new string[1] { "Default" }), (QueryTriggerInteraction)1)) { DebugLog("Gas.Skip.BlockedLOS", $"player={GetPlayerId(player)}"); return false; } } if (!ContainsPlayer(playersColliding, player)) { playersColliding.Add(player); } owner.PlayerInGas(player); DebugLog("Gas.PlayerInGas", $"player={GetPlayerId(player)} radius={radius:0.00}"); TrySpawnGasGuiderForHeadProxy(checkerInstance, owner, player, hitPoint); return true; } private static bool TryResolvePlayer(Collider collider, out PlayerAvatar? player) { player = null; if ((Object)(object)collider == (Object)null) { return false; } PlayerController componentInParent = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent.playerAvatarScript != (Object)null) { player = componentInParent.playerAvatarScript; return true; } PlayerAvatar componentInParent2 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { player = componentInParent2; return true; } PlayerTumble componentInParent3 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null && (Object)(object)componentInParent3.playerAvatar != (Object)null) { player = componentInParent3.playerAvatar; return true; } PlayerDeathHead componentInParent4 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent4 != (Object)null && (Object)(object)componentInParent4.playerAvatar != (Object)null) { player = componentInParent4.playerAvatar; return true; } return false; } private static bool ContainsPlayer(IList list, PlayerAvatar player) { for (int i = 0; i < list.Count; i++) { if (list[i] == player) { return true; } } return false; } private static void TrySpawnGasGuiderForHeadProxy(EnemyHeartHuggerGasChecker checkerInstance, EnemyHeartHugger owner, PlayerAvatar player, Vector3 hitPoint) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTransform(player, out Transform transform) || (Object)(object)transform == (Object)null) { DebugLog("Guider.SpawnSkip.NoHeadTransform", $"player={GetPlayerId(player)}"); return; } if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyPhysGrabObject(player, out PhysGrabObject physGrabObject) || (Object)(object)physGrabObject == (Object)null) { DebugLog("Guider.SpawnSkip.NoHeadPhys", $"player={GetPlayerId(player)}"); return; } GameObject gasGuider = checkerInstance.gasGuider; if ((Object)(object)gasGuider == (Object)null) { DebugLog("Guider.SpawnSkip.NoPrefab", $"player={GetPlayerId(player)}"); return; } GameObject val = Object.Instantiate(gasGuider, ((Component)checkerInstance).transform.position, Quaternion.identity); if ((Object)(object)val == (Object)null) { DebugLog("Guider.SpawnSkip.InstantiateNull", $"player={GetPlayerId(player)}"); return; } EnemyHeartHuggerGasGuider component = val.GetComponent(); if ((Object)(object)component == (Object)null) { DebugLog("Guider.SpawnSkip.NoComponent", $"player={GetPlayerId(player)} prefab={((Object)gasGuider).name}"); return; } component.playerTumble = player.tumble; component.targetTransform = transform; component.enemyHeartHugger = owner; component.headTransform = transform; component.startPosition = hitPoint; component.physGrabObject = physGrabObject; component.player = player; val.SetActive(true); DebugLog("Guider.Spawned", $"player={GetPlayerId(player)} start={hitPoint} head={transform.position}"); } private static void DebugLog(string reason, string detail) { if (InternalDebugFlags.DebugLastChanceHeartHuggerFlow && LogLimiter.ShouldLog("HeartHugger." + reason, 30)) { Log.LogInfo((object)("[HeartHugger][" + reason + "] " + detail)); } } private static int GetPlayerId(PlayerAvatar player) { PhotonView photonView = player.photonView; return ((Object)(object)photonView != (Object)null) ? photonView.ViewID : ((Object)player).GetInstanceID(); } } [HarmonyPatch(typeof(EnemyHeartHuggerGasGuider), "FixedUpdate")] internal static class LastChanceMonstersGasGuiderHeadProxyModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.HeartHugger"); [HarmonyPrefix] private static bool Prefix(EnemyHeartHuggerGasGuider __instance) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: 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) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0104: 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_010c: 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) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: 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_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled()) { return true; } PlayerAvatar player = __instance.player; if ((Object)(object)player == (Object)null || !LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player)) { return true; } PhysGrabObject physGrabObject = __instance.physGrabObject; if ((Object)(object)physGrabObject?.rb == (Object)null) { DebugLog("Guider.Fixed.SkipNoPhys", $"player={GetPlayerId(player)}"); return true; } EnemyHeartHugger enemyHeartHugger = __instance.enemyHeartHugger; if ((Object)(object)enemyHeartHugger?.headCenterTransform == (Object)null) { DebugLog("Guider.Fixed.SkipNoEnemyHead", $"player={GetPlayerId(player)}"); return true; } Rigidbody rb = physGrabObject.rb; Vector3 startPosition = __instance.startPosition; Vector3 val = enemyHeartHugger.headCenterTransform.position - startPosition; Vector3 normalized = ((Vector3)(ref val)).normalized; Vector3 position = rb.position; Vector3 position2 = ((Component)__instance).transform.position; physGrabObject.OverrideZeroGravity(0.1f); if (rb.isKinematic) { rb.position = Vector3.Lerp(position, position2, 0.3f); Quaternion val2 = Quaternion.LookRotation((((Vector3)(ref normalized)).sqrMagnitude > 0.0001f) ? normalized : ((Component)rb).transform.forward, Vector3.up); rb.rotation = Quaternion.Slerp(rb.rotation, val2, 0.25f); DebugLog("Guider.Fixed.Kinematic", $"player={GetPlayerId(player)} rbPos={position} target={position2}"); return false; } Vector3 val3 = SemiFunc.PhysFollowDirection(((Component)rb).transform, normalized, rb, 0.5f); rb.AddTorque(val3 / Mathf.Max(rb.mass, 0.0001f), (ForceMode)0); Vector3 val4 = SemiFunc.PhysFollowPosition(position, position2, rb.velocity, 5f); rb.AddForce(val4, (ForceMode)5); DebugLog("Guider.Fixed.Apply", $"player={GetPlayerId(player)} rbPos={position} target={position2} forceMag={((Vector3)(ref val4)).magnitude:0.00}"); return false; } private static void DebugLog(string reason, string detail) { if (InternalDebugFlags.DebugLastChanceHeartHuggerFlow && LogLimiter.ShouldLog("HeartHugger." + reason, 30)) { Log.LogInfo((object)("[HeartHugger][" + reason + "] " + detail)); } } private static int GetPlayerId(PlayerAvatar player) { PhotonView photonView = player.photonView; return ((Object)(object)photonView != (Object)null) ? photonView.ViewID : ((Object)player).GetInstanceID(); } } [HarmonyPatch] internal static class LastChanceMonstersGasVictimPositionModule { [HarmonyPatch(typeof(EnemyHeartHugger), "PlayersInGasLogic")] internal static class EnemyHeartHuggerPlayersInGasLogicPatch { [HarmonyPrefix] private static bool Prefix(EnemyHeartHugger __instance) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } ExecutePlayersInGasLogic(__instance); return false; } } [HarmonyPatch(typeof(EnemyHeartHugger), "PlayerInGas", new Type[] { typeof(PlayerAvatar) })] internal static class EnemyHeartHuggerPlayerInGasPatch { [HarmonyPrefix] private static bool Prefix(EnemyHeartHugger __instance, PlayerAvatar _player) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } ExecutePlayerInGas(__instance, _player); return false; } } private static void ExecutePlayersInGasLogic(EnemyHeartHugger instance) { //IL_009c: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0374: Unknown result type (might be due to invalid IL or missing references) if (!SemiFunc.IsMasterClientOrSingleplayer()) { return; } if (instance.playersInGas.Count > 0) { List list = new List(); foreach (PlayersInGas playersInGa in instance.playersInGas) { playersInGa.playerAvatar.upgradeTumbleWingsLogic.tumbleWingPinkTimer = 1f; if (playersInGa.inGasTime >= 2f) { playersInGa.isCaught = true; } playersInGa.inGasTime += Time.deltaTime; Vector3 effectivePlayerPosition = GetEffectivePlayerPosition(playersInGa.playerAvatar); float num = Vector3.Distance(playersInGa.lastPositionInsideGas, effectivePlayerPosition); if (playersInGa.outsideGasTime >= 3f || num > 2f) { list.Add(playersInGa); } playersInGa.outsideGasTime += Time.deltaTime; } foreach (PlayersInGas item in list) { instance.playersOnCooldown.Add(item.playerAvatar, Time.time); instance.playersInGas.Remove(item); } } if (!SemiFunc.PerSecond(5f, (Object)(object)instance)) { return; } foreach (PlayersInGas playersInGa2 in instance.playersInGas) { bool flag = true; foreach (PlayersInGas playersInGasPreviou in instance.playersInGasPrevious) { if ((Object)(object)playersInGa2.playerAvatar == (Object)(object)playersInGasPreviou.playerAvatar) { flag = false; } } if (flag) { if (SemiFunc.IsMultiplayer()) { instance.photonView.RPC("PlayerInGasClientRPC", (RpcTarget)0, new object[2] { playersInGa2.playerAvatar.photonView.ViewID, true }); } else { instance.PlayerInGasClientRPC(playersInGa2.playerAvatar.photonView.ViewID, true, default(PhotonMessageInfo)); } } } foreach (PlayersInGas playersInGasPreviou2 in instance.playersInGasPrevious) { bool flag2 = true; foreach (PlayersInGas playersInGa3 in instance.playersInGas) { if ((Object)(object)playersInGa3.playerAvatar == (Object)(object)playersInGasPreviou2.playerAvatar) { flag2 = false; } } if (flag2) { if (SemiFunc.IsMultiplayer()) { instance.photonView.RPC("PlayerInGasClientRPC", (RpcTarget)0, new object[2] { playersInGasPreviou2.playerAvatar.photonView.ViewID, false }); } else { instance.PlayerInGasClientRPC(playersInGasPreviou2.playerAvatar.photonView.ViewID, false, default(PhotonMessageInfo)); } } } instance.playersInGasPrevious.Clear(); instance.playersInGasPrevious.AddRange(instance.playersInGas); } private static void ExecutePlayerInGas(EnemyHeartHugger instance, PlayerAvatar player) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_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_0083: Expected O, but got Unknown foreach (PlayersInGas playersInGa in instance.playersInGas) { if ((Object)(object)playersInGa.playerAvatar != (Object)(object)player) { continue; } playersInGa.outsideGasTime = 0f; playersInGa.lastPositionInsideGas = GetEffectivePlayerPosition(player); return; } PlayersInGas item = new PlayersInGas { playerAvatar = player, outsideGasTime = 0f, lastPositionInsideGas = GetEffectivePlayerPosition(player) }; instance.playersInGas.Add(item); } internal static Vector3 GetEffectivePlayerPosition(PlayerAvatar? player) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) return LastChanceMonstersTargetProxyHelper.ResolveEffectivePlayerTargetPosition(player); } } [HarmonyPatch(typeof(EnemyHeadGrabber), "StateGrabHead")] internal static class LastChanceMonstersHeadGrabberReleaseHeadModule { [HarmonyPostfix] private static void Postfix(EnemyHeadGrabber __instance) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Invalid comparison between Unknown and I4 //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Invalid comparison between Unknown and I4 //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Invalid comparison between Unknown and I4 if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled()) { return; } PlayerDeathHead headTarget = __instance.headTarget; if (!__instance.headTargetActive || (Object)(object)headTarget == (Object)null) { return; } PlayerAvatar playerAvatar = headTarget.playerAvatar; if (!((Object)(object)playerAvatar == (Object)null) && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(playerAvatar)) { __instance.DeathHeadRelease(); __instance.nearbyHeadLogic = false; if ((Object)(object)__instance.playerTarget == (Object)null) { __instance.UpdatePlayerTarget(playerAvatar); } if ((int)__instance.currentState == 15 || (int)__instance.currentState == 12 || (int)__instance.currentState == 13) { __instance.UpdateState((State)10); } } } } [HarmonyPatch(typeof(EnemyHidden), "PlayerTumbleLogic")] internal static class LastChanceMonstersCarryProxyModule { private sealed class CarryAnchorState { internal int PlayerId; internal Vector3 PickupOrigin; internal bool HasOrigin; internal string LastState = string.Empty; } private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.HiddenCarry"); private static readonly Dictionary AnchorByCarrier = new Dictionary(); internal static void ResetRuntimeState() { AnchorByCarrier.Clear(); } [HarmonyPrefix] private static bool Prefix(EnemyHidden __instance) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } PlayerAvatar playerTarget = __instance.playerTarget; if ((Object)(object)playerTarget == (Object)null || !LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(playerTarget)) { ClearPickupOrigin(__instance); return true; } if (!IsCarryState(__instance)) { ClearPickupOrigin(__instance); return true; } PlayerDeathHead playerDeathHead = playerTarget.playerDeathHead; PhysGrabObject val = playerDeathHead?.physGrabObject; Rigidbody val2 = val?.rb; Vector3 val3 = (((Object)(object)val != (Object)null) ? val.centerPoint : (((Object)(object)playerDeathHead != (Object)null) ? ((Component)playerDeathHead).transform.position : Vector3.zero)); Transform playerPickupTransform = __instance.playerPickupTransform; if ((Object)(object)playerDeathHead == (Object)null || (Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)playerPickupTransform == (Object)null) { return true; } UpdatePickupOrigin(__instance, playerTarget, GetCurrentStateName(__instance), val3); if (InternalDebugFlags.DebugLastChanceHiddenCarryFlow && LogLimiter.ShouldLog("HiddenCarry.PrefixState", 120)) { Log.LogInfo((object)("[HiddenCarry][Prefix] state=" + GetCurrentStateName(__instance) + " " + $"headCenter={val3} bodyPos={((Component)playerTarget).transform.position} pickupPos={playerPickupTransform.position}")); } playerTarget.FallDamageResetSet(0.1f); val.OverrideMass(1f, 0.1f); val.OverrideAngularDrag(2f, 0.1f); val.OverrideDrag(1f, 0.1f); float num = 1f; if (val.playerGrabbing.Count > 0) { num = 0.5f; } else if (IsState(__instance, (State)8) || IsState(__instance, (State)6)) { num = 0.75f; } if (val2.isKinematic) { val2.position = Vector3.Lerp(val3, playerPickupTransform.position, 0.35f); val2.rotation = Quaternion.Slerp(((Component)playerDeathHead).transform.rotation, playerPickupTransform.rotation, 0.2f * num); return false; } Vector3 val4 = SemiFunc.PhysFollowPosition(val3, playerPickupTransform.position, val2.velocity, 10f * num); val2.AddForce(val4 * (10f * Time.fixedDeltaTime * num), (ForceMode)1); Vector3 val5 = SemiFunc.PhysFollowRotation(((Component)playerDeathHead).transform, playerPickupTransform.rotation, val2, 0.2f * num); val2.AddTorque(val5 * (1f * Time.fixedDeltaTime * num), (ForceMode)1); return false; } internal static bool TryGetPickupOrigin(object carrierInstance, PlayerAvatar player, out Vector3 origin) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) origin = default(Vector3); if (carrierInstance == null || (Object)(object)player == (Object)null) { return false; } int carrierKey = GetCarrierKey(carrierInstance); if (!AnchorByCarrier.TryGetValue(carrierKey, out CarryAnchorState value) || value == null || !value.HasOrigin) { return false; } int playerId = GetPlayerId(player); if (value.PlayerId != playerId) { return false; } origin = value.PickupOrigin; return true; } private static bool IsCarryState(EnemyHidden instance) { return IsState(instance, (State)6) || IsState(instance, (State)7) || IsState(instance, (State)8); } private static string GetCurrentStateName(EnemyHidden instance) { if ((Object)(object)instance == (Object)null) { return string.Empty; } return ((object)(State)(ref instance.currentState)).ToString(); } private static void UpdatePickupOrigin(object carrierInstance, PlayerAvatar player, string stateName, Vector3 currentHeadCenter) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) int carrierKey = GetCarrierKey(carrierInstance); if (!AnchorByCarrier.TryGetValue(carrierKey, out CarryAnchorState value) || value == null) { value = new CarryAnchorState(); AnchorByCarrier[carrierKey] = value; } int playerId = GetPlayerId(player); bool flag = string.Equals(stateName, "PlayerPickup", StringComparison.Ordinal) && !string.Equals(value.LastState, "PlayerPickup", StringComparison.Ordinal); bool flag2 = value.PlayerId != 0 && value.PlayerId != playerId; if (!value.HasOrigin || flag || flag2) { value.PickupOrigin = currentHeadCenter; value.HasOrigin = true; value.PlayerId = playerId; if (InternalDebugFlags.DebugLastChanceHiddenCarryFlow && LogLimiter.ShouldLog("HiddenCarry.PickupOriginSet", 120)) { Log.LogInfo((object)$"[HiddenCarry][OriginSet] state={stateName} origin={value.PickupOrigin} playerId={value.PlayerId}"); } } value.LastState = stateName; } private static void ClearPickupOrigin(object carrierInstance) { if (carrierInstance != null) { AnchorByCarrier.Remove(GetCarrierKey(carrierInstance)); } } private static int GetCarrierKey(object carrierInstance) { Object val = (Object)((carrierInstance is Object) ? carrierInstance : null); if (val != null) { return val.GetInstanceID(); } return carrierInstance.GetHashCode(); } private static int GetPlayerId(PlayerAvatar player) { PhotonView photonView = player.photonView; if ((Object)(object)photonView != (Object)null) { return photonView.ViewID; } return ((Object)player).GetInstanceID(); } private static bool IsState(EnemyHidden instance, State state) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)instance == (Object)null) { return false; } return instance.currentState == state; } } [HarmonyPatch] internal static class LastChanceMonstersHiddenDestinationModule { [HarmonyPatch(typeof(SemiFunc), "LevelPointGetPlayerDistance", new Type[] { typeof(Vector3), typeof(float), typeof(float), typeof(bool) })] internal static class SemiFuncLevelPointGetPlayerDistancePatch { [HarmonyPostfix] private static void Postfix(Vector3 _position, ref LevelPoint? __result) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__result != (Object)null) && LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { __result = SemiFunc.LevelPointGet(_position, 8f, 999f); } } } [HarmonyPatch(typeof(SemiFunc), "LevelPointGetFurthestFromPlayer", new Type[] { typeof(Vector3), typeof(float) })] internal static class SemiFuncLevelPointGetFurthestFromPlayerPatch { [HarmonyPostfix] private static void Postfix(Vector3 _position, ref LevelPoint? __result) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__result != (Object)null) && LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { __result = SemiFunc.LevelPointGet(_position, 8f, 999f); } } } private const float FallbackMinDistance = 8f; private const float FallbackMaxDistance = 999f; } internal static class LastChanceMonstersNoiseAggroModule { private const float DefaultAggroRadius = 18f; private const float DefaultAggroCooldown = 0.75f; private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.Monsters.NoiseAggro"); private static readonly Dictionary s_lastAggroByPlayerViewId = new Dictionary(); internal static void ResetRuntimeState() { s_lastAggroByPlayerViewId.Clear(); } internal static void Apply(Harmony harmony) { } internal static void Unapply() { ResetRuntimeState(); } [HarmonyPatch(typeof(ChargeHandler), "ChargeWindup", new Type[] { typeof(Vector3) })] [HarmonyPostfix] private static void ChargeWindupPostfix(ChargeHandler __instance) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled()) { return; } PlayerAvatar val = TryGetOwnerPlayer(__instance); if ((Object)(object)val == (Object)null || !LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(val, out var center)) { return; } int viewID = val.photonView.ViewID; float unscaledTime = Time.unscaledTime; if (s_lastAggroByPlayerViewId.TryGetValue(viewID, out var value) && unscaledTime - value < 0.75f) { return; } s_lastAggroByPlayerViewId[viewID] = unscaledTime; foreach (Enemy item in LastChanceMonstersTargetProxyHelper.EnumerateEnemies()) { if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).GetComponent() != (Object)null) { continue; } float num = Vector3.Distance(((Component)item).transform.position, center); if (!(num > 18f)) { bool hasStateInvestigate = item.HasStateInvestigate; EnemyStateInvestigate stateInvestigate = item.StateInvestigate; if (hasStateInvestigate && (Object)(object)stateInvestigate != (Object)null) { stateInvestigate.Set(center, false); } bool hasVision = item.HasVision; EnemyVision vision = item.Vision; if (hasVision && (Object)(object)vision != (Object)null) { bool near = num <= vision.VisionDistanceClose; LastChanceMonstersTargetProxyHelper.EnsureVisionTriggered(vision, val, near); } item.SetChaseTarget(val); } } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.NoiseAggro.Trigger", 30)) { Log.LogInfo((object)$"[LastChance] NoiseAggro triggered by charge windup. player={val.photonView.ViewID} pos={center}"); } } private static PlayerAvatar? TryGetOwnerPlayer(ChargeHandler chargeHandler) { DeathHeadController controller = chargeHandler.controller; if ((Object)(object)controller == (Object)null) { return null; } return controller.deathHead?.playerAvatar; } } [HarmonyPatch(typeof(EnemyTricycle), "IsPlayerBlockingNavmeshPath")] internal static class LastChanceMonstersPathBlockingModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.Tricycle"); private const float DefaultBlockingRadius = 0.5f; private const float DefaultBlockingDistance = 3f; private const float DefaultNavmeshRadius = 1f; private static float s_lastTargetSnapshotAt; [HarmonyPostfix] private static void Postfix(EnemyTricycle __instance, ref bool __result) { //IL_00c6: 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_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_04df: Unknown result type (might be due to invalid IL or missing references) //IL_04eb: Unknown result type (might be due to invalid IL or missing references) //IL_0544: Unknown result type (might be due to invalid IL or missing references) //IL_0553: Unknown result type (might be due to invalid IL or missing references) //IL_05db: Unknown result type (might be due to invalid IL or missing references) //IL_0705: Unknown result type (might be due to invalid IL or missing references) //IL_072c: Unknown result type (might be due to invalid IL or missing references) //IL_07ac: Unknown result type (might be due to invalid IL or missing references) //IL_07b3: Unknown result type (might be due to invalid IL or missing references) //IL_07b8: Unknown result type (might be due to invalid IL or missing references) //IL_0764: Unknown result type (might be due to invalid IL or missing references) //IL_0769: Unknown result type (might be due to invalid IL or missing references) //IL_0788: Unknown result type (might be due to invalid IL or missing references) //IL_0749: Unknown result type (might be due to invalid IL or missing references) //IL_074e: Unknown result type (might be due to invalid IL or missing references) //IL_081b: Unknown result type (might be due to invalid IL or missing references) //IL_07df: Unknown result type (might be due to invalid IL or missing references) bool flag = LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled(); if (__result || (Object)(object)__instance == (Object)null || !flag) { DebugLog("Skip.Early", $"result={__result} instanceNull={(Object)(object)__instance == (Object)null} runtimeMaster={flag}"); return; } Enemy enemy = __instance.enemy; if ((Object)(object)enemy == (Object)null || (Object)(object)enemy.CenterTransform == (Object)null) { DebugLog("Skip.NoEnemy", "enemy or center transform missing"); return; } EnemyNavMeshAgent navMeshAgent = enemy.NavMeshAgent; if ((Object)(object)navMeshAgent == (Object)null) { DebugLog("Skip.NoNavMeshAgent", "enemy=" + ((Object)enemy).name); return; } Vector3 agentDirection = __instance.agentDirection; if (((Vector3)(ref agentDirection)).sqrMagnitude <= 0.0001f) { DebugLog("Skip.NoHeading", $"enemy={((Object)enemy).name} heading={agentDirection}"); return; } bool isBlockedByPlayer = __instance.isBlockedByPlayer; PlayerAvatar isBlockedByPlayerAvatar = __instance.isBlockedByPlayerAvatar; PlayerAvatar playerTarget = __instance.playerTarget; string text = ((object)(State)(ref __instance.currentState)).ToString(); float num = (((Object)(object)playerTarget != (Object)null) ? Vector3.Distance(enemy.CenterTransform.position, ((Component)playerTarget).transform.position) : (-1f)); Vector3 center; float num2 = (((Object)(object)playerTarget != (Object)null && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(playerTarget, out center)) ? Vector3.Distance(enemy.CenterTransform.position, center) : (-1f)); float num3 = -1f; float num4 = -1f; float num5 = 0f; float num6 = 0f; float num7 = 0f; float num8 = 0f; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; if ((Object)(object)playerTarget != (Object)null) { Vector3 position = enemy.CenterTransform.position; Vector3 position2 = ((Component)playerTarget).transform.position; Vector3 val = position2 - position; Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(val.x, 0f, val.z); num3 = ((Vector3)(ref val2)).magnitude; num5 = val.y; flag4 = ((Vector3)(ref val)).sqrMagnitude > 0.0001f; num7 = (flag4 ? Vector3.Dot(((Vector3)(ref agentDirection)).normalized, ((Vector3)(ref val)).normalized) : 0f); flag2 = HasLineOfSight(position, position2, ((Component)enemy).transform); if (LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(playerTarget, out var center2)) { Vector3 val3 = center2 - position; Vector3 val4 = default(Vector3); ((Vector3)(ref val4))..ctor(val3.x, 0f, val3.z); num4 = ((Vector3)(ref val4)).magnitude; num6 = val3.y; flag5 = ((Vector3)(ref val3)).sqrMagnitude > 0.0001f; num8 = (flag5 ? Vector3.Dot(((Vector3)(ref agentDirection)).normalized, ((Vector3)(ref val3)).normalized) : 0f); flag3 = HasLineOfSight(position, center2, ((Component)enemy).transform); } } DebugLog("State", $"enemy={((Object)enemy).name} state={text} currentBlocked={isBlockedByPlayer} blockedAvatar={GetPlayerIdOrNone(isBlockedByPlayerAvatar)} " + "playerTarget=" + GetPlayerIdOrNone(playerTarget) + " targetBodyDist=" + ((num >= 0f) ? num.ToString("F2") : "n/a") + " targetHeadDist=" + ((num2 >= 0f) ? num2.ToString("F2") : "n/a") + " targetBodyH=" + ((num3 >= 0f) ? num3.ToString("F2") : "n/a") + " targetHeadH=" + ((num4 >= 0f) ? num4.ToString("F2") : "n/a") + " deltaYBody=" + (flag4 ? num5.ToString("F2") : "n/a") + " deltaYHead=" + (flag5 ? num6.ToString("F2") : "n/a") + " dotBody=" + (flag4 ? num7.ToString("F2") : "n/a") + " dotHead=" + (flag5 ? num8.ToString("F2") : "n/a") + " " + $"losBody={flag2} losHead={flag3} probeDist={3f:F2} headingMag={((Vector3)(ref agentDirection)).magnitude:F2}"); TryLogTargetSnapshot(enemy); RaycastHit[] array = Physics.SphereCastAll(enemy.CenterTransform.position, 0.5f, ((Vector3)(ref agentDirection)).normalized, 3f, LayerMask.GetMask(new string[2] { "Player", "PhysGrabObject" })); DebugLog("Probe", $"enemy={((Object)enemy).name} hits={array.Length} center={enemy.CenterTransform.position} heading={((Vector3)(ref agentDirection)).normalized}"); for (int i = 0; i < array.Length; i++) { Collider collider = ((RaycastHit)(ref array[i])).collider; if ((Object)(object)collider == (Object)null) { continue; } DebugLog("Hit", $"idx={i} collider={((Object)collider).name} dist={((RaycastHit)(ref array[i])).distance:F2} point={((RaycastHit)(ref array[i])).point} layer={LayerMask.LayerToName(((Component)collider).gameObject.layer)}"); if (!TryResolvePlayerFromBlockingCollider(collider, out PlayerAvatar player) || (Object)(object)player == (Object)null) { LastChanceMonstersTargetProxyHelper.TryGetPlayerFromDeathHeadCollider(collider, out PlayerAvatar player2); bool flag6 = ((Component)collider).transform.IsChildOf(((Component)enemy).transform); string text2 = (((Object)(object)((Component)collider).transform.root != (Object)null) ? ((Object)((Component)collider).transform.root).name : "n/a"); DebugLog("Hit.Skip.NoPlayer", $"collider={((Object)collider).name} layer={LayerMask.LayerToName(((Component)collider).gameObject.layer)} root={text2} self={flag6} deathHeadPlayer={GetPlayerIdOrNone(player2)}"); continue; } if (!TryResolveNavmeshPoint(player, preferBodyAnchor: false, out var point, out var fallbackPoint)) { DebugLog("Hit.Skip.NoPoint", $"player={GetPlayerId(player)}"); continue; } bool flag7 = navMeshAgent.OnNavmesh(point, 1f, true); if (!flag7 && fallbackPoint.HasValue) { flag7 = navMeshAgent.OnNavmesh(fallbackPoint.Value, 1f, true); if (flag7) { point = fallbackPoint.Value; } } if (!flag7) { float num9 = Vector3.Distance(enemy.CenterTransform.position, point); DebugLog("Hit.Skip.NotOnNavmesh", $"player={GetPlayerId(player)} point={point} distFromEnemy={num9:F2}"); continue; } if (!IsPointInsideProbeCorridor(enemy.CenterTransform.position, ((Vector3)(ref agentDirection)).normalized, point)) { DebugLog("Hit.Skip.OutsideProbe", $"player={GetPlayerId(player)} point={point}"); continue; } __instance.isBlockedByPlayerAvatar = player; __instance.isBlockedByPlayer = true; __result = true; DebugLog("Blocked.Set", $"player={GetPlayerId(player)} point={point}"); return; } DebugLog("Probe.NoBlock", "enemy=" + ((Object)enemy).name); } private static bool TryResolvePlayerFromBlockingCollider(Collider collider, out PlayerAvatar? player) { player = null; if ((Object)(object)collider == (Object)null) { return false; } PlayerController componentInParent = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent.playerAvatarScript != (Object)null) { player = componentInParent.playerAvatarScript; return true; } PlayerAvatar componentInParent2 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { player = componentInParent2; return true; } PlayerTumble componentInParent3 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null && (Object)(object)componentInParent3.playerAvatar != (Object)null) { player = componentInParent3.playerAvatar; return true; } PlayerDeathHead componentInParent4 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent4 != (Object)null && (Object)(object)componentInParent4.playerAvatar != (Object)null) { player = componentInParent4.playerAvatar; return true; } return false; } private static bool TryResolveNavmeshPoint(PlayerAvatar player, bool preferBodyAnchor, out Vector3 point, out Vector3? fallbackPoint) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) fallbackPoint = null; if (preferBodyAnchor) { point = ((Component)player).transform.position; return true; } if (LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(player, out var center)) { point = center; fallbackPoint = ((Component)player).transform.position; return true; } point = ((Component)player).transform.position; return true; } private static bool IsPointInsideProbeCorridor(Vector3 origin, Vector3 headingNormalized, Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) Vector3 val = point - origin; float num = Vector3.Dot(val, headingNormalized); if (num < 0f || num > 3.1f) { return false; } Vector3 val2 = origin + headingNormalized * num; float num2 = Vector3.Distance(val2, point); return num2 <= 0.85f; } private static int GetPlayerId(PlayerAvatar player) { return ((Object)(object)player.photonView != (Object)null) ? player.photonView.ViewID : ((Object)player).GetInstanceID(); } private static string GetPlayerIdOrNone(PlayerAvatar? player) { return ((Object)(object)player == (Object)null) ? "n/a" : GetPlayerId(player).ToString(); } private static bool HasLineOfSight(Vector3 from, Vector3 to, Transform? enemyRoot) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_002c: Unknown result type (might be due to invalid IL or missing references) Vector3 val = to - from; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude <= 0.001f) { return true; } RaycastHit[] array = Physics.RaycastAll(from, val / magnitude, magnitude, -1, (QueryTriggerInteraction)1); for (int i = 0; i < array.Length; i++) { Collider collider = ((RaycastHit)(ref array[i])).collider; if (!((Object)(object)collider == (Object)null)) { Transform transform = ((Component)collider).transform; if ((!((Object)(object)enemyRoot != (Object)null) || !transform.IsChildOf(enemyRoot)) && (!TryResolvePlayerFromBlockingCollider(collider, out PlayerAvatar player) || !((Object)(object)player != (Object)null))) { return false; } } } return true; } private static void TryLogTargetSnapshot(Enemy enemy) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) if (!InternalDebugFlags.DebugLastChanceTricycleFlow) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime - s_lastTargetSnapshotAt < 1.5f) { return; } s_lastTargetSnapshotAt = unscaledTime; List list = GameDirector.instance?.PlayerList; if (list == null || list.Count == 0) { DebugLog("Targets", "no players in GameDirector list"); return; } Vector3 position = enemy.CenterTransform.position; for (int i = 0; i < list.Count; i++) { PlayerAvatar val = list[i]; if (!((Object)(object)val == (Object)null)) { float num = Vector3.Distance(position, ((Component)val).transform.position); Vector3 center; bool flag = LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(val, out center); float num2 = (flag ? Vector3.Distance(position, center) : (-1f)); bool flag2 = LastChanceMonstersTargetProxyHelper.IsDisabled(val); bool flag3 = LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val); DebugLog("Targets", string.Format("player={0} disabled={1} headProxyActive={2} headProxy={3} bodyDist={4:F2} headDist={5} bodyPos={6} headPos={7}", GetPlayerId(val), flag2, flag3, flag, num, (num2 >= 0f) ? num2.ToString("F2") : "n/a", ((Component)val).transform.position, flag ? ((object)(Vector3)(ref center)).ToString() : "n/a")); } } } private static void DebugLog(string reason, string detail) { if (InternalDebugFlags.DebugLastChanceTricycleFlow && LogLimiter.ShouldLog("Tricycle." + reason, 30)) { Log.LogInfo((object)("[Tricycle][" + reason + "] " + detail)); } } } [HarmonyPatch(typeof(EnemySpinny), "LockInPlayer")] internal static class LastChanceMonstersSpinnyLockBridgeModule { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.Spinny"); [HarmonyPostfix] private static void Postfix(EnemySpinny __instance, bool _horizontalPull = false, bool _fixedUpdate = false) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled() || !IsSpinnyLockState(__instance.currentState)) { return; } PlayerAvatar playerTarget = __instance.playerTarget; if (!LastChanceMonstersLockBridgeCore.IsHeadProxyRuntimeApplicable(playerTarget)) { return; } PlayerTumble tumble = playerTarget.tumble; Rigidbody rb = tumble.rb; if ((Object)(object)rb == (Object)null) { DebugLog("Bridge.Skip.NoTumbleRb", $"enemyId={GetInstanceKey(__instance)} player={GetPlayerId(playerTarget)}"); return; } StabilizeTumbleAtLockPoint(__instance, rb, _fixedUpdate); if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyPhysGrabObject(playerTarget, out PhysGrabObject physGrabObject) || (Object)(object)physGrabObject?.rb == (Object)null) { DebugLog("Bridge.Skip.NoHeadPhys", $"enemyId={GetInstanceKey(__instance)} player={GetPlayerId(playerTarget)}"); return; } Rigidbody rb2 = physGrabObject.rb; LastChanceMonstersLockBridgeCore.HeadCoupleResult headCoupleResult = LastChanceMonstersLockBridgeCore.CoupleHeadToTarget(physGrabObject, rb2, rb, _fixedUpdate); if (headCoupleResult.HardSnap) { DebugLog("Bridge.HardSnap", $"enemyId={GetInstanceKey(__instance)} player={GetPlayerId(playerTarget)} dist={headCoupleResult.Distance:0.00}"); return; } DebugLog("Bridge.Apply", $"enemyId={GetInstanceKey(__instance)} player={GetPlayerId(playerTarget)} state={__instance.currentState} dist={headCoupleResult.Distance:0.00} follow={headCoupleResult.ForceMagnitude:0.00} fixed={_fixedUpdate} horizontal={_horizontalPull}"); } private static void StabilizeTumbleAtLockPoint(EnemySpinny instance, Rigidbody tumbleRb, bool fixedUpdate) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 if (!fixedUpdate || (int)instance.currentState != 7) { return; } Transform playerLockPoint = instance.playerLockPoint; if ((Object)(object)playerLockPoint == (Object)null) { return; } LastChanceMonstersLockBridgeCore.LockStabilizeResult lockStabilizeResult = LastChanceMonstersLockBridgeCore.StabilizeTargetAtLockPoint(tumbleRb, playerLockPoint, instance.offLockPointTimer, fixedUpdate, isPrimaryLockState: true); if (lockStabilizeResult.Applied) { if (lockStabilizeResult.Kind == LastChanceMonstersLockBridgeCore.LockStabilizeKind.Emergency) { DebugLog("Bridge.LockStabilizeEmergency", $"enemyId={GetInstanceKey(instance)} dist={lockStabilizeResult.Distance:0.00} offLock={lockStabilizeResult.OffLockTimer:0.00}"); } else if (lockStabilizeResult.Kind == LastChanceMonstersLockBridgeCore.LockStabilizeKind.Snap) { DebugLog("Bridge.LockStabilizeSnap", $"enemyId={GetInstanceKey(instance)} dist={lockStabilizeResult.Distance:0.00}"); } else if (lockStabilizeResult.Kind == LastChanceMonstersLockBridgeCore.LockStabilizeKind.Force) { DebugLog("Bridge.LockStabilizeForce", $"enemyId={GetInstanceKey(instance)} dist={lockStabilizeResult.Distance:0.00} force={lockStabilizeResult.ForceMagnitude:0.00}"); } } } private static bool IsSpinnyLockState(State state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 return (int)state == 7 || (int)state == 8 || (int)state == 9 || (int)state == 10 || (int)state == 11; } private static void DebugLog(string reason, string detail) { if (InternalDebugFlags.DebugLastChanceSpinnyFlow && (InternalDebugFlags.DebugLastChanceSpinnyVerbose || LogLimiter.ShouldLog("Spinny.Bridge." + reason, 8))) { Log.LogInfo((object)("[Spinny][" + reason + "] " + detail)); } } private static int GetPlayerId(PlayerAvatar player) { return ((Object)(object)player.photonView != (Object)null) ? player.photonView.ViewID : ((Object)player).GetInstanceID(); } private static int GetInstanceKey(EnemySpinny instance) { return ((Object)instance).GetInstanceID(); } } [HarmonyPatch] internal static class LastChanceMonstersUpscreamHeadAimModule { [HarmonyPatch(typeof(EnemyUpscreamAnim), "AttackImpulse")] [HarmonyPrefix] private static bool AttackImpulsePrefix(EnemyUpscreamAnim __instance) { //IL_00ba: 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_00c1: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: 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_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } PlayerAvatar val = (((Object)(object)__instance.controller != (Object)null) ? __instance.controller.targetPlayer : null); if ((Object)(object)val == (Object)null || !LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(val)) { return true; } if (SemiFunc.IsMultiplayer() && !SemiFunc.IsMasterClient()) { return false; } if (!LastChanceMonstersTargetProxyHelper.TryGetHeadProxyPhysGrabObject(val, out PhysGrabObject physGrabObject) || (Object)(object)physGrabObject?.rb == (Object)null) { return true; } Vector3 position = ((Component)__instance).transform.position; Vector3 val2 = physGrabObject.centerPoint - position; if (((Vector3)(ref val2)).sqrMagnitude <= 0.0001f) { val2 = ((Component)val).transform.position - position; } if (((Vector3)(ref val2)).sqrMagnitude <= 0.0001f) { val2 = ((Component)__instance).transform.forward; } Vector3 val3 = Vector3.Lerp(((Vector3)(ref val2)).normalized, Vector3.up, 0.6f); physGrabObject.rb.AddForce(val3 * 45f, (ForceMode)1); physGrabObject.rb.AddTorque(-((Component)val).transform.right * 45f, (ForceMode)1); return false; } [HarmonyPatch(typeof(EnemyUpscream), "StateGoToPlayer")] [HarmonyPostfix] private static void StateGoToPlayerPostfix(EnemyUpscream __instance) { //IL_0059: 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_0046: Unknown result type (might be due to invalid IL or missing references) if (!TryGetContext(__instance, out var targetPoint)) { return; } Enemy enemy = __instance.enemy; if (!((Object)(object)enemy == (Object)null)) { if (!enemy.Jump.jumping) { enemy.NavMeshAgent.SetDestination(targetPoint); } if (!(Vector3.Distance(((Component)enemy.Rigidbody).transform.position, targetPoint) >= 1.5f) && !enemy.Jump.jumping && !enemy.IsStunned()) { enemy.NavMeshAgent.ResetPath(); SemiFunc.EnemyCartJumpReset(enemy); __instance.UpdateState((State)6); } } } [HarmonyPatch(typeof(EnemyUpscream), "StateAttack")] [HarmonyPostfix] private static void StateAttackPostfix(EnemyUpscream __instance) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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) if (!TryGetContext(__instance, out var targetPoint)) { return; } Enemy enemy = __instance.enemy; if (!((Object)(object)enemy == (Object)null)) { Vector3 position = ((Component)enemy.Rigidbody).transform.position; Vector3 val = targetPoint - position; if (!(((Vector3)(ref val)).sqrMagnitude <= 0.0001f)) { Quaternion val2 = Quaternion.LookRotation(val); Quaternion val3 = Quaternion.Euler(0f, ((Quaternion)(ref val2)).eulerAngles.y, 0f); ((Component)__instance).transform.rotation = Quaternion.Slerp(((Component)__instance).transform.rotation, val3, 50f * Time.deltaTime); } } } [HarmonyPatch(typeof(EnemyUpscream), "HeadLogic")] [HarmonyPostfix] private static void HeadLogicPostfix(EnemyUpscream __instance) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0053: Unknown result type (might be due to invalid IL or missing references) if (!TryGetContext(__instance, out var targetPoint)) { return; } Transform headTransform = __instance.headTransform; if (!((Object)(object)headTransform == (Object)null)) { Vector3 val = targetPoint - headTransform.position; if (!(((Vector3)(ref val)).sqrMagnitude <= 0.0001f)) { headTransform.rotation = Quaternion.LookRotation(val); } } } private static bool TryGetContext(EnemyUpscream? upscream, out Vector3 targetPoint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) targetPoint = default(Vector3); if ((Object)(object)upscream == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return false; } PlayerAvatar targetPlayer = upscream.targetPlayer; if ((Object)(object)targetPlayer == (Object)null) { return false; } return LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(targetPlayer, out targetPoint) || LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(targetPlayer, out targetPoint); } } [HarmonyPatch] internal static class LastChanceMonstersValuableThrowerHeadAimModule { [HarmonyPatch(typeof(EnemyValuableThrower), "PlayerLookAt")] [HarmonyPostfix] private static void PlayerLookAtPostfix(EnemyValuableThrower __instance) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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) if (!TryGetContext(__instance, out var targetPoint)) { return; } Enemy enemy = __instance.enemy; if (!((Object)(object)enemy == (Object)null)) { Vector3 position = ((Component)enemy.Rigidbody).transform.position; Vector3 val = targetPoint - position; if (!(((Vector3)(ref val)).sqrMagnitude <= 0.0001f)) { Quaternion val2 = Quaternion.LookRotation(val); Quaternion val3 = Quaternion.Euler(0f, ((Quaternion)(ref val2)).eulerAngles.y, 0f); ((Component)__instance).transform.rotation = Quaternion.Slerp(((Component)__instance).transform.rotation, val3, 50f * Time.deltaTime); } } } [HarmonyPatch(typeof(EnemyValuableThrower), "Throw")] [HarmonyPrefix] private static bool ThrowPrefix(EnemyValuableThrower __instance) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: 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_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) if (!TryGetContext(__instance, out var targetPoint)) { return true; } PhysGrabObject valuableTarget = __instance.valuableTarget; if ((Object)(object)valuableTarget == (Object)null) { return false; } foreach (PhysGrabber item in valuableTarget.playerGrabbing.ToList()) { if (!SemiFunc.IsMultiplayer()) { item.ReleaseObject(valuableTarget.photonView.ViewID, 0.5f); continue; } item.photonView.RPC("ReleaseObjectRPC", (RpcTarget)0, new object[3] { false, 0.5f, valuableTarget.photonView.ViewID }); } Vector3 val = targetPoint - valuableTarget.centerPoint; val = Vector3.Lerp(((Component)__instance).transform.forward, val, 0.5f); valuableTarget.ResetMass(); float num = Mathf.Min(20f * valuableTarget.rb.mass, 100f); valuableTarget.ResetIndestructible(); valuableTarget.rb.AddForce(val * num, (ForceMode)1); valuableTarget.rb.AddTorque(((Component)valuableTarget).transform.right * 0.5f, (ForceMode)1); valuableTarget.impactDetector.PlayerHurtMultiplier(5f, 2f); __instance.valuableTarget = null; return false; } private static bool TryGetContext(EnemyValuableThrower? thrower, out Vector3 targetPoint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) targetPoint = default(Vector3); if ((Object)(object)thrower == (Object)null || !LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return false; } PlayerAvatar playerTarget = thrower.playerTarget; if ((Object)(object)playerTarget == (Object)null) { return false; } return LastChanceMonstersTargetProxyHelper.TryGetHeadProxyVisionTarget(playerTarget, out targetPoint) || LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(playerTarget, out targetPoint); } } internal static class LastChanceMonstersVisualCouplingDecoupleModule { [HarmonyPatch(typeof(EnemyHeartHugger), "PlayersInGasLogic")] internal static class EnemyHeartHuggerPlayersInGasLogicPatch { [HarmonyFinalizer] private static Exception? Finalizer(Exception? __exception) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return __exception; } if (__exception is NullReferenceException) { return null; } return __exception; } } [HarmonyPatch(typeof(EnemyHeartHuggerGasChecker), "Update")] internal static class EnemyHeartHuggerGasCheckerUpdatePatch { [HarmonyFinalizer] private static Exception? Finalizer(Exception? __exception) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return __exception; } if (__exception is NullReferenceException) { return null; } return __exception; } } [HarmonyPatch(typeof(PlayerAvatar), "UpgradeTumbleWingsVisualsActive", new Type[] { typeof(bool), typeof(bool) })] internal static class PlayerAvatarUpgradeTumbleWingsVisualsActivePatch { [HarmonyPrefix] private static bool Prefix(PlayerAvatar? __instance) { if (!LastChanceMonstersTargetProxyHelper.IsRuntimeEnabled()) { return true; } if ((Object)(object)__instance == (Object)null) { return false; } return (Object)(object)__instance.upgradeTumbleWingsLogic != (Object)null; } } } [HarmonyPatch(typeof(PlayerVoiceChat), "Update")] internal static class LastChanceMonstersVoiceEnemyOnlyModule { private const float VoiceChatDiscoveryRefreshSeconds = 1f; private static readonly Dictionary OriginalAudioSourceVolumeByViewId = new Dictionary(); private static readonly Dictionary OriginalTtsVolumeByViewId = new Dictionary(); private static readonly Dictionary TemporaryHeadSpectatedOverrideByViewId = new Dictionary(); internal static void ResetRuntimeState() { LastChanceMonstersDiscoveryCache.InvalidateVoiceChats(); PlayerVoiceChat[] playerVoiceChats = LastChanceMonstersDiscoveryCache.GetPlayerVoiceChats(1f); foreach (PlayerVoiceChat val in playerVoiceChats) { if (!((Object)(object)val == (Object)null)) { PhotonView photonView = val.photonView; int viewId = ((photonView != null) ? photonView.ViewID : (-1)); RestoreVolumes(val, viewId); } } OriginalAudioSourceVolumeByViewId.Clear(); OriginalTtsVolumeByViewId.Clear(); TemporaryHeadSpectatedOverrideByViewId.Clear(); } [HarmonyPrefix] private static void Prefix(PlayerVoiceChat __instance) { if (!((Object)(object)__instance == (Object)null)) { PlayerAvatar playerAvatar = __instance.playerAvatar; PhotonView photonView = __instance.photonView; if (!((Object)(object)playerAvatar == (Object)null) && !((Object)(object)photonView == (Object)null)) { TryApplyTemporaryHeadSpectatedOverride(playerAvatar, photonView.ViewID); } } } [HarmonyPostfix] private static void Postfix(PlayerVoiceChat __instance) { if ((Object)(object)__instance == (Object)null) { return; } PlayerAvatar playerAvatar = __instance.playerAvatar; PhotonView photonView = __instance.photonView; if (!((Object)(object)playerAvatar == (Object)null) && !((Object)(object)photonView == (Object)null)) { int viewID = photonView.ViewID; if (!ShouldApply(playerAvatar)) { RestoreVolumes(__instance, viewID); return; } ApplyEnemyOnlyVoiceMix(__instance, viewID); ForceTalkAnimationEnabled(__instance); } } [HarmonyFinalizer] private static Exception? Finalizer(PlayerVoiceChat __instance, Exception? __exception) { if ((Object)(object)__instance == (Object)null) { return __exception; } PhotonView photonView = __instance.photonView; if ((Object)(object)photonView == (Object)null) { return __exception; } RestoreTemporaryHeadSpectatedOverride(photonView.ViewID); return __exception; } private static bool ShouldApply(PlayerAvatar player) { return LastChanceMonstersTargetProxyHelper.IsRuntimeFeatureEnabled(FeatureFlags.LastChanceMonstersVoiceEnemyOnlyEnabled) && LastChanceMonstersTargetProxyHelper.IsHeadProxyActive(player); } private static void TryApplyTemporaryHeadSpectatedOverride(PlayerAvatar player, int viewId) { if (!TemporaryHeadSpectatedOverrideByViewId.ContainsKey(viewId) && LastChanceMonstersTargetProxyHelper.IsRuntimeFeatureEnabled(FeatureFlags.LastChanceMonstersVoiceEnemyOnlyEnabled)) { PlayerDeathHead playerDeathHead = player.playerDeathHead; DeathHeadController val = default(DeathHeadController); if (!((Object)(object)playerDeathHead == (Object)null) && !playerDeathHead.spectated && ((Component)playerDeathHead).TryGetComponent(ref val) && (Object)(object)val != (Object)null && val.spectated) { playerDeathHead.spectated = true; TemporaryHeadSpectatedOverrideByViewId[viewId] = playerDeathHead; } } } private static void RestoreTemporaryHeadSpectatedOverride(int viewId) { if (TemporaryHeadSpectatedOverrideByViewId.TryGetValue(viewId, out PlayerDeathHead value) && !((Object)(object)value == (Object)null)) { value.spectated = false; TemporaryHeadSpectatedOverrideByViewId.Remove(viewId); } } private static void ApplyEnemyOnlyVoiceMix(PlayerVoiceChat voiceChat, int viewId) { AudioSource audioSource = voiceChat.audioSource; if ((Object)(object)audioSource != (Object)null) { if (!OriginalAudioSourceVolumeByViewId.ContainsKey(viewId)) { OriginalAudioSourceVolumeByViewId[viewId] = audioSource.volume; } audioSource.volume = 0f; } AudioSource ttsAudioSource = voiceChat.ttsAudioSource; if ((Object)(object)ttsAudioSource != (Object)null) { if (!OriginalTtsVolumeByViewId.ContainsKey(viewId)) { OriginalTtsVolumeByViewId[viewId] = ttsAudioSource.volume; } ttsAudioSource.volume = 0f; } } private static void ForceTalkAnimationEnabled(PlayerVoiceChat voiceChat) { voiceChat.overrideNoTalkAnimationTimer = 0f; } private static void RestoreVolumes(PlayerVoiceChat voiceChat, int viewId) { AudioSource audioSource = voiceChat.audioSource; if ((Object)(object)audioSource != (Object)null && OriginalAudioSourceVolumeByViewId.TryGetValue(viewId, out var value)) { audioSource.volume = value; OriginalAudioSourceVolumeByViewId.Remove(viewId); } AudioSource ttsAudioSource = voiceChat.ttsAudioSource; if ((Object)(object)ttsAudioSource != (Object)null && OriginalTtsVolumeByViewId.TryGetValue(viewId, out var value2)) { ttsAudioSource.volume = value2; OriginalTtsVolumeByViewId.Remove(viewId); } } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Monsters.Adapters { [HarmonyPatch(typeof(EnemyDirector), "Update")] internal static class LastChanceMonstersBodyPositionProxyModule { internal static void ResetRuntimeState() { LastChanceMonstersDiscoveryCache.InvalidateEnemies(); } [HarmonyPostfix] private static void Postfix() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (!LastChanceMonstersTargetProxyHelper.IsRuntimeMasterContextEnabled()) { ResetRuntimeState(); return; } List list = GameDirector.instance?.PlayerList; if (list == null || list.Count == 0) { return; } for (int i = 0; i < list.Count; i++) { PlayerAvatar val = list[i]; if (!((Object)(object)val == (Object)null) && LastChanceMonstersTargetProxyHelper.TryGetHeadProxyTarget(val, out var center)) { ((Component)val).transform.position = center; } } } } internal static class LastChanceMonstersDiscoveryCache { internal static Enemy[] GetEnemies(float refreshSeconds) { return LastChanceRuntimeObjectRegistry.GetEnemiesSnapshot(); } internal static EnemyAnimal[] GetEnemyAnimals(float refreshSeconds) { return LastChanceRuntimeObjectRegistry.GetEnemyAnimalsSnapshot(); } internal static PlayerVoiceChat[] GetPlayerVoiceChats(float refreshSeconds) { return LastChanceRuntimeObjectRegistry.GetPlayerVoiceChatsSnapshot(); } internal static void InvalidateAll() { InvalidateEnemies(); InvalidateVoiceChats(); } internal static void InvalidateEnemies() { LastChanceRuntimeObjectRegistry.ClearEnemies(); } internal static void InvalidateVoiceChats() { LastChanceRuntimeObjectRegistry.ClearVoiceChats(); } } [HarmonyPatch(typeof(Enemy), "Awake")] internal static class LastChanceEnemyAwakeRegistryPatch { [HarmonyPostfix] private static void Postfix(Enemy __instance) { LastChanceRuntimeObjectRegistry.RegisterEnemy(__instance); } } [HarmonyPatch(typeof(EnemyAnimal), "Awake")] internal static class LastChanceEnemyAnimalAwakeRegistryPatch { [HarmonyPostfix] private static void Postfix(EnemyAnimal __instance) { LastChanceRuntimeObjectRegistry.RegisterEnemyAnimal(__instance); } } [HarmonyPatch(typeof(PlayerVoiceChat), "Awake")] internal static class LastChancePlayerVoiceChatAwakeRegistryPatch { [HarmonyPostfix] private static void Postfix(PlayerVoiceChat __instance) { LastChanceRuntimeObjectRegistry.RegisterPlayerVoiceChat(__instance); } } [HarmonyPatch(typeof(PlayerVoiceChat), "OnDestroy")] internal static class LastChancePlayerVoiceChatDestroyRegistryPatch { [HarmonyPrefix] private static void Prefix(PlayerVoiceChat __instance) { LastChanceRuntimeObjectRegistry.UnregisterPlayerVoiceChat(__instance); } } internal static class LastChanceMonstersTargetProxyHelper { [CompilerGenerated] private sealed class d__22 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private Enemy <>2__current; private int <>l__initialThreadId; private Enemy[] 5__1; private int 5__2; private Enemy 5__3; Enemy? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__22(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 5__3 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_007b; } <>1__state = -1; 5__1 = LastChanceMonstersDiscoveryCache.GetEnemies(1f); 5__2 = 0; goto IL_0093; IL_007b: 5__3 = null; 5__2++; goto IL_0093; IL_0093: if (5__2 < 5__1.Length) { 5__3 = 5__1[5__2]; if ((Object)(object)5__3 != (Object)null) { <>2__current = 5__3; <>1__state = 1; return true; } goto IL_007b; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__22(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private const float EnemyDiscoveryRefreshSeconds = 1f; private static bool IsRuntimeGateEnabled() { return FeatureFlags.LastChanceMonstersSearchEnabled && FeatureFlags.LastChangeMode && LastChanceRuntimeOrchestrator.IsRuntimeActive; } internal static bool IsRuntimeEnabled() { return IsRuntimeGateEnabled(); } internal static bool IsMasterContext() { return SemiFunc.IsMasterClientOrSingleplayer(); } internal static bool IsRuntimeMasterContextEnabled() { return IsRuntimeEnabled() && IsMasterContext(); } internal static bool IsRuntimeFeatureEnabled(bool featureEnabled) { return featureEnabled && IsRuntimeEnabled(); } internal static bool IsRuntimeMasterFeatureEnabled(bool featureEnabled) { return featureEnabled && IsRuntimeMasterContextEnabled(); } internal static bool IsDisabled(PlayerAvatar? player) { return (Object)(object)player != (Object)null && player.isDisabled; } internal static bool IsDebugNoVision(PlayerAvatar? player) { return (Object)(object)player != (Object)null && EnemyDirector.instance?.debugNoVision != null && EnemyDirector.instance.debugNoVision.Contains(player.steamID); } internal static bool IsHeadProxyActive(PlayerAvatar? player) { if ((Object)(object)player == (Object)null) { return false; } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead == (Object)null) { return player.deadSet || IsDisabled(player); } if (player.deadSet || IsDisabled(player)) { return true; } return playerDeathHead.triggered || (Object)(object)playerDeathHead.physGrabObject != (Object)null || ((Component)playerDeathHead).gameObject.activeInHierarchy; } internal static bool TryGetHeadCenter(PlayerAvatar? player, out Vector3 center) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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) center = default(Vector3); if ((Object)(object)player == (Object)null) { return false; } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead == (Object)null) { return false; } PhysGrabObject physGrabObject = playerDeathHead.physGrabObject; if ((Object)(object)physGrabObject != (Object)null) { center = physGrabObject.centerPoint; return true; } center = ((Component)playerDeathHead).transform.position; return true; } internal static bool TryGetHeadProxyTarget(PlayerAvatar? player, out Vector3 center) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) center = default(Vector3); return IsRuntimeEnabled() && IsHeadProxyActive(player) && TryGetHeadCenter(player, out center); } internal static bool TryGetHeadProxyVisionTarget(PlayerAvatar? player, out Vector3 point) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) point = default(Vector3); if (!IsRuntimeEnabled() || !IsHeadProxyActive(player) || (Object)(object)player?.playerDeathHead == (Object)null) { return false; } PlayerEyes playerEyes = player.playerDeathHead.playerEyes; if ((Object)(object)playerEyes != (Object)null) { point = ((Component)playerEyes).transform.position; return true; } return TryGetHeadCenter(player, out point); } internal static bool TryGetHeadProxyVisionTransform(PlayerAvatar? player, out Transform? transform) { transform = null; if (!IsRuntimeEnabled() || !IsHeadProxyActive(player) || (Object)(object)player?.playerDeathHead == (Object)null) { return false; } PlayerEyes playerEyes = player.playerDeathHead.playerEyes; if ((Object)(object)playerEyes != (Object)null) { transform = ((Component)playerEyes).transform; return (Object)(object)transform != (Object)null; } transform = ((Component)player.playerDeathHead).transform; return (Object)(object)transform != (Object)null; } internal static bool TryGetHeadProxyTransform(PlayerAvatar? player, out Transform? transform) { transform = null; if (!IsRuntimeEnabled() || !IsHeadProxyActive(player) || (Object)(object)player?.playerDeathHead == (Object)null) { return false; } transform = ((Component)player.playerDeathHead).transform; return (Object)(object)transform != (Object)null; } internal static bool TryGetHeadProxyPhysGrabObject(PlayerAvatar? player, out PhysGrabObject? physGrabObject) { physGrabObject = null; if (!IsRuntimeEnabled() || !IsHeadProxyActive(player) || (Object)(object)player?.playerDeathHead == (Object)null) { return false; } physGrabObject = player.playerDeathHead.physGrabObject; return (Object)(object)physGrabObject != (Object)null; } internal static bool TryGetPlayerFromDeathHeadCollider(Collider? other, out PlayerAvatar? player) { player = null; if ((Object)(object)other == (Object)null) { return false; } PlayerDeathHead componentInParent = ((Component)other).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { return false; } player = componentInParent.playerAvatar; return (Object)(object)player != (Object)null; } internal static bool TryResolvePlayerAvatarFromTransform(Transform? transform, out PlayerAvatar? player) { player = null; if ((Object)(object)transform == (Object)null) { return false; } PlayerAvatar componentInParent = ((Component)transform).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { player = componentInParent; return true; } PlayerController componentInParent2 = ((Component)transform).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { PlayerAvatar playerAvatarScript = componentInParent2.playerAvatarScript; if ((Object)(object)playerAvatarScript != (Object)null) { player = playerAvatarScript; return true; } } PlayerVisionTarget componentInParent3 = ((Component)transform).GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null && (Object)(object)componentInParent3.PlayerAvatar != (Object)null) { player = componentInParent3.PlayerAvatar; return true; } return false; } internal static Vector3 ResolveEffectivePlayerTargetPosition(PlayerAvatar? player) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return Vector3.zero; } if (!IsRuntimeEnabled()) { return ((Component)player).transform.position; } Vector3 center; return TryGetHeadProxyTarget(player, out center) ? center : ((Component)player).transform.position; } internal static Vector3 ResolveEffectiveTransformTargetPosition(Transform? transform) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_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_005e: 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_005b: 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_0052: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)transform == (Object)null) { return Vector3.zero; } if (!IsRuntimeEnabled()) { return transform.position; } TryResolvePlayerAvatarFromTransform(transform, out PlayerAvatar player); if ((Object)(object)player != (Object)null && TryGetHeadProxyTarget(player, out var center)) { return center; } return transform.position; } internal static bool IsLineOfSightToHead(Transform origin, Vector3 headCenter, LayerMask visionMask, PlayerAvatar player) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_0036: 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) Vector3 val = headCenter - origin.position; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude <= 0.001f) { return true; } RaycastHit[] array = Physics.RaycastAll(origin.position, ((Vector3)(ref val)).normalized, magnitude, LayerMask.op_Implicit(visionMask), (QueryTriggerInteraction)1); for (int i = 0; i < array.Length; i++) { Transform transform = ((RaycastHit)(ref array[i])).transform; if (!((Object)(object)transform == (Object)null) && !((Component)transform).CompareTag("Enemy")) { PlayerDeathHead componentInParent = ((Component)transform).GetComponentInParent(); if ((!((Object)(object)componentInParent != (Object)null) || !((Object)(object)componentInParent == (Object)(object)player.playerDeathHead)) && !((Object)(object)((Component)transform).GetComponentInParent() != (Object)null)) { return false; } } } return true; } internal static void EnsureVisionTriggered(EnemyVision vision, PlayerAvatar player, bool near) { if (!((Object)(object)vision == (Object)null) && !((Object)(object)player == (Object)null) && !((Object)(object)player.photonView == (Object)null)) { int viewID = player.photonView.ViewID; if (!vision.VisionTriggered.ContainsKey(viewID)) { vision.VisionTriggered[viewID] = false; } if (!vision.VisionsTriggered.ContainsKey(viewID)) { vision.VisionsTriggered[viewID] = 0; } vision.VisionTrigger(viewID, player, false, near); } } [IteratorStateMachine(typeof(d__22))] internal static IEnumerable EnumerateEnemies() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__22(-2); } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Guards { internal static class AllPlayersDeadGuard { [HarmonyPatch(typeof(RunManager), "ChangeLevel", new Type[] { typeof(bool), typeof(bool), typeof(ChangeLevelType) })] internal static class RunManagerChangeLevelPatch { [HarmonyPrefix] private static bool Prefix(RunManager __instance, bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) return ChangeLevelPrefix(__instance, _completedLevel, _levelFailed, _changeLevelType); } } [HarmonyPatch(typeof(RunManager), "Update")] internal static class RunManagerUpdateAllPlayersDeadPatch { [HarmonyPostfix] private static void Postfix(RunManager __instance) { UpdatePostfix(__instance); } } private const string ModuleTag = "[DHHFLastChanceMode] [Gameplay]"; private const string LogKey = "SuppressAllDeadTransition"; private const string UpdateGuardLogKey = "SuppressAllDeadFlag"; private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.Gameplay"); private static bool s_enabledLogged; private static bool s_suppressedLogged; private static bool s_allowAllPlayersDead; internal static void EnsureEnabled() { if (!s_enabledLogged) { s_enabledLogged = true; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("SuppressAllDeadTransition")) { Log.LogInfo((object)"[DHHFLastChanceMode] [Gameplay] Guard enabled via typed ChangeLevel prefix patch."); } } } internal static void Disable() { s_enabledLogged = false; s_suppressedLogged = false; s_allowAllPlayersDead = false; } internal static void AllowVanillaAllPlayersDead() { s_allowAllPlayersDead = true; } internal static void ResetVanillaAllPlayersDead() { s_allowAllPlayersDead = false; } private static bool ChangeLevelPrefix(RunManager __instance, bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 if (!ShouldSuppressAllPlayersDeadFlow()) { s_suppressedLogged = false; s_allowAllPlayersDead = false; return true; } if (s_allowAllPlayersDead) { s_suppressedLogged = false; s_allowAllPlayersDead = false; return true; } if (!_levelFailed || (int)_changeLevelType > 0) { return true; } if (!AllPlayersDisabled()) { s_suppressedLogged = false; return true; } if (FeatureFlags.DebugLogging && !s_suppressedLogged && LogLimiter.ShouldLog("SuppressAllDeadTransition", 600)) { Log.LogInfo((object)"[DHHFLastChanceMode] [Gameplay] Suppressed change level caused by all players dead."); s_suppressedLogged = true; } return false; } private static void UpdatePostfix(RunManager __instance) { if (!((Object)(object)__instance == (Object)null) && __instance.allPlayersDead && ShouldSuppressAllPlayersDeadFlow() && !s_allowAllPlayersDead) { __instance.allPlayersDead = false; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("SuppressAllDeadFlag", 120)) { Log.LogDebug((object)"[DHHFLastChanceMode] [Gameplay] Suppressed RunManager.allPlayersDead assignment during LastChance flow."); } } } private static bool ShouldSuppressAllPlayersDeadFlow() { if (!FeatureFlags.LastChangeMode) { return false; } if (!CompatibilityGate.IsFeatureUsable(ModFeatureGate.LastChanceCluster)) { return false; } if (LastChanceTimerController.IsSuppressedForRoom) { return false; } if (IsVanillaOnlyContext()) { return false; } return true; } private static bool IsVanillaOnlyContext() { return SemiFunc.RunIsArena() || SemiFunc.RunIsShop() || SemiFunc.RunIsLobbyMenu() || SemiFunc.RunIsLobby() || SemiFunc.RunIsTutorial() || SemiFunc.MenuLevel(); } internal static bool AllPlayersDisabled() { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return false; } if (instance.PlayerList.Count == 0) { return true; } foreach (PlayerAvatar player in instance.PlayerList) { if ((Object)(object)player == (Object)null || player.isDisabled) { continue; } return false; } return true; } } internal static class LastChanceSaveDeleteState { private static bool s_blockAutoDelete = true; internal static bool ShouldBlockAutoDelete() { if (!FeatureFlags.LastChangeMode || !LastChanceRuntimeOrchestrator.IsRuntimeActive) { return false; } if (!AllPlayersDeadGuard.AllPlayersDisabled()) { s_blockAutoDelete = true; return false; } return s_blockAutoDelete; } internal static void AllowAutoDelete() { s_blockAutoDelete = false; } internal static void ResetAutoDeleteBlock() { s_blockAutoDelete = true; } } [HarmonyPatch(typeof(StatsManager), "SaveFileDelete")] internal static class StatsManagerSaveFileDeleteLastChancePatch { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.SaveDelete"); [ThreadStatic] private static bool s_allowManualDelete; internal static void AllowManualDelete() { s_allowManualDelete = true; } [HarmonyPrefix] private static bool Prefix(string saveFileName) { if (!LastChanceSaveDeleteState.ShouldBlockAutoDelete()) { return true; } if (s_allowManualDelete) { return true; } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("LastChance.SaveDelete.Blocked", 120)) { Log.LogDebug((object)("[LastChance] Blocked auto delete '" + saveFileName + "'.")); } return false; } [HarmonyPostfix] private static void Postfix() { s_allowManualDelete = false; } } [HarmonyPatch(typeof(MenuPageSaves), "OnDeleteGame")] internal static class MenuPageSavesOnDeleteGameLastChancePatch { [HarmonyPrefix] private static void Prefix() { StatsManagerSaveFileDeleteLastChancePatch.AllowManualDelete(); } } } namespace DHHFLastChanceMode.Modules.Gameplay.LastChance.Diagnostics { internal static class LastChanceTruckDistanceLogger { private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.LastChance.TruckDistances"); private const string LogKey = "LastChance.TruckDistances"; internal static void LogDistances() { if (!FeatureFlags.DebugLogging || !LogLimiter.ShouldLog("LastChance.TruckDistances", 30)) { return; } PlayerTruckDistanceHelper.PlayerTruckDistance[] distancesFromTruck = PlayerTruckDistanceHelper.GetDistancesFromTruck(); if (distancesFromTruck.Length != 0) { StringBuilder stringBuilder = new StringBuilder("[LastChance] TruckDistances:"); PlayerTruckDistanceHelper.PlayerTruckDistance[] array = distancesFromTruck; for (int i = 0; i < array.Length; i++) { PlayerTruckDistanceHelper.PlayerTruckDistance playerTruckDistance = array[i]; string text = (((Object)(object)playerTruckDistance.PlayerAvatar != (Object)null) ? ((object)playerTruckDistance.PlayerAvatar).GetType().Name : "null"); string text2 = (playerTruckDistance.HasValidPath ? $"{playerTruckDistance.NavMeshDistance:F1}m" : "n/a"); string text3 = ((playerTruckDistance.HeightDelta >= 0f) ? $"+{playerTruckDistance.HeightDelta:F2}m" : $"{playerTruckDistance.HeightDelta:F2}m"); string text4 = ((playerTruckDistance.ShortestRoomPathToTruck >= 0) ? playerTruckDistance.ShortestRoomPathToTruck.ToString() : "n/a"); string text5 = ((playerTruckDistance.TotalMapRooms > 0) ? playerTruckDistance.TotalMapRooms.ToString() : "n/a"); stringBuilder.AppendFormat(" {0}=distance:{1},height:{2},shortestRoomPathToTruck:{3},totalMapRooms:{4}", text, text2, text3, text4, text5); } Log.LogDebug((object)stringBuilder.ToString()); } } } } namespace DHHFLastChanceMode.Modules.Gameplay.Core.Abilities { public static class AbilityModule { private sealed class DirectionIndicatorAbility : AbilityBase { public override string AbilityName => DirectionAbility_GetName(); public override float Cooldown => DirectionAbility_GetCooldown(); public override float EnergyCost => DirectionAbility_GetEnergyCost(); public override int AbilityLevel => DirectionAbility_GetLevel(); public override void OnAbilityDown() { DirectionAbility_OnDown(); } public override void OnAbilityHold() { DirectionAbility_OnHold(); } public override void OnAbilityUp() { DirectionAbility_OnUp(); } public override void OnAbilityCancel() { DirectionAbility_OnCancel(); } } private const int DirectionIndicatorSlotIndex = 1; private const string AbilityBarDemandSourceId = "DHHFLastChanceMode.Direction"; private const float AbilitySpotCacheRefreshSeconds = 0.5f; private static Sprite? s_directionSprite; private static float s_nextDirectionIconFallbackApplyAt; private static DirectionIndicatorAbility? s_directionAbility; internal static void RefreshDirectionSlotVisuals() { EnsureDirectionAbilitySlotState(); PushDirectionAbilityBarDemand(); AbilityModule.RefreshDirectionSlotVisuals(); TryApplyDirectionIconFallback(); } internal static void SetDirectionSlotActivationProgress(float progress01) { AbilityModule.SetDirectionSlotActivationProgress(progress01); } internal static void TriggerDirectionSlotCooldown(float cooldownSeconds) { AbilityModule.TriggerDirectionSlotCooldown(cooldownSeconds); } private static void PushDirectionAbilityBarDemand() { bool isDirectionIndicatorUiVisible = LastChanceTimerController.IsDirectionIndicatorUiVisible; try { AbilityBarVisibilityAnchor.SetExternalDemand("DHHFLastChanceMode.Direction", isDirectionIndicatorUiVisible); } catch { } } private static void TryApplyDirectionIconFallback() { if (Time.unscaledTime < s_nextDirectionIconFallbackApplyAt) { return; } s_nextDirectionIconFallbackApplyAt = Time.unscaledTime + 0.5f; if (((Object)(object)s_directionSprite == (Object)null && !ImageAssetLoader.TryLoadSprite("Direction.png", ImageAssetLoader.GetDefaultAssetsDirectory(), out s_directionSprite, out string _)) || (Object)(object)s_directionSprite == (Object)null) { return; } AbilitySpot[] abilitySpotsCached = GetAbilitySpotsCached(); if (abilitySpotsCached == null || abilitySpotsCached.Length == 0) { return; } foreach (AbilitySpot val in abilitySpotsCached) { if (!((Object)(object)val == (Object)null) && val.abilitySpotIndex == 1) { try { ApplyDirectionIcon(val, s_directionSprite); } catch { } } } } private static void EnsureDirectionAbilitySlotState() { bool isDirectionIndicatorUiVisible = LastChanceTimerController.IsDirectionIndicatorUiVisible; AbilitySpot[] abilitySpotsCached = GetAbilitySpotsCached(); if (abilitySpotsCached == null || abilitySpotsCached.Length == 0) { return; } AbilitySpot val = null; foreach (AbilitySpot val2 in abilitySpotsCached) { if (!((Object)(object)val2 == (Object)null) && GetSpotIndex(val2) == 1) { val = val2; break; } } if ((Object)(object)val == (Object)null) { return; } if (!isDirectionIndicatorUiVisible) { if ((Object)(object)s_directionAbility != (Object)null && val.CurrentAbility == s_directionAbility) { val.RemoveAbility(); } return; } if (s_directionAbility == null) { s_directionAbility = CreateDirectionAbilityInstance(); } if ((Object)(object)s_directionAbility == (Object)null) { return; } if ((Object)(object)s_directionSprite != (Object)null) { ((AbilityBase)s_directionAbility).icon = s_directionSprite; } if ((Object)(object)val.CurrentAbility == (Object)null) { val.EquipAbility((AbilityBase)(object)s_directionAbility); } else { if (val.CurrentAbility == s_directionAbility) { return; } bool flag = false; foreach (AbilitySpot val3 in abilitySpotsCached) { if (!((Object)(object)val3 == (Object)null) && val3.CurrentAbility == s_directionAbility) { flag = true; break; } } if (flag && TryRemoveDirectionAbilityFromSpots(abilitySpotsCached)) { val.EquipAbility((AbilityBase)(object)s_directionAbility); } } } private static bool TryRemoveDirectionAbilityFromSpots(AbilitySpot[] spots) { bool result = false; foreach (AbilitySpot val in spots) { if (!((Object)(object)val == (Object)null) && val.CurrentAbility == s_directionAbility) { try { val.RemoveAbility(); result = true; } catch { } } } return result; } private static DirectionIndicatorAbility CreateDirectionAbilityInstance() { DirectionIndicatorAbility directionIndicatorAbility = ScriptableObject.CreateInstance(); if ((Object)(object)s_directionSprite != (Object)null) { ((AbilityBase)directionIndicatorAbility).icon = s_directionSprite; } return directionIndicatorAbility; } private static AbilitySpot[] GetAbilitySpotsCached() { return AbilitySpotDiscoveryCache.GetCached(0.5f); } public static string DirectionAbility_GetName() { return "DirectionAbility"; } public static float DirectionAbility_GetCooldown() { return Mathf.Max(1f, FeatureFlags.LastChanceIndicatorDirectionCooldownSeconds); } public static float DirectionAbility_GetEnergyCost() { return Mathf.Max(0f, LastChanceTimerController.GetDirectionIndicatorPenaltySecondsPreview()); } public static int DirectionAbility_GetLevel() { return 1; } public static void DirectionAbility_OnDown() { LastChanceTimerController.OnDirectionAbilityInputDown(); } public static void DirectionAbility_OnHold() { LastChanceTimerController.OnDirectionAbilityInputHold(); } public static void DirectionAbility_OnUp() { LastChanceTimerController.OnDirectionAbilityInputUp(); } public static void DirectionAbility_OnCancel() { LastChanceTimerController.OnDirectionAbilityInputCancel(); } private static int GetSpotIndex(AbilitySpot spot) { return ((Object)(object)spot == (Object)null) ? (-1) : spot.abilitySpotIndex; } private static void ApplyDirectionIcon(AbilitySpot spot, Sprite sprite) { if (!((Object)(object)spot == (Object)null) && !((Object)(object)sprite == (Object)null)) { ((Behaviour)spot.backgroundIcon).enabled = true; spot.backgroundIcon.sprite = sprite; ((Behaviour)spot.cooldownIcon).enabled = true; spot.cooldownIcon.sprite = sprite; ((Behaviour)spot.noAbility).enabled = false; } } } internal static class AbilitySpotDiscoveryCache { internal static AbilitySpot[] GetCached(float refreshSeconds) { return LastChanceRuntimeObjectRegistry.GetAbilitySpotsSnapshot(); } internal static void Invalidate() { LastChanceRuntimeObjectRegistry.ClearAbilitySpots(); } } [HarmonyPatch(typeof(AbilitySpot), "Awake")] internal static class AbilitySpotAwakeRegistryPatch { [HarmonyPostfix] private static void Postfix(AbilitySpot __instance) { LastChanceRuntimeObjectRegistry.RegisterAbilitySpot(__instance); } } } namespace DHHFLastChanceMode.Modules.Config { internal enum ModFeatureGate { LastChanceCluster = 1 } internal sealed class CompatibilityGate : MonoBehaviourPunCallbacks, IOnEventCallback { private const string TracePrefix = "[LastChance][CompatGate][Trace]"; private const string LastChanceModeKey = "LastChangeMode"; private const string UnknownVersion = "unknown"; private const string ClientFixPresenceMessageType = "ClientFixPresence"; private const string HostFixPresenceRequestMessageType = "HostFixPresenceRequest"; private const string HostGateStateMessageType = "HostGateState"; private static readonly ManualLogSource Log = Logger.CreateLogSource("DHHFLastChanceMode.CompatibilityGate"); private static CompatibilityGate? s_instance; private static bool s_hostApprovedLastChanceCluster = true; private static bool s_receivedHostDecision; private static bool s_lastAppliedRuntimeDisable; private static string s_lastHostDecisionReason = string.Empty; private static string s_lastLoggedIncompatibilityReason = string.Empty; private readonly Dictionary _playersWithFixVersion = new Dictionary(); private readonly Dictionary _pendingPresenceSince = new Dictionary(); private readonly Dictionary _pendingPresenceNextRetryAt = new Dictionary(); private bool _postStartRetryArmed; private bool _postStartRetryConsumed; private bool _deferMissingPresenceUntilPostStartRetry; internal static event Action? HostApprovalChanged; internal static void EnsureCreated() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown if ((Object)(object)s_instance != (Object)null) { LogDebug("[LastChance][CompatGate][Trace] EnsureCreated skipped (already created)."); return; } GameObject val = new GameObject("DHHFLastChanceMode.CompatibilityGate"); Object.DontDestroyOnLoad((Object)(object)val); s_instance = val.AddComponent(); LogDebug("[LastChance][CompatGate][Trace] EnsureCreated created GameObject and component."); } internal static void ForceResolvePendingPresenceForStart() { s_instance?.ResolvePendingPresenceForStart(); } internal static void NotifyRuntimeSceneLoaded() { s_instance?.HandleRuntimeSceneLoaded(); } internal static bool IsFeatureUsable(ModFeatureGate feature) { if (feature != ModFeatureGate.LastChanceCluster) { return true; } if (!PhotonNetwork.InRoom || !SemiFunc.IsMultiplayer()) { return true; } if (PhotonNetwork.IsMasterClient) { return s_hostApprovedLastChanceCluster; } return s_receivedHostDecision && s_hostApprovedLastChanceCluster; } public override void OnEnable() { ((MonoBehaviourPunCallbacks)this).OnEnable(); LogDebug("[LastChance][CompatGate][Trace] OnEnable begin."); PhotonNetwork.AddCallbackTarget((object)this); LogDebug("[LastChance][CompatGate][Trace] OnEnable added Photon callback target."); } public override void OnDisable() { ((MonoBehaviourPunCallbacks)this).OnDisable(); LogDebug(string.Format("{0} OnDisable begin. inRoom={1} isMaster={2}", "[LastChance][CompatGate][Trace]", PhotonNetwork.InRoom, PhotonNetwork.IsMasterClient)); PhotonNetwork.RemoveCallbackTarget((object)this); LogDebug("[LastChance][CompatGate][Trace] OnDisable removed Photon callback target."); } private void OnDestroy() { LogDebug("[LastChance][CompatGate][Trace] OnDestroy fired."); if (s_instance == this) { s_instance = null; LogDebug("[LastChance][CompatGate][Trace] OnDestroy cleared static instance reference."); } } public override void OnJoinedRoom() { LogDebug("[LastChance][CompatGate][Trace] OnJoinedRoom begin."); LogJoinLeaveDebug("OnJoinedRoom"); _playersWithFixVersion.Clear(); _pendingPresenceSince.Clear(); _pendingPresenceNextRetryAt.Clear(); _postStartRetryArmed = false; _postStartRetryConsumed = false; _deferMissingPresenceUntilPostStartRetry = false; RegisterLocalPlayerFixVersion(); s_receivedHostDecision = PhotonNetwork.IsMasterClient; s_hostApprovedLastChanceCluster = PhotonNetwork.IsMasterClient; s_lastHostDecisionReason = string.Empty; s_lastLoggedIncompatibilityReason = string.Empty; AnnounceLocalFixPresence(); if (PhotonNetwork.IsMasterClient) { LogDebug("[LastChance][CompatGate][Trace] OnJoinedRoom host detected; deferred compatibility check."); LogJoinLeaveDebug("OnJoinedRoom host creation phase: compatibility check deferred"); } else { LogDebug("[LastChance][CompatGate][Trace] OnJoinedRoom client: presence announced, waiting host decision."); } } public override void OnLeftRoom() { LogJoinLeaveDebug("OnLeftRoom"); _playersWithFixVersion.Clear(); _pendingPresenceSince.Clear(); _pendingPresenceNextRetryAt.Clear(); _postStartRetryArmed = false; _postStartRetryConsumed = false; _deferMissingPresenceUntilPostStartRetry = false; s_receivedHostDecision = false; s_hostApprovedLastChanceCluster = true; s_lastHostDecisionReason = string.Empty; s_lastLoggedIncompatibilityReason = string.Empty; LastChanceRuntimeObjectRegistry.ResetForRoomExit(); ApplyRuntimeHostOverrides(); CompatibilityGate.HostApprovalChanged?.Invoke(); } public override void OnPlayerEnteredRoom(Player newPlayer) { LogInfo("[LastChance][CompatGate] Lobby check: player entered " + ((newPlayer == null) ? "null" : FormatPlayerTag(newPlayer)) + "."); if (PhotonNetwork.IsMasterClient) { if (newPlayer != null) { _playersWithFixVersion.Remove(newPlayer.ActorNumber); _pendingPresenceSince[newPlayer.ActorNumber] = Time.realtimeSinceStartup; _pendingPresenceNextRetryAt[newPlayer.ActorNumber] = 0f; RequestPresenceFromActor(newPlayer.ActorNumber, "OnPlayerEnteredRoom"); } EvaluateHostApprovalAndBroadcast(forceBroadcast: true); } } public override void OnPlayerLeftRoom(Player otherPlayer) { LogJoinLeaveDebug((otherPlayer == null) ? "OnPlayerLeftRoom player=null" : ("OnPlayerLeftRoom player=" + FormatPlayerTag(otherPlayer))); if (otherPlayer != null) { _playersWithFixVersion.Remove(otherPlayer.ActorNumber); _pendingPresenceSince.Remove(otherPlayer.ActorNumber); _pendingPresenceNextRetryAt.Remove(otherPlayer.ActorNumber); } if (PhotonNetwork.IsMasterClient) { EvaluateHostApprovalAndBroadcast(forceBroadcast: true); } } public override void OnMasterClientSwitched(Player newMasterClient) { _playersWithFixVersion.Clear(); _pendingPresenceSince.Clear(); _pendingPresenceNextRetryAt.Clear(); _postStartRetryArmed = false; _postStartRetryConsumed = false; _deferMissingPresenceUntilPostStartRetry = false; RegisterLocalPlayerFixVersion(); s_receivedHostDecision = PhotonNetwork.IsMasterClient; s_hostApprovedLastChanceCluster = PhotonNetwork.IsMasterClient; s_lastHostDecisionReason = string.Empty; s_lastLoggedIncompatibilityReason = string.Empty; AnnounceLocalFixPresence(); if (PhotonNetwork.IsMasterClient) { EvaluateHostApprovalAndBroadcast(forceBroadcast: true); } } public void OnEvent(EventData photonEvent) { if (photonEvent == null) { return; } if (photonEvent.Code == 84) { if (PhotonNetwork.IsMasterClient && NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope) && envelope.IsExpectedSource() && string.Equals(envelope.MessageType, "ClientFixPresence", StringComparison.Ordinal) && TryParseClientPresencePayload(envelope.Payload, out int actorNumber, out string version) && actorNumber > 0 && photonEvent.Sender > 0 && photonEvent.Sender == actorNumber) { if (_playersWithFixVersion.TryGetValue(actorNumber, out string value) && string.Equals(value, version, StringComparison.Ordinal)) { _pendingPresenceSince.Remove(actorNumber); _pendingPresenceNextRetryAt.Remove(actorNumber); return; } _playersWithFixVersion[actorNumber] = version; _pendingPresenceSince.Remove(actorNumber); _pendingPresenceNextRetryAt.Remove(actorNumber); LogDebug(string.Format("{0} ClientFixPresence received actor={1} version={2}.", "[LastChance][CompatGate][Trace]", actorNumber, version)); EvaluateHostApprovalAndBroadcast(forceBroadcast: false); } } else if (photonEvent.Code == 87) { if (PhotonNetwork.IsMasterClient) { return; } Player localPlayer = PhotonNetwork.LocalPlayer; int num = ((localPlayer != null) ? localPlayer.ActorNumber : 0); if (num <= 0) { return; } Player masterClient = PhotonNetwork.MasterClient; int num2 = ((masterClient != null) ? masterClient.ActorNumber : (-1)); if (num2 > 0 && photonEvent.Sender == num2) { if (!NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope2) || !envelope2.IsExpectedSource() || !string.Equals(envelope2.MessageType, "HostFixPresenceRequest", StringComparison.Ordinal) || !(envelope2.Payload is int num3) || num3 != num) { LogDebug(string.Format("{0} HostFixPresenceRequest ignored localActor={1} targetActor={2}.", "[LastChance][CompatGate][Trace]", num, (photonEvent.CustomData is int num4) ? num4 : (-1))); return; } LogDebug(string.Format("{0} HostFixPresenceRequest accepted localActor={1}, sending presence.", "[LastChance][CompatGate][Trace]", num)); AnnounceLocalFixPresence(); } } else { if (photonEvent.Code != 85) { return; } Player masterClient2 = PhotonNetwork.MasterClient; int num5 = ((masterClient2 != null) ? masterClient2.ActorNumber : (-1)); if (num5 <= 0 || photonEvent.Sender != num5 || !NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope3) || !envelope3.IsExpectedSource() || !string.Equals(envelope3.MessageType, "HostGateState", StringComparison.Ordinal) || !(envelope3.Payload is object[] array) || array.Length < 1) { return; } object obj = array[0]; bool flag = default(bool); int num6; if (obj is bool) { flag = (bool)obj; num6 = 1; } else { num6 = 0; } if (num6 != 0) { string reason = ((array.Length >= 2 && array[1] is string text) ? text : string.Empty); bool flag2 = !s_receivedHostDecision || s_hostApprovedLastChanceCluster != flag; s_receivedHostDecision = true; s_hostApprovedLastChanceCluster = flag; s_lastHostDecisionReason = reason; if (!flag) { EmitIncompatibilityWarning(reason); } if (flag2) { CompatibilityGate.HostApprovalChanged?.Invoke(); } } } } private static void AnnounceLocalFixPresence() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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_0058: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.InRoom) { LogDebug("[LastChance][CompatGate][Trace] AnnounceLocalFixPresence skipped (not in room)."); return; } Player localPlayer = PhotonNetwork.LocalPlayer; int num = ((localPlayer != null) ? localPlayer.ActorNumber : 0); if (num <= 0) { LogDebug("[LastChance][CompatGate][Trace] AnnounceLocalFixPresence skipped (invalid actor)."); return; } RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)2 }; NetworkEnvelope networkEnvelope = new NetworkEnvelope("DHHFLastChanceMode", 1, "ClientFixPresence", 0, new object[2] { num, GetLocalFixVersion() }); PhotonNetwork.RaiseEvent((byte)84, (object)networkEnvelope.ToEventPayload(), val, SendOptions.SendReliable); LogDebug(string.Format("{0} AnnounceLocalFixPresence sent actor={1} version={2}.", "[LastChance][CompatGate][Trace]", num, GetLocalFixVersion())); } private void RequestPresenceFromActor(int actorNumber, string source) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && actorNumber > 0) { RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { actorNumber }; RaiseEventOptions val2 = val; PhotonNetwork.RaiseEvent((byte)87, (object)new NetworkEnvelope("DHHFLastChanceMode", 1, "HostFixPresenceRequest", 0, actorNumber).ToEventPayload(), val2, SendOptions.SendReliable); LogDebug(string.Format("{0} RequestPresenceFromActor sent actor={1} source={2}.", "[LastChance][CompatGate][Trace]", actorNumber, source)); } } private void ProcessPendingPresenceCycle() { if (!PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom || _pendingPresenceSince.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; bool flag = false; List list = new List(_pendingPresenceSince.Keys); for (int i = 0; i < list.Count; i++) { int num = list[i]; if (_playersWithFixVersion.ContainsKey(num)) { _pendingPresenceSince.Remove(num); _pendingPresenceNextRetryAt.Remove(num); continue; } if ((!_pendingPresenceNextRetryAt.TryGetValue(num, out var value) || realtimeSinceStartup >= value) && !float.IsPositiveInfinity(value)) { RequestPresenceFromActor(num, "UpdateRetry"); _pendingPresenceNextRetryAt[num] = realtimeSinceStartup + InternalConfig.CompatibilityGatePresenceRetrySeconds; } if (!_pendingPresenceSince.TryGetValue(num, out var value2)) { continue; } float num2 = realtimeSinceStartup - value2; if (num2 >= InternalConfig.CompatibilityGatePresenceTimeoutSeconds) { _pendingPresenceSince.Remove(num); _pendingPresenceNextRetryAt.Remove(num); flag = true; if (_deferMissingPresenceUntilPostStartRetry && _postStartRetryConsumed) { _deferMissingPresenceUntilPostStartRetry = false; } LogDebug(string.Format("{0} Pending presence timeout reached actor={1} elapsed={2:0.000}s. Marking as missing and stopping retries.", "[LastChance][CompatGate][Trace]", num, num2)); } } if (flag) { LogDebug("[LastChance][CompatGate][Trace] Update forcing compatibility recheck due to pending timeout."); EvaluateHostApprovalAndBroadcast(forceBroadcast: true); } } private void Update() { if (InternalConfig.CompatibilityGatePresencePollingEnabled) { ProcessPendingPresenceCycle(); } } private void EvaluateHostApprovalAndBroadcast(bool forceBroadcast) { if (!PhotonNetwork.InRoom || !SemiFunc.IsMultiplayer()) { s_hostApprovedLastChanceCluster = true; s_receivedHostDecision = PhotonNetwork.IsMasterClient; ApplyRuntimeHostOverrides(); CompatibilityGate.HostApprovalChanged?.Invoke(); return; } if (!PhotonNetwork.IsMasterClient) { ApplyRuntimeHostOverrides(); CompatibilityGate.HostApprovalChanged?.Invoke(); return; } RegisterLocalPlayerFixVersion(); string localFixVersion = GetLocalFixVersion(); List list = new List(); List list2 = new List(); List list3 = new List(); bool flag = true; Player[] playerList = PhotonNetwork.PlayerList; float realtimeSinceStartup = Time.realtimeSinceStartup; foreach (Player val in playerList) { if (val == null) { continue; } if (!_playersWithFixVersion.TryGetValue(val.ActorNumber, out string value)) { string text = FormatPlayerTag(val); if (_pendingPresenceSince.TryGetValue(val.ActorNumber, out var value2)) { float num = realtimeSinceStartup - value2; if (num < InternalConfig.CompatibilityGatePresenceTimeoutSeconds) { list2.Add($"{text} ({num:0.0}s/{InternalConfig.CompatibilityGatePresenceTimeoutSeconds:0.0}s)"); flag = false; continue; } } flag = false; list.Add(text); } else if (!string.Equals(value, localFixVersion, StringComparison.Ordinal)) { flag = false; list3.Add(FormatPlayerTag(val) + "=" + value); } } string text2 = BuildIncompatibilityReason(localFixVersion, list, list3, list2); bool flag2 = list.Count > 0 || list2.Count > 0; bool flag3 = list3.Count > 0; if (_deferMissingPresenceUntilPostStartRetry) { if (_postStartRetryConsumed && list.Count > 0) { _deferMissingPresenceUntilPostStartRetry = false; } else if (!flag2) { _deferMissingPresenceUntilPostStartRetry = false; } if (_deferMissingPresenceUntilPostStartRetry) { flag = !flag3; text2 = BuildDeferredReason(text2, _postStartRetryConsumed); } } bool flag4 = s_hostApprovedLastChanceCluster != flag || !s_receivedHostDecision || !string.Equals(s_lastHostDecisionReason, text2, StringComparison.Ordinal); s_hostApprovedLastChanceCluster = flag; s_receivedHostDecision = true; s_lastHostDecisionReason = text2; ApplyRuntimeHostOverrides(); if (flag4) { LogInfo("[LastChance][CompatGate] Lobby check result: " + (flag ? "ENABLED" : "DISABLED") + " " + FormatReasonSuffix(text2)); } if (flag4 || forceBroadcast) { BroadcastHostApproval(); CompatibilityGate.HostApprovalChanged?.Invoke(); } } private void ResolvePendingPresenceForStart() { if (PhotonNetwork.IsMasterClient && PhotonNetwork.InRoom && _pendingPresenceSince.Count != 0) { _postStartRetryArmed = true; _postStartRetryConsumed = false; _deferMissingPresenceUntilPostStartRetry = true; LogDebug("[LastChance][CompatGate][Trace] Start pressed with unresolved presence. Arming one-shot post-start retry."); EvaluateHostApprovalAndBroadcast(forceBroadcast: true); } } private void HandleRuntimeSceneLoaded() { if (!PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom || !_postStartRetryArmed) { return; } _postStartRetryArmed = false; _postStartRetryConsumed = true; float realtimeSinceStartup = Time.realtimeSinceStartup; Player[] playerList = PhotonNetwork.PlayerList; foreach (Player obj in playerList) { int num = ((obj != null) ? obj.ActorNumber : 0); if (num > 0 && !_playersWithFixVersion.ContainsKey(num)) { _pendingPresenceSince[num] = realtimeSinceStartup; _pendingPresenceNextRetryAt[num] = float.PositiveInfinity; RequestPresenceFromActor(num, "PostStartRuntimeRetry"); } } LogDebug("[LastChance][CompatGate][Trace] Runtime scene loaded. One-shot post-start presence retry executed."); EvaluateHostApprovalAndBroadcast(forceBroadcast: true); } private static void BroadcastHostApproval() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient) { RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; NetworkEnvelope networkEnvelope = new NetworkEnvelope("DHHFLastChanceMode", 1, "HostGateState", 0, new object[2] { s_hostApprovedLastChanceCluster, s_lastHostDecisionReason }); PhotonNetwork.RaiseEvent((byte)85, (object)networkEnvelope.ToEventPayload(), val, SendOptions.SendReliable); } } private static void ApplyRuntimeHostOverrides() { if (!PhotonNetwork.IsMasterClient) { return; } bool flag = !s_hostApprovedLastChanceCluster; if (s_lastAppliedRuntimeDisable != flag) { s_lastAppliedRuntimeDisable = flag; if (flag) { ConfigManager.SetHostRuntimeOverride("LastChangeMode", bool.FalseString); EmitIncompatibilityWarning(s_lastHostDecisionReason); } else { ConfigManager.ClearHostRuntimeOverride("LastChangeMode"); s_lastLoggedIncompatibilityReason = string.Empty; } } } private static void EmitIncompatibilityWarning(string reason) { string text = (string.IsNullOrWhiteSpace(reason) ? "Unknown incompatibility reason." : reason.Trim()); if (!string.Equals(s_lastLoggedIncompatibilityReason, text, StringComparison.Ordinal)) { s_lastLoggedIncompatibilityReason = text; Log.LogWarning((object)("[LastChance] LastChange disabled due to incompatibility: " + text)); } } private static bool TryParseClientPresencePayload(object? customData, out int actorNumber, out string version) { actorNumber = 0; version = "unknown"; if (customData is int num) { actorNumber = num; return true; } if (customData is object[] array && array.Length >= 2 && array[0] is int num2 && array[1] is string value) { actorNumber = num2; version = NormalizeVersion(value); return true; } return false; } private void RegisterLocalPlayerFixVersion() { Player localPlayer = PhotonNetwork.LocalPlayer; int num = ((localPlayer != null) ? localPlayer.ActorNumber : 0); if (num > 0) { _playersWithFixVersion[num] = GetLocalFixVersion(); } } private static string GetLocalFixVersion() { return NormalizeVersion("0.1.4"); } private static string NormalizeVersion(string? value) { if (string.IsNullOrWhiteSpace(value)) { return "unknown"; } return value.Trim(); } private static string BuildIncompatibilityReason(string localVersion, List missingPlayers, List mismatchPlayers, List pendingPlayers) { List list = new List(); if (pendingPlayers.Count > 0) { list.Add("awaiting fix presence from: " + string.Join(", ", pendingPlayers)); } if (missingPlayers.Count > 0) { list.Add("missing fix presence from: " + string.Join(", ", missingPlayers)); } if (mismatchPlayers.Count > 0) { list.Add("version mismatch (host=" + localVersion + "): " + string.Join(", ", mismatchPlayers)); } return (list.Count == 0) ? string.Empty : string.Join(" | ", list); } private static string BuildDeferredReason(string reason, bool consumed) { string text = (consumed ? "post-start retry pending confirmation" : "post-start retry armed"); if (string.IsNullOrWhiteSpace(reason)) { return text; } return text + " | " + reason; } private static string FormatPlayerTag(Player player) { string arg = (string.IsNullOrWhiteSpace(player.NickName) ? "unknown" : player.NickName.Trim()); return $"{arg}#{player.ActorNumber}"; } private static void LogJoinLeaveDebug(string message) { if (FeatureFlags.DebugLogging) { bool inRoom = PhotonNetwork.InRoom; bool isMasterClient = PhotonNetwork.IsMasterClient; Player localPlayer = PhotonNetwork.LocalPlayer; int num = ((localPlayer != null) ? localPlayer.ActorNumber : 0); LogDebug($"[LastChance][CompatGate] {message} | inRoom={inRoom}, isMaster={isMasterClient}, localActor={num}"); } } private static string FormatReasonSuffix(string reason) { if (string.IsNullOrWhiteSpace(reason)) { return string.Empty; } return "| reason: " + reason; } private static void LogInfo(string message) { Log.LogInfo((object)message); } private static void LogDebug(string message) { if (FeatureFlags.DebugLogging) { Log.LogInfo((object)message); } } } [HarmonyPatch(typeof(MenuPageLobby), "ButtonStart")] internal static class CompatibilityGateStartHookPatch { private static void Prefix() { CompatibilityGate.ForceResolvePendingPresenceForStart(); } } [AttributeUsage(AttributeTargets.Field)] internal sealed class FeatureConfigEntryAttribute : Attribute { public string Section { get; } public string Description { get; } public string Key { get; set; } = string.Empty; public float Min { get; set; } = float.NaN; public float Max { get; set; } = float.NaN; public string[]? Options { get; set; } public bool HostControlled { get; set; } = true; public FeatureConfigEntryAttribute(string section, string description) { Section = section; Description = description; } } [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] internal sealed class FeatureConfigAliasAttribute : Attribute { public string OldSection { get; } public string OldKey { get; } public FeatureConfigAliasAttribute(string oldSection, string oldKey) { OldSection = oldSection ?? string.Empty; OldKey = oldKey ?? string.Empty; } } internal static class ConfigManager { private struct RangeF { public float Min; public float Max; } private struct RangeI { public int Min; public int Max; } private sealed class HostControlledAccessor { public Type ValueType { get; } public Func Getter { get; } public Action Setter { get; } public HostControlledAccessor(Type valueType, Func getter, Action setter) { ValueType = valueType; Getter = getter; Setter = setter; } } private static bool s_initialized; private static readonly char[] ColorSeparators = new char[2] { ',', ';' }; private static readonly Dictionary s_floatRanges = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary s_intRanges = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary> s_stringOptions = new Dictionary>(StringComparer.Ordinal); private static readonly Dictionary s_stringDefaults = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary s_hostControlledAccessors = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary s_hostControlledEntries = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary s_hostRuntimeOverrides = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary s_localHostControlledBaseline = new Dictionary(StringComparer.Ordinal); private static readonly HashSet s_suppressHostControlledEntryChange = new HashSet(StringComparer.Ordinal); private static int s_localBaselineRestoreDepth; internal static event Action? HostControlledChanged; internal static void Initialize(ConfigFile config) { if (!s_initialized && config != null) { s_initialized = true; BindConfigEntries(config); ConfigMigrationManager.Apply(config); CaptureLocalHostControlledBaseline(); } } private static void BindConfigEntries(ConfigFile config) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Expected O, but got Unknown //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Expected O, but got Unknown //IL_0406: Unknown result type (might be due to invalid IL or missing references) //IL_0410: Expected O, but got Unknown foreach (ConfigMetadata.EntryDefinition featureFlagEntry in ConfigMetadata.FeatureFlagEntries) { ConfigMetadata.BoolEntryDefinition boolDefinition = featureFlagEntry as ConfigMetadata.BoolEntryDefinition; if (boolDefinition != null) { ConfigEntry entry = config.Bind(boolDefinition.Section, boolDefinition.Key, (bool)boolDefinition.GetValue(), new ConfigDescription(boolDefinition.Description, (AcceptableValueBase)(object)new AcceptableValueList(new bool[2] { false, true }), Array.Empty())); RegisterHostControlledAccessor(boolDefinition); RegisterHostControlledEntry(boolDefinition, (ConfigEntryBase)(object)entry); ApplyAndWatch(entry, BuildRangeKey(boolDefinition.Section, boolDefinition.Key), delegate(bool value) { boolDefinition.Setter(value); }, boolDefinition.HostControlled); continue; } ConfigMetadata.IntEntryDefinition intDefinition = featureFlagEntry as ConfigMetadata.IntEntryDefinition; if (intDefinition != null) { ConfigEntry entry2 = config.Bind(intDefinition.Section, intDefinition.Key, (int)intDefinition.GetValue(), new ConfigDescription(intDefinition.Description, (AcceptableValueBase)(object)new AcceptableValueRange(intDefinition.Min, intDefinition.Max), Array.Empty())); RegisterIntRange(BuildRangeKey(intDefinition.Section, intDefinition.Key), intDefinition.Min, intDefinition.Max); RegisterHostControlledAccessor(intDefinition); RegisterHostControlledEntry(intDefinition, (ConfigEntryBase)(object)entry2); ApplyAndWatch(entry2, BuildRangeKey(intDefinition.Section, intDefinition.Key), delegate(int value) { intDefinition.Setter(value); }, intDefinition.HostControlled); continue; } ConfigMetadata.FloatEntryDefinition floatDefinition = featureFlagEntry as ConfigMetadata.FloatEntryDefinition; if (floatDefinition != null) { ConfigEntry entry3 = config.Bind(floatDefinition.Section, floatDefinition.Key, (float)floatDefinition.GetValue(), new ConfigDescription(floatDefinition.Description, (AcceptableValueBase)(object)new AcceptableValueRange(floatDefinition.Min, floatDefinition.Max), Array.Empty())); RegisterFloatRange(BuildRangeKey(floatDefinition.Section, floatDefinition.Key), floatDefinition.Min, floatDefinition.Max); RegisterHostControlledAccessor(floatDefinition); RegisterHostControlledEntry(floatDefinition, (ConfigEntryBase)(object)entry3); ApplyAndWatch(entry3, BuildRangeKey(floatDefinition.Section, floatDefinition.Key), delegate(float value) { floatDefinition.Setter(value); }, floatDefinition.HostControlled); continue; } ConfigMetadata.StringEntryDefinition stringDefinition = featureFlagEntry as ConfigMetadata.StringEntryDefinition; if (stringDefinition != null) { string text = (string)stringDefinition.GetValue(); string text2 = BuildRangeKey(stringDefinition.Section, stringDefinition.Key); RegisterStringOptions(text2, (stringDefinition.Options.Count > 0) ? new List(stringDefinition.Options).ToArray() : null, text); ConfigEntry entry4 = ((stringDefinition.Options.Count > 0) ? config.Bind(stringDefinition.Section, stringDefinition.Key, text, new ConfigDescription(stringDefinition.Description, (AcceptableValueBase)(object)new AcceptableValueList(new List(stringDefinition.Options).ToArray()), Array.Empty())) : config.Bind(stringDefinition.Section, stringDefinition.Key, text, stringDefinition.Description)); RegisterHostControlledAccessor(stringDefinition); RegisterHostControlledEntry(stringDefinition, (ConfigEntryBase)(object)entry4); ApplyAndWatch(entry4, text2, delegate(string value) { stringDefinition.Setter(value); }, stringDefinition.HostControlled); } } } private static void ApplyAndWatch(ConfigEntry entry, string rangeKey, Action setter, bool notifyHostControlled) { ConfigEntry entry2 = entry; Action setter2 = setter; string rangeKey2 = rangeKey; if (entry2 != null && setter2 != null) { Update(); entry2.SettingChanged += delegate { Update(); }; } void Update() { string key = ((ConfigEntryBase)entry2).Definition.Key; if (notifyHostControlled && !IsLocalBaselineRestoreInProgress() && ShouldRejectClientHostControlledWrite(key, out string authoritativeSerialized) && TryDeserialize(authoritativeSerialized, typeof(T), out object parsed) && parsed is T val) { if (!IsSuppressedHostControlledEntryChange(key)) { SetHostControlledEntryValue(key, val); } setter2(val); } else { setter2(SanitizeValue(entry2.Value, rangeKey2)); if (notifyHostControlled) { CaptureLocalHostControlledBaselineValue(key); ConfigManager.HostControlledChanged?.Invoke(); } } } } private static void ApplyAndWatch(ConfigEntry entry, Func parser, Action setter, bool notifyHostControlled) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) ConfigEntry entry2 = entry; Action setter2 = setter; Func parser2 = parser; if (entry2 == null || parser2 == null || setter2 == null) { return; } setter2(parser2(entry2.Value)); if (notifyHostControlled) { ConfigManager.HostControlledChanged?.Invoke(); } entry2.SettingChanged += delegate { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (notifyHostControlled && !IsLocalBaselineRestoreInProgress() && ShouldRejectClientHostControlledWrite(((ConfigEntryBase)entry2).Definition.Key, out string authoritativeSerialized)) { if (!IsSuppressedHostControlledEntryChange(((ConfigEntryBase)entry2).Definition.Key)) { SetHostControlledEntryValue(((ConfigEntryBase)entry2).Definition.Key, authoritativeSerialized); } setter2(parser2(authoritativeSerialized)); } else { setter2(parser2(entry2.Value)); if (notifyHostControlled) { CaptureLocalHostControlledBaselineValue(((ConfigEntryBase)entry2).Definition.Key); ConfigManager.HostControlledChanged?.Invoke(); } } }; } private static void RegisterHostControlledAccessor(ConfigMetadata.EntryDefinition definition) { if (definition.HostControlled) { s_hostControlledAccessors[definition.Key] = new HostControlledAccessor(definition.ValueType, definition.GetValue, definition.SetValue); } } private static void RegisterHostControlledEntry(ConfigMetadata.EntryDefinition definition, ConfigEntryBase entry) { if (definition.HostControlled && entry != null) { s_hostControlledEntries[definition.Key] = entry; } } internal static Dictionary SnapshotHostControlled() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair s_hostControlledAccessor in s_hostControlledAccessors) { if (s_hostRuntimeOverrides.TryGetValue(s_hostControlledAccessor.Key, out string value)) { dictionary[s_hostControlledAccessor.Key] = value; } else { dictionary[s_hostControlledAccessor.Key] = SerializeValue(s_hostControlledAccessor.Value.Getter(), s_hostControlledAccessor.Value.ValueType); } } return dictionary; } internal static Dictionary SnapshotHostControlledKeys(IEnumerable keys) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (keys == null) { return dictionary; } foreach (string key in keys) { if (!string.IsNullOrWhiteSpace(key) && s_hostControlledAccessors.TryGetValue(key, out HostControlledAccessor value)) { if (s_hostRuntimeOverrides.TryGetValue(key, out string value2)) { dictionary[key] = value2; } else { dictionary[key] = SerializeValue(value.Getter(), value.ValueType); } } } return dictionary; } internal static void SetHostRuntimeOverride(string key, string serializedValue) { if (string.IsNullOrWhiteSpace(key)) { return; } string key2 = key.Trim(); if (s_hostControlledAccessors.ContainsKey(key2)) { string text = serializedValue ?? string.Empty; if (!s_hostRuntimeOverrides.TryGetValue(key2, out string value) || !string.Equals(value, text, StringComparison.Ordinal)) { s_hostRuntimeOverrides[key2] = text; ConfigManager.HostControlledChanged?.Invoke(); } } } internal static void ClearHostRuntimeOverride(string key) { if (!string.IsNullOrWhiteSpace(key)) { string key2 = key.Trim(); if (s_hostRuntimeOverrides.Remove(key2)) { ConfigManager.HostControlledChanged?.Invoke(); } } } internal static void ApplyHostSnapshot(Dictionary snapshot) { if (snapshot == null) { return; } bool flag = false; List> list = new List>(snapshot); foreach (KeyValuePair item in list) { if (!s_hostControlledAccessors.TryGetValue(item.Key, out HostControlledAccessor value)) { continue; } object obj = DeserializeValue(item.Value, value.ValueType); if (obj != null) { object obj2 = value.Getter(); if (obj2 == null || !obj2.Equals(obj)) { flag = true; } value.Setter(obj); } SetHostControlledEntryValue(item.Key, item.Value); } if (flag) { ConfigManager.HostControlledChanged?.Invoke(); } } internal static void RestoreLocalHostControlledBaseline() { if (s_localHostControlledBaseline.Count == 0) { return; } s_localBaselineRestoreDepth++; try { ApplyHostSnapshot(s_localHostControlledBaseline); } finally { s_localBaselineRestoreDepth = Math.Max(0, s_localBaselineRestoreDepth - 1); } } private static string SerializeValue(object? value, Type fieldType) { //IL_00fd: 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) if (fieldType == typeof(bool)) { return ((bool)(value ?? ((object)false))).ToString(CultureInfo.InvariantCulture); } if (fieldType == typeof(int)) { return ((int)(value ?? ((object)0))).ToString(CultureInfo.InvariantCulture); } if (fieldType == typeof(float)) { return ((float)(value ?? ((object)0f))).ToString(CultureInfo.InvariantCulture); } if (fieldType == typeof(string)) { return (value as string) ?? string.Empty; } if (fieldType == typeof(Color)) { return ColorToString((Color)(value ?? ((object)Color.black))); } return value?.ToString() ?? string.Empty; } private static object? DeserializeValue(string value, Type fieldType) { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) bool result; if (fieldType == typeof(bool)) { return bool.TryParse(value, out result) && result; } int result2; if (fieldType == typeof(int)) { return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2) ? result2 : 0; } float result3; if (fieldType == typeof(float)) { return float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result3) ? result3 : 0f; } if (fieldType == typeof(string)) { return value ?? string.Empty; } if (fieldType == typeof(Color)) { return ColorFromString(value ?? string.Empty); } return null; } private static bool TryDeserialize(string value, Type targetType, out object? parsed) { parsed = DeserializeValue(value, targetType); if (parsed != null) { return true; } if (targetType == typeof(string)) { parsed = value ?? string.Empty; return true; } return false; } private static string ColorToString(Color input) { return string.Join(",", input.r.ToString(CultureInfo.InvariantCulture), input.g.ToString(CultureInfo.InvariantCulture), input.b.ToString(CultureInfo.InvariantCulture), input.a.ToString(CultureInfo.InvariantCulture)); } private static Color ColorFromString(string input) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(input)) { return Color.black; } string[] array = input.Split(ColorSeparators, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { return Color.black; } float slot = 0f; float slot2 = 0f; float slot3 = 0f; float slot4 = 1f; TryParseComponent(array, 0, ref slot); TryParseComponent(array, 1, ref slot2); TryParseComponent(array, 2, ref slot3); TryParseComponent(array, 3, ref slot4); return new Color(slot, slot2, slot3, slot4); } private static void TryParseComponent(string[] segments, int index, ref float slot) { if (index < segments.Length) { string s = segments[index].Trim(); if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { slot = result; } } } private static T SanitizeValue(T value, string key) { if (value is float) { object obj = value; float val = (float)((obj is float) ? obj : null); if (s_floatRanges.TryGetValue(key, out var value2)) { float num = Math.Min(value2.Max, Math.Max(value2.Min, val)); return (T)(object)num; } } if (value is int) { object obj2 = value; int val2 = (int)((obj2 is int) ? obj2 : null); if (s_intRanges.TryGetValue(key, out var value3)) { int num2 = Math.Min(value3.Max, Math.Max(value3.Min, val2)); return (T)(object)num2; } } if ((object)value is string item && s_stringOptions.TryGetValue(key, out HashSet value4) && s_stringDefaults.TryGetValue(key, out string value5) && value4.Count > 0 && !value4.Contains(item)) { return (T)(object)value5; } return value; } private static void RegisterStringOptions(string key, string[]? options, string defaultValue) { if (options == null || options.Length == 0) { s_stringOptions.Remove(key); s_stringDefaults.Remove(key); return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); string text = string.Empty; bool flag = false; foreach (string text2 in options) { if (!string.IsNullOrWhiteSpace(text2)) { if (!flag) { text = text2; flag = true; } hashSet.Add(text2); } } if (hashSet.Count == 0) { s_stringOptions.Remove(key); s_stringDefaults.Remove(key); } else { s_stringOptions[key] = hashSet; s_stringDefaults[key] = (hashSet.Contains(defaultValue) ? defaultValue : text); } } private static void RegisterFloatRange(string key, float min, float max) { s_floatRanges[key] = new RangeF { Min = min, Max = max }; } private static void RegisterIntRange(string key, int min, int max) { s_intRanges[key] = new RangeI { Min = min, Max = max }; } private static string BuildRangeKey(string section, string key) { return section + ":" + key; } private static void CaptureLocalHostControlledBaseline() { s_localHostControlledBaseline.Clear(); Dictionary dictionary = SnapshotHostControlled(); foreach (KeyValuePair item in dictionary) { s_localHostControlledBaseline[item.Key] = item.Value; } } private static void CaptureLocalHostControlledBaselineValue(string key) { if (!string.IsNullOrWhiteSpace(key) && s_hostControlledAccessors.TryGetValue(key, out HostControlledAccessor value)) { s_localHostControlledBaseline[key] = SerializeValue(value.Getter(), value.ValueType); } } private static bool ShouldRejectClientHostControlledWrite(string key, out string authoritativeSerialized) { authoritativeSerialized = string.Empty; if (string.IsNullOrWhiteSpace(key)) { return false; } if (!TryGetClientInRoomState(out var shouldRejectClientWrite) || !shouldRejectClientWrite) { return false; } if (!s_hostControlledAccessors.TryGetValue(key, out HostControlledAccessor value)) { return false; } authoritativeSerialized = SerializeValue(value.Getter(), value.ValueType); return true; } private static bool TryGetClientInRoomState(out bool shouldRejectClientWrite) { shouldRejectClientWrite = false; try { if (!SemiFunc.IsMultiplayer()) { return true; } shouldRejectClientWrite = !SemiFunc.IsMasterClientOrSingleplayer(); return true; } catch { return false; } } private static bool IsSuppressedHostControlledEntryChange(string key) { return !string.IsNullOrWhiteSpace(key) && s_suppressHostControlledEntryChange.Contains(key); } private static bool IsLocalBaselineRestoreInProgress() { return s_localBaselineRestoreDepth > 0; } private static void SetHostControlledEntryValue(string key, object value) { if (string.IsNullOrWhiteSpace(key) || !s_hostControlledEntries.TryGetValue(key, out ConfigEntryBase value2) || value2 == null) { return; } Type type = value?.GetType() ?? typeof(string); Type targetType = GetEntrySettingType(value2) ?? type; if (!TryConvertForEntry(value, targetType, out object converted)) { return; } s_suppressHostControlledEntryChange.Add(key); try { value2.BoxedValue = converted; } finally { s_suppressHostControlledEntryChange.Remove(key); } } private static Type? GetEntrySettingType(ConfigEntryBase entry) { return ((entry != null) ? entry.SettingType : null) ?? ((entry == null) ? null : entry.BoxedValue?.GetType()); } private static bool TryConvertForEntry(object? value, Type targetType, out object converted) { converted = value ?? string.Empty; if (value != null && targetType.IsInstanceOfType(value)) { return true; } string value2 = (value as string) ?? value?.ToString() ?? string.Empty; if (TryDeserialize(value2, targetType, out object parsed) && parsed != null) { converted = parsed; return true; } try { converted = Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture); return true; } catch { return false; } } } internal static class ConfigMetadata { internal sealed class AliasDefinition { public string Section { get; } public string Key { get; } public AliasDefinition(string section, string key) { Section = section ?? string.Empty; Key = key ?? string.Empty; } } internal abstract class EntryDefinition { public string Section { get; } public string Key { get; } public string Description { get; } public bool HostControlled { get; } public IReadOnlyList Aliases { get; } public abstract Type ValueType { get; } protected EntryDefinition(string section, string key, string description, bool hostControlled, IReadOnlyList? aliases = null) { Section = section ?? string.Empty; Key = key ?? string.Empty; Description = description ?? string.Empty; HostControlled = hostControlled; Aliases = aliases ?? Array.Empty(); } public abstract object? GetValue(); public abstract void SetValue(object? value); } internal sealed class BoolEntryDefinition : EntryDefinition { public Func Getter { get; } public Action Setter { get; } public override Type ValueType => typeof(bool); public BoolEntryDefinition(string section, string key, string description, Func getter, Action setter, bool hostControlled = true, IReadOnlyList? aliases = null) : base(section, key, description, hostControlled, aliases) { Getter = getter; Setter = setter; } public override object GetValue() { return Getter(); } public override void SetValue(object? value) { Setter((value as bool?).GetValueOrDefault()); } } internal sealed class IntEntryDefinition : EntryDefinition { public int Min { get; } public int Max { get; } public Func Getter { get; } public Action Setter { get; } public override Type ValueType => typeof(int); public IntEntryDefinition(string section, string key, string description, int min, int max, Func getter, Action setter, bool hostControlled = true, IReadOnlyList? aliases = null) : base(section, key, description, hostControlled, aliases) { Min = min; Max = max; Getter = getter; Setter = setter; } public override object GetValue() { return Getter(); } public override void SetValue(object? value) { Setter((value as int?).GetValueOrDefault()); } } internal sealed class FloatEntryDefinition : EntryDefinition { public float Min { get; } public float Max { get; } public Func Getter { get; } public Action Setter { get; } public override Type ValueType => typeof(float); public FloatEntryDefinition(string section, string key, string description, float min, float max, Func getter, Action setter, bool hostControlled = true, IReadOnlyList? aliases = null) : base(section, key, description, hostControlled, aliases) { Min = min; Max = max; Getter = getter; Setter = setter; } public override object GetValue() { return Getter(); } public override void SetValue(object? value) { Setter((value as float?).GetValueOrDefault()); } } internal sealed class StringEntryDefinition : EntryDefinition { public IReadOnlyList Options { get; } public Func Getter { get; } public Action Setter { get; } public override Type ValueType => typeof(string); public StringEntryDefinition(string section, string key, string description, string[]? options, Func getter, Action setter, bool hostControlled = true, IReadOnlyList? aliases = null) : base(section, key, description, hostControlled, aliases) { Options = options ?? Array.Empty(); Getter = getter; Setter = setter; } public override object GetValue() { return Getter() ?? string.Empty; } public override void SetValue(object? value) { Setter((value as string) ?? string.Empty); } } internal static readonly IReadOnlyList FeatureFlagEntries = new EntryDefinition[30] { new BoolEntryDefinition("1a. LastChance: Quick Setup", "LastChangeMode", "When true, prevent the vanilla run manager from switching to the dump level when all players die.", () => FeatureFlags.LastChangeMode, delegate(bool value) { FeatureFlags.LastChangeMode = value; }), new IntEntryDefinition("1a. LastChance: Quick Setup", "LastChanceTimerSeconds", "LastChance timer duration in seconds (integer, 30s steps).", 30, 600, () => FeatureFlags.LastChanceTimerSeconds, delegate(int value) { FeatureFlags.LastChanceTimerSeconds = value; }), new BoolEntryDefinition("1a. LastChance: Quick Setup", "LastChanceDynamicTimerEnabled", "Enable dynamic LastChance timer scaling from base timer and run context metrics.", () => FeatureFlags.LastChanceDynamicTimerEnabled, delegate(bool value) { FeatureFlags.LastChanceDynamicTimerEnabled = value; }), new IntEntryDefinition("1a. LastChance: Quick Setup", "LastChanceDynamicMaxMinutes", "Hard cap (minutes) for final LastChance timer after dynamic scaling.", 5, 20, () => FeatureFlags.LastChanceDynamicMaxMinutes, delegate(int value) { FeatureFlags.LastChanceDynamicMaxMinutes = value; }), new IntEntryDefinition("1a. LastChance: Quick Setup", "LastChanceDynamicMaxMinutesAtLevel", "Level where level-based growth stops increasing (timer still only capped by MaxMinutes).", 5, 60, () => FeatureFlags.LastChanceDynamicMaxMinutesAtLevel, delegate(int value) { FeatureFlags.LastChanceDynamicMaxMinutesAtLevel = value; }), new IntEntryDefinition("1a. LastChance: Quick Setup", "LastChanceMissingPlayers", "Number of players allowed to stay outside the truck before LastChance success triggers (0 = all players required).", 0, 32, () => FeatureFlags.LastChanceMissingPlayers, delegate(int value) { FeatureFlags.LastChanceMissingPlayers = value; }), new IntEntryDefinition("1a. LastChance: Quick Setup", "LastChanceTimerBonusPerMonsterDeathSeconds", "Seconds added to LastChance timer whenever a monster dies during active LastChance.", 0, 60, () => FeatureFlags.LastChanceTimerBonusPerMonsterDeathSeconds, delegate(int value) { FeatureFlags.LastChanceTimerBonusPerMonsterDeathSeconds = value; }), new BoolEntryDefinition("1a. LastChance: Quick Setup", "LastChanceMonstersSearchEnabled", "During LastChance, monsters treat disabled players as valid targets (harder return to truck).", () => FeatureFlags.LastChanceMonstersSearchEnabled, delegate(bool value) { FeatureFlags.LastChanceMonstersSearchEnabled = value; }), new BoolEntryDefinition("1a. LastChance: Quick Setup", "LastChanceMonstersVoiceEnemyOnlyEnabled", "During LastChance, disabled death-head voice keeps enemy reactions/talk animation but mutes playback to players (enemy-only voice aggro).", () => FeatureFlags.LastChanceMonstersVoiceEnemyOnlyEnabled, delegate(bool value) { FeatureFlags.LastChanceMonstersVoiceEnemyOnlyEnabled = value; }), new IntEntryDefinition("1a. LastChance: Quick Setup", "LastChanceConsolationMoney", "LastChance consolation money added on success (integer).", 0, 5, () => FeatureFlags.LastChanceConsolationMoney, delegate(int value) { FeatureFlags.LastChanceConsolationMoney = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceTimerPerMonsterSeconds", "Extra seconds added per active spawned monster when LastChanceMonstersSearch is enabled.", 0f, 60f, () => FeatureFlags.LastChanceTimerPerMonsterSeconds, delegate(float value) { FeatureFlags.LastChanceTimerPerMonsterSeconds = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceTimerPerRequiredPlayerSeconds", "Extra seconds added per required player that must reach the truck.", 0f, 120f, () => FeatureFlags.LastChanceTimerPerRequiredPlayerSeconds, delegate(float value) { FeatureFlags.LastChanceTimerPerRequiredPlayerSeconds = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceLevelContextRoomWeight", "Extra multiplier weight applied to level contribution from room-path difficulty (0 disables room context).", 0f, 3f, () => FeatureFlags.LastChanceLevelContextRoomWeight, delegate(float value) { FeatureFlags.LastChanceLevelContextRoomWeight = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceLevelContextMonsterWeight", "Extra multiplier weight applied to level contribution from active search monsters (0 disables monster context).", 0f, 3f, () => FeatureFlags.LastChanceLevelContextMonsterWeight, delegate(float value) { FeatureFlags.LastChanceLevelContextMonsterWeight = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceTimerPerFarthestMeterSeconds", "Extra seconds added per meter of total required-player distance to truck.", 0f, 20f, () => FeatureFlags.LastChanceTimerPerFarthestMeterSeconds, delegate(float value) { FeatureFlags.LastChanceTimerPerFarthestMeterSeconds = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceTimerPerRoomStepSeconds", "Extra seconds added per total room-step count (sum of required players shortest paths to truck).", 0f, 60f, () => FeatureFlags.LastChanceTimerPerRoomStepSeconds, delegate(float value) { FeatureFlags.LastChanceTimerPerRoomStepSeconds = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceTimerPerBelowTruckPlayerSeconds", "Extra seconds added per required player below the truck threshold height.", 0f, 120f, () => FeatureFlags.LastChanceTimerPerBelowTruckPlayerSeconds, delegate(float value) { FeatureFlags.LastChanceTimerPerBelowTruckPlayerSeconds = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceTimerPerBelowTruckMeterSeconds", "Extra seconds added per meter below threshold (only when height delta <= threshold).", 0f, 30f, () => FeatureFlags.LastChanceTimerPerBelowTruckMeterSeconds, delegate(float value) { FeatureFlags.LastChanceTimerPerBelowTruckMeterSeconds = value; }), new FloatEntryDefinition("1b. LastChance: Timer Calculation", "LastChanceBelowTruckThresholdMeters", "Height delta threshold (playerY - truckY) below which low-altitude penalties apply. -0.5 means at least half meter below.", -5f, 0f, () => FeatureFlags.LastChanceBelowTruckThresholdMeters, delegate(float value) { FeatureFlags.LastChanceBelowTruckThresholdMeters = value; }), new IntEntryDefinition("1c. LastChance: Gameplay & UI", "LastChanceSurrenderSeconds", "Seconds the player must hold Crouch to surrender during LastChance.", 2, 10, () => FeatureFlags.LastChanceSurrenderSeconds, delegate(int value) { FeatureFlags.LastChanceSurrenderSeconds = value; }), new StringEntryDefinition("1c. LastChance: Gameplay & UI", "LastChanceIndicators", "LastChance indicators mode: None, Direction.", new string[2] { "None", "Direction" }, () => FeatureFlags.LastChanceIndicators, delegate(string value) { FeatureFlags.LastChanceIndicators = value; }), new FloatEntryDefinition("1c. LastChance: Gameplay & UI", "LastChanceIndicatorHoldSeconds", "Seconds to hold Tumble before triggering the selected indicator.", 0.2f, 5f, () => FeatureFlags.LastChanceIndicatorHoldSeconds, delegate(float value) { FeatureFlags.LastChanceIndicatorHoldSeconds = value; }), new FloatEntryDefinition("1c. LastChance: Gameplay & UI", "LastChanceIndicatorDirectionDurationSeconds", "Seconds the Direction indicator stays active once triggered.", 0.5f, 20f, () => FeatureFlags.LastChanceIndicatorDirectionDurationSeconds, delegate(float value) { FeatureFlags.LastChanceIndicatorDirectionDurationSeconds = value; }), new FloatEntryDefinition("1c. LastChance: Gameplay & UI", "LastChanceIndicatorDirectionCooldownSeconds", "Cooldown seconds before Direction can be triggered again.", 1f, 60f, () => FeatureFlags.LastChanceIndicatorDirectionCooldownSeconds, delegate(float value) { FeatureFlags.LastChanceIndicatorDirectionCooldownSeconds = value; }), new FloatEntryDefinition("1c. LastChance: Gameplay & UI", "LastChanceIndicatorDirectionPenaltyMaxSeconds", "Maximum timer penalty per Direction trigger (low difficulty, and always used when dynamic timer is disabled).", 0f, 30f, () => FeatureFlags.LastChanceIndicatorDirectionPenaltyMaxSeconds, delegate(float value) { FeatureFlags.LastChanceIndicatorDirectionPenaltyMaxSeconds = value; }), new FloatEntryDefinition("1c. LastChance: Gameplay & UI", "LastChanceIndicatorDirectionPenaltyMinSeconds", "Minimum timer penalty per Direction trigger (high difficulty).", 0f, 60f, () => FeatureFlags.LastChanceIndicatorDirectionPenaltyMinSeconds, delegate(float value) { FeatureFlags.LastChanceIndicatorDirectionPenaltyMinSeconds = value; }), new BoolEntryDefinition("1c. LastChance: Gameplay & UI", "LastChancePupilVisualsEnabled", "When true, LastChance keeps death-head pupils visible and unlocks eye look-at behavior for head proxy players. When false, eyes/pupils stay vanilla during LastChance.", () => FeatureFlags.LastChancePupilVisualsEnabled, delegate(bool value) { FeatureFlags.LastChancePupilVisualsEnabled = value; }), new BoolEntryDefinition("2. Spectate", "SpectateDeadPlayers", "Allow SpectateCamera to cycle through disabled players (dead bodies) when toggling targets.", () => FeatureFlags.SpectateDeadPlayers, delegate(bool value) { FeatureFlags.SpectateDeadPlayers = value; }), new StringEntryDefinition("2. Spectate", "SpectateDeadPlayersMode", "Mode for dead-player spectate switch: Always, LastChanceOnly, Disabled.", new string[3] { "Always", "LastChanceOnly", "Disabled" }, () => FeatureFlags.SpectateDeadPlayersMode, delegate(string value) { FeatureFlags.SpectateDeadPlayersMode = value; }), new BoolEntryDefinition("3. Debug", "DebugLogging", "Dump extra log lines that help trace LastChance logic.", () => FeatureFlags.DebugLogging, delegate(bool value) { FeatureFlags.DebugLogging = value; }, hostControlled: false) }; } internal static class ConfigMigrationManager { private readonly struct FileEntry { public int LineIndex { get; } public string Section { get; } public string Key { get; } public string Value { get; } public FileEntry(int lineIndex, string section, string key, string value) { LineIndex = lineIndex; Section = section; Key = key; Value = value; } } internal static void Apply(ConfigFile config) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Expected O, but got Unknown if (config == null || string.IsNullOrWhiteSpace(config.ConfigFilePath) || !File.Exists(config.ConfigFilePath)) { return; } string[] array = File.ReadAllLines(config.ConfigFilePath); if (array.Length == 0) { return; } List list = ParseEntries(array); if (list.Count == 0) { return; } HashSet hashSet = new HashSet(config.Keys); if (hashSet.Count == 0) { return; } Dictionary dictionary = BuildAliasMap(); Dictionary> dictionary2 = BuildActiveByKey(hashSet); HashSet hashSet2 = new HashSet(); foreach (FileEntry item in list) { ConfigDefinition val = new ConfigDefinition(item.Section, item.Key); if (hashSet.Contains(val)) { continue; } bool flag = false; List value2; if (dictionary.TryGetValue(val, out var value)) { flag = TryMigrateValue(config, hashSet, value, item.Value); } else if (dictionary2.TryGetValue(item.Key, out value2) && value2.Count == 1) { ConfigDefinition val2 = value2[0]; if (!object.Equals(val2, val)) { flag = TryMigrateValue(config, hashSet, val2, item.Value); } } if (flag || !hashSet.Contains(val)) { hashSet2.Add(item.LineIndex); } } if (hashSet2.Count == 0) { return; } List list2 = new List(array.Length); for (int i = 0; i < array.Length; i++) { if (!hashSet2.Contains(i)) { list2.Add(array[i]); } } File.WriteAllLines(config.ConfigFilePath, list2.ToArray()); config.Reload(); } private static bool TryMigrateValue(ConfigFile config, HashSet activeDefinitions, ConfigDefinition destination, string serializedValue) { if (!activeDefinitions.Contains(destination) || !config.ContainsKey(destination)) { return false; } ConfigEntryBase val = config[destination]; if (val == null) { return false; } try { val.SetSerializedValue(serializedValue); return true; } catch { return false; } } private static Dictionary BuildAliasMap() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown Dictionary dictionary = new Dictionary(); foreach (ConfigMetadata.EntryDefinition featureFlagEntry in ConfigMetadata.FeatureFlagEntries) { ConfigDefinition value = new ConfigDefinition(featureFlagEntry.Section, featureFlagEntry.Key); foreach (ConfigMetadata.AliasDefinition alias in featureFlagEntry.Aliases) { if (alias != null && !string.IsNullOrWhiteSpace(alias.Key)) { ConfigDefinition key = new ConfigDefinition(alias.Section, alias.Key); dictionary[key] = value; } } } return dictionary; } private static Dictionary> BuildActiveByKey(HashSet activeDefinitions) { Dictionary> dictionary = new Dictionary>(StringComparer.Ordinal); foreach (ConfigDefinition activeDefinition in activeDefinitions) { if (!dictionary.TryGetValue(activeDefinition.Key, out var value)) { value = new List(); dictionary[activeDefinition.Key] = value; } value.Add(activeDefinition); } return dictionary; } private static List ParseEntries(string[] lines) { List list = new List(); string section = string.Empty; for (int i = 0; i < lines.Length; i++) { string text = lines[i]; if (string.IsNullOrWhiteSpace(text)) { continue; } string text2 = text.Trim(); if (text2.StartsWith("#", StringComparison.Ordinal) || text2.StartsWith(";", StringComparison.Ordinal)) { continue; } if (text2.Length >= 3 && text2[0] == '[' && text2[text2.Length - 1] == ']') { section = text2.Substring(1, text2.Length - 2).Trim(); continue; } int num = text2.IndexOf('='); if (num > 0) { string text3 = text2.Substring(0, num).Trim(); if (text3.Length != 0) { string value = text2.Substring(num + 1).Trim(); list.Add(new FileEntry(i, section, text3, value)); } } } return list; } } internal sealed class ConfigSyncManager : MonoBehaviourPunCallbacks, IOnEventCallback { private static ConfigSyncManager? s_instance; private const string ConfigSyncMessageType = "ConfigSync"; internal static void EnsureCreated() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (!((Object)(object)s_instance != (Object)null)) { GameObject val = new GameObject("DHHFix.ConfigSyncManager"); Object.DontDestroyOnLoad((Object)(object)val); s_instance = val.AddComponent(); } } public override void OnEnable() { ((MonoBehaviourPunCallbacks)this).OnEnable(); PhotonNetwork.AddCallbackTarget((object)this); ConfigManager.HostControlledChanged += OnHostControlledChanged; CompatibilityGate.HostApprovalChanged += OnHostControlledChanged; SceneManager.sceneLoaded += OnSceneLoaded; TrySendSnapshot(); } public override void OnDisable() { ((MonoBehaviourPunCallbacks)this).OnDisable(); PhotonNetwork.RemoveCallbackTarget((object)this); ConfigManager.HostControlledChanged -= OnHostControlledChanged; CompatibilityGate.HostApprovalChanged -= OnHostControlledChanged; SceneManager.sceneLoaded -= OnSceneLoaded; } private void OnHostControlledChanged() { if (PhotonNetwork.IsMasterClient) { TrySendSnapshot(); } } public override void OnJoinedRoom() { if (PhotonNetwork.IsMasterClient) { TrySendSnapshot(); } } public override void OnPlayerEnteredRoom(Player newPlayer) { if (PhotonNetwork.IsMasterClient && newPlayer != null) { TrySendSnapshot(new int[1] { newPlayer.ActorNumber }); } } public override void OnMasterClientSwitched(Player newMasterClient) { if (PhotonNetwork.IsMasterClient) { TrySendSnapshot(); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (PhotonNetwork.IsMasterClient) { TrySendSnapshot(); } } public override void OnLeftRoom() { ConfigManager.RestoreLocalHostControlledBaseline(); } internal static void RequestHostSnapshotBroadcast() { TrySendSnapshot(); } private static void TrySendSnapshot(int[]? targetActors = null) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_00cd: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient) { return; } Dictionary dictionary = ConfigManager.SnapshotHostControlled(); if (dictionary.Count == 0) { return; } Hashtable val = new Hashtable(dictionary.Count); foreach (KeyValuePair item in dictionary) { val[(object)item.Key] = item.Value; } RaiseEventOptions val2 = new RaiseEventOptions { Receivers = (ReceiverGroup)(targetActors != null), TargetActors = targetActors }; PhotonNetwork.RaiseEvent((byte)79, (object)new NetworkEnvelope("DHHFLastChanceMode", 1, "ConfigSync", 0, val).ToEventPayload(), val2, SendOptions.SendReliable); } public void OnEvent(EventData photonEvent) { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if (photonEvent == null || photonEvent.Code != 79 || PhotonNetwork.IsMasterClient) { return; } Player masterClient = PhotonNetwork.MasterClient; int num = ((masterClient != null) ? masterClient.ActorNumber : (-1)); if (num <= 0 || photonEvent.Sender != num || !NetworkEnvelope.TryParse(photonEvent.CustomData, out var envelope) || !envelope.IsExpectedSource() || !string.Equals(envelope.MessageType, "ConfigSync", StringComparison.Ordinal)) { return; } object? payload = envelope.Payload; Hashtable val = (Hashtable)((payload is Hashtable) ? payload : null); if (val == null) { return; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); DictionaryEntryEnumerator enumerator = val.GetEnumerator(); try { while (((DictionaryEntryEnumerator)(ref enumerator)).MoveNext()) { DictionaryEntry current = ((DictionaryEntryEnumerator)(ref enumerator)).Current; if (current.Key is string key && current.Value is string value) { dictionary[key] = value; } } } finally { ((IDisposable)(DictionaryEntryEnumerator)(ref enumerator)).Dispose(); } if (dictionary.Count > 0) { ConfigManager.ApplyHostSnapshot(dictionary); } } } internal static class FeatureFlags { internal static class Sections { public const string LastChanceQuick = "1a. LastChance: Quick Setup"; public const string LastChanceTimer = "1b. LastChance: Timer Calculation"; public const string LastChanceGameplay = "1c. LastChance: Gameplay & UI"; public const string Spectate = "2. Spectate"; public const string Debug = "3. Debug"; } internal static class Descriptions { public const string LastChanceMode = "When true, prevent the vanilla run manager from switching to the dump level when all players die."; public const string LastChanceTimerSeconds = "LastChance timer duration in seconds (integer, 30s steps)."; public const string LastChanceDynamicTimerEnabled = "Enable dynamic LastChance timer scaling from base timer and run context metrics."; public const string LastChanceTimerPerRequiredPlayerSeconds = "Extra seconds added per required player that must reach the truck."; public const string LastChanceLevelContextRoomWeight = "Extra multiplier weight applied to level contribution from room-path difficulty (0 disables room context)."; public const string LastChanceLevelContextMonsterWeight = "Extra multiplier weight applied to level contribution from active search monsters (0 disables monster context)."; public const string LastChanceTimerPerFarthestMeterSeconds = "Extra seconds added per meter of total required-player distance to truck."; public const string LastChanceTimerPerBelowTruckPlayerSeconds = "Extra seconds added per required player below the truck threshold height."; public const string LastChanceTimerPerBelowTruckMeterSeconds = "Extra seconds added per meter below threshold (only when height delta <= threshold)."; public const string LastChanceBelowTruckThresholdMeters = "Height delta threshold (playerY - truckY) below which low-altitude penalties apply. -0.5 means at least half meter below."; public const string LastChanceTimerPerRoomStepSeconds = "Extra seconds added per total room-step count (sum of required players shortest paths to truck)."; public const string LastChanceDynamicMaxMinutesAtLevel = "Level where level-based growth stops increasing (timer still only capped by MaxMinutes)."; public const string LastChanceDynamicMaxMinutes = "Hard cap (minutes) for final LastChance timer after dynamic scaling."; public const string LastChanceConsolationMoney = "LastChance consolation money added on success (integer)."; public const string LastChanceMissingPlayers = "Number of players allowed to stay outside the truck before LastChance success triggers (0 = all players required)."; public const string LastChanceTimerBonusPerMonsterDeathSeconds = "Seconds added to LastChance timer whenever a monster dies during active LastChance."; public const string LastChanceSurrenderSeconds = "Seconds the player must hold Crouch to surrender during LastChance."; public const string LastChanceIndicators = "LastChance indicators mode: None, Direction."; public const string LastChanceIndicatorHoldSeconds = "Seconds to hold Tumble before triggering the selected indicator."; public const string LastChanceIndicatorDirectionDurationSeconds = "Seconds the Direction indicator stays active once triggered."; public const string LastChanceIndicatorDirectionCooldownSeconds = "Cooldown seconds before Direction can be triggered again."; public const string LastChanceIndicatorDirectionPenaltyMaxSeconds = "Maximum timer penalty per Direction trigger (low difficulty, and always used when dynamic timer is disabled)."; public const string LastChanceIndicatorDirectionPenaltyMinSeconds = "Minimum timer penalty per Direction trigger (high difficulty)."; public const string LastChancePupilVisualsEnabled = "When true, LastChance keeps death-head pupils visible and unlocks eye look-at behavior for head proxy players. When false, eyes/pupils stay vanilla during LastChance."; public const string LastChanceMonstersSearchEnabled = "During LastChance, monsters treat disabled players as valid targets (harder return to truck)."; public const string LastChanceMonstersVoiceEnemyOnlyEnabled = "During LastChance, disabled death-head voice keeps enemy reactions/talk animation but mutes playback to players (enemy-only voice aggro)."; public const string LastChanceTimerPerMonsterSeconds = "Extra seconds added per active spawned monster when LastChanceMonstersSearch is enabled."; public const string SpectateDeadPlayers = "Allow SpectateCamera to cycle through disabled players (dead bodies) when toggling targets."; public const string SpectateDeadPlayersMode = "Mode for dead-player spectate switch: Always, LastChanceOnly, Disabled."; public const string DebugLogging = "Dump extra log lines that help trace LastChance logic."; } [FeatureConfigEntry("1a. LastChance: Quick Setup", "When true, prevent the vanilla run manager from switching to the dump level when all players die.")] public static bool LastChangeMode = true; [FeatureConfigEntry("1a. LastChance: Quick Setup", "LastChance timer duration in seconds (integer, 30s steps).", Min = 30f, Max = 600f)] public static int LastChanceTimerSeconds = 60; [FeatureConfigEntry("1a. LastChance: Quick Setup", "Enable dynamic LastChance timer scaling from base timer and run context metrics.")] public static bool LastChanceDynamicTimerEnabled = true; [FeatureConfigEntry("1a. LastChance: Quick Setup", "Hard cap (minutes) for final LastChance timer after dynamic scaling.", Min = 5f, Max = 20f)] public static int LastChanceDynamicMaxMinutes = 10; [FeatureConfigEntry("1a. LastChance: Quick Setup", "Level where level-based growth stops increasing (timer still only capped by MaxMinutes).", Min = 5f, Max = 60f)] public static int LastChanceDynamicMaxMinutesAtLevel = 25; [FeatureConfigEntry("1a. LastChance: Quick Setup", "Number of players allowed to stay outside the truck before LastChance success triggers (0 = all players required).", Min = 0f, Max = 32f)] public static int LastChanceMissingPlayers = 0; [FeatureConfigEntry("1a. LastChance: Quick Setup", "Seconds added to LastChance timer whenever a monster dies during active LastChance.", Min = 0f, Max = 60f)] public static int LastChanceTimerBonusPerMonsterDeathSeconds = 15; [FeatureConfigEntry("1a. LastChance: Quick Setup", "During LastChance, monsters treat disabled players as valid targets (harder return to truck).")] public static bool LastChanceMonstersSearchEnabled = true; [FeatureConfigEntry("1a. LastChance: Quick Setup", "During LastChance, disabled death-head voice keeps enemy reactions/talk animation but mutes playback to players (enemy-only voice aggro).")] public static bool LastChanceMonstersVoiceEnemyOnlyEnabled = true; [FeatureConfigEntry("1a. LastChance: Quick Setup", "LastChance consolation money added on success (integer).", Min = 0f, Max = 5f)] public static int LastChanceConsolationMoney = 1; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Extra seconds added per active spawned monster when LastChanceMonstersSearch is enabled.", Min = 0f, Max = 60f)] public static float LastChanceTimerPerMonsterSeconds = 3f; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Extra seconds added per required player that must reach the truck.", Min = 0f, Max = 120f)] public static float LastChanceTimerPerRequiredPlayerSeconds = 8f; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Extra multiplier weight applied to level contribution from room-path difficulty (0 disables room context).", Min = 0f, Max = 3f)] public static float LastChanceLevelContextRoomWeight = 0.5f; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Extra multiplier weight applied to level contribution from active search monsters (0 disables monster context).", Min = 0f, Max = 3f)] public static float LastChanceLevelContextMonsterWeight = 0.3f; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Extra seconds added per meter of total required-player distance to truck.", Min = 0f, Max = 20f)] public static float LastChanceTimerPerFarthestMeterSeconds = 0.6f; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Extra seconds added per total room-step count (sum of required players shortest paths to truck).", Min = 0f, Max = 60f)] public static float LastChanceTimerPerRoomStepSeconds = 3f; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Extra seconds added per required player below the truck threshold height.", Min = 0f, Max = 120f)] public static float LastChanceTimerPerBelowTruckPlayerSeconds = 15f; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Extra seconds added per meter below threshold (only when height delta <= threshold).", Min = 0f, Max = 30f)] public static float LastChanceTimerPerBelowTruckMeterSeconds = 15f; [FeatureConfigEntry("1b. LastChance: Timer Calculation", "Height delta threshold (playerY - truckY) below which low-altitude penalties apply. -0.5 means at least half meter below.", Min = -5f, Max = 0f)] public static float LastChanceBelowTruckThresholdMeters = -0.5f; [FeatureConfigEntry("1c. LastChance: Gameplay & UI", "Seconds the player must hold Crouch to surrender during LastChance.", Min = 2f, Max = 10f)] public static int LastChanceSurrenderSeconds = 5; [FeatureConfigEntry("1c. LastChance: Gameplay & UI", "LastChance indicators mode: None, Direction.", Options = new string[] { "None", "Direction" })] public static string LastChanceIndicators = "Direction"; [FeatureConfigEntry("1c. LastChance: Gameplay & UI", "Seconds to hold Tumble before triggering the selected indicator.", Min = 0.2f, Max = 5f)] public static float LastChanceIndicatorHoldSeconds = 2f; [FeatureConfigEntry("1c. LastChance: Gameplay & UI", "Seconds the Direction indicator stays active once triggered.", Min = 0.5f, Max = 20f)] public static float LastChanceIndicatorDirectionDurationSeconds = 5f; [FeatureConfigEntry("1c. LastChance: Gameplay & UI", "Cooldown seconds before Direction can be triggered again.", Min = 1f, Max = 60f)] public static float LastChanceIndicatorDirectionCooldownSeconds = 15f; [FeatureConfigEntry("1c. LastChance: Gameplay & UI", "Maximum timer penalty per Direction trigger (low difficulty, and always used when dynamic timer is disabled).", Min = 0f, Max = 30f)] public static float LastChanceIndicatorDirectionPenaltyMaxSeconds = 8f; [FeatureConfigEntry("1c. LastChance: Gameplay & UI", "Minimum timer penalty per Direction trigger (high difficulty).", Min = 0f, Max = 60f)] public static float LastChanceIndicatorDirectionPenaltyMinSeconds = 4f; [FeatureConfigEntry("1c. LastChance: Gameplay & UI", "When true, LastChance keeps death-head pupils visible and unlocks eye look-at behavior for head proxy players. When false, eyes/pupils stay vanilla during LastChance.")] public static bool LastChancePupilVisualsEnabled = true; [FeatureConfigEntry("2. Spectate", "Allow SpectateCamera to cycle through disabled players (dead bodies) when toggling targets.")] public static bool SpectateDeadPlayers = true; [FeatureConfigEntry("2. Spectate", "Mode for dead-player spectate switch: Always, LastChanceOnly, Disabled.", Options = new string[] { "Always", "LastChanceOnly", "Disabled" })] public static string SpectateDeadPlayersMode = "Always"; [FeatureConfigEntry("3. Debug", "Dump extra log lines that help trace LastChance logic.", HostControlled = false)] public static bool DebugLogging = false; } internal static class InternalConfig { internal static bool LastChanceMonstersForceCameraOnLock = true; internal static float LastChanceMonstersCameraLockMaxSeconds = 3f; internal static float LastChanceMonstersCameraLockCooldownSeconds = 10f; internal static float LastChanceMonstersCameraLockKeepAliveGraceSeconds = 0.6f; internal static float LastChanceMonstersVisionLockSourceBucketSize = 1f; internal static bool LastChanceMonstersVisionProxyEnabled = true; internal static float LastChanceMonstersVisionProxyTickSeconds = 0.25f; internal static float LastChanceMonstersHeadmanDeathHeadFocusMaxSeconds = 3f; internal static float LastChanceMonstersHeadmanDeathHeadFocusCooldownSeconds = 5f; internal static float CompatibilityGatePresenceRetrySeconds = 1f; internal static float CompatibilityGatePresenceTimeoutSeconds = 5f; internal static bool CompatibilityGatePresencePollingEnabled = true; internal static float LastChanceTimerChangePulseDurationSeconds = 0.4f; internal static float LastChanceTimerChangePulseScaleBoost = 0.22f; internal static float LastChanceTimerChangeFloatingDurationSeconds = 1.1f; internal static float LastChanceTimerChangeFloatingDropFontMultiplier = 2f; internal static float LastChanceTimerChangeFloatingFontSizeMultiplier = 2f; internal static float LastChanceTimerChangeLocalDeltaMinSeconds = 0.05f; internal static float LastChanceTimerChangeNetworkDeltaMinSeconds = 0.75f; } internal static class InternalDebugFlags { public static bool DebugLastChanceHiddenCarryFlow; public static bool DebugLastChanceCeilingEyeFlow; public static bool DebugLastChanceHeartHuggerFlow; public static bool DebugLastChanceTricycleFlow; public static bool DebugLastChanceSpinnyFlow; public static bool DebugLastChanceSpinnyVerbose; public static bool DebugLastChanceThinManFlow; public static bool DebugLastChanceEyesFlow; public static bool DebugLastChanceHeadmanSlowMouthFlow; } }