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.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using DeathHeadHopper; using DeathHeadHopper.Abilities; using DeathHeadHopper.Abilities.Charge; using DeathHeadHopper.DeathHead; using DeathHeadHopper.DeathHead.Handlers; using DeathHeadHopper.Items; using DeathHeadHopper.Managers; using DeathHeadHopper.UI; using DeathHeadHopperFix.Modules.Battery; using DeathHeadHopperFix.Modules.Config; using DeathHeadHopperFix.Modules.Gameplay.Core.Abilities; using DeathHeadHopperFix.Modules.Gameplay.Core.Audio; using DeathHeadHopperFix.Modules.Gameplay.Core.Bootstrap; using DeathHeadHopperFix.Modules.Gameplay.Core.Input; using DeathHeadHopperFix.Modules.Gameplay.Core.Interop; using DeathHeadHopperFix.Modules.Gameplay.Core.Runtime; using DeathHeadHopperFix.Modules.Gameplay.Stun; using DeathHeadHopperFix.Modules.Stamina; using DeathHeadHopperFix.Modules.Utilities; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using REPOLib.Modules; using TMPro; using UnityEngine; using UnityEngine.Events; 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 { [BepInPlugin("AdrenSnyder.DeathHeadHopperFix", "Death Head Hopper - Fix", "0.2.4")] public sealed class Plugin : BaseUnityPlugin { private const string TargetAssemblyName = "DeathHeadHopper"; private Harmony? _harmony; private bool _patched; private bool _patchAttempted; private Assembly? _targetAssembly; private static ManualLogSource? _log; private void Awake() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown _log = ((BaseUnityPlugin)this).Logger; ConfigManager.Initialize(((BaseUnityPlugin)this).Config); WarnUnsafeDebugFlagsInRelease(); _harmony = new Harmony("AdrenSnyder.DeathHeadHopperFix"); _harmony.PatchAll(typeof(Plugin).Assembly); DHHApiGuardModule.DetectGameApiChanges(); ApplyEarlyPatches(); AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly asm in assemblies) { TryPatchIfTargetAssembly(asm); } } private void OnDestroy() { AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad; } private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) { TryPatchIfTargetAssembly(args.LoadedAssembly); } private void ApplyEarlyPatches() { if (_harmony != null) { StatsModule.ApplyHooks(_harmony); ItemUpgradeModule.Apply(_harmony); } } private void TryPatchIfTargetAssembly(Assembly asm) { if (_patched || _patchAttempted || asm == null) { return; } string name = asm.GetName().Name; if (!string.Equals(name, "DeathHeadHopper", StringComparison.OrdinalIgnoreCase)) { return; } _patchAttempted = true; try { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)"Detected DeathHeadHopper assembly load. Applying patches..."); } Harmony harmony = _harmony; if (harmony == null) { throw new InvalidOperationException("Harmony instance is null."); } DHHStatsBootstrapModule.Apply(harmony, asm, _log); PrefabModule.Apply(harmony, asm, _log); AudioModule.Apply(harmony, asm, _log); DHHShopVanillaPoolModule.Apply(harmony, asm, _log); _targetAssembly = asm; DHHApiGuardModule.Apply(harmony, asm); BatteryJumpPatchModule.Apply(harmony, asm); JumpForceModule.Apply(harmony, asm, _log); ChargeAbilityTuningModule.Apply(harmony, asm); ChargeHoldReleaseModule.Apply(harmony, asm, _log); InputModule.Apply(harmony, asm, _log); AbilityModule.ApplyAbilitySpotLabelOverlay(harmony, asm); AbilityModule.ApplyAbilityManagerHooks(harmony, asm); _patched = true; ManualLogSource? log2 = _log; if (log2 != null) { log2.LogInfo((object)"Patches applied successfully."); } } catch (Exception ex) { ManualLogSource? log3 = _log; if (log3 != null) { log3.LogError((object)ex); } } } private void WarnUnsafeDebugFlagsInRelease() { if (!Debug.isDebugBuild && (InternalDebugFlags.DisableBatteryModule || InternalDebugFlags.DisableAbilityPatches || InternalDebugFlags.DisableSpectateChecks)) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[DebugSafety] Internal debug bypass flags are enabled in a non-debug build. " + $"DisableBatteryModule={InternalDebugFlags.DisableBatteryModule}, " + $"DisableAbilityPatches={InternalDebugFlags.DisableAbilityPatches}, " + $"DisableSpectateChecks={InternalDebugFlags.DisableSpectateChecks}")); } } } } } namespace DeathHeadHopperFix.Modules.Utilities { internal static class LastChanceInteropBridge { private const BindingFlags StaticAny = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static Type? s_timerControllerType; private static Type? s_spectateHelperType; private static Type? s_featureFlagsType; private static Type? s_imageAssetLoaderType; private static PropertyInfo? s_isActiveProperty; private static PropertyInfo? s_directionUiVisibleProperty; private static MethodInfo? s_isDirectionEnergySufficientMethod; private static MethodInfo? s_getDirectionPenaltyPreviewMethod; private static MethodInfo? s_getDirectionEnergyDebugSnapshotMethod; private static MethodInfo? s_isPlayerSurrenderedForDataMethod; private static MethodInfo? s_allPlayersDisabledMethod; private static MethodInfo? s_resetForceStateMethod; private static MethodInfo? s_shouldForceLocalDeathHeadSpectateMethod; private static MethodInfo? s_ensureSpectatePlayerLocalMethod; private static MethodInfo? s_forceDeathHeadSpectateIfPossibleMethod; private static MethodInfo? s_debugLogStateMethod; private static MethodInfo? s_isManualSwitchInputDownMethod; private static FieldInfo? s_lastChanceModeField; private static FieldInfo? s_spectateDeadPlayersField; private static FieldInfo? s_spectateDeadPlayersModeField; private static FieldInfo? s_lastChanceIndicatorsField; private static MethodInfo? s_tryLoadSpriteMethod; internal static bool IsLastChanceActive() { return TryGetBoolProperty(ref s_isActiveProperty, "DeathHeadHopperFix.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController", "IsActive"); } internal static bool IsDirectionIndicatorUiVisible() { return TryGetBoolProperty(ref s_directionUiVisibleProperty, "DeathHeadHopperFix.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController", "IsDirectionIndicatorUiVisible"); } internal static bool IsDirectionIndicatorEnergySufficientPreview() { ResolveMembers(); if (s_isDirectionEnergySufficientMethod == null) { return false; } return (s_isDirectionEnergySufficientMethod.Invoke(null, null) as bool?).GetValueOrDefault(); } internal static float GetDirectionIndicatorPenaltySecondsPreview() { ResolveMembers(); if (s_getDirectionPenaltyPreviewMethod == null) { return 0f; } return (s_getDirectionPenaltyPreviewMethod.Invoke(null, null) as float?).GetValueOrDefault(); } internal static void GetDirectionIndicatorEnergyDebugSnapshot(out bool visible, out float timerRemaining, out float penaltyPreview, out bool hasEnoughEnergy) { visible = false; timerRemaining = 0f; penaltyPreview = 0f; hasEnoughEnergy = false; ResolveMembers(); if (!(s_getDirectionEnergyDebugSnapshotMethod == null)) { object[] array = new object[4] { false, 0f, 0f, false }; s_getDirectionEnergyDebugSnapshotMethod.Invoke(null, array); visible = (array[0] as bool?).GetValueOrDefault(); timerRemaining = (array[1] as float?).GetValueOrDefault(); penaltyPreview = (array[2] as float?).GetValueOrDefault(); hasEnoughEnergy = (array[3] as bool?).GetValueOrDefault(); } } internal static bool IsPlayerSurrenderedForData(PlayerAvatar? player) { ResolveMembers(); if (s_isPlayerSurrenderedForDataMethod == null) { return false; } return (s_isPlayerSurrenderedForDataMethod.Invoke(null, new object[1] { player }) as bool?).GetValueOrDefault(); } internal static bool AllPlayersDisabled() { ResolveMembers(); if (s_allPlayersDisabledMethod == null) { return false; } return (s_allPlayersDisabledMethod.Invoke(null, null) as bool?).GetValueOrDefault(); } internal static void ResetSpectateForceState() { ResolveMembers(); s_resetForceStateMethod?.Invoke(null, null); } internal static bool ShouldForceLocalDeathHeadSpectate() { ResolveMembers(); if (s_shouldForceLocalDeathHeadSpectateMethod == null) { return false; } return (s_shouldForceLocalDeathHeadSpectateMethod.Invoke(null, null) as bool?).GetValueOrDefault(); } internal static void EnsureSpectatePlayerLocal(SpectateCamera? spectate) { ResolveMembers(); s_ensureSpectatePlayerLocalMethod?.Invoke(null, new object[1] { spectate }); } internal static void ForceDeathHeadSpectateIfPossible() { ResolveMembers(); s_forceDeathHeadSpectateIfPossibleMethod?.Invoke(null, null); } internal static void DebugLogState(SpectateCamera? spectate) { ResolveMembers(); s_debugLogStateMethod?.Invoke(null, new object[1] { spectate }); } internal static bool IsManualSwitchInputDown() { ResolveMembers(); if (s_isManualSwitchInputDownMethod == null) { return false; } return (s_isManualSwitchInputDownMethod.Invoke(null, null) as bool?).GetValueOrDefault(); } internal static bool IsLastChanceModeEnabled() { ResolveMembers(); if (s_lastChanceModeField == null) { return false; } return (s_lastChanceModeField.GetValue(null) as bool?).GetValueOrDefault(); } internal static bool IsSpectateDeadPlayersEnabled() { ResolveMembers(); if (s_spectateDeadPlayersField == null) { return false; } return (s_spectateDeadPlayersField.GetValue(null) as bool?).GetValueOrDefault(); } internal static string GetSpectateDeadPlayersMode() { ResolveMembers(); if (s_spectateDeadPlayersModeField == null) { return string.Empty; } return (s_spectateDeadPlayersModeField.GetValue(null) as string) ?? string.Empty; } internal static string GetLastChanceIndicatorsMode() { ResolveMembers(); if (s_lastChanceIndicatorsField == null) { return string.Empty; } return (s_lastChanceIndicatorsField.GetValue(null) as string) ?? string.Empty; } internal static bool TryGetDirectionSlotSprite(out Sprite? sprite) { sprite = null; ResolveMembers(); if (s_tryLoadSpriteMethod == null) { return false; } object[] array = new object[5] { "Direction.png", null, null, string.Empty, 100f }; if (!(s_tryLoadSpriteMethod.Invoke(null, array) as bool?).GetValueOrDefault()) { return false; } object obj = array[2]; sprite = (Sprite?)((obj is Sprite) ? obj : null); return (Object)(object)sprite != (Object)null; } private static bool TryGetBoolProperty(ref PropertyInfo? property, string typeName, string propertyName) { ResolveMembers(); if (property == null) { property = ResolveType(typeName)?.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } return (property?.GetValue(null) as bool?).GetValueOrDefault(); } private static void ResolveMembers() { if ((object)s_timerControllerType == null) { s_timerControllerType = ResolveType("DeathHeadHopperFix.Modules.Gameplay.LastChance.Runtime.LastChanceTimerController"); } if ((object)s_spectateHelperType == null) { s_spectateHelperType = ResolveType("DeathHeadHopperFix.Modules.Gameplay.LastChance.Spectate.LastChanceSpectateHelper"); } if ((object)s_featureFlagsType == null) { s_featureFlagsType = ResolveType("DHHFLastChanceMode.Modules.Config.FeatureFlags"); } if ((object)s_imageAssetLoaderType == null) { s_imageAssetLoaderType = ResolveType("DeathHeadHopperFix.Modules.Utilities.ImageAssetLoader"); } if (s_timerControllerType != null) { if ((object)s_isDirectionEnergySufficientMethod == null) { s_isDirectionEnergySufficientMethod = s_timerControllerType.GetMethod("IsDirectionIndicatorEnergySufficientPreview", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_getDirectionPenaltyPreviewMethod == null) { s_getDirectionPenaltyPreviewMethod = s_timerControllerType.GetMethod("GetDirectionIndicatorPenaltySecondsPreview", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_getDirectionEnergyDebugSnapshotMethod == null) { s_getDirectionEnergyDebugSnapshotMethod = s_timerControllerType.GetMethod("GetDirectionIndicatorEnergyDebugSnapshot", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_isPlayerSurrenderedForDataMethod == null) { s_isPlayerSurrenderedForDataMethod = s_timerControllerType.GetMethod("IsPlayerSurrenderedForData", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } } if (s_spectateHelperType != null) { if ((object)s_allPlayersDisabledMethod == null) { s_allPlayersDisabledMethod = s_spectateHelperType.GetMethod("AllPlayersDisabled", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_resetForceStateMethod == null) { s_resetForceStateMethod = s_spectateHelperType.GetMethod("ResetForceState", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_shouldForceLocalDeathHeadSpectateMethod == null) { s_shouldForceLocalDeathHeadSpectateMethod = s_spectateHelperType.GetMethod("ShouldForceLocalDeathHeadSpectate", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_ensureSpectatePlayerLocalMethod == null) { s_ensureSpectatePlayerLocalMethod = s_spectateHelperType.GetMethod("EnsureSpectatePlayerLocal", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_forceDeathHeadSpectateIfPossibleMethod == null) { s_forceDeathHeadSpectateIfPossibleMethod = s_spectateHelperType.GetMethod("ForceDeathHeadSpectateIfPossible", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_debugLogStateMethod == null) { s_debugLogStateMethod = s_spectateHelperType.GetMethod("DebugLogState", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_isManualSwitchInputDownMethod == null) { s_isManualSwitchInputDownMethod = s_spectateHelperType.GetMethod("IsManualSwitchInputDown", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } } if (s_featureFlagsType != null) { if ((object)s_lastChanceModeField == null) { s_lastChanceModeField = s_featureFlagsType.GetField("LastChangeMode", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_spectateDeadPlayersField == null) { s_spectateDeadPlayersField = s_featureFlagsType.GetField("SpectateDeadPlayers", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_spectateDeadPlayersModeField == null) { s_spectateDeadPlayersModeField = s_featureFlagsType.GetField("SpectateDeadPlayersMode", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_lastChanceIndicatorsField == null) { s_lastChanceIndicatorsField = s_featureFlagsType.GetField("LastChanceIndicators", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } } if (s_imageAssetLoaderType != null && (object)s_tryLoadSpriteMethod == null) { s_tryLoadSpriteMethod = s_imageAssetLoaderType.GetMethod("TryLoadSprite", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } } private static Type? ResolveType(string fullName) { Type type = Type.GetType(fullName, throwOnError: false); if (type != null) { return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { type = assembly.GetType(fullName, throwOnError: false); if (type != null) { return type; } } return null; } } internal static class LogLimiter { public const int DefaultFrameInterval = 240; private static readonly Dictionary _lastFrameByKey = new Dictionary(StringComparer.Ordinal); public static bool ShouldLog(string key, int frameInterval = 240) { if (string.IsNullOrEmpty(key)) { return true; } if (frameInterval <= 0) { return true; } int frameCount = Time.frameCount; if (_lastFrameByKey.TryGetValue(key, out var value) && frameCount - value < frameInterval) { return false; } _lastFrameByKey[key] = frameCount; return true; } public static void Reset(string key) { if (!string.IsNullOrEmpty(key)) { _lastFrameByKey.Remove(key); } } public static void Clear() { _lastFrameByKey.Clear(); } } internal static class NetworkProtocol { internal const string ModId = "DeathHeadHopperFix"; internal const int ProtocolVersion = 1; internal const string RoomKeyPrefix = "DeathHeadHopperFix.Room."; internal static string BuildRoomKey(string localKey) { return string.IsNullOrWhiteSpace(localKey) ? "DeathHeadHopperFix.Room." : ("DeathHeadHopperFix.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 == "DeathHeadHopperFix" && 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 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; } } private static readonly FieldInfo? s_playerNameField = AccessTools.Field(typeof(PlayerAvatar), "playerName"); private static readonly FieldInfo? s_playerDeadSetField = AccessTools.Field(typeof(PlayerAvatar), "deadSet"); private static readonly FieldInfo? s_playerIsDisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled"); private static readonly FieldInfo? s_playerRoomVolumeCheckField = AccessTools.Field(typeof(PlayerAvatar), "RoomVolumeCheck"); private static readonly FieldInfo? s_playerSteamIdShortField = AccessTools.Field(typeof(PlayerAvatar), "steamIDshort"); private static readonly FieldInfo? s_visualColorField = AccessTools.Field(typeof(PlayerAvatarVisuals), "color"); private static FieldInfo? s_roomVolumeCheckInTruckField; private static FieldInfo? s_deathHeadInTruckField; private static FieldInfo? s_deathHeadRoomVolumeCheckField; 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 = LastChanceInteropBridge.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 (s_playerNameField != null && s_playerNameField.GetValue(player) is string text && !string.IsNullOrWhiteSpace(text)) { return text; } return "unknown"; } private static Color GetPlayerColor(PlayerAvatar player) { //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_0061: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_0056: 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; } if (s_visualColorField != null && s_visualColorField.GetValue(playerAvatarVisuals) is Color result) { return result; } return Color.black; } private static bool IsDeadSet(PlayerAvatar player) { bool flag = default(bool); int num; if (s_playerDeadSetField != null) { object value = s_playerDeadSetField.GetValue(player); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static bool IsDisabled(PlayerAvatar player) { bool flag = default(bool); int num; if (s_playerIsDisabledField != null) { object value = s_playerIsDisabledField.GetValue(player); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static bool IsPlayerInTruck(PlayerAvatar player, bool isDisabled) { if (!isDisabled) { object roomVolumeCheck = GetRoomVolumeCheck(player); return roomVolumeCheck != null && IsRoomVolumeInTruck(roomVolumeCheck); } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead == (Object)null) { return false; } object deathHeadRoomVolumeCheck = GetDeathHeadRoomVolumeCheck(playerDeathHead); if (deathHeadRoomVolumeCheck != null) { return IsRoomVolumeInTruck(deathHeadRoomVolumeCheck); } FieldInfo deathHeadInTruckField = GetDeathHeadInTruckField(((object)playerDeathHead).GetType()); bool flag = default(bool); int num; if (deathHeadInTruckField != null) { object value = deathHeadInTruckField.GetValue(playerDeathHead); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static object? GetRoomVolumeCheck(PlayerAvatar player) { if (s_playerRoomVolumeCheckField == null) { return null; } return s_playerRoomVolumeCheckField.GetValue(player); } private static bool IsRoomVolumeInTruck(object roomVolumeCheck) { if (roomVolumeCheck == null) { return false; } FieldInfo fieldInfo = s_roomVolumeCheckInTruckField; if (fieldInfo == null || fieldInfo.DeclaringType != roomVolumeCheck.GetType()) { fieldInfo = (s_roomVolumeCheckInTruckField = AccessTools.Field(roomVolumeCheck.GetType(), "inTruck")); } bool flag = default(bool); int num; if (fieldInfo != null) { object value = fieldInfo.GetValue(roomVolumeCheck); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static FieldInfo? GetDeathHeadInTruckField(Type deathHeadType) { FieldInfo fieldInfo = s_deathHeadInTruckField; if (fieldInfo == null || fieldInfo.DeclaringType != deathHeadType) { fieldInfo = (s_deathHeadInTruckField = AccessTools.Field(deathHeadType, "inTruck")); } return fieldInfo; } private static object? GetDeathHeadRoomVolumeCheck(PlayerDeathHead deathHead) { FieldInfo fieldInfo = s_deathHeadRoomVolumeCheckField; if (fieldInfo == null || fieldInfo.DeclaringType != ((object)deathHead).GetType()) { fieldInfo = (s_deathHeadRoomVolumeCheckField = AccessTools.Field(((object)deathHead).GetType(), "roomVolumeCheck")); } return fieldInfo?.GetValue(deathHead); } private static int GetSteamIdShort(PlayerAvatar player) { return (s_playerSteamIdShortField != null && s_playerSteamIdShortField.GetValue(player) is int num) ? num : 0; } } internal static class SpectateContextHelper { private static readonly FieldInfo? s_spectatePlayerField = AccessTools.Field(typeof(SpectateCamera), "player"); private static readonly FieldInfo? s_playerDeathHeadSpectatedField = AccessTools.Field(typeof(PlayerDeathHead), "spectated"); internal static bool IsSpectatingLocalPlayerTarget() { SpectateCamera instance = SpectateCamera.instance; PlayerAvatar instance2 = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || s_spectatePlayerField == null) { return false; } object? value = s_spectatePlayerField.GetValue(instance); PlayerAvatar val = (PlayerAvatar)((value is PlayerAvatar) ? value : null); return (Object)(object)val != (Object)null && val == instance2; } internal static bool IsSpectatingLocalDeathHead() { SpectateCamera instance = SpectateCamera.instance; if ((Object)(object)instance == (Object)null || !instance.CheckState((State)2)) { return false; } PlayerDeathHead val = PlayerController.instance?.playerAvatarScript?.playerDeathHead; if ((Object)(object)val == (Object)null || s_playerDeathHeadSpectatedField == null) { return false; } return (s_playerDeathHeadSpectatedField.GetValue(val) as bool?).GetValueOrDefault(); } internal static bool IsLocalDeathHeadSpectated() { PlayerDeathHead val = PlayerController.instance?.playerAvatarScript?.playerDeathHead; if ((Object)(object)val == (Object)null || s_playerDeathHeadSpectatedField == null) { return false; } return (s_playerDeathHeadSpectatedField.GetValue(val) as bool?).GetValueOrDefault(); } } } namespace DeathHeadHopperFix.Modules.Stamina { internal sealed class StaminaRechargeModule : MonoBehaviour { private static readonly Type? s_deathHeadControllerType = AccessTools.TypeByName("DeathHeadHopper.DeathHead.DeathHeadController"); private object? _controllerInstance; private PhotonView? _photonView; private bool _isOwner; private float _rechargeAccumulator; private Rigidbody? _rb; private void Awake() { if (s_deathHeadControllerType == null) { ((Behaviour)this).enabled = false; return; } _controllerInstance = ((Component)this).GetComponent(s_deathHeadControllerType); if (_controllerInstance == null) { ((Behaviour)this).enabled = false; return; } _photonView = ((Component)this).GetComponent(); _isOwner = !SemiFunc.IsMultiplayer() || ((Object)(object)_photonView != (Object)null && _photonView.IsMine); _rb = ((Component)this).GetComponent(); } private void Update() { if (!_isOwner) { return; } if (!FeatureFlags.RechargeWithStamina) { _rechargeAccumulator = 0f; } else { if (_controllerInstance == null) { return; } _rechargeAccumulator += Time.deltaTime; if (!(_rechargeAccumulator < FeatureFlags.RechargeTickInterval)) { bool flag = !FeatureFlags.RechargeStaminaOnlyStationary || IsHeadStationary(); (bool, bool?, float, float) tuple = DHHBatteryHelper.EvaluateJumpAllowance(); if (flag || !tuple.Item1) { DHHBatteryHelper.RechargeDhhAbilityEnergy(_controllerInstance, _rechargeAccumulator); } _rechargeAccumulator = 0f; } } } private bool IsHeadStationary() { //IL_001b: 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) if ((Object)(object)_rb == (Object)null) { return true; } Vector3 velocity = _rb.velocity; return ((Vector3)(ref velocity)).sqrMagnitude < FeatureFlags.HeadStationaryVelocitySqrThreshold; } } } namespace DeathHeadHopperFix.Modules.Gameplay.Stun { internal static class ChargeHoldReleaseModule { private sealed class ChargeHoldState { public float StartTime; public bool IsHolding; public float LaunchScale = 1f; } private readonly struct DiminishingReturnsResult { public float BaseValue { get; } public float IncreasePerLevel { get; } public int AppliedLevel { get; } public int ThresholdLevel { get; } public float DiminishingFactor { get; } public int LinearLevels { get; } public int ExtraLevels { get; } public float LinearContribution { get; } public float DiminishingContribution { get; } public float DiminishingComponent { get; } public float FinalValue { get; } public DiminishingReturnsResult(float baseValue, float increasePerLevel, int appliedLevel, int thresholdLevel, float diminishingFactor, int linearLevels, int extraLevels, float linearContribution, float diminishingContribution, float diminishingComponent, float finalValue) { BaseValue = baseValue; IncreasePerLevel = increasePerLevel; AppliedLevel = appliedLevel; ThresholdLevel = thresholdLevel; DiminishingFactor = diminishingFactor; LinearLevels = linearLevels; ExtraLevels = extraLevels; LinearContribution = linearContribution; DiminishingContribution = diminishingContribution; DiminishingComponent = diminishingComponent; FinalValue = finalValue; } } private const string ChargeStrengthLogKey = "Fix:Charge.Strength"; private const string ChargePermissiveFallbackLogKey = "Fix:Charge.PermissiveFallback"; private const float RemoteReleaseCommandTag = -777f; private const float RemoteCancelCommandTag = -778f; private static ManualLogSource? s_log; private static readonly Dictionary s_chargeHoldStates = new Dictionary(); private static float s_lastLocalHoldInputStartTime; private static bool s_localHoldUiActive; private static bool s_localHoldInputPending; internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { s_log = log; PatchChargeHandlerDamageModeIfPossible(harmony, asm); PatchChargeAbilityHoldReleaseIfPossible(harmony, asm); PatchStunHandlerHoldScalingIfPossible(harmony, asm); } private static void PatchChargeHandlerDamageModeIfPossible(Harmony harmony, Assembly asm) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Expected O, but got Unknown //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Expected O, but got Unknown //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Expected O, but got Unknown //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Expected O, but got Unknown //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Expected O, but got Unknown //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0336: Expected O, but got Unknown //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Expected O, but got Unknown //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_03a1: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ChargeHandler), "ChargeWindup", new Type[1] { typeof(Vector3) }, (Type[])null); MethodInfo method = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_ChargeWindup_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo != null && method != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method2 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_ChargeWindup_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo != null && method2 != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(typeof(ChargeHandler), "ResetState", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(ChargeHandler), "FixedUpdate", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(ChargeHandler), "CancelCharge", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo5 = AccessTools.Method(typeof(ChargeHandler), "EnemyHit", (Type[])null, (Type[])null); MethodInfo methodInfo6 = AccessTools.Method(typeof(ChargeHandler), "UpdateWindupDirection", new Type[1] { typeof(Vector3) }, (Type[])null); MethodInfo methodInfo7 = AccessTools.Method(typeof(ChargeHandler), "SyncChargeStateRPC", (Type[])null, (Type[])null); MethodInfo method3 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_SyncChargeStateRPC_Prefix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method4 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_ResetState_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo2 != null && method4 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(method4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo8 = AccessTools.Method(typeof(ChargeHandler), "EndCharge", Type.EmptyTypes, (Type[])null); MethodInfo method5 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_EndCharge_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo8 != null && method5 != null) { harmony.Patch((MethodBase)methodInfo8, (HarmonyMethod)null, new HarmonyMethod(method5), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method6 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_FixedUpdate_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo3 != null && method6 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(method6), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method7 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_CancelCharge_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo4 != null && method7 != null) { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(method7), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method8 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_EnemyHit_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo5 != null && method8 != null) { harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(method8), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method9 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_UpdateWindupDirection_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo6 != null && method9 != null) { harmony.Patch((MethodBase)methodInfo6, new HarmonyMethod(method9), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method10 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_SyncChargeStateRPC_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo7 != null) { if (method3 != null) { harmony.Patch((MethodBase)methodInfo7, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (method10 != null) { harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, new HarmonyMethod(method10), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool ChargeHandler_ChargeWindup_Prefix(ChargeHandler __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (IsChargeHandlerHeadGrabbed(__instance)) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Charge.Grabbed", 30)) { Debug.Log((object)"[Fix:Charge] Charge windup blocked because the head is grabbed."); } return false; } return true; } private static void ChargeHandler_ChargeWindup_Postfix(ChargeHandler __instance) { if (!((Object)(object)__instance == (Object)null) && IsChargeState(__instance, "Windup")) { int unityObjectInstanceId = GetUnityObjectInstanceId((Object)(object)__instance); if (unityObjectInstanceId != 0) { ChargeHoldState orCreateChargeHoldState = GetOrCreateChargeHoldState(unityObjectInstanceId); orCreateChargeHoldState.StartTime = Time.time; orCreateChargeHoldState.IsHolding = true; orCreateChargeHoldState.LaunchScale = 1f; s_localHoldUiActive = true; AbilityModule.SetChargeSlotActivationProgress(0f); } } } private static void ChargeHandler_FixedUpdate_Postfix(ChargeHandler __instance) { if ((Object)(object)__instance == (Object)null) { return; } int unityObjectInstanceId = GetUnityObjectInstanceId((Object)(object)__instance); if (unityObjectInstanceId == 0) { return; } if (!IsLocalChargeHandler(__instance)) { if (s_chargeHoldStates.TryGetValue(unityObjectInstanceId, out ChargeHoldState value)) { if (!IsChargeState(__instance, "Windup")) { s_chargeHoldStates.Remove(unityObjectInstanceId); } else if (value.IsHolding) { __instance.windupTimer = Mathf.Max(0.01f, __instance.windupTime); } } return; } if (!s_chargeHoldStates.TryGetValue(unityObjectInstanceId, out ChargeHoldState value2)) { if (!IsChargeState(__instance, "Windup") || !s_localHoldInputPending) { if (!s_localHoldUiActive) { AbilityModule.SetChargeSlotActivationProgress(0f); } return; } value2 = GetOrCreateChargeHoldState(unityObjectInstanceId); value2.StartTime = ((s_lastLocalHoldInputStartTime > 0f) ? s_lastLocalHoldInputStartTime : Time.time); value2.IsHolding = true; value2.LaunchScale = 1f; s_localHoldUiActive = true; AbilityModule.SetChargeSlotActivationProgress(0f); } if (!IsChargeState(__instance, "Windup")) { s_chargeHoldStates.Remove(unityObjectInstanceId); s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); if (!s_localHoldUiActive) { AbilityModule.SetChargeSlotActivationProgress(0f); } return; } if (s_localHoldUiActive && value2.IsHolding) { float num = Mathf.Max(0.2f, FeatureFlags.ChargeAbilityHoldSeconds); float progress = Mathf.Clamp01((Time.time - value2.StartTime) / num); float minimumChargeReleaseScale = GetMinimumChargeReleaseScale(__instance); AbilityModule.SetChargeSlotActivationProgress(progress, minimumChargeReleaseScale); } else { if (!value2.IsHolding) { if (!s_localHoldUiActive) { AbilityModule.SetChargeSlotActivationProgress(0f); } if (!IsChargeState(__instance, "Windup")) { s_chargeHoldStates.Remove(unityObjectInstanceId); } return; } if (!IsChargeState(__instance, "Windup")) { s_chargeHoldStates.Remove(unityObjectInstanceId); AbilityModule.SetChargeSlotActivationProgress(0f); return; } float num2 = Mathf.Max(0.2f, FeatureFlags.ChargeAbilityHoldSeconds); float progress2 = Mathf.Clamp01((Time.time - value2.StartTime) / num2); float minimumChargeReleaseScale2 = GetMinimumChargeReleaseScale(__instance); AbilityModule.SetChargeSlotActivationProgress(progress2, minimumChargeReleaseScale2); } if (value2.IsHolding) { __instance.windupTimer = Mathf.Max(0.01f, __instance.windupTime); } } private static void PatchChargeAbilityHoldReleaseIfPossible(Harmony harmony, Assembly asm) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Expected O, but got Unknown //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ChargeAbility), "OnAbilityDown", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ChargeAbility), "OnAbilityUp", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(ChargeAbility), "OnAbilityCancel", Type.EmptyTypes, (Type[])null); if (methodInfo2 == null) { return; } MethodInfo method = typeof(ChargeHoldReleaseModule).GetMethod("ChargeAbility_OnAbilityUp_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { MethodInfo method2 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeAbility_OnAbilityDown_Postfix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method3 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeAbility_OnAbilityCancel_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo != null && method2 != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (methodInfo3 != null && method3 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void PatchStunHandlerHoldScalingIfPossible(Harmony harmony, Assembly asm) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(StunHandler), "StunDuration"); if (!(methodInfo == null)) { MethodInfo method = typeof(ChargeHoldReleaseModule).GetMethod("StunHandler_StunDuration_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void ChargeHandler_CancelCharge_Postfix(ChargeHandler __instance) { StopChargeWindupLoop(__instance); ClearChargeHoldState(__instance); } private static void ChargeHandler_SyncChargeStateRPC_Prefix(ChargeHandler __instance, ChargeState state) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 if (!((Object)(object)__instance == (Object)null) && (int)state != 1 && (int)state != 2) { StopChargeWindupLoop(__instance); ClearChargeHoldState(__instance); } } private static void ChargeHandler_SyncChargeStateRPC_Postfix(ChargeHandler __instance) { if (!((Object)(object)__instance == (Object)null) && !IsChargeState(__instance, "Windup") && !IsChargeState(__instance, "Charging")) { StopChargeWindupLoop(__instance); ClearChargeHoldState(__instance); } } private static bool ChargeHandler_EnemyHit_Prefix(ChargeHandler __instance) { if ((Object)(object)__instance == (Object)null) { return true; } int unityObjectInstanceId = GetUnityObjectInstanceId((Object)(object)__instance); if (unityObjectInstanceId == 0 || !s_chargeHoldStates.TryGetValue(unityObjectInstanceId, out ChargeHoldState value)) { return true; } __instance.enemiesHit++; int abilityLevel = __instance.AbilityLevel; int num = Mathf.FloorToInt(EvaluateStatWithDiminishingReturns(1f, 0.5f, abilityLevel, 20, 0.9f).FinalValue); int num2 = Mathf.Max(1, Mathf.RoundToInt((float)num * Mathf.Clamp01(value.LaunchScale))); if (__instance.enemiesHit >= num2) { __instance.EndCharge(); } return false; } private static bool StunHandler_StunDuration_Prefix(StunHandler __instance, ref float __result) { if ((Object)(object)__instance == (Object)null) { return true; } ChargeHandler chargeHandler = __instance.chargeHandler; if ((Object)(object)chargeHandler == (Object)null) { return true; } int unityObjectInstanceId = GetUnityObjectInstanceId((Object)(object)chargeHandler); if (unityObjectInstanceId == 0 || !s_chargeHoldStates.TryGetValue(unityObjectInstanceId, out ChargeHoldState value)) { return true; } int abilityLevel = chargeHandler.AbilityLevel; float num = 5f + 1f * (float)abilityLevel; __result = num * Mathf.Clamp01(value.LaunchScale); return false; } private static bool ChargeAbility_OnAbilityUp_Prefix() { return TryReleaseHeldCharge(); } private static void ChargeAbility_OnAbilityDown_Postfix() { s_lastLocalHoldInputStartTime = Time.time; s_localHoldInputPending = true; AbilityModule.SetChargeSlotActivationProgress(0f); ChargeHandler localChargeHandler = GetLocalChargeHandler(); if ((Object)(object)localChargeHandler != (Object)null && !IsChargeState(localChargeHandler, "Windup")) { ClearChargeHoldState(localChargeHandler); } } private static void ChargeAbility_OnAbilityCancel_Postfix() { s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); ChargeHandler localChargeHandler = GetLocalChargeHandler(); if (!((Object)(object)localChargeHandler == (Object)null)) { ClearChargeHoldState(localChargeHandler); } } private static bool TryReleaseHeldCharge() { ChargeHandler localChargeHandler = GetLocalChargeHandler(); if ((Object)(object)localChargeHandler == (Object)null) { s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); return true; } if (!IsChargeState(localChargeHandler, "Windup")) { s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); return true; } int unityObjectInstanceId = GetUnityObjectInstanceId((Object)(object)localChargeHandler); if (unityObjectInstanceId == 0) { s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); return true; } if (!s_chargeHoldStates.TryGetValue(unityObjectInstanceId, out ChargeHoldState value)) { value = GetOrCreateChargeHoldState(unityObjectInstanceId); value.StartTime = ((s_lastLocalHoldInputStartTime > 0f) ? s_lastLocalHoldInputStartTime : Time.time); value.IsHolding = true; value.LaunchScale = 1f; } if (!value.IsHolding) { return true; } if (SemiFunc.IsMultiplayer() && !SemiFunc.IsMasterClientOrSingleplayer()) { if (!ConfigSyncManager.IsRemoteHostFixCompatible()) { value.IsHolding = false; value.LaunchScale = 0f; s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); StopChargeWindupLoop(localChargeHandler); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Charge.PermissiveFallback", 120)) { Debug.Log((object)"[Fix:DHHCharge][PermissiveGate] Host fix marker missing. Sending authoritative cancel fallback."); } TrySendVanillaRemoteCancelCommand(localChargeHandler); return false; } value.IsHolding = false; s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); if (!TrySendRemoteReleaseCommand(localChargeHandler)) { StopChargeWindupLoop(localChargeHandler); TrySendVanillaRemoteCancelCommand(localChargeHandler); } return false; } float num = Mathf.Max(0.2f, FeatureFlags.ChargeAbilityHoldSeconds); float num2 = Mathf.Clamp01((Time.time - value.StartTime) / num); float minimumChargeReleaseScale = GetMinimumChargeReleaseScale(localChargeHandler); if (num2 < minimumChargeReleaseScale) { value.IsHolding = false; value.LaunchScale = 0f; s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); StopChargeWindupLoop(localChargeHandler); localChargeHandler.CancelCharge(); return false; } value.IsHolding = false; value.LaunchScale = num2; localChargeHandler.chargeStrength *= num2; localChargeHandler.maxBounces = Mathf.Max(0f, localChargeHandler.maxBounces * num2); localChargeHandler.windupTimer = -1f; s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); return true; } private static bool ChargeHandler_UpdateWindupDirection_Prefix(ChargeHandler __instance, Vector3 chargeDirection) { //IL_002c: 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) if ((Object)(object)__instance == (Object)null) { return true; } if (!SemiFunc.IsMasterClientOrSingleplayer()) { return true; } if (Mathf.Abs(chargeDirection.x - -778f) < 0.001f) { __instance.CancelCharge(); return false; } if (Mathf.Abs(chargeDirection.x - -777f) > 0.001f) { return true; } int unityObjectInstanceId = GetUnityObjectInstanceId((Object)(object)__instance); if (unityObjectInstanceId == 0 || !s_chargeHoldStates.TryGetValue(unityObjectInstanceId, out ChargeHoldState value)) { return false; } if (!IsChargeState(__instance, "Windup")) { return false; } float num = Mathf.Max(0.2f, FeatureFlags.ChargeAbilityHoldSeconds); float num2 = Mathf.Clamp01((Time.time - value.StartTime) / num); float minimumChargeReleaseScale = GetMinimumChargeReleaseScale(__instance); value.IsHolding = false; if (num2 < minimumChargeReleaseScale) { value.LaunchScale = 0f; __instance.CancelCharge(); return false; } value.LaunchScale = num2; __instance.chargeStrength *= num2; __instance.maxBounces = Mathf.Max(0f, __instance.maxBounces * num2); __instance.windupTimer = -1f; return false; } private static bool TrySendRemoteReleaseCommand(ChargeHandler chargeHandler) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) PhotonView chargePhotonView = GetChargePhotonView(chargeHandler); if ((Object)(object)chargePhotonView == (Object)null || chargePhotonView.ViewID <= 0) { return false; } if (!PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient) { return false; } chargePhotonView.RPC("UpdateWindupDirection", (RpcTarget)2, new object[1] { (object)new Vector3(-777f, 0f, 0f) }); return true; } private static bool TrySendVanillaRemoteCancelCommand(ChargeHandler chargeHandler) { PhotonView chargePhotonView = GetChargePhotonView(chargeHandler); if ((Object)(object)chargePhotonView == (Object)null || chargePhotonView.ViewID <= 0) { return false; } if (!PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient) { return false; } chargePhotonView.RPC("CancelCharge", (RpcTarget)2, Array.Empty()); return true; } private static PhotonView? GetChargePhotonView(ChargeHandler chargeHandler) { if (chargeHandler != null) { PhotonView component = ((Component)chargeHandler).GetComponent(); if ((Object)(object)component != (Object)null && component.ViewID > 0) { return component; } } return GetDhhInputManagerHeadPhotonView(); } private static PhotonView? GetDhhInputManagerHeadPhotonView() { return ((Object)(object)DHHInputManager.instance != (Object)null) ? DHHInputManager.instance.headPhotonView : null; } private static float GetMinimumChargeReleaseScale(ChargeHandler chargeHandler) { if ((Object)(object)chargeHandler == (Object)null) { return 0f; } float num = 0f; TryGetEffectiveChargeAbilityLevel(chargeHandler, out var abilityLevel); float effectiveChargeStrengthForThreshold = GetEffectiveChargeStrengthForThreshold(chargeHandler, abilityLevel); if (effectiveChargeStrengthForThreshold > 0f) { num = Mathf.Max(num, RequiredScaleForMinimumOne(effectiveChargeStrengthForThreshold)); } float effectiveMaxBouncesForThreshold = GetEffectiveMaxBouncesForThreshold(chargeHandler, abilityLevel); if (effectiveMaxBouncesForThreshold > 0f) { num = Mathf.Max(num, RequiredScaleForMinimumOne(effectiveMaxBouncesForThreshold)); } if (abilityLevel > 0) { float baseValue = 5f + 1f * (float)abilityLevel; num = Mathf.Max(num, RequiredScaleForMinimumOne(baseValue)); } if (float.IsNaN(num) || float.IsInfinity(num)) { return 1f; } return Mathf.Clamp01(num); } private static float GetEffectiveChargeStrengthForThreshold(ChargeHandler chargeHandler, int abilityLevel) { if (chargeHandler.chargeStrength > 0f) { return chargeHandler.chargeStrength; } if (abilityLevel <= 0) { return 0f; } return EvaluateStatWithDiminishingReturns(FeatureFlags.DHHChargeStrengthBaseValue, FeatureFlags.DHHChargeStrengthIncreasePerLevel, abilityLevel, FeatureFlags.DHHChargeStrengthThresholdLevel, FeatureFlags.DHHChargeStrengthDiminishingFactor).FinalValue; } private static float GetEffectiveMaxBouncesForThreshold(ChargeHandler chargeHandler, int abilityLevel) { if (chargeHandler.maxBounces > 0f) { return chargeHandler.maxBounces; } if (abilityLevel <= 0) { return 0f; } float baseValue = ((chargeHandler.baseMaxBounces > 0) ? ((float)chargeHandler.baseMaxBounces) : 3f); return Mathf.FloorToInt(EvaluateStatWithDiminishingReturns(baseValue, 0.5f, abilityLevel, 20, 0.9f).FinalValue); } private static bool TryGetEffectiveChargeAbilityLevel(ChargeHandler chargeHandler, out int abilityLevel) { abilityLevel = Mathf.Max(0, chargeHandler.AbilityLevel); if (abilityLevel > 0) { return true; } if (!SemiFunc.IsMasterClientOrSingleplayer() && IsLocalChargeHandler(chargeHandler) && TryGetLocalPlayerChargeUpgrade(out var upgrade)) { abilityLevel = Mathf.Max(0, upgrade); return true; } return abilityLevel >= 0; } private static bool TryGetLocalPlayerChargeUpgrade(out int upgrade) { upgrade = 0; PlayerAvatar instance = PlayerAvatar.instance; string text = (((Object)(object)instance != (Object)null) ? SemiFunc.PlayerGetSteamID(instance) : null); if (string.IsNullOrWhiteSpace(text)) { return false; } try { upgrade = DHHStatsManager.GetHeadChargeUpgrade(text); return true; } catch { return false; } } private static float RequiredScaleForMinimumOne(float baseValue) { if (baseValue <= 0f) { return float.PositiveInfinity; } return 1f / baseValue; } private static bool IsChargeHandlerHeadGrabbed(ChargeHandler chargeHandler) { PhysGrabObjectImpactDetector impactDetector = chargeHandler.impactDetector; if ((Object)(object)impactDetector == (Object)null) { return false; } PhysGrabObject physGrabObject = impactDetector.physGrabObject; if ((Object)(object)physGrabObject == (Object)null) { return false; } return physGrabObject.grabbed; } private static int GetUnityObjectInstanceId(Object obj) { return (obj != (Object)null) ? obj.GetInstanceID() : 0; } private static ChargeHoldState GetOrCreateChargeHoldState(int id) { if (!s_chargeHoldStates.TryGetValue(id, out ChargeHoldState value)) { value = new ChargeHoldState(); s_chargeHoldStates[id] = value; } return value; } private static bool IsChargeState(ChargeHandler chargeHandler, string stateName) { //IL_0011: 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) if ((Object)(object)chargeHandler == (Object)null) { return false; } ChargeState state = chargeHandler.State; return string.Equals(((object)(ChargeState)(ref state)).ToString(), stateName, StringComparison.Ordinal); } private static bool IsLocalChargeHandler(ChargeHandler chargeHandler) { ChargeHandler localChargeHandler = GetLocalChargeHandler(); if ((Object)(object)localChargeHandler == (Object)null) { return false; } return localChargeHandler == chargeHandler; } private static ChargeHandler? GetLocalChargeHandler() { PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance?.playerDeathHead == (Object)null) { return null; } return ((Component)instance.playerDeathHead).GetComponent()?.chargeHandler; } private static void ClearChargeHoldState(ChargeHandler? chargeHandler) { if (!((Object)(object)chargeHandler == (Object)null)) { int unityObjectInstanceId = GetUnityObjectInstanceId((Object)(object)chargeHandler); if (unityObjectInstanceId != 0) { s_chargeHoldStates.Remove(unityObjectInstanceId); } AbilityModule.SetChargeSlotActivationProgress(0f); } } private static void ChargeHandler_ResetState_Postfix(ChargeHandler __instance) { StopChargeWindupLoop(__instance); ClearChargeHoldState(__instance); if (!((Object)(object)__instance == (Object)null)) { int abilityLevel = __instance.AbilityLevel; DiminishingReturnsResult stat = EvaluateStatWithDiminishingReturns(FeatureFlags.DHHChargeStrengthBaseValue, FeatureFlags.DHHChargeStrengthIncreasePerLevel, abilityLevel, FeatureFlags.DHHChargeStrengthThresholdLevel, FeatureFlags.DHHChargeStrengthDiminishingFactor); __instance.chargeStrength = stat.FinalValue; LogChargeStrength(__instance, stat); } } private static void ChargeHandler_EndCharge_Postfix(ChargeHandler __instance) { StopChargeWindupLoop(__instance); ClearChargeHoldState(__instance); } private static void StopChargeWindupLoop(ChargeHandler? chargeHandler) { if ((Object)(object)chargeHandler == (Object)null) { return; } try { DeathHeadController controller = chargeHandler.controller; if ((Object)(object)controller == (Object)null) { return; } ChargeEffects componentInChildren = ((Component)controller).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { try { componentInChildren.StopWindupState(); } catch { } try { componentInChildren.StopChargeState(); return; } catch { return; } } AudioHandler audioHandler = controller.audioHandler; if (!((Object)(object)audioHandler == (Object)null)) { audioHandler.StopWindupSound(); } } catch { } } private static DiminishingReturnsResult EvaluateStatWithDiminishingReturns(float baseValue, float increasePerLevel, int currentLevel, int thresholdLevel, float diminishingFactor) { int num = Math.Max(0, currentLevel - 1); int num2 = Math.Max(0, thresholdLevel - 1); int num3 = Mathf.Min(num, num2); int num4 = Mathf.Max(0, num - num2); float num5 = (float)num4 * Mathf.Pow(diminishingFactor, (float)num4); float num6 = increasePerLevel * (float)num3; float num7 = increasePerLevel * num5; float finalValue = baseValue + num6 + num7; return new DiminishingReturnsResult(baseValue, increasePerLevel, currentLevel, thresholdLevel, diminishingFactor, num3, num4, num6, num7, num5, finalValue); } private static string GetHandlerLabel(object? handler, string fallback) { Component val = (Component)((handler is Component) ? handler : null); if (val != null) { return ((Object)val).name ?? ((object)val).GetType().Name; } return handler?.GetType().Name ?? fallback; } private static void LogChargeStrength(object chargeHandler, DiminishingReturnsResult stat) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Charge.Strength", 60)) { string handlerLabel = GetHandlerLabel(chargeHandler, "ChargeHandler"); string text = $"[Fix:Charge] {handlerLabel} Strength={stat.FinalValue:F3} base={stat.BaseValue:F3} inc={stat.IncreasePerLevel:F3} level={stat.AppliedLevel} fullUpgrades={stat.LinearLevels} dimUpgrades={stat.ExtraLevels} linearDelta={stat.LinearContribution:F3} dimDelta={stat.DiminishingContribution:F3} thresh={stat.ThresholdLevel} dimFactor={stat.DiminishingFactor:F3}"; ManualLogSource? obj = s_log; if (obj != null) { obj.LogInfo((object)text); } Debug.Log((object)text); } } private static Vector3 CalculateEnemyBounceNormal(Transform? self, Vector3 enemyCenterPoint) { //IL_0015: 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) //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_002b: 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_0032: 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_0041: 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) //IL_000c: 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_0049: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)self == (Object)null) { return Vector3.up; } Vector3 val = self.TransformPoint(Vector3.up * 0.3f); Vector3 val2 = val - enemyCenterPoint; Vector3 val3 = Vector3.ProjectOnPlane(val2, Vector3.up); return ((Vector3)(ref val3)).normalized; } } [HarmonyPatch(typeof(StunHandler), "HandleStun")] internal static class StunHandlerReleasePatch { [CompilerGenerated] private sealed class d__3 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private CodeInstruction <>2__current; private int <>l__initialThreadId; private IEnumerable instructions; public IEnumerable <>3__instructions; private IEnumerator <>s__1; private CodeInstruction 5__2; CodeInstruction IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__3(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || (uint)(num - 1) <= 1u) { try { } finally { <>m__Finally1(); } } <>s__1 = null; 5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>s__1 = instructions.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; case 2: <>1__state = -3; 5__2 = null; break; } if (<>s__1.MoveNext()) { 5__2 = <>s__1.Current; if (s_targetCall != null && s_replacement != null && CodeInstructionExtensions.Calls(5__2, s_targetCall)) { <>2__current = new CodeInstruction(OpCodes.Call, (object)s_replacement); <>1__state = 1; return true; } <>2__current = 5__2; <>1__state = 2; return true; } <>m__Finally1(); <>s__1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>s__1 != null) { <>s__1.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__3 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__3(0); } d__.instructions = <>3__instructions; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private const int ReleaseObjectViewId = -1; private static readonly MethodInfo? s_targetCall = AccessTools.Method(typeof(StunHandler), "PhysObjectHurt", new Type[3] { typeof(PhysGrabObject), typeof(BreakImpact), typeof(float) }, (Type[])null); private static readonly MethodInfo? s_replacement = AccessTools.Method(typeof(StunHandlerReleasePatch), "CustomPhysObjectHurt", new Type[4] { typeof(StunHandler), typeof(PhysGrabObject), typeof(BreakImpact), typeof(float) }, (Type[])null); [IteratorStateMachine(typeof(d__3))] [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__3(-2) { <>3__instructions = instructions }; } private static void CustomPhysObjectHurt(StunHandler self, PhysGrabObject physGrabObject, BreakImpact impact, float hitForce) { //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_0013: 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_0015: 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_0029: Expected I4, but got Unknown //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) if ((Object)(object)physGrabObject == (Object)null) { return; } switch (impact - 1) { case 0: physGrabObject.lightBreakImpulse = true; break; case 1: physGrabObject.mediumBreakImpulse = true; break; case 2: physGrabObject.heavyBreakImpulse = true; break; } if (!(hitForce >= 5f) || physGrabObject.playerGrabbing.Count <= 0) { return; } foreach (PhysGrabber item in physGrabObject.playerGrabbing.ToList()) { if (!((Object)(object)item == (Object)null)) { if (!SemiFunc.IsMultiplayer()) { item.ReleaseObjectRPC(true, 2f, -1, default(PhotonMessageInfo)); continue; } item.photonView.RPC("ReleaseObjectRPC", (RpcTarget)0, new object[3] { false, 1f, -1 }); } } } } } namespace DeathHeadHopperFix.Modules.Gameplay.Spectate { public static class AbilityBarVisibilityAnchor { private static readonly HashSet ActiveDemands = new HashSet(); public static bool HasExternalDemand() { lock (ActiveDemands) { return ActiveDemands.Count > 0; } } public static void SetExternalDemand(string sourceId, bool active) { if (string.IsNullOrWhiteSpace(sourceId)) { return; } lock (ActiveDemands) { if (active) { ActiveDemands.Add(sourceId); } else { ActiveDemands.Remove(sourceId); } } } public static void ClearExternalDemands() { lock (ActiveDemands) { ActiveDemands.Clear(); } } } [HarmonyPatch(typeof(SpectateCamera), "LateUpdate")] internal static class AbilityBarVisibilityModule { private static Type? s_dhhAbilityManagerType; private static PropertyInfo? s_dhhAbilityManagerInstanceProperty; private static MethodInfo? s_hasEquippedAbilityMethod; private static Type? s_abilityUiType; private static FieldInfo? s_abilityUiInstanceField; private static MethodInfo? s_abilityUiShowMethod; private static Type? s_abilityEnergyUiType; private static FieldInfo? s_abilityEnergyUiInstanceField; private static MethodInfo? s_abilityEnergyUiShowMethod; private static bool? s_lastShouldShow; [HarmonyPostfix] private static void LateUpdatePostfix() { if (InternalDebugFlags.DisableAbilityPatches) { return; } if (!ShouldEvaluateInCurrentContext()) { s_lastShouldShow = null; return; } if (!ShouldShowAbilityBar()) { if (FeatureFlags.DebugLogging && s_lastShouldShow.GetValueOrDefault()) { Debug.Log((object)"[Fix:AbilityBar] hidden by policy (native=false, external=false)."); } s_lastShouldShow = false; return; } if (FeatureFlags.DebugLogging && !s_lastShouldShow.GetValueOrDefault()) { bool flag = HasNativeEquippedAbility(); bool flag2 = HasExternalAbilityUiDemand(); Debug.Log((object)$"[Fix:AbilityBar] show by policy (native={flag}, external={flag2})."); } TryShowAbilityUi(); TryShowAbilityEnergyUi(); s_lastShouldShow = true; } private static bool ShouldShowAbilityBar() { return HasNativeEquippedAbility() || HasExternalAbilityUiDemand(); } private static bool HasExternalAbilityUiDemand() { return AbilityBarVisibilityAnchor.HasExternalDemand(); } private static bool ShouldEvaluateInCurrentContext() { if (!SemiFunc.RunIsLevel() && !SemiFunc.RunIsShop()) { return false; } if (HasExternalAbilityUiDemand()) { return true; } return SpectateContextHelper.IsLocalDeathHeadSpectated(); } private static bool HasNativeEquippedAbility() { ResolveDhhAbilityManagerReflection(); if (s_dhhAbilityManagerInstanceProperty == null || s_hasEquippedAbilityMethod == null) { return false; } object value = s_dhhAbilityManagerInstanceProperty.GetValue(null); if (value == null) { return false; } return (s_hasEquippedAbilityMethod.Invoke(value, null) as bool?).GetValueOrDefault(); } private static void TryShowAbilityUi() { ResolveAbilityUiReflection(); object obj = s_abilityUiInstanceField?.GetValue(null); if (obj == null || s_abilityUiShowMethod == null) { return; } try { s_abilityUiShowMethod.Invoke(obj, null); } catch { } } private static void TryShowAbilityEnergyUi() { ResolveAbilityEnergyUiReflection(); object obj = s_abilityEnergyUiInstanceField?.GetValue(null); if (obj == null || s_abilityEnergyUiShowMethod == null) { return; } try { s_abilityEnergyUiShowMethod.Invoke(obj, null); } catch { } } private static void ResolveDhhAbilityManagerReflection() { if ((object)s_dhhAbilityManagerType == null) { s_dhhAbilityManagerType = AccessTools.TypeByName("DeathHeadHopper.Managers.DHHAbilityManager"); } if (!(s_dhhAbilityManagerType == null)) { if ((object)s_dhhAbilityManagerInstanceProperty == null) { s_dhhAbilityManagerInstanceProperty = s_dhhAbilityManagerType.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_hasEquippedAbilityMethod == null) { s_hasEquippedAbilityMethod = s_dhhAbilityManagerType.GetMethod("HasEquippedAbility", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } } private static void ResolveAbilityUiReflection() { if ((object)s_abilityUiType == null) { s_abilityUiType = AccessTools.TypeByName("DeathHeadHopper.UI.AbilityUI"); } if (!(s_abilityUiType == null)) { if ((object)s_abilityUiInstanceField == null) { s_abilityUiInstanceField = s_abilityUiType.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_abilityUiShowMethod == null) { s_abilityUiShowMethod = s_abilityUiType.GetMethod("Show", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } } private static void ResolveAbilityEnergyUiReflection() { if ((object)s_abilityEnergyUiType == null) { s_abilityEnergyUiType = AccessTools.TypeByName("DeathHeadHopper.UI.AbilityEnergyUI"); } if (!(s_abilityEnergyUiType == null)) { if ((object)s_abilityEnergyUiInstanceField == null) { s_abilityEnergyUiInstanceField = s_abilityEnergyUiType.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_abilityEnergyUiShowMethod == null) { s_abilityEnergyUiShowMethod = s_abilityEnergyUiType.GetMethod("Show", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } } } internal static class DeathMinimapBridge { internal static void Reset() { } internal static void Tick() { } } [HarmonyPatch(typeof(SpectateCamera), "PlayerSwitch")] internal static class SpectateDeadPlayersModule { private sealed class CameraSnapshot { public string Reason = string.Empty; public bool LastChanceEnabled; public bool LastChanceActive; public bool AllPlayersDisabled; public string CurrentState = string.Empty; public string PlayerName = string.Empty; public string OverrideName = string.Empty; public bool CurrentPlayerDisabled; public bool LocalPlayerDisabled; public bool LocalPlayerDeadSet; public string? MainCameraName; public string? SpectateCameraName; public float? MainNearClip; public float? SpectateNearClip; public float? MainFov; public float? SpectateFov; public Rect? MainRect; public string? MainRectKey; public int MainCullingMask; public string MainClearFlags = string.Empty; public string? MainParentName; public float? FogStartDistance; public float? MainMinusSpectateNearClip; public bool HasMeaningfulDelta(CameraSnapshot other) { return !NullableFloatEquals(MainNearClip, other.MainNearClip) || !NullableFloatEquals(SpectateNearClip, other.SpectateNearClip) || !NullableFloatEquals(MainFov, other.MainFov) || !NullableRectEquals(MainRect, other.MainRect) || MainCullingMask != other.MainCullingMask || !string.Equals(MainClearFlags, other.MainClearFlags, StringComparison.Ordinal) || !string.Equals(MainParentName, other.MainParentName, StringComparison.Ordinal) || !NullableFloatEquals(FogStartDistance, other.FogStartDistance) || !NullableFloatEquals(MainMinusSpectateNearClip, other.MainMinusSpectateNearClip) || !string.Equals(MainCameraName, other.MainCameraName, StringComparison.Ordinal) || !string.Equals(SpectateCameraName, other.SpectateCameraName, StringComparison.Ordinal); } private static bool NullableFloatEquals(float? left, float? right) { if (!left.HasValue && !right.HasValue) { return true; } if (!left.HasValue || !right.HasValue) { return false; } return Mathf.Abs(left.Value - right.Value) < 0.0001f; } private static bool NullableRectEquals(Rect? left, Rect? right) { //IL_0049: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (!left.HasValue && !right.HasValue) { return true; } if (!left.HasValue || !right.HasValue) { return false; } Rect value = left.Value; Rect value2 = right.Value; return Mathf.Abs(((Rect)(ref value)).x - ((Rect)(ref value2)).x) < 0.0001f && Mathf.Abs(((Rect)(ref value)).y - ((Rect)(ref value2)).y) < 0.0001f && Mathf.Abs(((Rect)(ref value)).width - ((Rect)(ref value2)).width) < 0.0001f && Mathf.Abs(((Rect)(ref value)).height - ((Rect)(ref value2)).height) < 0.0001f; } } private sealed class NearClipSnapshot { public string Reason = string.Empty; public bool LastChanceEnabled; public bool LastChanceActive; public bool AllPlayersDisabled; public string CurrentState = string.Empty; public string PlayerName = string.Empty; public string OverrideName = string.Empty; public bool CurrentPlayerDisabled; public bool LocalPlayerDisabled; public bool LocalPlayerDeadSet; public string? MainCameraName; public string? SpectateCameraName; public float? MainNearClip; public float? SpectateNearClip; public float? FogStartDistance; public float? MainMinusSpectateNearClip; public bool HasMeaningfulDelta(NearClipSnapshot other) { return !NullableFloatEquals(MainNearClip, other.MainNearClip) || !NullableFloatEquals(SpectateNearClip, other.SpectateNearClip) || !NullableFloatEquals(FogStartDistance, other.FogStartDistance) || !NullableFloatEquals(MainMinusSpectateNearClip, other.MainMinusSpectateNearClip) || !string.Equals(MainCameraName, other.MainCameraName, StringComparison.Ordinal) || !string.Equals(SpectateCameraName, other.SpectateCameraName, StringComparison.Ordinal); } private static bool NullableFloatEquals(float? left, float? right) { if (!left.HasValue && !right.HasValue) { return true; } if (!left.HasValue || !right.HasValue) { return false; } return Mathf.Abs(left.Value - right.Value) < 0.0001f; } } [CompilerGenerated] private sealed class d__31 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public SpectateCamera spectate; public CameraSnapshot baseline; private CameraSnapshot 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__31(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(3f); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)spectate == (Object)null || !FeatureFlags.DebugLogging) { return false; } 5__1 = CaptureCameraSnapshot(spectate, baseline.Reason); if (!5__1.HasMeaningfulDelta(baseline)) { return false; } if (!LogCameraSnapshot("delayed", 5__1)) { return false; } LogCameraDelta(baseline, 5__1); 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(); } } [CompilerGenerated] private sealed class d__30 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public SpectateCamera spectate; public CameraSnapshot baseline; private CameraSnapshot 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__30(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)spectate == (Object)null || !FeatureFlags.DebugLogging) { return false; } 5__1 = CaptureCameraSnapshot(spectate, baseline.Reason); if (!5__1.HasMeaningfulDelta(baseline)) { return false; } LogCameraDelta(baseline, 5__1); 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(); } } [CompilerGenerated] private sealed class d__39 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public SpectateCamera spectate; public NearClipSnapshot baseline; private NearClipSnapshot 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__39(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(3f); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)spectate == (Object)null || !FeatureFlags.DebugLogging) { return false; } 5__1 = CaptureNearClipSnapshot(spectate, baseline.Reason); if (!5__1.HasMeaningfulDelta(baseline)) { return false; } if (!LogNearClipSnapshot("delayed", 5__1)) { return false; } LogNearClipDelta(baseline, 5__1); 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(); } } [CompilerGenerated] private sealed class d__38 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public SpectateCamera spectate; public NearClipSnapshot baseline; private NearClipSnapshot 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__38(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)spectate == (Object)null || !FeatureFlags.DebugLogging) { return false; } 5__1 = CaptureNearClipSnapshot(spectate, baseline.Reason); if (!5__1.HasMeaningfulDelta(baseline)) { return false; } LogNearClipDelta(baseline, 5__1); 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(); } } [CompilerGenerated] private sealed class d__25 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public SpectateCamera spectate; public string reason; private int 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__25(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(3f); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)spectate == (Object)null || !IsDeadPlayersSpectateEnabledNow() || !ShouldApplyDhhFovRecovery()) { return false; } 5__1 = FeatureFlags.DHHSpectateDefaultFov; TryRestoreDhhFov(spectate, 5__1, reason); 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 ModuleId = "DeathHeadHopperFix.Spectate.DeadPlayers"; private static PlayerAvatar? s_stateNormalPatchedPlayer; private static Transform? s_stateNormalOriginalSpectatePoint; private static Transform? s_stateNormalOrbitProxy; private static string? s_lastNearClipSnapshotKey; private static string? s_lastCameraSnapshotKey; private static int s_lastFovRecoveryFrame = -1; [HarmonyPrefix] private static bool PlayerSwitchPrefix(SpectateCamera __instance, bool _next) { if (ShouldBlockJumpDrivenPlayerSwitch(_next)) { return false; } if (ShouldBlockPlayerSwitchForLastChance()) { return false; } if ((Object)(object)__instance == (Object)null) { return true; } List list = GameDirector.instance?.PlayerList; if (list == null || list.Count == 0) { return true; } if (IsDeadPlayersSpectateEnabledNow()) { TraceCameraActivation(__instance, "PlayerSwitchPrefix"); return HandleDeadPlayersSpectateSwitch(__instance, list, _next); } return HandleVanillaEquivalentPlayerSwitch(__instance, list, _next); } private static bool HandleDeadPlayersSpectateSwitch(SpectateCamera spectate, IList playerList, bool next) { TraceCameraActivation(spectate, "HandleDeadPlayersSpectateSwitch"); bool flag = true; foreach (PlayerAvatar player in playerList) { if ((Object)(object)player == (Object)null || player.isDisabled) { continue; } flag = false; break; } if (!flag) { return true; } TryPlayerSwitch(spectate, playerList, next, includeDisabled: true); return false; } private static bool HandleVanillaEquivalentPlayerSwitch(SpectateCamera spectate, IList playerList, bool next) { if (playerList.All((PlayerAvatar p) => (Object)(object)p == (Object)null || p.isDisabled)) { return false; } if (TryPlayerSwitch(spectate, playerList, next, includeDisabled: false)) { return false; } return true; } private static bool ShouldBlockJumpDrivenPlayerSwitch(bool next) { return next && SemiFunc.InputDown((InputKey)1) && !SemiFunc.InputDown((InputKey)23); } [HarmonyPatch(typeof(SpectateCamera), "StateNormal")] [HarmonyPrefix] private static void StateNormalPrefix(SpectateCamera __instance) { //IL_00ac: 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_00e1: 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_00e4: 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_00c7: 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) //IL_00d7: 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) if ((Object)(object)__instance == (Object)null || !IsDeadPlayersSpectateEnabledNow()) { return; } PlayerAvatar player = __instance.player; if ((Object)(object)player == (Object)null || player == PlayerAvatar.instance || !player.isDisabled || !TryGetDeathHeadAnchor(player, out var anchor)) { return; } Transform spectatePoint = player.spectatePoint; if ((Object)(object)spectatePoint != (Object)null) { Transform val = EnsureStateNormalOrbitProxy(); if ((Object)(object)val == (Object)null) { return; } Vector3 val2 = Vector3.zero; if ((Object)(object)((Component)player).transform != (Object)null) { val2 = spectatePoint.position - ((Component)player).transform.position; } val.position = anchor + val2; val.rotation = spectatePoint.rotation; s_stateNormalPatchedPlayer = player; s_stateNormalOriginalSpectatePoint = spectatePoint; player.spectatePoint = val; } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.Anchor", 120)) { Debug.Log((object)("[SpectateDeadPlayers] Orbit source moved to DeathHead center for " + GetPlayerName(player))); } } [HarmonyPatch(typeof(SpectateCamera), "StateNormal")] [HarmonyPostfix] private static void StateNormalPostfix(SpectateCamera __instance) { if ((Object)(object)s_stateNormalPatchedPlayer == (Object)null) { HandleLastChanceStateNormalPostfix(__instance); MaintainDhhFovState(__instance); return; } if ((Object)(object)s_stateNormalOriginalSpectatePoint != (Object)null) { s_stateNormalPatchedPlayer.spectatePoint = s_stateNormalOriginalSpectatePoint; } s_stateNormalPatchedPlayer = null; s_stateNormalOriginalSpectatePoint = null; HandleLastChanceStateNormalPostfix(__instance); MaintainDhhFovState(__instance); } [HarmonyPatch(typeof(SpectateCamera), "UpdateState")] [HarmonyPrefix] private static bool UpdateStatePrefix(SpectateCamera __instance, State _state) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (!LastChanceInteropBridge.IsLastChanceModeEnabled() || (Object)(object)__instance == (Object)null) { return true; } if ((int)_state != 2) { return true; } if (LastChanceInteropBridge.IsLastChanceActive()) { return false; } return !LastChanceInteropBridge.AllPlayersDisabled(); } private static bool IsDeadPlayersSpectateEnabledNow() { if (!LastChanceInteropBridge.IsSpectateDeadPlayersEnabled()) { return false; } string text = LastChanceInteropBridge.GetSpectateDeadPlayersMode().Trim(); if (text.Equals("Disabled", StringComparison.OrdinalIgnoreCase)) { return false; } if (text.Equals("LastChanceOnly", StringComparison.OrdinalIgnoreCase)) { return LastChanceInteropBridge.IsLastChanceModeEnabled() && LastChanceInteropBridge.IsLastChanceActive() && IsLocalPlayerDeadOrDisabled(); } return true; } private static bool TryPlayerSwitch(SpectateCamera spectate, IList players, bool next, bool includeDisabled) { //IL_011c: 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_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0167: 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_0170: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0184: 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) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: 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) if (players.Count == 0) { return false; } PlayerAvatar player = spectate.player; PlayerAvatar val = spectate.playerOverride; Transform normalTransformPivot = spectate.normalTransformPivot; Transform normalTransformDistance = spectate.normalTransformDistance; if ((Object)(object)normalTransformPivot == (Object)null || (Object)(object)normalTransformDistance == (Object)null) { return false; } int num = spectate.currentPlayerListIndex; int count = players.Count; for (int i = 0; i < count; i++) { num = (next ? ((num + 1) % count) : ((num - 1 + count) % count)); PlayerAvatar val2 = players[num]; if ((Object)(object)val2 == (Object)null || ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)(object)val)) { continue; } val = null; if ((Object)(object)player == (Object)(object)val2 || (Object)(object)val2.spectatePoint == (Object)null || (!includeDisabled && val2.isDisabled)) { continue; } spectate.playerOverride = null; spectate.currentPlayerListIndex = num; spectate.player = val2; normalTransformPivot.position = val2.spectatePoint.position; float num2 = (spectate.normalAimHorizontal = ((Component)val2).transform.eulerAngles.y); spectate.normalAimVertical = 0f; normalTransformPivot.rotation = Quaternion.Euler(0f, num2, 0f); Quaternion localRotation = normalTransformPivot.localRotation; float x = ((Quaternion)(ref localRotation)).eulerAngles.x; localRotation = normalTransformPivot.localRotation; normalTransformPivot.localRotation = Quaternion.Euler(x, ((Quaternion)(ref localRotation)).eulerAngles.y, 0f); normalTransformDistance.localPosition = new Vector3(0f, 0f, -2f); ((Component)spectate).transform.position = normalTransformDistance.position; ((Component)spectate).transform.rotation = normalTransformDistance.rotation; if (SemiFunc.IsMultiplayer()) { SemiFunc.HUDSpectateSetName(GetPlayerName(val2)); } SemiFunc.LightManagerSetCullTargetTransform(((Component)val2).transform); spectate.CameraTeleportImpulse(); spectate.normalMaxDistance = 3f; PlayerController instance = PlayerController.instance; if (instance != null) { PlayerAvatar playerAvatarScript = instance.playerAvatarScript; if (playerAvatarScript != null) { PlayerLocalCamera localCamera = playerAvatarScript.localCamera; if (localCamera != null) { localCamera.Teleported(); } } } return true; } spectate.playerOverride = null; return false; } private static string GetPlayerName(PlayerAvatar? player) { if ((Object)(object)player == (Object)null) { return "unknown"; } return player.playerName ?? "unknown"; } private static bool TryGetDeathHeadAnchor(PlayerAvatar player, out Vector3 anchor) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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) anchor = default(Vector3); PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead == (Object)null) { return false; } anchor = ((Component)playerDeathHead).transform.position; PhysGrabObject physGrabObject = playerDeathHead.physGrabObject; if ((Object)(object)physGrabObject != (Object)null) { anchor = physGrabObject.centerPoint; } return true; } private static bool IsLocalPlayerDeadOrDisabled() { PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null) { return false; } if (instance.isDisabled) { return true; } if (instance.deadSet) { return true; } return false; } private static Transform? EnsureStateNormalOrbitProxy() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown if ((Object)(object)s_stateNormalOrbitProxy != (Object)null) { return s_stateNormalOrbitProxy; } GameObject val = GameObject.Find("DHHFix.SpectateDeadPlayers.OrbitProxy"); if ((Object)(object)val == (Object)null) { val = new GameObject("DHHFix.SpectateDeadPlayers.OrbitProxy"); Object.DontDestroyOnLoad((Object)(object)val); } s_stateNormalOrbitProxy = val.transform; return s_stateNormalOrbitProxy; } private static void HandleLastChanceStateNormalPostfix(SpectateCamera __instance) { if (!LastChanceInteropBridge.IsLastChanceModeEnabled()) { return; } if (!LastChanceInteropBridge.IsLastChanceActive()) { LastChanceInteropBridge.ResetSpectateForceState(); return; } if (!LastChanceInteropBridge.AllPlayersDisabled()) { LastChanceInteropBridge.ResetSpectateForceState(); return; } TraceCameraActivation(__instance, "LastChanceStateNormalPostfix"); if (LastChanceInteropBridge.ShouldForceLocalDeathHeadSpectate()) { if ((Object)(object)__instance != (Object)null) { LastChanceInteropBridge.EnsureSpectatePlayerLocal(__instance); } LastChanceInteropBridge.ForceDeathHeadSpectateIfPossible(); } LastChanceInteropBridge.DebugLogState(__instance); } private static void TraceCameraActivation(SpectateCamera? spectate, string reason) { if (!((Object)(object)spectate == (Object)null)) { CameraSnapshot cameraSnapshot = CaptureCameraSnapshot(spectate, reason); MaintainDhhFovState(spectate); StartDhhFovRecovery(spectate, cameraSnapshot); if (FeatureFlags.DebugLogging && LogCameraSnapshot("activation", cameraSnapshot)) { ((MonoBehaviour)spectate).StartCoroutine(LogCameraFollowUp(spectate, cameraSnapshot)); ((MonoBehaviour)spectate).StartCoroutine(LogCameraDelayedFollowUp(spectate, cameraSnapshot)); } } } private static void StartDhhFovRecovery(SpectateCamera spectate, CameraSnapshot snapshot) { if (!((Object)(object)spectate == (Object)null) && ShouldApplyDhhFovRecovery() && Time.frameCount != s_lastFovRecoveryFrame) { s_lastFovRecoveryFrame = Time.frameCount; ((MonoBehaviour)spectate).StartCoroutine(RunDhhFovRecovery(spectate, snapshot.Reason)); } } private static void MaintainDhhFovState(SpectateCamera? spectate) { if (!((Object)(object)spectate == (Object)null) && IsDeadPlayersSpectateEnabledNow() && ShouldApplyDhhFovRecovery()) { int dHHSpectateDefaultFov = FeatureFlags.DHHSpectateDefaultFov; if (NeedsDhhFovRestore(spectate, dHHSpectateDefaultFov)) { ApplyDhhFovState(spectate, dHHSpectateDefaultFov); } } } [HarmonyPatch(typeof(SpectateCamera), "LateUpdate")] [HarmonyPostfix] private static void LateUpdatePostfix(SpectateCamera __instance) { MaintainDhhFovState(__instance); } [IteratorStateMachine(typeof(d__25))] private static IEnumerator RunDhhFovRecovery(SpectateCamera spectate, string reason) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__25(0) { spectate = spectate, reason = reason }; } private static bool NeedsDhhFovRestore(SpectateCamera spectate, float targetFov) { if (targetFov <= 0f) { return false; } Camera main = Camera.main; CameraZoom instance = CameraZoom.Instance; float? num = (((Object)(object)main != (Object)null) ? new float?(main.fieldOfView) : null); float? num2 = (((Object)(object)instance != (Object)null) ? new float?(instance.playerZoomDefault) : null); float? num3 = (((Object)(object)instance != (Object)null) ? new float?(instance.zoomCurrent) : null); float? num4 = (((Object)(object)instance != (Object)null) ? new float?(instance.zoomNew) : null); if (!num.HasValue || num.Value <= 0.01f || Mathf.Abs(num.Value - targetFov) > 0.01f) { return true; } if (!num2.HasValue || Mathf.Abs(num2.Value - targetFov) > 0.01f) { return true; } if (!num3.HasValue || Mathf.Abs(num3.Value - targetFov) > 0.01f) { return true; } if (!num4.HasValue || Mathf.Abs(num4.Value - targetFov) > 0.01f) { return true; } return Mathf.Abs(spectate.cameraFieldOfView - targetFov) > 0.01f; } private static bool TryRestoreDhhFov(SpectateCamera spectate, float targetFov, string reason) { if (targetFov <= 0f) { return false; } Camera main = Camera.main; Camera mainCamera = spectate.MainCamera; CameraZoom instance = CameraZoom.Instance; float? num = (((Object)(object)main != (Object)null) ? new float?(main.fieldOfView) : null); float? num2 = (((Object)(object)instance != (Object)null) ? new float?(instance.playerZoomDefault) : null); float? num3 = (((Object)(object)instance != (Object)null) ? new float?(instance.zoomCurrent) : null); float? num4 = (((Object)(object)instance != (Object)null) ? new float?(instance.zoomNew) : null); float cameraFieldOfView = spectate.cameraFieldOfView; bool flag = !num.HasValue || num.Value <= 0.01f || Mathf.Abs(num.Value - targetFov) > 0.01f; flag |= !num2.HasValue || Mathf.Abs(num2.Value - targetFov) > 0.01f; flag |= !num3.HasValue || Mathf.Abs(num3.Value - targetFov) > 0.01f; flag |= !num4.HasValue || Mathf.Abs(num4.Value - targetFov) > 0.01f; if (!(flag | (Mathf.Abs(cameraFieldOfView - targetFov) > 0.01f))) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.DhhFov.Check3", 30)) { Debug.Log((object)("[SpectateDeadPlayers][DhhFov] " + string.Format("phase={0} t=3s current={1} target={2:0.###} restored=False ", reason, num.HasValue ? num.Value.ToString("0.###") : "n/a", targetFov) + "default=" + (num2.HasValue ? num2.Value.ToString("0.###") : "n/a") + " currentZoom=" + (num3.HasValue ? num3.Value.ToString("0.###") : "n/a") + " newZoom=" + (num4.HasValue ? num4.Value.ToString("0.###") : "n/a") + " cameraFieldOfView=" + cameraFieldOfView.ToString("0.###"))); } return false; } ApplyDhhFovState(spectate, targetFov); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.DhhFov.Restore3", 30)) { Debug.Log((object)("[SpectateDeadPlayers][DhhFov] " + string.Format("phase={0} t=3s current={1} target={2:0.###} restored=True ", reason, num.HasValue ? num.Value.ToString("0.###") : "n/a", targetFov) + "default=" + (((Object)(object)instance != (Object)null) ? instance.playerZoomDefault.ToString("0.###") : "n/a") + " currentZoom=" + (((Object)(object)instance != (Object)null) ? instance.zoomCurrent.ToString("0.###") : "n/a") + " newZoom=" + (((Object)(object)instance != (Object)null) ? instance.zoomNew.ToString("0.###") : "n/a") + " cameraFieldOfView=" + spectate.cameraFieldOfView.ToString("0.###") + " mainCam=" + (((Object)(object)main != (Object)null) ? ((Object)main).name : "null") + " spectateCam=" + (((Object)(object)mainCamera != (Object)null) ? ((Object)mainCamera).name : "null"))); } return true; } private static void ApplyDhhFovState(SpectateCamera spectate, float targetFov) { if ((Object)(object)spectate == (Object)null || targetFov <= 0f) { return; } Camera main = Camera.main; Camera mainCamera = spectate.MainCamera; Camera topCamera = spectate.TopCamera; CameraZoom instance = CameraZoom.Instance; if ((Object)(object)instance != (Object)null) { instance.playerZoomDefault = targetFov; instance.zoomPrev = targetFov; instance.zoomCurrent = targetFov; instance.zoomNew = targetFov; if (instance.OverrideActive || instance.OverrideZoomTimer > 0f) { instance.OverrideZoomSet(targetFov, 0.1f, 3f, 3f, ((Component)spectate).gameObject, 150); } } if ((Object)(object)main != (Object)null) { main.fieldOfView = targetFov; } if ((Object)(object)mainCamera != (Object)null) { mainCamera.fieldOfView = targetFov; } if ((Object)(object)topCamera != (Object)null) { topCamera.fieldOfView = targetFov; } spectate.cameraFieldOfView = targetFov; } private static bool ShouldApplyDhhFovRecovery() { return (float)FeatureFlags.DHHSpectateDefaultFov > 0f; } [IteratorStateMachine(typeof(d__30))] private static IEnumerator LogCameraFollowUp(SpectateCamera spectate, CameraSnapshot baseline) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__30(0) { spectate = spectate, baseline = baseline }; } [IteratorStateMachine(typeof(d__31))] private static IEnumerator LogCameraDelayedFollowUp(SpectateCamera spectate, CameraSnapshot baseline) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__31(0) { spectate = spectate, baseline = baseline }; } private static bool LogCameraSnapshot(string phase, CameraSnapshot snapshot) { if (!FeatureFlags.DebugLogging) { return false; } string key = "Spectate.DeadPlayers.Camera." + phase; string text = $"{snapshot.Reason}|{snapshot.LastChanceEnabled}|{snapshot.LastChanceActive}|{snapshot.AllPlayersDisabled}|{snapshot.CurrentPlayerDisabled}|{snapshot.LocalPlayerDisabled}|{snapshot.LocalPlayerDeadSet}|{snapshot.CurrentState}|{snapshot.PlayerName}|{snapshot.OverrideName}|{snapshot.MainCameraName}|{snapshot.SpectateCameraName}|{snapshot.MainNearClip}|{snapshot.SpectateNearClip}|{snapshot.MainFov}|{snapshot.SpectateFov}|{snapshot.MainRectKey}|{snapshot.MainCullingMask}|{snapshot.MainClearFlags}|{snapshot.MainParentName}"; if (text == s_lastCameraSnapshotKey) { return false; } s_lastCameraSnapshotKey = text; if (!LogLimiter.ShouldLog(key, 30)) { return false; } Debug.Log((object)("[SpectateDeadPlayers][Camera] phase=" + phase + " reason=" + snapshot.Reason + " " + $"lastChanceEnabled={snapshot.LastChanceEnabled} " + $"lastChanceActive={snapshot.LastChanceActive} " + $"allPlayersDisabled={snapshot.AllPlayersDisabled} " + $"currentPlayerDisabled={snapshot.CurrentPlayerDisabled} " + $"localPlayerDisabled={snapshot.LocalPlayerDisabled} " + $"localPlayerDeadSet={snapshot.LocalPlayerDeadSet} " + "state=" + snapshot.CurrentState + " player=" + snapshot.PlayerName + " override=" + snapshot.OverrideName + " mainCam=" + (snapshot.MainCameraName ?? "null") + " mainNear=" + (snapshot.MainNearClip.HasValue ? snapshot.MainNearClip.Value.ToString("0.###") : "n/a") + " mainFov=" + (snapshot.MainFov.HasValue ? snapshot.MainFov.Value.ToString("0.###") : "n/a") + " mainRect=" + (snapshot.MainRectKey ?? "n/a") + " " + $"mainMask={snapshot.MainCullingMask} " + "mainClear=" + snapshot.MainClearFlags + " mainParent=" + (snapshot.MainParentName ?? "null") + " spectateCam=" + (snapshot.SpectateCameraName ?? "null") + " spectateNear=" + (snapshot.SpectateNearClip.HasValue ? snapshot.SpectateNearClip.Value.ToString("0.###") : "n/a") + " spectateFov=" + (snapshot.SpectateFov.HasValue ? snapshot.SpectateFov.Value.ToString("0.###") : "n/a") + " fogStart=" + (snapshot.FogStartDistance.HasValue ? snapshot.FogStartDistance.Value.ToString("0.###") : "n/a") + " mainMinusSpectate=" + (snapshot.MainMinusSpectateNearClip.HasValue ? snapshot.MainMinusSpectateNearClip.Value.ToString("0.###") : "n/a"))); return true; } private static void LogCameraDelta(CameraSnapshot baseline, CameraSnapshot current) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.Camera.Delta", 30)) { Debug.Log((object)("[SpectateDeadPlayers][CameraDelta] reason=" + baseline.Reason + " mainNear=" + FormatFloat(baseline.MainNearClip) + "->" + FormatFloat(current.MainNearClip) + " delta=" + FormatDelta(baseline.MainNearClip, current.MainNearClip) + " spectateNear=" + FormatFloat(baseline.SpectateNearClip) + "->" + FormatFloat(current.SpectateNearClip) + " delta=" + FormatDelta(baseline.SpectateNearClip, current.SpectateNearClip) + " mainFov=" + FormatFloat(baseline.MainFov) + "->" + FormatFloat(current.MainFov) + " delta=" + FormatDelta(baseline.MainFov, current.MainFov) + " spectateFov=" + FormatFloat(baseline.SpectateFov) + "->" + FormatFloat(current.SpectateFov) + " delta=" + FormatDelta(baseline.SpectateFov, current.SpectateFov) + " fogStart=" + FormatFloat(baseline.FogStartDistance) + "->" + FormatFloat(current.FogStartDistance) + " delta=" + FormatDelta(baseline.FogStartDistance, current.FogStartDistance) + " mainMinusSpectate=" + FormatFloat(baseline.MainMinusSpectateNearClip) + "->" + FormatFloat(current.MainMinusSpectateNearClip) + " delta=" + FormatDelta(baseline.MainMinusSpectateNearClip, current.MainMinusSpectateNearClip) + " mainCam=" + (baseline.MainCameraName ?? "null") + "->" + (current.MainCameraName ?? "null") + " mainRect=" + (baseline.MainRectKey ?? "n/a") + "->" + (current.MainRectKey ?? "n/a") + " " + $"mainMask={baseline.MainCullingMask}->{current.MainCullingMask} " + "mainClear=" + baseline.MainClearFlags + "->" + current.MainClearFlags + " mainParent=" + (baseline.MainParentName ?? "null") + "->" + (current.MainParentName ?? "null") + " spectateCam=" + (baseline.SpectateCameraName ?? "null") + "->" + (current.SpectateCameraName ?? "null"))); } } private static CameraSnapshot CaptureCameraSnapshot(SpectateCamera spectate, string reason) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: 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) //IL_0232: Unknown result type (might be due to invalid IL or missing references) Camera main = Camera.main; Camera mainCamera = spectate.MainCamera; float? mainNearClip = (((Object)(object)main != (Object)null) ? new float?(main.nearClipPlane) : null); float? spectateNearClip = (((Object)(object)mainCamera != (Object)null) ? new float?(mainCamera.nearClipPlane) : null); Rect? mainRect = (((Object)(object)main != (Object)null) ? new Rect?(main.rect) : null); Transform val = (((Object)(object)main != (Object)null) ? ((Component)main).transform.parent : null); CameraSnapshot obj = new CameraSnapshot { Reason = reason, LastChanceEnabled = LastChanceInteropBridge.IsLastChanceModeEnabled(), LastChanceActive = LastChanceInteropBridge.IsLastChanceActive(), AllPlayersDisabled = LastChanceInteropBridge.AllPlayersDisabled(), CurrentState = ((object)(State)(ref spectate.currentState)).ToString(), PlayerName = GetPlayerName(spectate.player), OverrideName = GetPlayerName(spectate.playerOverride), CurrentPlayerDisabled = ((Object)(object)spectate.player != (Object)null && spectate.player.isDisabled), LocalPlayerDisabled = ((Object)(object)PlayerAvatar.instance != (Object)null && PlayerAvatar.instance.isDisabled), LocalPlayerDeadSet = ((Object)(object)PlayerAvatar.instance != (Object)null && PlayerAvatar.instance.deadSet), MainCameraName = (((Object)(object)main != (Object)null) ? ((Object)main).name : null), SpectateCameraName = (((Object)(object)mainCamera != (Object)null) ? ((Object)mainCamera).name : null), MainNearClip = mainNearClip, SpectateNearClip = spectateNearClip, MainFov = (((Object)(object)main != (Object)null) ? new float?(main.fieldOfView) : null), SpectateFov = (((Object)(object)mainCamera != (Object)null) ? new float?(mainCamera.fieldOfView) : null), MainRect = mainRect, MainRectKey = (mainRect.HasValue ? FormatRect(mainRect.Value) : null), MainCullingMask = (((Object)(object)main != (Object)null) ? main.cullingMask : 0) }; object mainClearFlags; if (!((Object)(object)main != (Object)null)) { mainClearFlags = "n/a"; } else { CameraClearFlags clearFlags = main.clearFlags; mainClearFlags = ((object)(CameraClearFlags)(ref clearFlags)).ToString(); } obj.MainClearFlags = (string)mainClearFlags; obj.MainParentName = (((Object)(object)val != (Object)null) ? ((Object)val).name : null); obj.FogStartDistance = RenderSettings.fogStartDistance; obj.MainMinusSpectateNearClip = ((mainNearClip.HasValue && spectateNearClip.HasValue) ? new float?(mainNearClip.Value - spectateNearClip.Value) : null); return obj; } private static string FormatRect(Rect rect) { return $"({((Rect)(ref rect)).x:0.###},{((Rect)(ref rect)).y:0.###},{((Rect)(ref rect)).width:0.###},{((Rect)(ref rect)).height:0.###})"; } private static void TraceNearClipActivation(SpectateCamera? spectate, string reason) { if (FeatureFlags.DebugLogging && !((Object)(object)spectate == (Object)null)) { NearClipSnapshot nearClipSnapshot = CaptureNearClipSnapshot(spectate, reason); if (LogNearClipSnapshot("activation", nearClipSnapshot)) { ((MonoBehaviour)spectate).StartCoroutine(LogNearClipFollowUp(spectate, nearClipSnapshot)); ((MonoBehaviour)spectate).StartCoroutine(LogNearClipDelayedFollowUp(spectate, nearClipSnapshot)); } } } [IteratorStateMachine(typeof(d__38))] private static IEnumerator LogNearClipFollowUp(SpectateCamera spectate, NearClipSnapshot baseline) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__38(0) { spectate = spectate, baseline = baseline }; } [IteratorStateMachine(typeof(d__39))] private static IEnumerator LogNearClipDelayedFollowUp(SpectateCamera spectate, NearClipSnapshot baseline) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__39(0) { spectate = spectate, baseline = baseline }; } private static bool LogNearClipSnapshot(string phase, NearClipSnapshot snapshot) { if (!FeatureFlags.DebugLogging) { return false; } string key = "Spectate.DeadPlayers.NearClip." + phase; string text = $"{snapshot.Reason}|{snapshot.LastChanceEnabled}|{snapshot.LastChanceActive}|{snapshot.AllPlayersDisabled}|{snapshot.CurrentPlayerDisabled}|{snapshot.LocalPlayerDisabled}|{snapshot.LocalPlayerDeadSet}|{snapshot.CurrentState}|{snapshot.PlayerName}|{snapshot.OverrideName}|{snapshot.MainCameraName}|{snapshot.SpectateCameraName}|{snapshot.MainNearClip}|{snapshot.SpectateNearClip}|{snapshot.FogStartDistance}|{snapshot.MainMinusSpectateNearClip}"; if (text == s_lastNearClipSnapshotKey) { return false; } s_lastNearClipSnapshotKey = text; if (!LogLimiter.ShouldLog(key, 30)) { return false; } Debug.Log((object)("[SpectateDeadPlayers][NearClip] phase=" + phase + " reason=" + snapshot.Reason + " " + $"lastChanceEnabled={snapshot.LastChanceEnabled} " + $"lastChanceActive={snapshot.LastChanceActive} " + $"allPlayersDisabled={snapshot.AllPlayersDisabled} " + $"currentPlayerDisabled={snapshot.CurrentPlayerDisabled} " + $"localPlayerDisabled={snapshot.LocalPlayerDisabled} " + $"localPlayerDeadSet={snapshot.LocalPlayerDeadSet} " + "state=" + snapshot.CurrentState + " player=" + snapshot.PlayerName + " override=" + snapshot.OverrideName + " mainCam=" + (snapshot.MainCameraName ?? "null") + " mainNear=" + (snapshot.MainNearClip.HasValue ? snapshot.MainNearClip.Value.ToString("0.###") : "n/a") + " spectateCam=" + (snapshot.SpectateCameraName ?? "null") + " spectateNear=" + (snapshot.SpectateNearClip.HasValue ? snapshot.SpectateNearClip.Value.ToString("0.###") : "n/a") + " fogStart=" + (snapshot.FogStartDistance.HasValue ? snapshot.FogStartDistance.Value.ToString("0.###") : "n/a") + " mainMinusSpectate=" + (snapshot.MainMinusSpectateNearClip.HasValue ? snapshot.MainMinusSpectateNearClip.Value.ToString("0.###") : "n/a"))); return true; } private static void LogNearClipDelta(NearClipSnapshot baseline, NearClipSnapshot current) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.NearClip.Delta", 30)) { Debug.Log((object)("[SpectateDeadPlayers][NearClipDelta] reason=" + baseline.Reason + " mainNear=" + FormatFloat(baseline.MainNearClip) + "->" + FormatFloat(current.MainNearClip) + " delta=" + FormatDelta(baseline.MainNearClip, current.MainNearClip) + " spectateNear=" + FormatFloat(baseline.SpectateNearClip) + "->" + FormatFloat(current.SpectateNearClip) + " delta=" + FormatDelta(baseline.SpectateNearClip, current.SpectateNearClip) + " fogStart=" + FormatFloat(baseline.FogStartDistance) + "->" + FormatFloat(current.FogStartDistance) + " delta=" + FormatDelta(baseline.FogStartDistance, current.FogStartDistance) + " mainMinusSpectate=" + FormatFloat(baseline.MainMinusSpectateNearClip) + "->" + FormatFloat(current.MainMinusSpectateNearClip) + " delta=" + FormatDelta(baseline.MainMinusSpectateNearClip, current.MainMinusSpectateNearClip) + " mainCam=" + (baseline.MainCameraName ?? "null") + "->" + (current.MainCameraName ?? "null") + " spectateCam=" + (baseline.SpectateCameraName ?? "null") + "->" + (current.SpectateCameraName ?? "null"))); } } private static NearClipSnapshot CaptureNearClipSnapshot(SpectateCamera spectate, string reason) { Camera main = Camera.main; Camera mainCamera = spectate.MainCamera; float? mainNearClip = (((Object)(object)main != (Object)null) ? new float?(main.nearClipPlane) : null); float? spectateNearClip = (((Object)(object)mainCamera != (Object)null) ? new float?(mainCamera.nearClipPlane) : null); return new NearClipSnapshot { Reason = reason, LastChanceEnabled = LastChanceInteropBridge.IsLastChanceModeEnabled(), LastChanceActive = LastChanceInteropBridge.IsLastChanceActive(), AllPlayersDisabled = LastChanceInteropBridge.AllPlayersDisabled(), CurrentState = ((object)(State)(ref spectate.currentState)).ToString(), PlayerName = GetPlayerName(spectate.player), OverrideName = GetPlayerName(spectate.playerOverride), CurrentPlayerDisabled = ((Object)(object)spectate.player != (Object)null && spectate.player.isDisabled), LocalPlayerDisabled = ((Object)(object)PlayerAvatar.instance != (Object)null && PlayerAvatar.instance.isDisabled), LocalPlayerDeadSet = ((Object)(object)PlayerAvatar.instance != (Object)null && PlayerAvatar.instance.deadSet), MainCameraName = (((Object)(object)main != (Object)null) ? ((Object)main).name : null), SpectateCameraName = (((Object)(object)mainCamera != (Object)null) ? ((Object)mainCamera).name : null), MainNearClip = mainNearClip, SpectateNearClip = spectateNearClip, FogStartDistance = RenderSettings.fogStartDistance, MainMinusSpectateNearClip = ((mainNearClip.HasValue && spectateNearClip.HasValue) ? new float?(mainNearClip.Value - spectateNearClip.Value) : null) }; } private static string FormatFloat(float? value) { return value.HasValue ? value.Value.ToString("0.###") : "n/a"; } private static string FormatDelta(float? before, float? after) { if (!before.HasValue || !after.HasValue) { return "n/a"; } return (after.Value - before.Value).ToString("0.###"); } private static bool ShouldBlockPlayerSwitchForLastChance() { if (!LastChanceInteropBridge.IsLastChanceModeEnabled()) { return false; } if (LastChanceInteropBridge.IsManualSwitchInputDown()) { return false; } if (!LastChanceInteropBridge.IsLastChanceActive()) { return false; } if (!LastChanceInteropBridge.AllPlayersDisabled()) { return false; } return true; } } } namespace DeathHeadHopperFix.Modules.Gameplay.Core.Runtime { internal static class StatsModule { private static bool _statsHooksApplied; internal static void ApplyHooks(Harmony harmony) { if (harmony != null && !_statsHooksApplied) { PatchSemiFuncStatGetItemsPurchasedIfPossible(harmony); PatchStatsManagerGetItemsUpgradesPurchasedIfPossible(harmony); PatchStatsManagerItemPurchaseIfPossible(harmony); _statsHooksApplied = true; } } internal static void EnsureStatsManagerKey(string itemName) { if (!string.IsNullOrWhiteSpace(itemName)) { StatsManager instance = StatsManager.instance; if (!((Object)(object)instance == (Object)null)) { EnsureKey(instance.itemsPurchased, itemName); EnsureKey(instance.itemsPurchasedTotal, itemName); EnsureKey(instance.itemsUpgradesPurchased, itemName); } } } internal static void EnsureStatsEntriesForItem(Object itemObj) { try { if (!(itemObj == (Object)null)) { StatsManager instance = StatsManager.instance; if (!((Object)(object)instance == (Object)null)) { EnsureKey(instance.itemsPurchased, itemObj.name); EnsureKey(instance.itemsPurchasedTotal, itemObj.name); EnsureKey(instance.itemsUpgradesPurchased, itemObj.name); } } } catch { } } private static void PatchSemiFuncStatGetItemsPurchasedIfPossible(Harmony harmony) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(SemiFunc), "StatGetItemsPurchased", new Type[1] { typeof(string) }, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(StatsModule).GetMethod("SemiFunc_StatGetItemsPurchased_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool SemiFunc_StatGetItemsPurchased_Prefix(string itemName, ref int __result) { try { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { return true; } EnsureKey(instance.itemsPurchased, itemName); __result = instance.itemsPurchased[itemName]; return false; } catch { } return true; } private static void PatchStatsManagerGetItemsUpgradesPurchasedIfPossible(Harmony harmony) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(StatsManager), "GetItemsUpgradesPurchased", new Type[1] { typeof(string) }, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(StatsModule).GetMethod("StatsManager_GetItemsUpgradesPurchased_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool StatsManager_GetItemsUpgradesPurchased_Prefix(string itemName, ref int __result) { try { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { return true; } EnsureKey(instance.itemsUpgradesPurchased, itemName); __result = instance.itemsUpgradesPurchased[itemName]; return false; } catch { } return true; } private static void PatchStatsManagerItemPurchaseIfPossible(Harmony harmony) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(StatsManager), "ItemPurchase", new Type[1] { typeof(string) }, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(StatsModule).GetMethod("StatsManager_ItemPurchase_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool StatsManager_ItemPurchase_Prefix(string itemName) { EnsureStatsManagerKey(itemName); return true; } private static void EnsureKey(IDictionary dictionary, string key) { if (dictionary != null && !string.IsNullOrWhiteSpace(key) && !dictionary.ContainsKey(key)) { dictionary[key] = 0; } } } } namespace DeathHeadHopperFix.Modules.Gameplay.Core.Interop { internal static class DHHApiGuardModule { private static bool s_guardMissingLocalCameraPosition; internal static void DetectGameApiChanges() { try { Type type = AccessTools.TypeByName("PlayerAvatar"); if (type == null) { s_guardMissingLocalCameraPosition = true; return; } FieldInfo field = type.GetField("localCameraPosition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); s_guardMissingLocalCameraPosition = field == null; } catch { s_guardMissingLocalCameraPosition = true; } } internal static void Apply(Harmony harmony, Assembly asm) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown if (!s_guardMissingLocalCameraPosition) { return; } Type type = asm.GetType("DeathHeadHopper.DeathHead.DeathHeadController", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "CancelReviveInCart", (Type[])null, (Type[])null); if (methodInfo != null) { MethodInfo method = typeof(DHHApiGuardModule).GetMethod("DeathHeadController_CancelReviveInCart_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (method != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } MethodInfo methodInfo2 = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); if (methodInfo2 != null) { MethodInfo method2 = typeof(DHHApiGuardModule).GetMethod("DeathHeadController_Update_Finalizer", BindingFlags.Static | BindingFlags.NonPublic); if (method2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null); } } } private static bool DeathHeadController_CancelReviveInCart_Prefix() { return !s_guardMissingLocalCameraPosition; } private static Exception? DeathHeadController_Update_Finalizer(Exception? __exception) { if (__exception == null) { return null; } if (s_guardMissingLocalCameraPosition && __exception is MissingFieldException) { return null; } return __exception; } } internal static class DHHPunViewFixModule { private sealed class DHHPunViewRoomSyncRelay : MonoBehaviourPunCallbacks { private MonoBehaviour? _sourceInstance; internal void Bind(MonoBehaviour sourceInstance) { _sourceInstance = sourceInstance; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } public override void OnJoinedRoom() { OnRoomSyncChanged(_sourceInstance); } public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { if (propertiesThatChanged != null && ((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"DHHFix.PunViewId")) { OnRoomSyncChanged(_sourceInstance); } } public override void OnMasterClientSwitched(Player newMasterClient) { OnRoomSyncChanged(_sourceInstance); } } private const string DhhPunViewIdRoomKey = "DHHFix.PunViewId"; private static ManualLogSource? _log; private static readonly MethodInfo? VersionCheckMethodInfo = AccessTools.Method("DeathHeadHopper.Managers.DHHPunManager:VersionCheck", (Type[])null, (Type[])null); internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Expected O, but got Unknown //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown _log = log; if (harmony == null || asm == null) { return; } Type type = asm.GetType("DeathHeadHopper.Managers.DHHPunManager", throwOnError: false); if (!(type == null)) { MethodInfo methodInfo = AccessTools.Method(type, "Awake", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(type, "EnsurePhotonView", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(type, "VersionCheck", (Type[])null, (Type[])null); MethodInfo method = typeof(DHHPunViewFixModule).GetMethod("DHHPunManager_Awake_Postfix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method2 = typeof(DHHPunViewFixModule).GetMethod("DHHPunManager_EnsurePhotonView_Prefix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method3 = typeof(DHHPunViewFixModule).GetMethod("DHHPunManager_VersionCheck_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo != null && method != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo2 != null && method2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo3 != null && method3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void DHHPunManager_Awake_Postfix(MonoBehaviour __instance) { try { if (!((Object)(object)__instance == (Object)null)) { BindDedicatedPhotonView(__instance); EnsureRoomSyncRelay(__instance); } } catch (Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("DHHPunViewFix Awake postfix failed: " + ex.GetType().Name)); } } } private static bool DHHPunManager_EnsurePhotonView_Prefix(MonoBehaviour __instance) { try { if ((Object)(object)__instance == (Object)null) { return true; } BindDedicatedPhotonView(__instance); EnsureRoomSyncRelay(__instance); } catch (Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("DHHPunViewFix EnsurePhotonView prefix failed: " + ex.GetType().Name)); } } return true; } private static bool DHHPunManager_VersionCheck_Prefix(MonoBehaviour __instance) { try { if ((Object)(object)__instance == (Object)null) { return false; } BindDedicatedPhotonView(__instance); PhotonView component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null || component.ViewID <= 0) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:DHHPunView.VersionCheck.NoView", 30)) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)"[Fix:DHHPunView] Skipping VersionCheck RPC because PhotonView ID is not valid yet."); } } return false; } } catch (Exception ex) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogWarning((object)("DHHPunViewFix VersionCheck prefix failed: " + ex.GetType().Name)); } return false; } return true; } private static void BindDedicatedPhotonView(MonoBehaviour sourceInstance) { Type type = ((object)sourceInstance).GetType(); PropertyInfo property = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field = type.GetField("photonView", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((Object)(object)((Component)sourceInstance).gameObject == (Object)null) { return; } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:DHHPunView.Host", 30)) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)("[Fix:DHHPunView] Using dedicated host GO='" + ((Object)((Component)sourceInstance).gameObject).name + "'.")); } } Object.DontDestroyOnLoad((Object)(object)((Component)sourceInstance).gameObject); PhotonView val = ((Component)sourceInstance).gameObject.GetComponent(); if ((Object)(object)val == (Object)null) { val = ((Component)sourceInstance).gameObject.AddComponent(); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:DHHPunView.AddPhotonView", 30)) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogInfo((object)"[Fix:DHHPunView] Added PhotonView to DHHPunManager GO."); } } } EnsureSynchronizedViewId(val); property?.SetValue(null, sourceInstance, null); field?.SetValue(sourceInstance, val); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:DHHPunView.Bound", 15)) { ManualLogSource? log3 = _log; if (log3 != null) { log3.LogInfo((object)$"[Fix:DHHPunView] Bound DHHPunManager to dedicated PhotonView {val.ViewID} on '{((Object)((Component)sourceInstance).gameObject).name}'."); } } } private static void EnsureSynchronizedViewId(PhotonView photonView) { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_00d7: 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_00ef: Expected O, but got Unknown if ((Object)(object)photonView == (Object)null || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return; } try { if (PhotonNetwork.IsMasterClient) { if (photonView.ViewID <= 0 && !PhotonNetwork.AllocateViewID(photonView)) { return; } int viewID = photonView.ViewID; Hashtable customProperties = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; if (customProperties != null && ((Dictionary)(object)customProperties).TryGetValue((object)"DHHFix.PunViewId", out object value) && value is int num && num > 0) { TryAssignViewId(photonView, num, "master-sync-existing"); return; } Hashtable val = new Hashtable { [(object)"DHHFix.PunViewId"] = viewID }; Hashtable val2 = new Hashtable { [(object)"DHHFix.PunViewId"] = null }; if (PhotonNetwork.CurrentRoom.SetCustomProperties(val, val2, (WebFlags)null)) { return; } Hashtable customProperties2 = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; if (customProperties2 != null && ((Dictionary)(object)customProperties2).TryGetValue((object)"DHHFix.PunViewId", out object value2) && value2 is int) { int num2 = (int)value2; if (num2 > 0) { TryAssignViewId(photonView, num2, "master-sync-after-cas-fail"); } } return; } Hashtable customProperties3 = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; if (customProperties3 != null && ((Dictionary)(object)customProperties3).TryGetValue((object)"DHHFix.PunViewId", out object value3) && value3 is int) { int num3 = (int)value3; if (num3 > 0) { TryAssignViewId(photonView, num3, "client-sync"); } } } catch (Exception ex) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:DHHPunView.Sync.Error", 30)) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("DHHPunViewFix sync failed: " + ex.GetType().Name)); } } } } private static bool TryAssignViewId(PhotonView photonView, int viewId, string reason) { if ((Object)(object)photonView == (Object)null || viewId <= 0) { return false; } if (photonView.ViewID == viewId) { return true; } PhotonView val = PhotonView.Find(viewId); if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)photonView) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:DHHPunView.Sync.Conflict", 15)) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)$"[Fix:DHHPunView] Refusing ViewID {viewId} ({reason}) because it is already owned by '{((Object)((Component)val).gameObject).name}'."); } } return false; } photonView.ViewID = viewId; return photonView.ViewID == viewId; } private static void EnsureRoomSyncRelay(MonoBehaviour sourceInstance) { if (!((Object)(object)sourceInstance == (Object)null) && !((Object)(object)((Component)sourceInstance).gameObject == (Object)null)) { DHHPunViewRoomSyncRelay dHHPunViewRoomSyncRelay = ((Component)sourceInstance).gameObject.GetComponent(); if ((Object)(object)dHHPunViewRoomSyncRelay == (Object)null) { dHHPunViewRoomSyncRelay = ((Component)sourceInstance).gameObject.AddComponent(); } dHHPunViewRoomSyncRelay.Bind(sourceInstance); } } private static void OnRoomSyncChanged(MonoBehaviour? sourceInstance) { if (!((Object)(object)sourceInstance == (Object)null)) { BindDedicatedPhotonView(sourceInstance); TryInvokeVersionCheck(sourceInstance); } } private static void TryInvokeVersionCheck(MonoBehaviour sourceInstance) { if ((Object)(object)sourceInstance == (Object)null) { return; } PhotonView component = ((Component)sourceInstance).GetComponent(); if (component == null || component.ViewID <= 0) { return; } try { VersionCheckMethodInfo?.Invoke(sourceInstance, Array.Empty()); } catch (Exception ex) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:DHHPunView.VersionCheck.InvokeFail", 30)) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("DHHPunViewFix VersionCheck invoke failed: " + ex.GetType().Name)); } } } } } internal static class DHHShopVanillaPoolModule { private static bool _hooksApplied; private static ManualLogSource? _log; private static readonly HashSet LoggedWarnings = new HashSet(StringComparer.OrdinalIgnoreCase); internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { _log = log; if (harmony == null || _hooksApplied) { return; } try { Patch(harmony, AccessTools.Method(typeof(DHHPunManager), "LoadShopAtticShelves", (Type[])null, (Type[])null)); Patch(harmony, AccessTools.Method(typeof(DHHPunManager), "LoadShopAtticShelvesRPC", (Type[])null, (Type[])null)); Patch(harmony, AccessTools.Method(typeof(DHHShopManager), "LoadShopAtticShelves", (Type[])null, (Type[])null)); Patch(harmony, AccessTools.Method(typeof(DHHShopManager), "LoadItems", (Type[])null, (Type[])null)); Patch(harmony, AccessTools.Method(typeof(DHHShopManager), "ShopPopulateItemVolumes", new Type[1] { typeof(PunManager) }, (Type[])null)); PatchItemManagerGetPurchasedItems(harmony); PatchLevelGeneratorItemSetup(harmony); PatchShopInitialize(harmony); PatchShopItemsCollection(harmony); PatchUpgradeStandSelection(harmony); _hooksApplied = true; ManualLogSource? log2 = _log; if (log2 != null) { log2.LogInfo((object)"[Fix:Shop] Original DeathHeadHopper custom shop flow disabled; vanilla shop pool orchestrator enabled."); } } catch (Exception ex) { ManualLogSource? log3 = _log; if (log3 != null) { log3.LogWarning((object)("[Fix:Shop] Failed to disable original DeathHeadHopper custom shop flow: " + ex.Message)); } } } private static void Patch(Harmony harmony, MethodInfo? original) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown if (!(original == null)) { HarmonyMethod val = new HarmonyMethod(typeof(DHHShopVanillaPoolModule), "BlockOriginalShopMethod_Prefix", (Type[])null); harmony.Patch((MethodBase)original, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void PatchShopInitialize(Harmony harmony) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ShopManager), "ShopInitialize", (Type[])null, (Type[])null); if (!(methodInfo == null)) { HarmonyMethod val = new HarmonyMethod(typeof(DHHShopVanillaPoolModule), "ShopManager_ShopInitialize_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void PatchShopItemsCollection(Harmony harmony) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ShopManager), "GetAllItemsFromStatsManager", (Type[])null, (Type[])null); if (!(methodInfo == null)) { HarmonyMethod val = new HarmonyMethod(typeof(DHHShopVanillaPoolModule), "ShopManager_GetAllItemsFromStatsManager_Postfix", (Type[])null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void PatchUpgradeStandSelection(Harmony harmony) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(UpgradeStand), "GetWeightedUpgradeExcluding", (Type[])null, (Type[])null); if (!(methodInfo == null)) { HarmonyMethod val = new HarmonyMethod(typeof(DHHShopVanillaPoolModule), "UpgradeStand_GetWeightedUpgradeExcluding_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void PatchLevelGeneratorItemSetup(Harmony harmony) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(LevelGenerator), "ItemSetup", (Type[])null, (Type[])null); if (!(methodInfo == null)) { HarmonyMethod val = new HarmonyMethod(typeof(DHHShopVanillaPoolModule), "LevelGenerator_ItemSetup_Postfix", (Type[])null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void PatchItemManagerGetPurchasedItems(Harmony harmony) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ItemManager), "GetPurchasedItems", (Type[])null, (Type[])null); if (!(methodInfo == null)) { HarmonyMethod val = new HarmonyMethod(typeof(DHHShopVanillaPoolModule), "ItemManager_GetPurchasedItems_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static bool BlockOriginalShopMethod_Prefix() { return false; } private static bool UpgradeStand_GetWeightedUpgradeExcluding_Prefix(Item excludeItem, Dictionary displayedCounts, Dictionary selectedDuringReroll, ref Item __result) { string mode = NormalizePoolMode(FeatureFlags.DHHUpgradesShopPoolMode); __result = GetWeightedUpgradeExcludingWithDhhMode(excludeItem, displayedCounts, selectedDuringReroll, mode); return false; } private static void ShopManager_ShopInitialize_Prefix() { if (SemiFunc.RunIsShop()) { EnsureDhhItemsInVanillaDictionary(); ValidateDhhPrefabRefsForVanillaSpawn(); } } private static void ShopManager_GetAllItemsFromStatsManager_Postfix(ShopManager __instance) { if ((Object)(object)__instance == (Object)null) { return; } int num = __instance.potentialItems?.Count(IsHeadChargerItem) ?? 0; int num2 = __instance.potentialItemUpgrades?.Count(IsDhhUpgradeItem) ?? 0; List potentialItems = __instance.potentialItems; if (potentialItems != null) { ApplyPoolMode(potentialItems, IsHeadChargerItem, FeatureFlags.HeadChargerShopPoolMode); } List potentialItemUpgrades = __instance.potentialItemUpgrades; if (potentialItemUpgrades != null) { ApplyPoolMode(potentialItemUpgrades, IsDhhUpgradeItem, FeatureFlags.DHHUpgradesShopPoolMode); } if (ShouldLogShopDebug()) { int num3 = __instance.potentialItems?.Count(IsHeadChargerItem) ?? 0; int num4 = __instance.potentialItemUpgrades?.Count(IsDhhUpgradeItem) ?? 0; ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)string.Format("[Fix:Shop] Pool mode context mode={0} headCharger before={1} after={2} mode={3} upgrades before={4} after={5} mode={6}", SemiFunc.IsMultiplayer() ? "MP" : "SP", num, num3, FeatureFlags.HeadChargerShopPoolMode, num2, num4, FeatureFlags.DHHUpgradesShopPoolMode)); } } } private static void LevelGenerator_ItemSetup_Postfix() { EnsureDhhItemsInVanillaDictionary(); ValidateDhhPrefabRefsForVanillaSpawn(); } private static void ItemManager_GetPurchasedItems_Prefix() { EnsureDhhItemsInVanillaDictionary(); ValidateDhhPrefabRefsForVanillaSpawn(); } internal static void EnsureDhhItemsInVanillaDictionary() { StatsManager instance = StatsManager.instance; if (instance?.itemDictionary == null) { return; } Dictionary shopItems = DHHAssetManager.shopItems; if (shopItems == null || shopItems.Count == 0) { return; } int num = 0; foreach (Item item in from x in shopItems.Values where (Object)(object)x != (Object)null group x by ((Object)x).GetInstanceID() into x select x.First()) { NormalizeDhhShopItemForVanillaLists(item); string name = ((Object)item).name; if (string.IsNullOrWhiteSpace(name)) { continue; } if (instance.itemDictionary.TryGetValue(name, out var value)) { if ((Object)(object)value != (Object)(object)item) { LogWarningOnce("dictionary-conflict:" + name, "[Fix:Shop] StatsManager.itemDictionary already contains key '" + name + "' for a different item. Keeping vanilla entry."); } } else if (!instance.itemDictionary.Values.Any((Item existingItem) => (Object)(object)existingItem == (Object)(object)item)) { instance.itemDictionary.Add(name, item); StatsModule.EnsureStatsEntriesForItem((Object)(object)item); num++; } } if (ShouldLogShopDebug() && num > 0) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)$"[Fix:Shop] Added {num} DeathHeadHopper item(s) to the vanilla shop dictionary."); } } } internal static void ValidateDhhPrefabRefsForVanillaSpawn() { Dictionary shopItems = DHHAssetManager.shopItems; if (shopItems == null || shopItems.Count == 0) { return; } foreach (Item item in from x in shopItems.Values where (Object)(object)x != (Object)null group x by ((Object)x).GetInstanceID() into x select x.First()) { NormalizeDhhShopItemForVanillaLists(item); string text = (string.IsNullOrWhiteSpace(((Object)item).name) ? item.itemName : ((Object)item).name); if (item.prefab == null) { LogWarningOnce("prefab-null:" + text, "[Fix:Shop] DHH item '" + text + "' has no PrefabRef and cannot spawn through the vanilla shop."); continue; } if (!item.prefab.IsValid()) { LogWarningOnce("prefab-invalid:" + text, "[Fix:Shop] DHH item '" + text + "' has an invalid PrefabRef resource path."); continue; } if (SemiFunc.IsMultiplayer() && string.IsNullOrWhiteSpace(item.prefab.ResourcePath)) { LogWarningOnce("prefab-resourcepath:" + text, "[Fix:Shop] DHH item '" + text + "' has an empty multiplayer ResourcePath."); } if (!SemiFunc.IsMultiplayer() && (Object)(object)RunManager.instance != (Object)null && (Object)(object)item.prefab.Prefab == (Object)null) { LogWarningOnce("prefab-singleplayer:" + text, "[Fix:Shop] DHH item '" + text + "' could not resolve its singleplayer prefab."); } } } private static void NormalizeDhhShopItemForVanillaLists(Item item) { //IL_001c: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)item == (Object)null)) { if (IsHeadChargerItem(item)) { item.itemType = (itemType)13; item.itemSecretShopType = (itemSecretShopType)0; } else if (IsDhhUpgradeItem(item)) { item.itemType = (itemType)3; item.itemSecretShopType = (itemSecretShopType)0; } } } private static void ApplyPoolMode(List list, Predicate matcher, string mode) { Predicate matcher2 = matcher; if (list == null || matcher2 == null) { return; } mode = NormalizePoolMode(mode); if (string.Equals(mode, "Default", StringComparison.OrdinalIgnoreCase)) { ApplyDefaultPoolMode(list, matcher2); return; } List list2 = list.Where((Item item) => (Object)(object)item != (Object)null && matcher2(item)).ToList(); if (list2.Count == 0) { return; } int count = list2.Count; foreach (Item item in list2) { list.Remove(item); } int num = 0; if (string.Equals(mode, "Reduced", StringComparison.OrdinalIgnoreCase)) { foreach (Item item2 in from item in list2 group item by ((Object)item).GetInstanceID() into @group select @group.First()) { list.Add(item2); num++; } } if (ShouldLogShopDebug()) { string text = (SemiFunc.IsMultiplayer() ? "MP" : "SP"); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)$"[Fix:Shop] ApplyPoolMode mode={text} matcher={matcher2.Method.Name} original={count} removed={count} poolMode={mode} addedCopies={num} final={list.Count((Item item) => (Object)(object)item != (Object)null && matcher2(item))}"); } } ListExtension.Shuffle((IList)list); } private static void ApplyDefaultPoolMode(List list, Predicate matcher) { Predicate matcher2 = matcher; List list2 = list.Where((Item item) => (Object)(object)item != (Object)null && matcher2(item)).ToList(); if (list2.Count == 0) { return; } int balancedCopies = GetBalancedPoolCopyCount(list, matcher2); List> list3 = (from item in list2 group item by ((Object)item).GetInstanceID() into @group select @group.ToList()).ToList(); int count = list2.Count; int num = list3.Sum((List group) => Math.Min(group.Count, balancedCopies)); if (num >= count) { return; } foreach (Item item in list2) { list.Remove(item); } int num2 = 0; foreach (List item2 in list3) { int num3 = Math.Min(item2.Count, balancedCopies); for (int i = 0; i < num3; i++) { list.Add(item2[0]); num2++; } } if (ShouldLogShopDebug()) { string text = (SemiFunc.IsMultiplayer() ? "MP" : "SP"); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)$"[Fix:Shop] ApplyDefaultPoolMode mode={text} matcher={matcher2.Method.Name} original={count} balancedCopies={balancedCopies} addedCopies={num2} final={list.Count((Item item) => (Object)(object)item != (Object)null && matcher2(item))}"); } } ListExtension.Shuffle((IList)list); } private static int GetBalancedPoolCopyCount(List list, Predicate matcher) { Predicate matcher2 = matcher; List list2 = (from item in list where (Object)(object)item != (Object)null && !matcher2(item) group item by ((Object)item).GetInstanceID() into @group select @group.Count() into count where count > 0 orderby count select count).ToList(); if (list2.Count == 0) { return 1; } return Math.Max(1, list2[list2.Count / 2]); } private static string NormalizePoolMode(string mode) { if (string.Equals(mode, "Disabled", StringComparison.OrdinalIgnoreCase)) { return "Disabled"; } if (string.Equals(mode, "Reduced", StringComparison.OrdinalIgnoreCase)) { return "Reduced"; } return "Default"; } private static Item? GetWeightedUpgradeExcludingWithDhhMode(Item excludeItem, Dictionary? displayedCounts, Dictionary? selectedDuringReroll, string mode) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Invalid comparison between Unknown and I4 StatsManager instance = StatsManager.instance; if (instance?.itemDictionary == null) { return null; } ShopManager instance2 = ShopManager.instance; if ((Object)(object)instance2 == (Object)null) { return null; } List list = new List(); int currency = SemiFunc.StatGetRunCurrency(); List list2 = (from item in instance.itemDictionary.Values where (Object)(object)item != (Object)null group item by ((Object)item).GetInstanceID() into @group select @group.First()).ToList(); int balancedUpgradeStandMaxAmount = GetBalancedUpgradeStandMaxAmount(list2); foreach (Item item in list2) { if ((int)item.itemType == 3 && !((Object)(object)item == (Object)(object)excludeItem)) { bool flag = IsDhhUpgradeItem(item); if ((!flag || !string.Equals(mode, "Disabled", StringComparison.OrdinalIgnoreCase)) && IsUpgradeStandCandidateAllowed(item, flag, displayedCounts, selectedDuringReroll, currency, instance2, mode, balancedUpgradeStandMaxAmount)) { list.Add(item); } } } return (list.Count == 0) ? null : list[Random.Range(0, list.Count)]; } private static bool IsUpgradeStandCandidateAllowed(Item item, bool isDhhUpgrade, Dictionary? displayedCounts, Dictionary? selectedDuringReroll, int currency, ShopManager shop, string mode, int balancedDhhUpgradeMaxAmount) { string name = ((Object)item).name; int itemsUpgradesPurchased = StatsManager.instance.GetItemsUpgradesPurchased(name); int value; int num = ((displayedCounts != null && displayedCounts.TryGetValue(name, out value)) ? value : 0); int value2; int num2 = ((selectedDuringReroll != null && selectedDuringReroll.TryGetValue(name, out value2)) ? value2 : 0); int num3 = itemsUpgradesPurchased + num + num2; int effectiveUpgradeStandMaxAmount = GetEffectiveUpgradeStandMaxAmount(item, isDhhUpgrade, mode, balancedDhhUpgradeMaxAmount); if (effectiveUpgradeStandMaxAmount > 0 && num3 >= effectiveUpgradeStandMaxAmount) { return false; } if (item.maxPurchase && StatsManager.instance.GetItemsUpgradesPurchasedTotal(name) >= item.maxPurchaseAmount) { return false; } if (item.minPlayerCount > 1 && GameDirector.instance.PlayerList.Count < item.minPlayerCount) { return false; } float num4 = shop.UpgradeValueGet(item.value.valueMax / 1000f * 4f, item); return num4 <= (float)currency || Random.Range(0, 4) == 0; } private static int GetEffectiveUpgradeStandMaxAmount(Item item, bool isDhhUpgrade, string mode, int balancedDhhUpgradeMaxAmount) { if (!isDhhUpgrade) { return item.maxAmountInShop; } if (string.Equals(mode, "Reduced", StringComparison.OrdinalIgnoreCase)) { return 1; } if (string.Equals(mode, "Default", StringComparison.OrdinalIgnoreCase)) { return Math.Min((item.maxAmountInShop <= 0) ? balancedDhhUpgradeMaxAmount : item.maxAmountInShop, balancedDhhUpgradeMaxAmount); } return item.maxAmountInShop; } private static int GetBalancedUpgradeStandMaxAmount(List allItems) { List list = (from item in allItems where (Object)(object)item != (Object)null && (int)item.itemType == 3 && !IsDhhUpgradeItem(item) && item.maxAmountInShop > 0 select item.maxAmountInShop into amount orderby amount select amount).ToList(); if (list.Count == 0) { return 1; } return Math.Max(1, list[list.Count / 2]); } private static bool IsHeadChargerItem(Item item) { GameObject val = TryGetPrefab(item); if ((Object)(object)val != (Object)null && ((Object)(object)val.GetComponent() != (Object)null || (Object)(object)val.GetComponentInChildren(true) != (Object)null)) { return true; } string dhhItemKey = GetDhhItemKey(item); return dhhItemKey.Contains("head charger") || dhhItemKey.Contains("head charge"); } private static bool IsDhhUpgradeItem(Item item) { string dhhItemKey = GetDhhItemKey(item); return dhhItemKey.Contains("upgrade dhh charge") || dhhItemKey.Contains("upgrade dhh power"); } private static string GetDhhItemKey(Item item) { if ((Object)(object)item == (Object)null) { return string.Empty; } string text = ((item.prefab != null) ? item.prefab.PrefabName : string.Empty); return (((Object)item).name + " " + item.itemName + " " + text).ToLowerInvariant(); } private static GameObject? TryGetPrefab(Item item) { if (item?.prefab == null || (Object)(object)RunManager.instance == (Object)null) { return null; } try { return item.prefab.Prefab; } catch { return null; } } private static void LogWarningOnce(string key, string message) { if (LoggedWarnings.Add(key)) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)message); } } } private static bool ShouldLogShopDebug() { return FeatureFlags.DebugLogging; } } [HarmonyPatch] internal static class PlayerDeathHeadReleaseOnRevivePatch { private const int ReleaseObjectViewId = -1; private static readonly FieldInfo? PlayerDeadSetField = AccessTools.Field(typeof(PlayerAvatar), "deadSet"); private static readonly FieldInfo? PlayerIsDisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled"); private static readonly FieldInfo? PlayerDeathHeadPhysGrabObjectField = AccessTools.Field(typeof(PlayerDeathHead), "physGrabObject"); private static readonly Dictionary LastDeadStateByPlayer = new Dictionary(); [HarmonyPatch(typeof(PlayerAvatar), "Update")] [HarmonyPostfix] private static void PlayerAvatar_Update_Postfix(PlayerAvatar __instance) { if (!((Object)(object)__instance == (Object)null) && LastChanceInteropBridge.IsLastChanceModeEnabled()) { int instanceID = ((Object)__instance).GetInstanceID(); bool flag = IsPlayerDead(__instance); if (LastDeadStateByPlayer.TryGetValue(instanceID, out var value) && value && !flag) { TryReleaseDeathHeadGrabbers(__instance); } LastDeadStateByPlayer[instanceID] = flag; } } [HarmonyPatch(typeof(PlayerAvatar), "OnDestroy")] [HarmonyPostfix] private static void PlayerAvatar_OnDestroy_Postfix(PlayerAvatar __instance) { if (!((Object)(object)__instance == (Object)null)) { LastDeadStateByPlayer.Remove(((Object)__instance).GetInstanceID()); } } private static bool IsPlayerDead(PlayerAvatar player) { bool valueOrDefault = (PlayerDeadSetField?.GetValue(player) as bool?).GetValueOrDefault(); bool valueOrDefault2 = (PlayerIsDisabledField?.GetValue(player) as bool?).GetValueOrDefault(); return valueOrDefault || valueOrDefault2; } private static void TryReleaseDeathHeadGrabbers(PlayerAvatar player) { //IL_00aa: 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) PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead == (Object)null) { return; } object? obj = PlayerDeathHeadPhysGrabObjectField?.GetValue(playerDeathHead); PhysGrabObject val = (PhysGrabObject)((obj is PhysGrabObject) ? obj : null); if ((Object)(object)val == (Object)null || val.playerGrabbing == null || val.playerGrabbing.Count == 0) { return; } foreach (PhysGrabber item in val.playerGrabbing.ToList()) { if (!((Object)(object)item == (Object)null)) { if (!SemiFunc.IsMultiplayer()) { item.ReleaseObjectRPC(true, 2f, -1, default(PhotonMessageInfo)); continue; } item.photonView.RPC("ReleaseObjectRPC", (RpcTarget)0, new object[3] { false, 1f, -1 }); } } } } [HarmonyPatch(typeof(PlayerDeathHead), "Update")] internal static class PlayerDeathHeadUpdatePatch { private const string JumpTraceKey = "Fix:Jump.Trace"; private static float s_jumpTimer = 1f; [HarmonyPrefix] private static void Prefix(PlayerDeathHead __instance, PhysGrabObject ___physGrabObject) { //IL_009a: 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_0150: 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_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0164: 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_016a: 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_0171: 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_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0197: 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) //IL_019d: 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) //IL_01b1: 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_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: 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_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0206: 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_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: 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_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_0387: 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_038e: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_03bd: Unknown result type (might be due to invalid IL or missing references) //IL_03bf: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_03a7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)___physGrabObject == (Object)null) { return; } PlayerAvatar playerAvatar = __instance.playerAvatar; if (!((Object)(object)playerAvatar != (Object)null) || (GameManager.Multiplayer() && (!((Object)(object)playerAvatar.photonView != (Object)null) || !playerAvatar.photonView.IsMine)) || (Object)(object)playerAvatar == (Object)null || !__instance.triggered) { return; } ((Component)playerAvatar).transform.position = ((Component)___physGrabObject).transform.position; if ((SemiFunc.InputMovementX() != 0f || SemiFunc.InputMovementY() != 0f) && (Object)(object)SpectateCamera.instance != (Object)null) { SpectateCamera.instance.player = playerAvatar; } if (s_jumpTimer > 0f) { s_jumpTimer -= Time.deltaTime; } else { if (!SemiFunc.InputHold((InputKey)1)) { return; } s_jumpTimer = 1f; Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return; } Vector3 val = Vector3.ProjectOnPlane(((Component)main).transform.forward, Vector3.up); Vector3 normalized = ((Vector3)(ref val)).normalized; Vector3 val2 = Vector3.Cross(Vector3.up, normalized); Vector3 val3 = Vector3.zero; float num = SemiFunc.InputMovementX(); float num2 = SemiFunc.InputMovementY(); if (num2 > 0f) { val3 += normalized; } if (num2 < 0f) { val3 -= normalized; } if (num < 0f) { val3 -= val2; } if (num > 0f) { val3 += val2; } val = val3 + Vector3.up; val3 = ((Vector3)(ref val)).normalized * 4.8f; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Jump.Trace", 2)) { Debug.Log((object)$"[Fix:Jump] Triggered jump input. move=({num:0.00},{num2:0.00}) dirMag={((Vector3)(ref val3)).magnitude:0.00}"); } if (FeatureFlags.BatteryJumpEnabled) { (bool, bool?, float, float) tuple = DHHBatteryHelper.EvaluateJumpAllowance(); if (!tuple.Item1) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Jump.Trace.Blocked", 2)) { Debug.Log((object)$"[Fix:Jump] Blocked by battery gate. energy={tuple.Item4:0.000} ref={tuple.Item3:0.000} ready={tuple.Item2}"); } return; } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Jump.Trace.BatteryOk", 2)) { Debug.Log((object)$"[Fix:Jump] Battery gate passed. energy={tuple.Item4:0.000} ref={tuple.Item3:0.000} ready={tuple.Item2}"); } } else if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Jump.Trace.NoBattery", 2)) { Debug.Log((object)"[Fix:Jump] Battery gate disabled (BatteryJumpEnabled=false)."); } PhysGrabber physGrabber = playerAvatar.physGrabber; PhotonView val4 = physGrabber?.photonView; if ((Object)(object)physGrabber == (Object)null || (Object)(object)val4 == (Object)null) { return; } Vector3 val5 = physGrabber.physGrabPointPullerPosition - val3; if (!GameManager.Multiplayer()) { ApplyLocalGrabLink(___physGrabObject, physGrabber, val5, 0); } else { ___physGrabObject.GrabLink(val4.ViewID, 0, val5, Vector3.zero, Vector3.zero); } ___physGrabObject.GrabStarted(physGrabber); ___physGrabObject.GrabEnded(physGrabber); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Jump.Trace.Applied", 2)) { Debug.Log((object)"[Fix:Jump] Applied grab-based jump impulse."); } if (!FeatureFlags.BatteryJumpEnabled || DHHBatteryHelper.HasRecentJumpConsumption()) { return; } SpectateCamera instance = SpectateCamera.instance; if ((Object)(object)instance != (Object)null) { float effectiveBatteryJumpUsage = DHHBatteryHelper.GetEffectiveBatteryJumpUsage(); float jumpThreshold = DHHBatteryHelper.GetJumpThreshold(); DHHBatteryHelper.ApplyConsumption(instance, effectiveBatteryJumpUsage, jumpThreshold); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Jump.Trace.Consume", 2)) { Debug.Log((object)$"[Fix:Jump] Applied battery consumption. usage={effectiveBatteryJumpUsage:0.000} ref={jumpThreshold:0.000}"); } } } } private static void ApplyLocalGrabLink(PhysGrabObject grabObject, PhysGrabber grabber, Vector3 point, int colliderId) { //IL_0023: 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_0037: 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_00cd: 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) //IL_00d6: 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_00e8: 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_00f6: 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_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: 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) if (!((Object)(object)grabObject == (Object)null) && !((Object)(object)grabber == (Object)null)) { grabber.physGrabPoint.position = point; grabber.localGrabPosition = ((Component)grabObject).transform.InverseTransformPoint(point); grabber.grabbedObjectTransform = ((Component)grabObject).transform; Transform val = grabObject.FindColliderFromID(colliderId); grabber.grabbedPhysGrabObjectColliderID = colliderId; grabber.grabbedPhysGrabObjectCollider = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); grabber.prevGrabbed = grabber.grabbed; grabber.grabbed = true; grabber.grabbedObject = grabObject.rb; grabber.grabbedPhysGrabObject = grabObject; PlayerAvatar playerAvatar = grabber.playerAvatar; object obj; if (playerAvatar == null) { obj = null; } else { PlayerLocalCamera localCamera = playerAvatar.localCamera; obj = ((localCamera != null) ? ((Component)localCamera).transform : null); } Transform val2 = (Transform)obj; if ((Object)(object)val2 != (Object)null) { Vector3 val3 = val2.InverseTransformDirection(((Component)grabObject).transform.forward); grabber.cameraRelativeGrabbedForward = ((Vector3)(ref val3)).normalized; val3 = val2.InverseTransformDirection(((Component)grabObject).transform.up); grabber.cameraRelativeGrabbedUp = ((Vector3)(ref val3)).normalized; } if (grabObject.playerGrabbing.Count == 0) { grabObject.camRelForward = ((Component)grabObject).transform.InverseTransformDirection(((Component)grabObject).transform.forward); grabObject.camRelUp = ((Component)grabObject).transform.InverseTransformDirection(((Component)grabObject).transform.up); } } } } internal static class PrefabModule { private static readonly Dictionary PendingPool = new Dictionary(StringComparer.OrdinalIgnoreCase); private static AssetBundle? _dhhBundle; private static ManualLogSource? _log; private static readonly HashSet _knownPrefabKeys = new HashSet(StringComparer.OrdinalIgnoreCase); internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { _log = log; if (harmony != null && !(asm == null)) { PatchDhhAssetManagerIfPossible(harmony); PatchRunManagerAwakeIfPossible(harmony); PatchMultiplayerPoolIfPossible(harmony); PatchPhotonDefaultPoolIfPossible(harmony); } } private static void PatchDhhAssetManagerIfPossible(Harmony harmony) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(DHHAssetManager), "LoadAssets", (Type[])null, (Type[])null); if (!(methodInfo == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PrefabModule), "DHHAssetManager_LoadAssets_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static bool DHHAssetManager_LoadAssets_Prefix() { try { string text = Path.Combine(Paths.PluginPath, "Cronchy-DeathHeadHopper", "deathheadhopper"); if (!File.Exists(text)) { ManualLogSource? log = _log; if (log != null) { log.LogError((object)("AssetBundle not found at: " + text)); } return false; } AssetBundle val = _dhhBundle; if ((Object)(object)val != (Object)null) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogInfo((object)"[Fix] Reusing already loaded AssetBundle instance."); } } else { string text2 = "Assets/DeathHeadHopper/Materials/Head Phase.mat"; foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { if ((Object)(object)allLoadedAssetBundle == (Object)null || (!string.Equals(((Object)allLoadedAssetBundle).name, "deathheadhopper", StringComparison.OrdinalIgnoreCase) && !string.Equals(((Object)allLoadedAssetBundle).name, Path.GetFileName(text), StringComparison.OrdinalIgnoreCase) && !allLoadedAssetBundle.Contains(text2) && !LooksLikeDhhBundle(allLoadedAssetBundle))) { continue; } val = allLoadedAssetBundle; break; } if ((Object)(object)val != (Object)null) { ManualLogSource? log3 = _log; if (log3 != null) { log3.LogInfo((object)"[Fix] Found already loaded DeathHeadHopper AssetBundle."); } } else { ManualLogSource? log4 = _log; if (log4 != null) { log4.LogInfo((object)("[Fix] Loading AssetBundle from: " + text)); } val = AssetBundle.LoadFromFile(text); if ((Object)(object)val == (Object)null) { ManualLogSource? log5 = _log; if (log5 != null) { log5.LogError((object)"[Fix] AssetBundle.LoadFromFile returned null."); } return false; } } if ((Object)(object)val == (Object)null) { ManualLogSource? log6 = _log; if (log6 != null) { log6.LogError((object)"[Fix] AssetBundle not available."); } return false; } _dhhBundle = val; } DHHAssetManager.headPhaseMaterial = val.LoadAsset("Assets/DeathHeadHopper/Materials/Head Phase.mat"); LoadItemsCompatible(val); DHHAssetManager.LoadAbilities(val); DHHAssetManager.LoadChargeAssets(val); DHHUIManager.abilityUIPrefab = val.LoadAsset("Assets/DeathHeadHopper/Ability UI.prefab"); ManualLogSource? log7 = _log; if (log7 != null) { log7.LogInfo((object)"[Fix] LoadAssets compatible flow completed."); } } catch (Exception ex) { ManualLogSource? log8 = _log; if (log8 != null) { log8.LogError((object)ex); } } return false; } private static void LoadItemsCompatible(AssetBundle bundle) { //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown Dictionary shopItems = DHHAssetManager.shopItems; shopItems.Clear(); List list = (from x in bundle.GetAllAssetNames() where x.EndsWith(".asset", StringComparison.OrdinalIgnoreCase) && x.IndexOf("/items/", StringComparison.OrdinalIgnoreCase) >= 0 select x).ToList(); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)$"[Fix] Found {list.Count} item assets in bundle."); } foreach (string item in list) { Item val = null; try { val = bundle.LoadAsset(item); } catch (Exception arg) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogError((object)$"[Fix] Exception loading item asset '{item}': {arg}"); } } if ((Object)(object)val == (Object)null) { ManualLogSource? log3 = _log; if (log3 != null) { log3.LogError((object)("[Fix] Failed to load item asset: " + item)); } continue; } string text = item.Replace(".asset", ".prefab"); GameObject val2 = bundle.LoadAsset(text); if ((Object)(object)val2 == (Object)null) { ManualLogSource? log4 = _log; if (log4 != null) { log4.LogError((object)("[Fix] Failed to load item prefab: " + text)); } continue; } Item val3 = val; if (val3.prefab == null) { val3.prefab = new PrefabRef(); } val.prefab.SetPrefab(val2, text); string name = ((Object)val).name; CachePrefabEntry(text, val2); string fileName = Path.GetFileName(text); if (!string.IsNullOrEmpty(fileName)) { CachePrefabEntry(fileName, val2); } CachePrefabEntry(name, val2); CachePrefabEntry("Items/" + name, val2); CacheShopItemKey(shopItems, name, val); DhhUpgradeOrchestrator.DisableLegacyToggleListeners(val2); TryRegisterItemWithRepolib(val, val2); TryRegisterUpgradeWithRepolib(val, val2); EnsureStatsItemDictionaryEntry(val); StatsModule.EnsureStatsEntriesForItem((Object)(object)val); ManualLogSource? log5 = _log; if (log5 != null) { log5.LogInfo((object)("[Fix] Loaded item '" + name + "' from '" + item + "' and bound prefab '" + text + "'.")); } } } private static void PatchRunManagerAwakeIfPossible(Harmony harmony) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown Harmony harmony2 = harmony; MethodInfo methodInfo = AccessTools.Method(typeof(RunManager), "Awake", (Type[])null, (Type[])null); if (!(methodInfo == null)) { Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo); if (patchInfo == null || !patchInfo.Postfixes.Any((Patch p) => p.owner == harmony2.Id)) { harmony2.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PrefabModule), "RunManager_Awake_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void RunManager_Awake_Postfix() { try { TryInjectPendingPool(); } catch (Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogError((object)ex); } } } private static void TryInjectPendingPool() { if (PendingPool.Count == 0) { return; } RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { return; } Dictionary singleplayerPool = instance.singleplayerPool; int num = 0; foreach (KeyValuePair item in PendingPool.ToList()) { if (!singleplayerPool.ContainsKey(item.Key) && (Object)(object)item.Value != (Object)null) { singleplayerPool[item.Key] = item.Value; num++; } ManualLogSource? log = _log; if (log != null) { log.LogDebug((object)$"[Fix] RunManager cache already has '{item.Key}'? {singleplayerPool.ContainsKey(item.Key)}"); } } if (num > 0) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogInfo((object)$"[Fix] Injected {num} prefabs into RunManager.singleplayerPool."); } } } private static void PatchPhotonDefaultPoolIfPossible(Harmony harmony) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(DefaultPool), "Instantiate", new Type[3] { typeof(string), typeof(Vector3), typeof(Quaternion) }, (Type[])null); if (!(methodInfo == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PrefabModule), "DefaultPool_Instantiate_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void PatchMultiplayerPoolIfPossible(Harmony harmony) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(MultiplayerPool), "Instantiate", new Type[3] { typeof(string), typeof(Vector3), typeof(Quaternion) }, (Type[])null); if (!(methodInfo == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PrefabModule), "MultiplayerPool_Instantiate_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static bool MultiplayerPool_Instantiate_Prefix(MultiplayerPool __instance, string prefabId, Vector3 position, Quaternion rotation, ref GameObject __result) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(prefabId)) { return true; } if (!TryGetPendingPrefab(prefabId, out GameObject prefab, out string normalized) || (Object)(object)prefab == (Object)null) { return true; } if (__instance != null && !__instance.ResourceCache.ContainsKey(prefabId)) { __instance.ResourceCache[prefabId] = prefab; } __result = Object.Instantiate(prefab, position, rotation); __result.SetActive(false); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)("[Fix] MultiplayerPool cached prefab '" + prefabId + "' (normalized '" + normalized + "')")); } return false; } private static bool DefaultPool_Instantiate_Prefix(string prefabId, Vector3 position, Quaternion rotation, ref GameObject __result) { //IL_0039: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) string text = NormalizePrefabKey(prefabId); if (string.IsNullOrEmpty(text)) { return true; } if (TryGetPendingPrefab(prefabId, out GameObject prefab, out string normalized) && (Object)(object)prefab != (Object)null) { __result = Object.Instantiate(prefab, position, rotation); __result.SetActive(false); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)("[Fix] DefaultPool cached prefab '" + prefabId + "' (normalized '" + normalized + "')")); } return false; } if (TryLoadPrefabFromBundle(prefabId, out prefab) && (Object)(object)prefab != (Object)null) { __result = Object.Instantiate(prefab, position, rotation); __result.SetActive(false); ManualLogSource? log2 = _log; if (log2 != null) { log2.LogInfo((object)("[Fix] DefaultPool loaded prefab '" + prefabId + "' from bundle.")); } return false; } if (FeatureFlags.DebugLogging && IsKnownModPrefab(prefabId, text)) { ManualLogSource? log3 = _log; if (log3 != null) { log3.LogWarning((object)("[Fix] DefaultPool missing cached prefab '" + prefabId + "' (normalized '" + text + "')")); } } return true; } private static bool TryGetPendingPrefab(string prefabId, out GameObject? prefab, out string normalized) { prefab = null; normalized = NormalizePrefabKey(prefabId); if (string.IsNullOrEmpty(normalized)) { return false; } if (PendingPool.TryGetValue(prefabId, out prefab)) { return true; } if (!string.Equals(prefabId, normalized, StringComparison.Ordinal) && PendingPool.TryGetValue(normalized, out prefab)) { return true; } return false; } private static void CachePrefabEntry(string? key, GameObject? prefab) { if (!((Object)(object)prefab == (Object)null) && !string.IsNullOrWhiteSpace(key)) { PendingPool[key] = prefab; string text = NormalizePrefabKey(key); if (!string.Equals(text, key, StringComparison.Ordinal)) { PendingPool[text] = prefab; } ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)("[Fix] Cached prefab '" + key + "' as normalized '" + text + "'")); } AddKnownPrefabKey(key); AddKnownPrefabKey(text); } } private static void AddKnownPrefabKey(string? key) { if (!string.IsNullOrWhiteSpace(key)) { _knownPrefabKeys.Add(key); } } private static bool ContainsKnownPrefabKey(string? key) { return !string.IsNullOrEmpty(key) && _knownPrefabKeys.Contains(key); } private static bool IsKnownModPrefab(string? prefabId, string normalizedId) { return ContainsKnownPrefabKey(prefabId) || ContainsKnownPrefabKey(normalizedId); } private static bool TryLoadPrefabFromBundle(string prefabId, out GameObject? prefab) { prefab = null; if ((Object)(object)_dhhBundle == (Object)null) { return false; } List list = new List(); list.Add(prefabId); list.Add(prefabId?.TrimStart(new char[1] { '/' })); List list2 = list; string text = NormalizePrefabKey(prefabId); if (!string.IsNullOrEmpty(text)) { list2.Add(text); } foreach (string item in list2) { if (string.IsNullOrEmpty(item)) { continue; } try { GameObject val = _dhhBundle.LoadAsset(item); if ((Object)(object)val != (Object)null) { prefab = val; if (!string.IsNullOrWhiteSpace(prefabId)) { CachePrefabEntry(prefabId, prefab); } return true; } } catch { } } return false; } private static string NormalizePrefabKey(string? key) { string text = key?.Trim(); return string.IsNullOrEmpty(text) ? string.Empty : text.ToLowerInvariant(); } private static void CacheShopItemKey(IDictionary dict, string? key, Item value) { if (!((Object)(object)value == (Object)null) && !string.IsNullOrWhiteSpace(key)) { if (dict.ContainsKey(key)) { dict[key] = value; } else { dict.Add(key, value); } } } private static void EnsureStatsItemDictionaryEntry(Item itemObj) { if ((Object)(object)itemObj == (Object)null) { return; } StatsManager instance = StatsManager.instance; if (!((Object)(object)instance == (Object)null) && instance.itemDictionary != null) { string name = ((Object)itemObj).name; if (!string.IsNullOrWhiteSpace(name) && (!instance.itemDictionary.TryGetValue(name, out var value) || !((Object)(object)value == (Object)(object)itemObj))) { instance.itemDictionary[name] = itemObj; } } } private static void TryRegisterItemWithRepolib(Item itemObj, GameObject prefab) { try { ItemAttributes val = (((Object)(object)prefab != (Object)null) ? (prefab.GetComponent() ?? prefab.GetComponentInChildren()) : null); if ((Object)(object)val == (Object)null) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[Fix] REPOLib RegisterItem skipped for '" + ((Object)itemObj).name + "': prefab has no ItemAttributes.")); } } else { if ((Object)(object)val.item == (Object)null) { val.item = itemObj; } Items.RegisterItem(val); } } catch (Exception ex) { if (LogLimiter.ShouldLog("Fix:REPOLib.RegisterItem:" + ((itemObj != null) ? ((Object)itemObj).name : null), 600)) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogWarning((object)("[Fix] REPOLib RegisterItem failed for '" + ((itemObj != null) ? ((Object)itemObj).name : null) + "': " + ex.Message)); } } } } private static void TryRegisterUpgradeWithRepolib(Item itemObj, GameObject prefab) { if ((Object)(object)itemObj == (Object)null || (Object)(object)prefab == (Object)null) { return; } try { bool flag = DhhUpgradeOrchestrator.HasChargeUpgrade(prefab); bool flag2 = DhhUpgradeOrchestrator.HasPowerUpgrade(prefab); if (flag || flag2) { if (flag) { RegisterOrBindUpgrade("HeadCharge", itemObj, isChargeUpgrade: true); } if (flag2) { RegisterOrBindUpgrade("HeadPower", itemObj, isChargeUpgrade: false); } } } catch (Exception ex) { if (LogLimiter.ShouldLog("Fix:REPOLib.RegisterUpgrade:" + ((itemObj != null) ? ((Object)itemObj).name : null), 600)) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[Fix] REPOLib RegisterUpgrade failed for '" + ((itemObj != null) ? ((Object)itemObj).name : null) + "': " + ex.Message)); } } } } private static void RegisterOrBindUpgrade(string upgradeId, Item itemObj, bool isChargeUpgrade) { PlayerUpgrade val = Upgrades.GetUpgrade(upgradeId); if (val == null) { val = Upgrades.RegisterUpgrade(upgradeId, itemObj, (Action)null, (Action)null); if (val == null) { return; } } DHHStatsManager instance = DHHStatsManager.instance; if (!((Object)(object)instance == (Object)null)) { val.PlayerDictionary = (isChargeUpgrade ? instance.playerUpgradeHeadCharge : instance.playerUpgradeHeadPower); } } private static bool LooksLikeDhhBundle(AssetBundle bundle) { try { string[] allAssetNames = bundle.GetAllAssetNames(); foreach (string text in allAssetNames) { if (text.IndexOf("deathheadhopper", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (text.IndexOf("head phase", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } catch { } return false; } } [HarmonyPatch] internal static class RigidbodyKinematicVelocityGuardPatch { private static readonly ManualLogSource Log = Logger.CreateLogSource("DeathHeadHopperFix.PhysicsGuard"); [HarmonyPatch(typeof(Rigidbody), "set_velocity")] [HarmonyPrefix] private static bool Rigidbody_SetVelocity_Prefix(Rigidbody __instance) { return AllowVelocityWrite(__instance, "linear"); } [HarmonyPatch(typeof(Rigidbody), "set_angularVelocity")] [HarmonyPrefix] private static bool Rigidbody_SetAngularVelocity_Prefix(Rigidbody __instance) { return AllowVelocityWrite(__instance, "angular"); } private static bool AllowVelocityWrite(Rigidbody rb, string kind) { if ((Object)(object)rb == (Object)null || !rb.isKinematic) { return true; } if (InternalDebugFlags.DebugPhysicsKinematicVelocityGuardLog && LogLimiter.ShouldLog($"PhysicsGuard.{kind}.{((Object)rb).GetInstanceID()}", 120)) { Log.LogInfo((object)$"[PhysicsGuard] Skipped {kind} velocity write on kinematic body '{((Object)rb).name}' (id={((Object)rb).GetInstanceID()})."); } return false; } } } namespace DeathHeadHopperFix.Modules.Gameplay.Core.Input { internal static class InputModule { [CompilerGenerated] private sealed class d__5 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public DHHPunManager punManager; private int 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; if ((Object)(object)punManager == (Object)null) { return false; } 5__1 = 0; break; case 1: <>1__state = -1; 5__1++; break; } if (5__1 < 300 && (!((Object)(object)punManager.photonView != (Object)null) || punManager.photonView.ViewID <= 0)) { <>2__current = null; <>1__state = 1; return true; } if ((Object)(object)punManager.photonView == (Object)null || punManager.photonView.ViewID <= 0) { return false; } punManager.VersionCheck(); 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(); } } [CompilerGenerated] private sealed class d__4 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private string 5__1; private int 5__2; private string 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 5__3 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = 0; break; case 1: <>1__state = -1; 5__3 = null; 5__2++; break; } if (5__2 < 300) { 5__3 = DHHPunManager.hostVersion; if (!string.IsNullOrWhiteSpace(5__3) && !string.Equals(5__3, "pending", StringComparison.OrdinalIgnoreCase)) { return false; } <>2__current = null; <>1__state = 1; return true; } 5__1 = DHHPunManager.hostVersion; if (string.Equals(5__1, "pending", StringComparison.OrdinalIgnoreCase)) { DHHPunManager.hostVersion = string.Empty; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)"Host does not have DeathHeadHopper installed!"); } } 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 static ManualLogSource? _log; internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { _log = log; PatchDhhInputManagerAwakeIfPossible(harmony, asm); DHHPunViewFixModule.Apply(harmony, asm, log); } private static void PatchDhhInputManagerAwakeIfPossible(Harmony harmony, Assembly asm) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown if (harmony == null || asm == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(DHHInputManager), "Awake", (Type[])null, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(InputModule).GetMethod("DHHInputManager_Awake_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void DHHInputManager_Awake_Prefix(MonoBehaviour __instance) { try { if (!string.IsNullOrWhiteSpace(DHHPunManager.hostVersion)) { return; } if (SemiFunc.IsMasterClientOrSingleplayer()) { string text = DHHPunManager.localVersion; if (string.IsNullOrWhiteSpace(text)) { text = GetDeathHeadHopperVersionString(); } if (!string.IsNullOrWhiteSpace(text)) { DHHPunManager.hostVersion = text; } } else { DHHPunManager.hostVersion = "pending"; __instance.StartCoroutine(DHHInputManager_InvokeVersionCheckWhenReady(DHHPunManager.instance)); __instance.StartCoroutine(DHHInputManager_WaitForHostVersion()); } } catch { } } [IteratorStateMachine(typeof(d__4))] private static IEnumerator DHHInputManager_WaitForHostVersion() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__4(0); } [IteratorStateMachine(typeof(d__5))] private static IEnumerator DHHInputManager_InvokeVersionCheckWhenReady(DHHPunManager? punManager) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__5(0) { punManager = punManager }; } private static string? GetDeathHeadHopperVersionString() { return DeathHeadHopper.Version.ToString(); } } } namespace DeathHeadHopperFix.Modules.Gameplay.Core.Bootstrap { [HarmonyPatch(typeof(GameDirector), "Awake")] internal static class ConfigSyncBootstrapPatch { [HarmonyPostfix] private static void Postfix() { ConfigSyncManager.EnsureCreated(); } } internal static class DHHStatsBootstrapModule { private const string HeadChargeKey = "playerUpgradeHeadCharge"; private const string HeadPowerKey = "playerUpgradeHeadPower"; private static ManualLogSource? _log; internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { _log = log; PatchDhhStatsManagerAwakeIfPossible(harmony); PatchDhhStatsManagerUpgradeMethodsIfPossible(harmony); PatchDhhStatsManagerUpdateMethodsIfPossible(harmony); PatchStatsManagerAwakeIfPossible(harmony); PatchStatsManagerLoadGameIfPossible(harmony); } private static void EnsureDhhStatsLabels() { StatsManager instance = StatsManager.instance; if (!((Object)(object)instance == (Object)null) && instance.upgradesInfo != null) { EnsureDhhUpgradeInfo(instance, "playerUpgradeHeadCharge", "Head Charge"); EnsureDhhUpgradeInfo(instance, "playerUpgradeHeadPower", "Head Power"); } } private static void PatchDhhStatsManagerAwakeIfPossible(Harmony harmony) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(DHHStatsManager), "Awake", (Type[])null, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(DHHStatsBootstrapModule).GetMethod("DHHStatsManager_Awake_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void DHHStatsManager_Awake_Postfix(DHHStatsManager __instance) { EnsureDhhUpgradeDictionaries(); EnsureRepolibUpgradeDictionaries(); EnsureDhhStatsManagerDisabled(); if ((Object)(object)__instance != (Object)null) { ((Behaviour)__instance).enabled = false; } } private static void PatchDhhStatsManagerUpgradeMethodsIfPossible(Harmony harmony) { //IL_008f: 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_00a1: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(DHHStatsManager), "UpgradeHeadCharge", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(DHHStatsManager), "UpgradeHeadPower", (Type[])null, (Type[])null); if (methodInfo == null && methodInfo2 == null) { return; } MethodInfo method = typeof(DHHStatsBootstrapModule).GetMethod("DHHStatsManager_Upgrade_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { if (methodInfo != null) { HarmonyMethod val = new HarmonyMethod(method) { priority = 800 }; harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo2 != null) { HarmonyMethod val2 = new HarmonyMethod(method) { priority = 800 }; harmony.Patch((MethodBase)methodInfo2, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void PatchDhhStatsManagerUpdateMethodsIfPossible(Harmony harmony) { //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(DHHStatsManager), "UpdateHeadChargeStat", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(DHHStatsManager), "UpdateHeadPowerStat", (Type[])null, (Type[])null); if (methodInfo == null && methodInfo2 == null) { return; } MethodInfo method = typeof(DHHStatsBootstrapModule).GetMethod("DHHStatsManager_UpdateHeadChargeStat_Prefix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method2 = typeof(DHHStatsBootstrapModule).GetMethod("DHHStatsManager_UpdateHeadChargeStat_Postfix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method3 = typeof(DHHStatsBootstrapModule).GetMethod("DHHStatsManager_UpdateHeadPowerStat_Prefix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method4 = typeof(DHHStatsBootstrapModule).GetMethod("DHHStatsManager_UpdateHeadPowerStat_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null) && !(method2 == null) && !(method3 == null) && !(method4 == null)) { if (methodInfo != null) { HarmonyMethod val = new HarmonyMethod(method) { priority = 800 }; HarmonyMethod val2 = new HarmonyMethod(method2); harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo2 != null) { HarmonyMethod val3 = new HarmonyMethod(method3) { priority = 800 }; HarmonyMethod val4 = new HarmonyMethod(method4); harmony.Patch((MethodBase)methodInfo2, val3, val4, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool DHHStatsManager_Upgrade_Prefix(DHHStatsManager __instance, string playerId) { EnsureDhhPlayerUpgradeKey(__instance, playerId, isChargeUpgrade: false); EnsureDhhPlayerUpgradeKey(__instance, playerId, isChargeUpgrade: true); return true; } private static bool DHHStatsManager_UpdateHeadChargeStat_Prefix(DHHStatsManager __instance, string playerId, ref int __state) { EnsureDhhPlayerUpgradeKey(__instance, playerId, isChargeUpgrade: true); __instance.playerUpgradeHeadCharge.TryGetValue(playerId, out __state); return true; } private static void DHHStatsManager_UpdateHeadChargeStat_Postfix(string playerId, int value, int __state) { DhhUpgradeOrchestrator.PlayAuthorizedLocalFeedback(playerId, value, __state); } private static bool DHHStatsManager_UpdateHeadPowerStat_Prefix(DHHStatsManager __instance, string playerId, ref int __state) { EnsureDhhPlayerUpgradeKey(__instance, playerId, isChargeUpgrade: false); __instance.playerUpgradeHeadPower.TryGetValue(playerId, out __state); return true; } private static void DHHStatsManager_UpdateHeadPowerStat_Postfix(string playerId, int value, int __state) { DhhUpgradeOrchestrator.PlayAuthorizedLocalFeedback(playerId, value, __state); } private static void PatchStatsManagerAwakeIfPossible(Harmony harmony) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(StatsManager), "Awake", (Type[])null, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(DHHStatsBootstrapModule).GetMethod("StatsManager_Awake_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void StatsManager_Awake_Postfix() { EnsureDhhStatsLabels(); EnsureDhhUpgradeDictionaries(); EnsureRepolibUpgradeDictionaries(); EnsureDhhStatsManagerDisabled(); } private static void PatchStatsManagerLoadGameIfPossible(Harmony harmony) { //IL_0080: 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_0093: Expected O, but got Unknown //IL_0093: Expected O, but got Unknown if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(StatsManager), "LoadGame", (Type[])null, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(DHHStatsBootstrapModule).GetMethod("StatsManager_LoadGame_Prefix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method2 = typeof(DHHStatsBootstrapModule).GetMethod("StatsManager_LoadGame_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null) && !(method2 == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void StatsManager_LoadGame_Prefix() { EnsureDhhStatsLabels(); EnsureDhhUpgradeDictionaries(); EnsureRepolibUpgradeDictionaries(); EnsureDhhStatsManagerDisabled(); } private static void StatsManager_LoadGame_Postfix() { EnsureDhhStatsLabels(); EnsureDhhUpgradeDictionaries(); EnsureRepolibUpgradeDictionaries(); EnsureDhhStatsManagerDisabled(); try { DHHAbilityManager instance = DHHAbilityManager.instance; if (instance != null) { instance.EquipAbilities(); } } catch (Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[Fix:Stats] Failed to refresh DHH abilities after load: " + ex.Message)); } } } private static void EnsureDhhStatsManagerDisabled() { DHHStatsManager instance = DHHStatsManager.instance; if ((Object)(object)instance == (Object)null || !((Behaviour)instance).enabled) { return; } ((Behaviour)instance).enabled = false; if (FeatureFlags.DebugLogging) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)"[Fix:Stats] DHHStatsManager disabled so the Fix can own the compatibility path."); } } } private static void EnsureDhhUpgradeDictionaries() { StatsManager instance = StatsManager.instance; DHHStatsManager instance2 = DHHStatsManager.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || instance.dictionaryOfDictionaries == null) { return; } instance.dictionaryOfDictionaries["playerUpgradeHeadCharge"] = instance2.playerUpgradeHeadCharge; instance.dictionaryOfDictionaries["playerUpgradeHeadPower"] = instance2.playerUpgradeHeadPower; if (FeatureFlags.DebugLogging) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)"[Fix:Stats] Bound DHH upgrade dictionaries into StatsManager."); } } } private static void EnsureRepolibUpgradeDictionaries() { DHHStatsManager instance = DHHStatsManager.instance; if (!((Object)(object)instance == (Object)null)) { BindRepolibUpgrade("HeadCharge", instance.playerUpgradeHeadCharge, "charge"); BindRepolibUpgrade("HeadPower", instance.playerUpgradeHeadPower, "power"); } } private static void BindRepolibUpgrade(string upgradeId, Dictionary dictionary, string label) { PlayerUpgrade upgrade = Upgrades.GetUpgrade(upgradeId); if (upgrade == null) { return; } upgrade.PlayerDictionary = dictionary; if (FeatureFlags.DebugLogging) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)("[Fix:Stats] Bound REPOLib upgrade '" + upgradeId + "' to DHH " + label + " dictionary.")); } } } private static void EnsureDhhPlayerUpgradeKey(DHHStatsManager? stats, string playerId, bool isChargeUpgrade) { if (!((Object)(object)stats == (Object)null) && !string.IsNullOrWhiteSpace(playerId)) { Dictionary dictionary = (isChargeUpgrade ? stats.playerUpgradeHeadCharge : stats.playerUpgradeHeadPower); if (!dictionary.ContainsKey(playerId)) { dictionary[playerId] = 0; } } } private static void EnsureDhhUpgradeInfo(StatsManager stats, string key, string displayName) { //IL_0029: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown if (stats.upgradesInfo == null || stats.upgradesInfo.ContainsKey(key)) { return; } stats.upgradesInfo.Add(key, new UpgradeInfo { displayName = displayName, displayNameLocalized = null }); if (FeatureFlags.DebugLogging) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)("[Fix:Stats] Added upgrade label '" + key + "' -> '" + displayName + "'.")); } } } } } namespace DeathHeadHopperFix.Modules.Gameplay.Core.Audio { internal static class AudioModule { [CompilerGenerated] private sealed class d__4 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public AudioHandler handler; private int 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__1 = 0; break; case 1: <>1__state = -1; break; } if (5__1++ < 600) { if ((Object)(object)handler == (Object)null) { return false; } if (TryInitAudioHandlerSafe(handler)) { return false; } <>2__current = null; <>1__state = 1; return true; } ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)"[Fix] AudioHandler init timed out; audio may be partially disabled."); } 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(); } } [CompilerGenerated] private sealed class d__7 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private CodeInstruction <>2__current; private int <>l__initialThreadId; private IEnumerable instructions; public IEnumerable <>3__instructions; private MethodInfo 5__1; private MethodInfo 5__2; private IEnumerator <>s__3; private CodeInstruction 5__4; private IEnumerator <>s__5; private CodeInstruction 5__6; CodeInstruction IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__7(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { switch (<>1__state) { case -3: case 1: try { } finally { <>m__Finally1(); } break; case -4: case 2: case 3: try { } finally { <>m__Finally2(); } break; } 5__1 = null; 5__2 = null; <>s__3 = null; 5__4 = null; <>s__5 = null; 5__6 = null; <>1__state = -2; } private bool MoveNext() { //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__1 = AccessTools.Method(typeof(AudioHandler), "PlayWindupSoundLoop", new Type[1] { typeof(float) }, (Type[])null); 5__2 = AccessTools.Method(typeof(AudioModule), "PlayWindupSoundLoopCompat", new Type[2] { typeof(AudioHandler), typeof(float) }, (Type[])null); if (5__1 == null || 5__2 == null) { <>s__3 = instructions.GetEnumerator(); <>1__state = -3; goto IL_0120; } <>s__5 = instructions.GetEnumerator(); <>1__state = -4; break; case 1: <>1__state = -3; 5__4 = null; goto IL_0120; case 2: <>1__state = -4; break; case 3: { <>1__state = -4; 5__6 = null; break; } IL_0120: if (<>s__3.MoveNext()) { 5__4 = <>s__3.Current; <>2__current = 5__4; <>1__state = 1; return true; } <>m__Finally1(); <>s__3 = null; return false; } if (<>s__5.MoveNext()) { 5__6 = <>s__5.Current; if (5__6.opcode == OpCodes.Callvirt && object.Equals(5__6.operand, 5__1)) { <>2__current = new CodeInstruction(OpCodes.Call, (object)5__2); <>1__state = 2; return true; } <>2__current = 5__6; <>1__state = 3; return true; } <>m__Finally2(); <>s__5 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>s__3 != null) { <>s__3.Dispose(); } } private void <>m__Finally2() { <>1__state = -1; if (<>s__5 != null) { <>s__5.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__7 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__7(0); } d__.instructions = <>3__instructions; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private static readonly HashSet AudioInitDone = new HashSet(); private static ManualLogSource? _log; internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown _log = log; if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(AudioHandler), "Awake", Type.EmptyTypes, (Type[])null); if (methodInfo == null) { return; } MethodInfo method = typeof(AudioModule).GetMethod("AudioHandler_Awake_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ChargeEffects), "Update", Type.EmptyTypes, (Type[])null); MethodInfo method2 = typeof(AudioModule).GetMethod("ChargeEffects_Update_Transpiler", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo2 != null && method2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool AudioHandler_Awake_Prefix(AudioHandler __instance) { try { int instanceID = ((Object)__instance).GetInstanceID(); if (AudioInitDone.Contains(instanceID)) { return false; } ((MonoBehaviour)__instance).StartCoroutine(AudioHandler_InitWhenReady(__instance)); } catch (Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogError((object)ex); } } return false; } [IteratorStateMachine(typeof(d__4))] private static IEnumerator AudioHandler_InitWhenReady(AudioHandler handler) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__4(0) { handler = handler }; } private static bool TryInitAudioHandlerSafe(AudioHandler handler) { int instanceID = ((Object)handler).GetInstanceID(); if (AudioInitDone.Contains(instanceID)) { return true; } if (handler.controller == null) { handler.controller = ((Component)handler).GetComponent(); } DeathHeadController controller = handler.controller; if ((Object)(object)controller == (Object)null) { return false; } PlayerDeathHead deathHead = controller.deathHead; PlayerAvatar val = (((Object)(object)deathHead != (Object)null) ? deathHead.playerAvatar : null); PhysAudio val2 = ((Component)handler).GetComponent()?.audioPreset; if ((Object)(object)val2 == (Object)null && (Object)(object)val == (Object)null && (Object)(object)deathHead == (Object)null) { return false; } if (!TryEnsureSound(handler, val2, deathHead, val)) { return false; } AudioInitDone.Add(instanceID); if (FeatureFlags.DebugLogging) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)"[Fix] AudioHandler initialized safely (deferred)."); } } return true; } private static bool TryEnsureSound(AudioHandler handler, PhysAudio? audioPreset, PlayerDeathHead? deathHead, PlayerAvatar? playerAvatar) { try { AudioHandler val = handler; if (val.jumpSound == null) { val.jumpSound = CreateSound(CloneClips(audioPreset?.impactMedium), null, 0.12f, 0f, 0.8f, 0f); } } catch { } try { AudioHandler val = handler; if (val.anchorBreakSound == null) { val.anchorBreakSound = CreateSound(CloneClips(playerAvatar?.tumbleBreakFreeSound), handler.CreateAudioSource(), 0.1f, 0f, 1f, 0f); } } catch { } try { AudioHandler val = handler; if (val.anchorAttachSound == null) { val.anchorAttachSound = CreateSound(CloneClips(deathHead?.eyeFlashNegativeSound), handler.CreateAudioSource(), 0.5f, 0f, 0.3f, 0.03f); } } catch { } try { AudioHandler val = handler; if (val.windupSound == null) { val.windupSound = CreateSound(CloneClips(PlayerAvatar.instance?.tumble?.tumbleMoveSound), handler.CreateAudioSource(), 0.4f, 0.02f, 0.8f, 0f); } } catch { } try { AudioHandler val = handler; if (val.rechargeSound == null) { val.rechargeSound = CreateSound(CloneClips(AssetManager.instance?.batteryChargeSound), handler.CreateAudioSource(), 0.2f, 0.01f, 1f, 0.02f); } } catch { } try { MaterialPreset val2 = (((Object)(object)Materials.Instance != (Object)null) ? ((IEnumerable)Materials.Instance.MaterialList).FirstOrDefault((Func)((MaterialPreset x) => (int)x.Type == 2)) : null); AudioHandler val = handler; if (val.unAnchoringSound == null) { val.unAnchoringSound = CreateSound(CloneClips(val2?.SlideOneShot), handler.CreateAudioSource(), 1f, 0f, 0.6f, 0f); } } catch { } return handler.jumpSound != null || handler.anchorBreakSound != null || handler.anchorAttachSound != null || handler.windupSound != null || handler.rechargeSound != null || handler.unAnchoringSound != null; } [IteratorStateMachine(typeof(d__7))] private static IEnumerable ChargeEffects_Update_Transpiler(IEnumerable instructions) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__7(-2) { <>3__instructions = instructions }; } private static void PlayWindupSoundLoopCompat(AudioHandler? handler, float pitch) { try { Sound val = (((Object)(object)handler != (Object)null) ? handler.windupSound : null); if (val != null) { val.PlayLoop(true, 3f, 3f, pitch, 1f); } } catch (Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[Fix] Failed to play DHH windup sound via compatibility prefix: " + ex.Message)); } } } private static AudioClip[]? CloneClips(Sound? sound) { return (sound?.Sounds != null && sound.Sounds.Length != 0) ? (sound.Sounds.Clone() as AudioClip[]) : null; } private static Sound? CreateSound(AudioClip[]? clips, AudioSource? src, float vol, float volRand, float pitch, float pitchRand) { //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_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown if (clips == null || clips.Length == 0) { return null; } return new Sound { Source = src, Sounds = clips, Volume = vol, VolumeRandom = volRand, Pitch = pitch, PitchRandom = pitchRand }; } } } namespace DeathHeadHopperFix.Modules.Gameplay.Core.Abilities { internal static class AbilityModule { private sealed class AbilitySpotUpdateDriver : MonoBehaviour { private AbilitySpot? _spot; internal void Initialize(AbilitySpot spot) { _spot = spot; } private void Update() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) AbilitySpot spot = _spot; if (!((Object)(object)spot == (Object)null) && !InternalDebugFlags.DisableAbilityPatches) { AbilityBase currentAbility = spot.CurrentAbility; if ((Object)(object)currentAbility == (Object)null) { ((SemiUI)spot).SemiUIScoot(new Vector2(0f, -20f), 0.2f); } else { ((TMP_Text)spot.level).text = $"LV. {currentAbility.AbilityLevel}"; } RunSemiUiUpdate((SemiUI)(object)spot); AfterAbilitySpotUpdate(spot); } } private void OnDestroy() { if ((Object)(object)_spot != (Object)null) { AbilitySpot_OnDestroy(_spot); } } private static void RunSemiUiUpdate(SemiUI ui) { if (ui.initializedTimer > 0f) { ui.initializedTimer -= Time.deltaTime; return; } float deltaTime = Time.deltaTime; if (ui.scootTimer >= 0f) { ui.scootTimer -= deltaTime; } ui.FlashColorLogic(deltaTime); ui.HideAnimationLogic(deltaTime); ui.HideTimer(deltaTime); ui.SpringScaleLogic(deltaTime); ui.ScootPositionLogic(deltaTime); ui.SpringShakeLogic(deltaTime); ui.UpdatePositionLogic(); ui.prevShowTimer = ui.showTimer; ui.prevHideTimer = ui.hideTimer; ui.prevScootTimer = ui.scootTimer; ui.prevStopHidingTimer = ui.stopHidingTimer; ui.prevStopShowingTimer = ui.stopShowingTimer; if (ui.hideTimer >= 0f) { ui.hideTimer -= deltaTime; } if (ui.showTimer >= 0f) { ui.showTimer -= deltaTime; } if (ui.stopShowingTimer >= 0f) { ui.stopShowingTimer -= deltaTime; } if (ui.stopHidingTimer >= 0f) { ui.stopHidingTimer -= deltaTime; } if (ui.stopScootingTimer >= 0f) { ui.stopScootingTimer -= deltaTime; } } } private static class AbilitySpotLabelOverlay { private static readonly Dictionary Labels = new Dictionary(); internal static void EnsureLabel(AbilitySpot spot) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_006a: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00ce: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)spot == (Object)null) && !Labels.ContainsKey(spot)) { GameObject val = new GameObject("DHHAbilityLabel", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(((Component)spot).transform, false); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0f, 0f); component.anchorMax = new Vector2(1f, 0f); component.pivot = new Vector2(0.5f, 0f); component.anchoredPosition = new Vector2(0f, 2f); component.sizeDelta = new Vector2(0f, 16f); ((Transform)component).localScale = Vector3.one; ((Transform)component).SetAsLastSibling(); TextMeshProUGUI val2 = val.AddComponent(); SetLabelDefaults(val2); Labels[spot] = val2; UpdateLabel(spot); } } internal static void UpdateLabel(AbilitySpot spot) { if (!((Object)(object)spot == (Object)null)) { TextMeshProUGUI label = GetLabel(spot); if (!((Object)(object)label == (Object)null)) { string slotTag = GetSlotTag(spot); SetLabelText(label, slotTag); } } } internal static void SetDirectionLabel(AbilitySpot spot, string text) { TextMeshProUGUI label = GetLabel(spot); if (!((Object)(object)label == (Object)null)) { SetLabelText(label, text); } } internal static void ClearLabel(AbilitySpot spot) { if (!((Object)(object)spot == (Object)null) && Labels.TryGetValue(spot, out TextMeshProUGUI value)) { Labels.Remove(spot); Object.Destroy((Object)(object)((Component)value).gameObject); } } private static TextMeshProUGUI? GetLabel(AbilitySpot spot) { TextMeshProUGUI value; return Labels.TryGetValue(spot, out value) ? value : null; } private static string GetSlotTag(AbilitySpot spot) { return string.Empty; } private static void SetLabelDefaults(TextMeshProUGUI label) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)label == (Object)null)) { ((Graphic)label).color = Color.white; ((TMP_Text)label).fontSize = 11f; ((TMP_Text)label).enableAutoSizing = false; ((TMP_Text)label).enableWordWrapping = false; ((TMP_Text)label).richText = false; ((TMP_Text)label).alignment = (TextAlignmentOptions)514; SetLabelText(label, string.Empty); } } private static void SetLabelText(TextMeshProUGUI label, string text) { if (!((Object)(object)label == (Object)null)) { ((TMP_Text)label).text = text; ((Behaviour)label).enabled = !string.IsNullOrEmpty(text); } } } private static class SlotLayoutOverrides { internal static void EnsureBasePosition(AbilitySpot spot) { //IL_0043: 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_002e: 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) if (!((Object)(object)spot == (Object)null)) { if (!s_spotBaseLocalPos.TryGetValue(spot, out var value)) { value = ((Component)spot).transform.localPosition; s_spotBaseLocalPos[spot] = value; } ((Component)spot).transform.localPosition = value; } } internal static void RestoreBasePosition(AbilitySpot spot) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)spot == (Object)null) && s_spotBaseLocalPos.TryGetValue(spot, out var value)) { ((Component)spot).transform.localPosition = value; } } } private static class SlotCostOverrides { internal static void SetDirectionCostText(AbilitySpot spot, string costText) { if (!((Object)(object)spot?.energyCost == (Object)null)) { ((TMP_Text)spot.energyCost).text = costText ?? string.Empty; } } internal static void RestoreDefaultCostText(AbilitySpot spot) { if (!((Object)(object)spot?.energyCost == (Object)null)) { string text = "0"; AbilityBase currentAbility = spot.CurrentAbility; if ((Object)(object)currentAbility != (Object)null) { text = Mathf.RoundToInt(currentAbility.EnergyCost).ToString(); } ((TMP_Text)spot.energyCost).text = text; } } } private static class SlotVisualOverrides { private static readonly Dictionary s_cooldownIconBaseColors = new Dictionary(); private static readonly Dictionary s_chargeHoldRestoreFillAmounts = new Dictionary(); private static readonly Dictionary s_chargeHoldRestoreColors = new Dictionary(); internal static void ApplyDirectionIcon(AbilitySpot spot, Sprite sprite) { if (!((Object)(object)spot == (Object)null) && !((Object)(object)sprite == (Object)null)) { SetImageSpriteAndEnable(spot.backgroundIcon, sprite); SetImageSpriteAndEnable(spot.cooldownIcon, sprite); if ((Object)(object)spot.noAbility != (Object)null) { ((Behaviour)spot.noAbility).enabled = false; } } } internal static void RestoreDefaultIcon(AbilitySpot spot) { if (!((Object)(object)spot == (Object)null) && !((Object)(object)((Component)spot).gameObject == (Object)null)) { AbilityBase currentAbility = spot.CurrentAbility; try { spot.SetIcon(currentAbility?.icon); } catch { return; } if ((Object)(object)spot.noAbility != (Object)null) { ((Behaviour)spot.noAbility).enabled = (Object)(object)currentAbility == (Object)null; } } } internal static void ApplyDirectionActivationProgress(AbilitySpot spot, float progress01) { //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_004e: 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_00a3: 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) if ((Object)(object)spot == (Object)null) { return; } Image cooldownIcon = spot.cooldownIcon; if (!((Object)(object)cooldownIcon == (Object)null)) { if (!s_cooldownIconBaseColors.TryGetValue(cooldownIcon, out var value)) { value = ((Graphic)cooldownIcon).color; s_cooldownIconBaseColors[cooldownIcon] = value; } float num = Mathf.Clamp01(progress01); if (num <= 0f) { cooldownIcon.fillAmount = 0f; ((Graphic)cooldownIcon).color = value; } else { cooldownIcon.fillAmount = num; ((Graphic)cooldownIcon).color = new Color(0.2f, 1f, 0.2f, value.a); ((Behaviour)cooldownIcon).enabled = true; } } } internal static void ApplyDirectionEnergyAvailability(AbilitySpot spot, bool hasEnoughEnergy, float progress01) { //IL_0041: 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) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0078: 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) if ((Object)(object)spot == (Object)null) { return; } Image cooldownIcon = spot.cooldownIcon; if (!((Object)(object)cooldownIcon == (Object)null)) { if (!s_cooldownIconBaseColors.TryGetValue(cooldownIcon, out var value)) { value = ((Graphic)cooldownIcon).color; s_cooldownIconBaseColors[cooldownIcon] = value; } float num = Mathf.Clamp01(progress01); float num2 = ((num > 0f) ? num : (hasEnoughEnergy ? 1f : 0f)); Color color = value; color.a = ((num2 < 1f) ? 0.3f : 1f); cooldownIcon.fillAmount = num2; ((Graphic)cooldownIcon).color = color; } } internal static void ApplyChargeActivationProgress(AbilitySpot spot, float progress01, bool canReleaseActivate) { //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_0050: 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_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_0185: 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_0169: 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_0190: 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_00fa: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)spot == (Object)null) { return; } Image cooldownIcon = spot.cooldownIcon; if ((Object)(object)cooldownIcon == (Object)null) { return; } if (!s_cooldownIconBaseColors.TryGetValue(cooldownIcon, out var value)) { value = ((Graphic)cooldownIcon).color; s_cooldownIconBaseColors[cooldownIcon] = value; } float num = Mathf.Clamp01(progress01); if (num <= 0f) { bool flag = false; bool flag2 = false; if (s_chargeHoldRestoreFillAmounts.TryGetValue(cooldownIcon, out var value2)) { cooldownIcon.fillAmount = value2; s_chargeHoldRestoreFillAmounts.Remove(cooldownIcon); flag = true; } if (s_chargeHoldRestoreColors.TryGetValue(cooldownIcon, out var value3)) { ((Graphic)cooldownIcon).color = value3; s_chargeHoldRestoreColors.Remove(cooldownIcon); flag2 = true; } if (!flag) { cooldownIcon.fillAmount = 1f; } if (!flag2) { ((Graphic)cooldownIcon).color = value; } } else { if (!s_chargeHoldRestoreFillAmounts.ContainsKey(cooldownIcon)) { s_chargeHoldRestoreFillAmounts[cooldownIcon] = cooldownIcon.fillAmount; } if (!s_chargeHoldRestoreColors.ContainsKey(cooldownIcon)) { s_chargeHoldRestoreColors[cooldownIcon] = ((Graphic)cooldownIcon).color; } Color color = (canReleaseActivate ? new Color(0.2f, 1f, 0.2f, value.a) : new Color(1f, 0.2f, 0.2f, value.a)); cooldownIcon.fillAmount = num; ((Graphic)cooldownIcon).color = color; ((Behaviour)cooldownIcon).enabled = true; } } private static void SetImageSpriteAndEnable(Image? image, Sprite sprite) { if (!((Object)(object)image == (Object)null) && !((Object)(object)sprite == (Object)null)) { image.sprite = sprite; ((Behaviour)image).enabled = true; } } } private const string DirectionEnergyLogKey = "Fix:Ability.DirectionEnergy"; private static readonly HashSet s_trackedSpots = new HashSet(); private static readonly Dictionary s_spotBaseLocalPos = new Dictionary(); private static readonly Dictionary s_lastDirectionVisibilityBySpot = new Dictionary(); private static readonly Dictionary s_lastDirectionCostLabelBySpot = new Dictionary(); private static readonly Dictionary s_lastDirectionProgressBySpot = new Dictionary(); private static readonly Dictionary s_lastDirectionEnergySufficientBySpot = new Dictionary(); private static float s_directionActivationProgress; private const int DirectionIndicatorSlotIndex = 1; private const int ChargeAbilitySlotIndex = 0; internal static void ApplyAbilitySpotLabelOverlay(Harmony harmony, Assembly asm) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(AbilitySpot), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(AbilityModule), "AbilitySpot_Start_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(AbilitySpot), "UpdateUI", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(AbilityModule), "AbilitySpot_UpdateUI_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } internal static void ApplyAbilityManagerHooks(Harmony harmony, Assembly asm) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(DHHAbilityManager), "OnAbilityUsed", new Type[1] { typeof(AbilityBase) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(AbilityModule), "DHHAbilityManager_OnAbilityUsed_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void AbilitySpot_Start_Postfix(AbilitySpot __instance) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null) && !InternalDebugFlags.DisableAbilityPatches) { s_trackedSpots.Add(__instance); s_spotBaseLocalPos[__instance] = ((Component)__instance).transform.localPosition; AbilitySpotLabelOverlay.EnsureLabel(__instance); ApplySlot2DirectionVisual(__instance); AbilitySpotUpdateDriver abilitySpotUpdateDriver = ((Component)__instance).GetComponent() ?? ((Component)__instance).gameObject.AddComponent(); abilitySpotUpdateDriver.Initialize(__instance); ((Behaviour)__instance).enabled = false; } } private static void AbilitySpot_UpdateUI_Postfix(AbilitySpot __instance) { if (!((Object)(object)__instance == (Object)null) && !InternalDebugFlags.DisableAbilityPatches) { AbilitySpotLabelOverlay.UpdateLabel(__instance); ApplySlot2DirectionVisual(__instance); } } private static void AfterAbilitySpotUpdate(AbilitySpot __instance) { if (!((Object)(object)__instance == (Object)null) && !InternalDebugFlags.DisableAbilityPatches && GetAbilityIndex(__instance) == 1 && LastChanceInteropBridge.IsDirectionIndicatorUiVisible()) { SlotLayoutOverrides.EnsureBasePosition(__instance); } } private static void AbilitySpot_OnDestroy(AbilitySpot spot) { if (!((Object)(object)spot == (Object)null)) { s_trackedSpots.Remove(spot); s_spotBaseLocalPos.Remove(spot); s_lastDirectionVisibilityBySpot.Remove(spot); s_lastDirectionCostLabelBySpot.Remove(spot); s_lastDirectionProgressBySpot.Remove(spot); s_lastDirectionEnergySufficientBySpot.Remove(spot); AbilitySpotLabelOverlay.ClearLabel(spot); } } internal static void TriggerDirectionSlotCooldown(float cooldownSeconds) { if (InternalDebugFlags.DisableAbilityPatches) { return; } s_directionActivationProgress = 0f; float num = Mathf.Max(0f, cooldownSeconds); if (num <= 0f) { return; } foreach (AbilitySpot s_trackedSpot in s_trackedSpots) { if (!((Object)(object)s_trackedSpot == (Object)null) && GetAbilityIndex(s_trackedSpot) == 1) { try { s_trackedSpot.SetCooldown(num); SlotVisualOverrides.ApplyDirectionActivationProgress(s_trackedSpot, 0f); SlotVisualOverrides.ApplyDirectionEnergyAvailability(s_trackedSpot, LastChanceInteropBridge.IsDirectionIndicatorEnergySufficientPreview(), s_directionActivationProgress); } catch { } } } } internal static void SetDirectionSlotActivationProgress(float progress01) { if (InternalDebugFlags.DisableAbilityPatches) { return; } s_directionActivationProgress = Mathf.Clamp01(progress01); if (s_trackedSpots.Count == 0) { return; } foreach (AbilitySpot s_trackedSpot in s_trackedSpots) { if (IsSpotUsable(s_trackedSpot) && GetAbilityIndex(s_trackedSpot) == 1 && LastChanceInteropBridge.IsDirectionIndicatorUiVisible()) { SlotVisualOverrides.ApplyDirectionActivationProgress(s_trackedSpot, s_directionActivationProgress); SlotVisualOverrides.ApplyDirectionEnergyAvailability(s_trackedSpot, LastChanceInteropBridge.IsDirectionIndicatorEnergySufficientPreview(), s_directionActivationProgress); } } } internal static void SetChargeSlotActivationProgress(float progress01, float releaseThreshold01 = 0f) { if (InternalDebugFlags.DisableAbilityPatches || s_trackedSpots.Count == 0) { return; } float num = Mathf.Clamp01(progress01); float num2 = Mathf.Clamp01(releaseThreshold01); bool canReleaseActivate = num >= num2; foreach (AbilitySpot s_trackedSpot in s_trackedSpots) { if (IsSpotUsable(s_trackedSpot) && GetAbilityIndex(s_trackedSpot) == 0) { SlotVisualOverrides.ApplyChargeActivationProgress(s_trackedSpot, num, canReleaseActivate); } } } internal static void RefreshDirectionSlotVisuals() { if (InternalDebugFlags.DisableAbilityPatches || s_trackedSpots.Count == 0) { return; } List list = new List(); foreach (AbilitySpot s_trackedSpot in s_trackedSpots) { if (!IsSpotUsable(s_trackedSpot)) { list.Add(s_trackedSpot); } else if (GetAbilityIndex(s_trackedSpot) == 1) { try { ApplySlot2DirectionVisual(s_trackedSpot); } catch { list.Add(s_trackedSpot); } } } if (list.Count <= 0) { return; } foreach (AbilitySpot item in list) { s_trackedSpots.Remove(item); s_spotBaseLocalPos.Remove(item); } } private static void ApplySlot2DirectionVisual(AbilitySpot spot) { int abilityIndex = GetAbilityIndex(spot); if (abilityIndex != 1) { return; } bool flag = LastChanceInteropBridge.IsDirectionIndicatorUiVisible(); bool value; bool? flag2 = (s_lastDirectionVisibilityBySpot.TryGetValue(spot, out value) ? new bool?(value) : null); s_lastDirectionVisibilityBySpot[spot] = flag; if (!flag) { if (FeatureFlags.DebugLogging && flag2 != false) { Debug.Log((object)$"[Fix:Ability] Slot2 hidden. slotIndex={abilityIndex} visible={flag} mode={LastChanceInteropBridge.GetLastChanceIndicatorsMode()}"); } if (flag2 != false) { AbilitySpotLabelOverlay.SetDirectionLabel(spot, string.Empty); SlotCostOverrides.RestoreDefaultCostText(spot); SlotVisualOverrides.RestoreDefaultIcon(spot); SlotVisualOverrides.ApplyDirectionActivationProgress(spot, 0f); SlotLayoutOverrides.RestoreBasePosition(spot); s_lastDirectionCostLabelBySpot.Remove(spot); s_lastDirectionProgressBySpot.Remove(spot); s_lastDirectionEnergySufficientBySpot.Remove(spot); } return; } if (FeatureFlags.DebugLogging && !flag2.GetValueOrDefault()) { Debug.Log((object)$"[Fix:Ability] Slot2 apply icon. slotIndex={abilityIndex} visible={flag} mode={LastChanceInteropBridge.GetLastChanceIndicatorsMode()}"); } string directionCostLabel = GetDirectionCostLabel(); float num = Mathf.Round(s_directionActivationProgress * 1000f) * 0.001f; float value2; bool flag3 = !s_lastDirectionProgressBySpot.TryGetValue(spot, out value2) || Mathf.Abs(value2 - num) > 0.0001f; bool flag4 = LastChanceInteropBridge.IsDirectionIndicatorEnergySufficientPreview(); if (FeatureFlags.DebugLogging && InternalDebugFlags.DebugDirectionSlotEnergyPreviewLog && LogLimiter.ShouldLog("Fix:Ability.DirectionEnergy", 120)) { LastChanceInteropBridge.GetDirectionIndicatorEnergyDebugSnapshot(out var visible, out var timerRemaining, out var penaltyPreview, out var hasEnoughEnergy); Debug.Log((object)$"[Fix:Ability] Slot2 energy preview visible={visible} timer={timerRemaining:F1}s cost={penaltyPreview:F1}s enough={hasEnoughEnergy} appliedEnough={flag4} progress={num:F3}"); } bool value3; bool flag5 = !s_lastDirectionEnergySufficientBySpot.TryGetValue(spot, out value3) || value3 != flag4; string value4; bool flag6 = !s_lastDirectionCostLabelBySpot.TryGetValue(spot, out value4) || !string.Equals(value4, directionCostLabel, StringComparison.Ordinal); bool flag7 = !flag2.GetValueOrDefault(); if (!flag7 && !flag3 && !flag6 && !flag5) { return; } AbilitySpotLabelOverlay.SetDirectionLabel(spot, string.Empty); if (flag7 || flag6) { SlotCostOverrides.SetDirectionCostText(spot, directionCostLabel); if (LastChanceInteropBridge.TryGetDirectionSlotSprite(out Sprite sprite) && (Object)(object)sprite != (Object)null) { SlotVisualOverrides.ApplyDirectionIcon(spot, sprite); } SlotLayoutOverrides.EnsureBasePosition(spot); s_lastDirectionCostLabelBySpot[spot] = directionCostLabel; } if (flag7 || flag3) { SlotVisualOverrides.ApplyDirectionActivationProgress(spot, num); s_lastDirectionProgressBySpot[spot] = num; } SlotVisualOverrides.ApplyDirectionEnergyAvailability(spot, flag4, num); s_lastDirectionEnergySufficientBySpot[spot] = flag4; } private static string GetDirectionCostLabel() { float directionIndicatorPenaltySecondsPreview = LastChanceInteropBridge.GetDirectionIndicatorPenaltySecondsPreview(); int num = Mathf.RoundToInt(Mathf.Max(0f, directionIndicatorPenaltySecondsPreview)); return $"{num}s"; } private static int GetAbilityIndex(AbilitySpot spot) { if ((Object)(object)spot == (Object)null) { return -1; } return spot.abilitySpotIndex; } private static bool IsSpotUsable(AbilitySpot spot) { if ((Object)(object)spot == (Object)null) { return false; } return (Object)(object)((Component)spot).gameObject != (Object)null; } private static void DHHAbilityManager_OnAbilityUsed_Postfix(DHHAbilityManager __instance, AbilityBase ability) { if ((Object)(object)ability == (Object)null) { return; } float cooldown = ability.Cooldown; if (cooldown <= 0f || __instance?.abilitySpots == null) { return; } List list = new List(); AbilitySpot[] abilitySpots = __instance.abilitySpots; foreach (AbilitySpot val in abilitySpots) { if (!((Object)(object)val == (Object)null) && val.CurrentAbility == ability) { list.Add(val); } } if (list.Count == 0 && !string.IsNullOrWhiteSpace(ability.AbilityName)) { AbilitySpot[] abilitySpots2 = __instance.abilitySpots; foreach (AbilitySpot val2 in abilitySpots2) { if (!((Object)(object)((val2 != null) ? val2.CurrentAbility : null) == (Object)null) && string.Equals(val2.CurrentAbility.AbilityName, ability.AbilityName, StringComparison.Ordinal)) { list.Add(val2); } } } foreach (AbilitySpot item in list) { try { item.SetCooldown(cooldown); } catch { } } } } internal static class ChargeAbilityTuningModule { private static readonly FieldInfo? s_playerAvatarDeadSetField = AccessTools.Field(typeof(PlayerAvatar), "deadSet"); private static readonly FieldInfo? s_playerAvatarIsDisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled"); private static FieldInfo? s_abilityEnergyHandlerControllerField; private static FieldInfo? s_deathHeadControllerDeathHeadField; private static FieldInfo? s_playerDeathHeadAvatarField; internal static void Apply(Harmony harmony, Assembly asm) { PatchAbilityEnergyHandlerRechargeSoundIfPossible(harmony, asm); PatchChargeAbilityGettersIfPossible(harmony, asm); } private static void PatchAbilityEnergyHandlerRechargeSoundIfPossible(Harmony harmony, Assembly asm) { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.DeathHead.Handlers.AbilityEnergyHandler", throwOnError: false); if (type == null) { return; } if ((object)s_abilityEnergyHandlerControllerField == null) { s_abilityEnergyHandlerControllerField = type.GetField("controller", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } Type type2 = asm.GetType("DeathHeadHopper.DeathHead.DeathHeadController", throwOnError: false); if (type2 != null && (object)s_deathHeadControllerDeathHeadField == null) { s_deathHeadControllerDeathHeadField = type2.GetField("deathHead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } Type type3 = asm.GetType("PlayerDeathHead", throwOnError: false); if (type3 != null && (object)s_playerDeathHeadAvatarField == null) { s_playerDeathHeadAvatarField = type3.GetField("playerAvatar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } MethodInfo methodInfo = AccessTools.Method(type, "PlayRechargeSound", Type.EmptyTypes, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(ChargeAbilityTuningModule).GetMethod("AbilityEnergyHandler_PlayRechargeSound_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool AbilityEnergyHandler_PlayRechargeSound_Prefix(object __instance) { if (SpectateContextHelper.IsSpectatingLocalDeathHead() || IsLocalPlayerDead()) { return false; } PlayerAvatar abilityEnergyHandlerPlayerAvatar = GetAbilityEnergyHandlerPlayerAvatar(__instance); if (IsAbilityPlayerDisabled(abilityEnergyHandlerPlayerAvatar)) { return false; } return true; } private static PlayerAvatar? GetAbilityEnergyHandlerPlayerAvatar(object? handler) { if (handler == null || s_abilityEnergyHandlerControllerField == null) { return null; } object value = s_abilityEnergyHandlerControllerField.GetValue(handler); if (value == null || s_deathHeadControllerDeathHeadField == null) { return null; } object value2 = s_deathHeadControllerDeathHeadField.GetValue(value); if (value2 == null || s_playerDeathHeadAvatarField == null) { return null; } object? value3 = s_playerDeathHeadAvatarField.GetValue(value2); return (PlayerAvatar?)((value3 is PlayerAvatar) ? value3 : null); } private static bool IsAbilityPlayerDisabled(PlayerAvatar? avatar) { if ((Object)(object)avatar == (Object)null || s_playerAvatarIsDisabledField == null) { return false; } object value = s_playerAvatarIsDisabledField.GetValue(avatar); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static bool IsLocalPlayerDead() { PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null || s_playerAvatarDeadSetField == null) { return false; } object value = s_playerAvatarDeadSetField.GetValue(instance); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static void PatchChargeAbilityGettersIfPossible(Harmony harmony, Assembly asm) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.Abilities.Charge.ChargeAbility", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.PropertyGetter(type, "EnergyCost"); MethodInfo methodInfo2 = AccessTools.PropertyGetter(type, "Cooldown"); if (methodInfo == null) { return; } MethodInfo method = typeof(ChargeAbilityTuningModule).GetMethod("ChargeAbility_EnergyCost_Postfix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method2 = typeof(ChargeAbilityTuningModule).GetMethod("ChargeAbility_Cooldown_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null) && !(method2 == null)) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void ChargeAbility_EnergyCost_Postfix(Object __instance, ref float __result) { if (__instance == (Object)null || InternalDebugFlags.DisableAbilityPatches) { return; } float num = Mathf.Max(0f, __result); float num2 = Mathf.Max(0f, (float)FeatureFlags.ChargeAbilityStaminaCost); if (num2 <= 0f) { __result = num; return; } __result = num2; if (FeatureFlags.DebugLogging && InternalDebugFlags.DebugDhhChargeTuningLog && LogLimiter.ShouldLog("DHHCharge.Cost", 120)) { Debug.Log((object)$"[Fix:DHHCharge] Charge cost override: custom={num2:F3} base={num:F3}"); } } private static bool ChargeAbility_Cooldown_Prefix(Object __instance, ref float __result) { if (__instance == (Object)null) { return true; } if (InternalDebugFlags.DisableAbilityPatches) { return true; } float num = (__result = Mathf.Max(0f, (float)FeatureFlags.ChargeAbilityCooldown)); if (FeatureFlags.DebugLogging && InternalDebugFlags.DebugDhhChargeTuningLog && LogLimiter.ShouldLog("DHHCharge.Cooldown", 120)) { Debug.Log((object)$"[Fix:DHHCharge] Cooldown override: custom={num:F3}"); } return false; } } internal static class DhhUpgradeOrchestrator { [CompilerGenerated] private sealed class d__13 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public ItemToggle toggle; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__13(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.35f); <>1__state = 1; return true; case 1: <>1__state = -1; DestroyUpgradeItemNow(toggle); 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 float UpgradeDestroyDelaySeconds = 0.35f; private const float UpgradeReleaseDisableSeconds = 0.55f; internal static bool IsDhhUpgrade(GameObject prefab) { return (Object)(object)prefab != (Object)null && (HasChargeUpgrade(prefab) || HasPowerUpgrade(prefab)); } internal static bool HasChargeUpgrade(GameObject prefab) { return (Object)(object)prefab != (Object)null && ((Object)(object)prefab.GetComponent() != (Object)null || (Object)(object)prefab.GetComponentInChildren(true) != (Object)null); } internal static bool HasPowerUpgrade(GameObject prefab) { return (Object)(object)prefab != (Object)null && ((Object)(object)prefab.GetComponent() != (Object)null || (Object)(object)prefab.GetComponentInChildren(true) != (Object)null); } internal static void DisableLegacyToggleListeners(GameObject prefab) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown if (IsDhhUpgrade(prefab)) { ItemToggle val = prefab.GetComponent() ?? prefab.GetComponentInChildren(true); if (!((Object)(object)val == (Object)null)) { val.onToggle = new UnityEvent(); } } } internal static bool TryHandleToggle(ItemToggle toggle, int player) { if ((Object)(object)toggle == (Object)null || !IsDhhUpgrade(((Component)toggle).gameObject)) { return false; } if (GameManager.Multiplayer() && !PhotonNetwork.IsMasterClient) { return true; } bool flag = TryRunPowerUpgrade(toggle, player); bool flag2 = TryRunChargeUpgrade(toggle, player); if (!flag && !flag2) { return true; } PlayUpgradeFx(toggle, player); RegisterConsumedUpgrade(toggle); ScheduleDestroyUpgradeItem(toggle); return true; } internal static void PlayAuthorizedLocalFeedback(string playerId, int newValue, int previousValue) { if (newValue <= previousValue || string.IsNullOrWhiteSpace(playerId) || !GameManager.Multiplayer() || PhotonNetwork.IsMasterClient) { return; } PlayerAvatar instance = PlayerAvatar.instance; if (!((Object)(object)instance == (Object)null) && !(instance.steamID != playerId)) { StatsUI instance2 = StatsUI.instance; if (instance2 != null) { instance2.Fetch(); } StatsUI instance3 = StatsUI.instance; if (instance3 != null) { instance3.ShowStats(); } CameraGlitch instance4 = CameraGlitch.Instance; if (instance4 != null) { instance4.PlayUpgrade(); } } } private static bool TryRunPowerUpgrade(ItemToggle toggle, int player) { if ((Object)(object)((Component)toggle).GetComponent() == (Object)null) { return false; } if (!TryGetToggleSteamId(toggle, player, out string playerId)) { return false; } if (!EnsureLegacyDhhUpgradeKey(playerId, isChargeUpgrade: false)) { return false; } DHHItemUpgradePower component = ((Component)toggle).GetComponent(); if (component != null) { component.Upgrade(); } return true; } private static bool TryRunChargeUpgrade(ItemToggle toggle, int player) { if ((Object)(object)((Component)toggle).GetComponent() == (Object)null) { return false; } if (!TryGetToggleSteamId(toggle, player, out string playerId)) { return false; } if (!EnsureLegacyDhhUpgradeKey(playerId, isChargeUpgrade: true)) { return false; } DHHItemUpgradeCharge component = ((Component)toggle).GetComponent(); if (component != null) { component.Upgrade(); } return true; } private static bool TryGetToggleSteamId(ItemToggle toggle, int player, out string playerId) { playerId = string.Empty; try { PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(player); if ((Object)(object)val == (Object)null && (Object)(object)toggle != (Object)null) { val = SemiFunc.PlayerAvatarGetFromPhotonID(toggle.playerTogglePhotonID); } playerId = (((Object)(object)val != (Object)null) ? SemiFunc.PlayerGetSteamID(val) : string.Empty); return !string.IsNullOrWhiteSpace(playerId); } catch { return false; } } private static bool EnsureLegacyDhhUpgradeKey(string playerId, bool isChargeUpgrade) { if (string.IsNullOrWhiteSpace(playerId)) { return false; } DHHStatsManager instance = DHHStatsManager.instance; if ((Object)(object)instance == (Object)null) { return false; } Dictionary dictionary = (isChargeUpgrade ? instance.playerUpgradeHeadCharge : instance.playerUpgradeHeadPower); if (!dictionary.ContainsKey(playerId)) { dictionary[playerId] = 0; } return true; } private static void ScheduleDestroyUpgradeItem(ItemToggle toggle) { if (!((Object)(object)toggle == (Object)null)) { DisableConsumedToggle(toggle); ForceReleaseGrabbers(toggle); ((MonoBehaviour)toggle).StartCoroutine(DestroyUpgradeItemAfterRelease(toggle)); } } [IteratorStateMachine(typeof(d__13))] private static IEnumerator DestroyUpgradeItemAfterRelease(ItemToggle toggle) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__13(0) { toggle = toggle }; } private static void DisableConsumedToggle(ItemToggle toggle) { try { toggle.disabled = true; ((Behaviour)toggle).enabled = false; PhysGrabObject val = ((Component)toggle).GetComponent() ?? ((Component)toggle).GetComponentInChildren() ?? ((Component)toggle).GetComponentInParent(); if ((Object)(object)val != (Object)null) { val.grabDisableTimer = Mathf.Max(val.grabDisableTimer, 0.55f); } } catch { } } private static void DestroyUpgradeItemNow(ItemToggle toggle) { if (!((Object)(object)toggle == (Object)null)) { PhysGrabObjectImpactDetector val = ((Component)toggle).GetComponent() ?? ((Component)toggle).GetComponentInChildren(); if ((Object)(object)val == (Object)null) { val = ((Component)toggle).GetComponentInParent(); } if (val != null) { val.DestroyObject(false); } } } private static void ForceReleaseGrabbers(ItemToggle toggle) { if (!SemiFunc.IsMasterClientOrSingleplayer()) { return; } PhysGrabObject val = ((Component)toggle).GetComponent() ?? ((Component)toggle).GetComponentInChildren() ?? ((Component)toggle).GetComponentInParent(); if ((Object)(object)val == (Object)null || val.playerGrabbing == null || val.playerGrabbing.Count == 0) { return; } int num = (((Object)(object)val.photonView != (Object)null) ? val.photonView.ViewID : (-1)); foreach (PhysGrabber item in new List(val.playerGrabbing)) { if ((Object)(object)item == (Object)null) { continue; } if (!SemiFunc.IsMultiplayer()) { item.ReleaseObject(num, 0.55f); continue; } PhotonView photonView = item.photonView; if (photonView != null) { photonView.RPC("ReleaseObjectRPC", (RpcTarget)0, new object[3] { false, 0.55f, num }); } } } private static void PlayUpgradeFx(ItemToggle toggle, int player) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) try { PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(player); if ((Object)(object)val == (Object)null) { return; } PhotonView photonView = val.photonView; if (!GameManager.Multiplayer() || ((Object)(object)photonView != (Object)null && photonView.IsMine)) { StatsUI instance = StatsUI.instance; if (instance != null) { instance.Fetch(); } StatsUI instance2 = StatsUI.instance; if (instance2 != null) { instance2.ShowStats(); } CameraGlitch instance3 = CameraGlitch.Instance; if (instance3 != null) { instance3.PlayUpgrade(); } } else { GameDirector instance4 = GameDirector.instance; if (instance4 != null) { CameraShake cameraImpact = instance4.CameraImpact; if (cameraImpact != null) { cameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)toggle).transform.position, 0.2f); } } } if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient) { PlayerHealth playerHealth = val.playerHealth; if (playerHealth != null) { playerHealth.MaterialEffectOverride((Effect)0); } } } catch { } } private static void RegisterConsumedUpgrade(ItemToggle toggle) { if ((Object)(object)toggle == (Object)null) { return; } string text = TryGetStatsItemName(toggle); if (!string.IsNullOrWhiteSpace(text)) { StatsModule.EnsureStatsManagerKey(text); StatsManager instance = StatsManager.instance; if (!((Object)(object)instance == (Object)null)) { instance.itemsPurchased[text] = Mathf.Max(instance.itemsPurchased[text] - 1, 0); } } } private static string? TryGetStatsItemName(ItemToggle toggle) { ItemAttributes val = ((Component)toggle).GetComponent() ?? ((Component)toggle).GetComponentInChildren() ?? ((Component)toggle).GetComponentInParent(); if ((Object)(object)val?.item != (Object)null && !string.IsNullOrWhiteSpace(((Object)val.item).name)) { return ((Object)val.item).name; } string name = ((Object)toggle).name; return string.IsNullOrWhiteSpace(name) ? null : name; } } internal static class ItemUpgradeModule { internal static void Apply(Harmony harmony) { PatchItemToggleUpgradeHook(harmony); } private static void PatchItemToggleUpgradeHook(Harmony harmony) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(ItemToggle), "ToggleItemLogic", new Type[2] { typeof(bool), typeof(int) }, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(ItemUpgradeModule).GetMethod("ItemToggle_ToggleItemLogic_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void ItemToggle_ToggleItemLogic_Postfix(ItemToggle __instance, bool toggle, int player) { if (toggle && !((Object)(object)__instance == (Object)null)) { DhhUpgradeOrchestrator.TryHandleToggle(__instance, player); } } } internal static class JumpForceModule { private readonly struct DiminishingReturnsResult { public float BaseValue { get; } public float IncreasePerLevel { get; } public int AppliedLevel { get; } public int ThresholdLevel { get; } public float DiminishingFactor { get; } public int LinearLevels { get; } public int ExtraLevels { get; } public float LinearContribution { get; } public float DiminishingContribution { get; } public float DiminishingComponent { get; } public float FinalValue { get; } public DiminishingReturnsResult(float baseValue, float increasePerLevel, int appliedLevel, int thresholdLevel, float diminishingFactor, int linearLevels, int extraLevels, float linearContribution, float diminishingContribution, float diminishingComponent, float finalValue) { BaseValue = baseValue; IncreasePerLevel = increasePerLevel; AppliedLevel = appliedLevel; ThresholdLevel = thresholdLevel; DiminishingFactor = diminishingFactor; LinearLevels = linearLevels; ExtraLevels = extraLevels; LinearContribution = linearContribution; DiminishingContribution = diminishingContribution; DiminishingComponent = diminishingComponent; FinalValue = finalValue; } } private const string HopForceLogKey = "Fix:Hop.JumpForce"; private const string JumpForceLogKey = "Fix:Jump.HeadJumpForce"; private static MethodInfo? s_hopHandlerPowerLevelGetter; private static MethodInfo? s_jumpHandlerPowerLevelGetter; private static ManualLogSource? s_log; internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { s_log = log; PatchJumpHandlerJumpForceIfPossible(harmony, asm); PatchHopHandlerJumpForceIfPossible(harmony, asm); } private static void PatchJumpHandlerJumpForceIfPossible(Harmony harmony, Assembly asm) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.DeathHead.Handlers.JumpHandler", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.PropertyGetter(type, "JumpForce"); if (!(methodInfo == null)) { if ((object)s_jumpHandlerPowerLevelGetter == null) { s_jumpHandlerPowerLevelGetter = AccessTools.PropertyGetter(type, "PowerLevel"); } MethodInfo method = typeof(JumpForceModule).GetMethod("JumpHandler_JumpForce_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool JumpHandler_JumpForce_Prefix(object __instance, ref float __result) { if (__instance == null || s_jumpHandlerPowerLevelGetter == null) { return true; } int num2 = ((s_jumpHandlerPowerLevelGetter.Invoke(__instance, null) is int num) ? num : 0); int num3 = num2 + 1; DiminishingReturnsResult stat = EvaluateStatWithDiminishingReturns(FeatureFlags.DHHJumpForceBaseValue, FeatureFlags.DHHJumpForceIncreasePerLevel, num3, FeatureFlags.DHHJumpForceThresholdLevel, FeatureFlags.DHHJumpForceDiminishingFactor); __result = stat.FinalValue; LogJumpForce(__instance, num2, num3, stat); return false; } private static void PatchHopHandlerJumpForceIfPossible(Harmony harmony, Assembly asm) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.DeathHead.Handlers.HopHandler", throwOnError: false); if (!(type == null)) { MethodInfo methodInfo = AccessTools.PropertyGetter(type, "JumpForce"); if ((object)s_hopHandlerPowerLevelGetter == null) { s_hopHandlerPowerLevelGetter = AccessTools.PropertyGetter(type, "PowerLevel"); } MethodInfo method = typeof(JumpForceModule).GetMethod("HopHandler_JumpForce_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo != null && method != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool HopHandler_JumpForce_Prefix(object __instance, ref float __result) { if (__instance == null || s_hopHandlerPowerLevelGetter == null) { return true; } int num2 = ((s_hopHandlerPowerLevelGetter.Invoke(__instance, null) is int num) ? num : 0); int num3 = num2 + 1; DiminishingReturnsResult stat = EvaluateStatWithDiminishingReturns(FeatureFlags.DHHHopJumpBaseValue, FeatureFlags.DHHHopJumpIncreasePerLevel, num3, FeatureFlags.DHHHopJumpThresholdLevel, FeatureFlags.DHHHopJumpDiminishingFactor); __result = stat.FinalValue; LogHopJumpForce(__instance, num2, num3, stat); return false; } private static DiminishingReturnsResult EvaluateStatWithDiminishingReturns(float baseValue, float increasePerLevel, int currentLevel, int thresholdLevel, float diminishingFactor) { int num = Math.Max(0, currentLevel - 1); int num2 = Math.Max(0, thresholdLevel - 1); int num3 = Mathf.Min(num, num2); int num4 = Mathf.Max(0, num - num2); float num5 = (float)num4 * Mathf.Pow(diminishingFactor, (float)num4); float num6 = increasePerLevel * (float)num3; float num7 = increasePerLevel * num5; float finalValue = baseValue + num6 + num7; return new DiminishingReturnsResult(baseValue, increasePerLevel, currentLevel, thresholdLevel, diminishingFactor, num3, num4, num6, num7, num5, finalValue); } private static void LogHopJumpForce(object hopHandler, int powerLevel, int appliedLevel, DiminishingReturnsResult stat) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Hop.JumpForce", 30)) { string handlerLabel = GetHandlerLabel(hopHandler, "HopHandler"); string text = $"[Fix:Hop] {handlerLabel} JumpForce={stat.FinalValue:F3} powerLevel={powerLevel} appliedLevel={appliedLevel} base={stat.BaseValue:F3} inc={stat.IncreasePerLevel:F3} fullUpgrades={stat.LinearLevels} dimUpgrades={stat.ExtraLevels} linearDelta={stat.LinearContribution:F3} dimDelta={stat.DiminishingContribution:F3} thresh={stat.ThresholdLevel} dimFactor={stat.DiminishingFactor:F3}"; ManualLogSource? obj = s_log; if (obj != null) { obj.LogInfo((object)text); } Debug.Log((object)text); } } private static void LogJumpForce(object jumpHandler, int powerLevel, int appliedLevel, DiminishingReturnsResult stat) { if (FeatureFlags.DebugLogging && InternalDebugFlags.DebugJumpForceLog && LogLimiter.ShouldLog("Fix:Jump.HeadJumpForce", 30)) { string handlerLabel = GetHandlerLabel(jumpHandler, "JumpHandler"); string text = $"[Fix:Jump] {handlerLabel} JumpForce={stat.FinalValue:F3} powerLevel={powerLevel} appliedLevel={appliedLevel} base={stat.BaseValue:F3} inc={stat.IncreasePerLevel:F3} fullUpgrades={stat.LinearLevels} dimUpgrades={stat.ExtraLevels} linearDelta={stat.LinearContribution:F3} dimDelta={stat.DiminishingContribution:F3} thresh={stat.ThresholdLevel} dimFactor={stat.DiminishingFactor:F3}"; ManualLogSource? obj = s_log; if (obj != null) { obj.LogInfo((object)text); } Debug.Log((object)text); } } private static string GetHandlerLabel(object? handler, string fallback) { Component val = (Component)((handler is Component) ? handler : null); if (val != null) { return ((Object)val).name ?? ((object)val).GetType().Name; } return handler?.GetType().Name ?? fallback; } } } namespace DeathHeadHopperFix.Modules.Config { [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 bool HasRange => !float.IsNaN(Min) && !float.IsNaN(Max); 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 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_hostControlledFields = 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, typeof(FeatureFlags), "General"); ConfigMigrationManager.Apply(config, typeof(FeatureFlags), "General"); CaptureLocalHostControlledBaseline(); } } private static void BindConfigEntries(ConfigFile config, Type targetType, string defaultSection) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Expected O, but got Unknown //IL_03f6: Unknown result type (might be due to invalid IL or missing references) //IL_03fb: Unknown result type (might be due to invalid IL or missing references) //IL_0402: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Unknown result type (might be due to invalid IL or missing references) //IL_0377: Expected O, but got Unknown FieldInfo[] fields = targetType.GetFields(BindingFlags.Static | BindingFlags.Public); foreach (FieldInfo field in fields) { FeatureConfigEntryAttribute customAttribute = field.GetCustomAttribute(); if (customAttribute == null) { continue; } string text = (string.IsNullOrWhiteSpace(customAttribute.Section) ? defaultSection : customAttribute.Section); string text2 = (string.IsNullOrWhiteSpace(customAttribute.Key) ? field.Name : customAttribute.Key); string text3 = BuildRangeKey(text, text2); string text4 = customAttribute.Description ?? string.Empty; if (field.FieldType == typeof(bool)) { bool flag = (bool)field.GetValue(null); ConfigEntry entry = config.Bind(text, text2, flag, new ConfigDescription(text4, (AcceptableValueBase)(object)new AcceptableValueList(new bool[2] { false, true }), Array.Empty())); RegisterHostControlledField(customAttribute, text2, field); RegisterHostControlledEntry(customAttribute, text2, (ConfigEntryBase)(object)entry); ApplyAndWatch(entry, text3, delegate(bool value) { field.SetValue(null, value); }, customAttribute.HostControlled); } else if (field.FieldType == typeof(int)) { int num = (int)field.GetValue(null); ConfigEntry entry2; if (customAttribute.HasRange) { int intRangeStart = GetIntRangeStart(customAttribute); int intRangeEnd = GetIntRangeEnd(customAttribute); entry2 = config.Bind(text, text2, num, new ConfigDescription(text4, (AcceptableValueBase)(object)new AcceptableValueRange(intRangeStart, intRangeEnd), Array.Empty())); RegisterIntRange(text3, intRangeStart, intRangeEnd); } else { entry2 = config.Bind(text, text2, num, text4); } ApplyAndWatch(entry2, text3, delegate(int value) { field.SetValue(null, value); }, customAttribute.HostControlled); RegisterHostControlledField(customAttribute, text2, field); RegisterHostControlledEntry(customAttribute, text2, (ConfigEntryBase)(object)entry2); } else if (field.FieldType == typeof(float)) { float num2 = (float)field.GetValue(null); ConfigEntry entry3; if (customAttribute.HasRange) { float num3 = Math.Min(customAttribute.Min, customAttribute.Max); float num4 = Math.Max(customAttribute.Min, customAttribute.Max); entry3 = config.Bind(text, text2, num2, new ConfigDescription(text4, (AcceptableValueBase)(object)new AcceptableValueRange(num3, num4), Array.Empty())); RegisterFloatRange(text3, num3, num4); } else { entry3 = config.Bind(text, text2, num2, text4); } ApplyAndWatch(entry3, text3, delegate(float value) { field.SetValue(null, value); }, customAttribute.HostControlled); RegisterHostControlledField(customAttribute, text2, field); RegisterHostControlledEntry(customAttribute, text2, (ConfigEntryBase)(object)entry3); } else if (field.FieldType == typeof(string)) { string text5 = (field.GetValue(null) as string) ?? string.Empty; RegisterStringOptions(text3, customAttribute.Options, text5); ConfigEntry entry4 = ((customAttribute.Options == null || customAttribute.Options.Length == 0) ? config.Bind(text, text2, text5, text4) : config.Bind(text, text2, text5, new ConfigDescription(text4, (AcceptableValueBase)(object)new AcceptableValueList(customAttribute.Options), Array.Empty()))); RegisterHostControlledField(customAttribute, text2, field); RegisterHostControlledEntry(customAttribute, text2, (ConfigEntryBase)(object)entry4); ApplyAndWatch(entry4, text3, delegate(string value) { field.SetValue(null, value); }, customAttribute.HostControlled); } else if (field.FieldType == typeof(Color)) { Color input = (Color)field.GetValue(null); ConfigEntry entry5 = config.Bind(text, text2, ColorToString(input), text4); RegisterHostControlledField(customAttribute, text2, field); RegisterHostControlledEntry(customAttribute, text2, (ConfigEntryBase)(object)entry5); ApplyAndWatch(entry5, ColorFromString, delegate(Color value) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) field.SetValue(null, value); }, customAttribute.HostControlled); } } } private static int GetIntRangeStart(FeatureConfigEntryAttribute attribute) { ValidateIntegerRange(attribute); return (int)Math.Min(attribute.Min, attribute.Max); } private static int GetIntRangeEnd(FeatureConfigEntryAttribute attribute) { ValidateIntegerRange(attribute); return (int)Math.Max(attribute.Min, attribute.Max); } private static void ValidateIntegerRange(FeatureConfigEntryAttribute attribute) { if (!IsWholeNumber(attribute.Min) || !IsWholeNumber(attribute.Max)) { throw new InvalidOperationException("FeatureConfigEntryAttribute integer range values must be whole numbers."); } } private static bool IsWholeNumber(float value) { double num = Math.Truncate(value); return Math.Abs((double)value - num) < 1.401298464324817E-45; } 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 RegisterHostControlledField(FeatureConfigEntryAttribute attribute, string key, FieldInfo field) { if (attribute.HostControlled) { s_hostControlledFields[key] = field; } } private static void RegisterHostControlledEntry(FeatureConfigEntryAttribute attribute, string key, ConfigEntryBase entry) { if (attribute.HostControlled && entry != null) { s_hostControlledEntries[key] = entry; } } internal static Dictionary SnapshotHostControlled() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair s_hostControlledField in s_hostControlledFields) { if (s_hostRuntimeOverrides.TryGetValue(s_hostControlledField.Key, out string value)) { dictionary[s_hostControlledField.Key] = value; continue; } FieldInfo value2 = s_hostControlledField.Value; object value3 = value2.GetValue(null); dictionary[s_hostControlledField.Key] = SerializeValue(value3, value2.FieldType); } 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_hostControlledFields.TryGetValue(key, out FieldInfo value)) { if (s_hostRuntimeOverrides.TryGetValue(key, out string value2)) { dictionary[key] = value2; continue; } object value3 = value.GetValue(null); dictionary[key] = SerializeValue(value3, value.FieldType); } } return dictionary; } internal static void SetHostRuntimeOverride(string key, string serializedValue) { if (string.IsNullOrWhiteSpace(key)) { return; } string key2 = key.Trim(); if (s_hostControlledFields.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_hostControlledFields.TryGetValue(item.Key, out FieldInfo value)) { continue; } object obj = DeserializeValue(item.Value, value.FieldType); if (obj != null) { object value2 = value.GetValue(null); if (value2 == null || !value2.Equals(obj)) { flag = true; } value.SetValue(null, 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_hostControlledFields.TryGetValue(key, out FieldInfo value)) { object value2 = value.GetValue(null); s_localHostControlledBaseline[key] = SerializeValue(value2, value.FieldType); } } 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_hostControlledFields.TryGetValue(key, out FieldInfo value)) { return false; } authoritativeSerialized = SerializeValue(value.GetValue(null), value.FieldType); 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) { if (entry == null) { return null; } try { if (((object)entry).GetType().GetProperty("SettingType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(entry) is Type result) { return result; } } catch { } return 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 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, Type flagsType, string defaultSection) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown if (config == null || flagsType == 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(flagsType, defaultSection); 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(Type flagsType, string defaultSection) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown Dictionary dictionary = new Dictionary(); FieldInfo[] fields = flagsType.GetFields(BindingFlags.Static | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { FeatureConfigEntryAttribute customAttribute = fieldInfo.GetCustomAttribute(); if (customAttribute == null) { continue; } string text = (string.IsNullOrWhiteSpace(customAttribute.Section) ? defaultSection : customAttribute.Section); string text2 = (string.IsNullOrWhiteSpace(customAttribute.Key) ? fieldInfo.Name : customAttribute.Key); ConfigDefinition value = new ConfigDefinition(text, text2); IEnumerable customAttributes = fieldInfo.GetCustomAttributes(); foreach (FeatureConfigAliasAttribute item in customAttributes) { if (item != null && !string.IsNullOrWhiteSpace(item.OldKey)) { string text3 = (string.IsNullOrWhiteSpace(item.OldSection) ? defaultSection : item.OldSection); ConfigDefinition key = new ConfigDefinition(text3, item.OldKey); 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 static readonly string HostFixPresenceRoomKey = NetworkProtocol.BuildRoomKey("HostFixPresence"); private const string ConfigSyncMessageType = "ConfigSync"; private static float s_remoteHostFixPresencePendingSince = -1f; 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; SceneManager.sceneLoaded += OnSceneLoaded; TrySendSnapshot(); TryPublishHostFixPresence(); } public override void OnDisable() { ((MonoBehaviourPunCallbacks)this).OnDisable(); PhotonNetwork.RemoveCallbackTarget((object)this); ConfigManager.HostControlledChanged -= OnHostControlledChanged; SceneManager.sceneLoaded -= OnSceneLoaded; } private void OnHostControlledChanged() { if (PhotonNetwork.IsMasterClient) { TrySendSnapshot(); } } public override void OnJoinedRoom() { if (PhotonNetwork.IsMasterClient) { TrySendSnapshot(); TryPublishHostFixPresence(); s_remoteHostFixPresencePendingSince = -1f; } else { s_remoteHostFixPresencePendingSince = Time.realtimeSinceStartup; } } 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(); TryPublishHostFixPresence(); s_remoteHostFixPresencePendingSince = -1f; } else { s_remoteHostFixPresencePendingSince = Time.realtimeSinceStartup; } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (PhotonNetwork.IsMasterClient) { TrySendSnapshot(); TryPublishHostFixPresence(); } } public override void OnLeftRoom() { s_remoteHostFixPresencePendingSince = -1f; ConfigManager.RestoreLocalHostControlledBaseline(); } internal static bool IsRemoteHostFixCompatible() { if (!PhotonNetwork.InRoom || !SemiFunc.IsMultiplayer()) { s_remoteHostFixPresencePendingSince = -1f; return true; } if (PhotonNetwork.IsMasterClient) { s_remoteHostFixPresencePendingSince = -1f; return true; } Room currentRoom = PhotonNetwork.CurrentRoom; Hashtable val = ((currentRoom != null) ? ((RoomInfo)currentRoom).CustomProperties : null); if (val == null || !((Dictionary)(object)val).TryGetValue((object)HostFixPresenceRoomKey, out object value)) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (s_remoteHostFixPresencePendingSince < 0f) { s_remoteHostFixPresencePendingSince = realtimeSinceStartup; } if (realtimeSinceStartup - s_remoteHostFixPresencePendingSince < InternalConfig.HostFixPresenceGraceSeconds) { return true; } return false; } s_remoteHostFixPresencePendingSince = -1f; return value is int num && num == 1; } 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("DeathHeadHopperFix", 1, "ConfigSync", 0, val).ToEventPayload(), val2, SendOptions.SendReliable); } private static void TryPublishHostFixPresence() { //IL_005f: 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_007c: Expected O, but got Unknown if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null) { return; } try { Hashtable customProperties = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; if (customProperties == null || !((Dictionary)(object)customProperties).TryGetValue((object)HostFixPresenceRoomKey, out object value) || !(value is int num) || num != 1) { Hashtable val = new Hashtable { [(object)HostFixPresenceRoomKey] = 1 }; PhotonNetwork.CurrentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); } } catch { } } 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 RechargeBattery = "1. Battery"; public const string StaminaRecharge = "2. Stamina & Recharge"; public const string ChargeAbility = "3. Charge ability tunables (DHH)"; public const string Jump = "4. Jump (DHH)"; public const string ChargeVanilla = "5. Charge (DHH)"; public const string Shop = "6. Shop"; public const string Debug = "7. Debug"; public const string Camera = "8. Camera"; } internal static class Descriptions { public const string BatteryJumpEnabled = "Enables the battery authority system that blocks jumps when the energy meter is too low."; public const string BatteryJumpUsage = "Amount of battery drained per death-head jump; larger values drain faster."; public const string BatteryJumpMinimumEnergy = "Minimum battery level that must be filled before the death head can hop. 0.25f matches the vanilla talk threshold so the head can still speak."; public const string JumpBlockDuration = "Duration (in seconds) that jump blocking remains active after the energy warning fires."; public const string HeadStationaryVelocitySqrThreshold = "Velocity squared threshold the death head must stay below to be considered stationary for recharge."; public const string RechargeTickInterval = "Interval (seconds) between stamina-based recharge ticks."; public const string EnergyWarningCheckInterval = "Interval (seconds) between energy warning / SpectateCamera checks."; public const string RechargeWithStamina = "Mirrors vanilla stamina regen to refill the death-head battery instead of draining energy."; public const string RechargeStaminaOnlyStationary = "When true, the death-head only recharges while standing still, matching vanilla stamina guard behavior."; public const string ChargeAbilityStaminaCost = "Charge ability custom stamina cost (always read). How much player stamina the vanilla Charge ability consumes when executed."; public const string ChargeAbilityCooldown = "Cooldown in seconds before Charge can be used again."; public const string ChargeAbilityHoldSeconds = "Seconds to hold slot1 Charge to reach 100% power. Release before this value to launch at proportional power."; public const string DHHChargeStrengthBaseValue = "Strength upgrade custom tunables (used only when DHHEnableCustomDHHValues is true). Default values mirror vanilla ChargeHandler.ResetState: DHHFunc.StatWithDiminishingReturns(baseStrength(12f), ChargeStrengthIncrease, AbilityLevel, 10, 0.75f). Base impact strength used to compute the Charge ability hit force."; public const string DHHChargeStrengthIncreasePerLevel = "Strength upgrade custom tunables (used only when DHHEnableCustomDHHValues is true). Strength increase applied each ability level before diminishing returns."; public const string DHHChargeStrengthThresholdLevel = "Strength upgrade custom tunables (used only when DHHEnableCustomDHHValues is true). Ability level threshold where extra strength gain starts to shrink."; public const string DHHChargeStrengthDiminishingFactor = "Strength upgrade custom tunables (used only when DHHEnableCustomDHHValues is true). Fraction that scales down extra strength beyond the threshold."; public const string DHHHopJumpBaseValue = "Default values mirror vanilla HopHandler.JumpForce: DHHFunc.StatWithDiminishingReturns(3f, jumpIncrease(0.11f), PowerLevel+1, 5, 0.9f). Base slot value that determines the vertical boost for hop upgrades."; public const string DHHHopJumpIncreasePerLevel = "Additional boost added for each hop upgrade level before the threshold."; public const string DHHJumpForceBaseValue = "Default values mirror DeathHeadHopper JumpHandler: DHHFunc.StatWithDiminishingReturns(2.8f, forceIncrease(0.4f), PowerLevel+1, 5, 0.9f). Base jump force the death head uses when leaping off the ground."; public const string DHHJumpForceIncreasePerLevel = "Force increment applied for each power level before the threshold."; public const string DHHHopJumpThresholdLevel = "Level after which hop upgrades start diminishing in effectiveness."; public const string DHHHopJumpDiminishingFactor = "Curve factor that controls how quickly extra hop levels taper off."; public const string DHHJumpForceThresholdLevel = "Threshold level where jump force increases start to diminish."; public const string DHHJumpForceDiminishingFactor = "Diminishing factor that cuts additional force beyond the threshold."; public const string HeadChargerShopPoolMode = "Controls how Item DHH Head Charge enters the vanilla shop item pool. Disabled = never eligible, Default = use vanilla shop stands with balanced copy count, Reduced = minimum shop presence."; public const string DHHUpgradesShopPoolMode = "Controls how Item Upgrade DHH Charge and Item Upgrade DHH Power enter the vanilla shop upgrade pool. Disabled = never eligible, Default = use vanilla upgrade stands with balanced copy count, Reduced = minimum shop presence."; public const string DebugLogging = "Dump extra log lines that help trace the battery/ability logic."; public const string DHHSpectateDefaultFov = "Default field of view restored while DHH spectate is active when the active camera FOV is invalid or stuck."; } internal static class ShopPoolModes { public const string Disabled = "Disabled"; public const string Default = "Default"; public const string Reduced = "Reduced"; } [FeatureConfigEntry("1. Battery", "Enables the battery authority system that blocks jumps when the energy meter is too low.")] public static bool BatteryJumpEnabled = false; [FeatureConfigEntry("1. Battery", "Amount of battery drained per death-head jump; larger values drain faster.", Min = 0.01f, Max = 1f)] public static float BatteryJumpUsage = 0.02f; [FeatureConfigEntry("1. Battery", "Minimum battery level that must be filled before the death head can hop. 0.25f matches the vanilla talk threshold so the head can still speak.", Min = 0.01f, Max = 1f)] public static float BatteryJumpMinimumEnergy = 0.25f; [FeatureConfigEntry("1. Battery", "Duration (in seconds) that jump blocking remains active after the energy warning fires.", Min = 0.1f, Max = 1f)] public static float JumpBlockDuration = 0.5f; [FeatureConfigEntry("1. Battery", "Velocity squared threshold the death head must stay below to be considered stationary for recharge.", Min = 0.01f, Max = 1f)] public static float HeadStationaryVelocitySqrThreshold = 0.04f; [FeatureConfigEntry("1. Battery", "Interval (seconds) between stamina-based recharge ticks.", Min = 0.1f, Max = 1f)] public static float RechargeTickInterval = 0.5f; [FeatureConfigEntry("1. Battery", "Interval (seconds) between energy warning / SpectateCamera checks.", Min = 0.1f, Max = 1f)] public static float EnergyWarningCheckInterval = 0.5f; [FeatureConfigEntry("2. Stamina & Recharge", "Mirrors vanilla stamina regen to refill the death-head battery instead of draining energy.")] public static bool RechargeWithStamina = true; [FeatureConfigEntry("2. Stamina & Recharge", "When true, the death-head only recharges while standing still, matching vanilla stamina guard behavior.")] public static bool RechargeStaminaOnlyStationary = false; [FeatureConfigEntry("3. Charge ability tunables (DHH)", "Charge ability custom stamina cost (always read). How much player stamina the vanilla Charge ability consumes when executed.", Min = 10f, Max = 200f)] public static int ChargeAbilityStaminaCost = 60; [FeatureConfigEntry("3. Charge ability tunables (DHH)", "Cooldown in seconds before Charge can be used again.", Min = 1f, Max = 20f)] public static int ChargeAbilityCooldown = 6; [FeatureConfigEntry("3. Charge ability tunables (DHH)", "Seconds to hold slot1 Charge to reach 100% power. Release before this value to launch at proportional power.", Min = 0.2f, Max = 5f)] public static float ChargeAbilityHoldSeconds = 2f; [FeatureConfigEntry("5. Charge (DHH)", "Strength upgrade custom tunables (used only when DHHEnableCustomDHHValues is true). Default values mirror vanilla ChargeHandler.ResetState: DHHFunc.StatWithDiminishingReturns(baseStrength(12f), ChargeStrengthIncrease, AbilityLevel, 10, 0.75f). Base impact strength used to compute the Charge ability hit force.", Min = 1f, Max = 100f)] public static int DHHChargeStrengthBaseValue = 12; [FeatureConfigEntry("5. Charge (DHH)", "Strength upgrade custom tunables (used only when DHHEnableCustomDHHValues is true). Strength increase applied each ability level before diminishing returns.", Min = 1f, Max = 10f)] public static int DHHChargeStrengthIncreasePerLevel = 1; [FeatureConfigEntry("5. Charge (DHH)", "Strength upgrade custom tunables (used only when DHHEnableCustomDHHValues is true). Ability level threshold where extra strength gain starts to shrink.", Min = 1f, Max = 100f)] public static int DHHChargeStrengthThresholdLevel = 10; [FeatureConfigEntry("5. Charge (DHH)", "Strength upgrade custom tunables (used only when DHHEnableCustomDHHValues is true). Fraction that scales down extra strength beyond the threshold.", Min = 0.1f, Max = 0.99f)] public static float DHHChargeStrengthDiminishingFactor = 0.75f; [FeatureConfigEntry("4. Jump (DHH)", "Default values mirror DeathHeadHopper JumpHandler: DHHFunc.StatWithDiminishingReturns(2.8f, forceIncrease(0.4f), PowerLevel+1, 5, 0.9f). Base jump force the death head uses when leaping off the ground.", Min = 0.1f, Max = 5f)] public static float DHHJumpForceBaseValue = 0.5f; [FeatureConfigEntry("4. Jump (DHH)", "Force increment applied for each power level before the threshold.", Min = 0.1f, Max = 2f)] public static float DHHJumpForceIncreasePerLevel = 0.25f; [FeatureConfigEntry("4. Jump (DHH)", "Threshold level where jump force increases start to diminish.", Min = 1f, Max = 10f)] public static int DHHJumpForceThresholdLevel = 5; [FeatureConfigEntry("4. Jump (DHH)", "Diminishing factor that cuts additional force beyond the threshold.", Min = 0.1f, Max = 0.99f)] public static float DHHJumpForceDiminishingFactor = 0.9f; [FeatureConfigEntry("4. Jump (DHH)", "Additional boost added for each hop upgrade level before the threshold.", Min = 0.1f, Max = 1f)] public static float DHHHopJumpIncreasePerLevel = 0.25f; [FeatureConfigEntry("4. Jump (DHH)", "Curve factor that controls how quickly extra hop levels taper off.", Min = 0.1f, Max = 0.99f)] public static float DHHHopJumpDiminishingFactor = 0.9f; [FeatureConfigEntry("4. Jump (DHH)", "Default values mirror vanilla HopHandler.JumpForce: DHHFunc.StatWithDiminishingReturns(3f, jumpIncrease(0.11f), PowerLevel+1, 5, 0.9f). Base slot value that determines the vertical boost for hop upgrades.", Min = 1f, Max = 10f)] public static int DHHHopJumpBaseValue = 2; [FeatureConfigEntry("4. Jump (DHH)", "Level after which hop upgrades start diminishing in effectiveness.", Min = 1f, Max = 10f)] public static int DHHHopJumpThresholdLevel = 5; [FeatureConfigEntry("6. Shop", "Controls how Item DHH Head Charge enters the vanilla shop item pool. Disabled = never eligible, Default = use vanilla shop stands with balanced copy count, Reduced = minimum shop presence.", Options = new string[] { "Disabled", "Default", "Reduced" })] public static string HeadChargerShopPoolMode = "Default"; [FeatureConfigEntry("6. Shop", "Controls how Item Upgrade DHH Charge and Item Upgrade DHH Power enter the vanilla shop upgrade pool. Disabled = never eligible, Default = use vanilla upgrade stands with balanced copy count, Reduced = minimum shop presence.", Options = new string[] { "Disabled", "Default", "Reduced" })] public static string DHHUpgradesShopPoolMode = "Default"; [FeatureConfigEntry("7. Debug", "Dump extra log lines that help trace the battery/ability logic.", HostControlled = false)] public static bool DebugLogging = false; [FeatureConfigEntry("8. Camera", "Default field of view restored while DHH spectate is active when the active camera FOV is invalid or stuck.", Min = 0f, Max = 120f, HostControlled = false)] public static int DHHSpectateDefaultFov = 70; } internal static class InternalConfig { internal static float HostFixPresenceGraceSeconds = 5f; } internal static class InternalDebugFlags { public static bool DisableBatteryModule = false; public static bool DisableAbilityPatches = false; public static bool DisableSpectateChecks = false; public static bool DebugJumpForceLog = true; public static bool DebugDhhChargeTuningLog = true; public static bool DebugDhhChargeRechargeLog = true; public static bool DebugDhhBatteryJumpAllowanceLog = true; public static bool DebugDirectionSlotEnergyPreviewLog = true; public static bool DebugPhysicsKinematicVelocityGuardLog = true; } } namespace DeathHeadHopperFix.Modules.Battery { internal sealed class BatteryJumpModule : MonoBehaviour { private const float JumpBlockDuration = 0.5f; private const float EnergyWarningCheckInterval = 0.5f; private static readonly FieldInfo? s_spectateCurrentStateField = AccessTools.Field(typeof(SpectateCamera), "currentState"); private static readonly FieldInfo? s_overrideSpectatedField = AccessTools.Field(typeof(PlayerDeathHead), "overrideSpectated"); private static readonly MethodInfo? s_overrideSpectatedResetMethod = AccessTools.Method(typeof(PlayerDeathHead), "OverrideSpectatedReset", (Type[])null, (Type[])null); private static readonly FieldInfo? s_headJumpEventField = AccessTools.TypeByName("DeathHeadHopper.DeathHead.DeathHeadController")?.GetField("m_HeadJumpEvent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly Type? s_eyeHandlerType = AccessTools.TypeByName("DeathHeadHopper.DeathHead.Handlers.EyeHandler"); private static readonly FieldInfo? s_eyeNegativeConditionsField = s_eyeHandlerType?.GetField("eyeNegativeConditions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private object? _controllerInstance; private UnityEvent? _jumpEvent; private UnityAction? _jumpAction; private IList? _eyeNegativeConditions; private Func? _eyeCondition; private PhotonView? _photonView; private bool _isOwner; private bool _lastSyncedEyeWarningState; private float _jumpBlockedTimer; private float _lastBlockedLogTime; private bool _jumpBlocked; private bool _overrideSpectatedCleared; private float _energyWarningAccumulator; private bool _inactiveStateApplied; private static readonly Type? s_physGrabObjectType = AccessTools.TypeByName("PhysGrabObject"); private static readonly FieldInfo? s_physGrabObjectGrabbedField = ((s_physGrabObjectType != null) ? AccessTools.Field(s_physGrabObjectType, "grabbed") : null); private void Awake() { //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown if (s_headJumpEventField == null) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("DHHBattery.HeadJumpEvent.Missing", 600)) { Debug.Log((object)"[Fix:DHHBattery] Head jump event field not found; BatteryJumpModule disabled."); } ((Behaviour)this).enabled = false; return; } _controllerInstance = ((Component)this).GetComponent(s_headJumpEventField.DeclaringType); if (_controllerInstance == null) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("DHHBattery.HeadJumpEvent.NoController", 600)) { Debug.Log((object)"[Fix:DHHBattery] Head jump controller component missing; BatteryJumpModule disabled."); } ((Behaviour)this).enabled = false; return; } ref UnityEvent? jumpEvent = ref _jumpEvent; object? value = s_headJumpEventField.GetValue(_controllerInstance); jumpEvent = (UnityEvent?)((value is UnityEvent) ? value : null); if (_jumpEvent == null) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("DHHBattery.HeadJumpEvent.Null", 600)) { Debug.Log((object)"[Fix:DHHBattery] Head jump UnityEvent is null; BatteryJumpModule disabled."); } ((Behaviour)this).enabled = false; return; } _jumpAction = new UnityAction(OnHeadJump); _jumpEvent.AddListener(_jumpAction); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("DHHBattery.HeadJumpEvent.Hooked", 600)) { Debug.Log((object)"[Fix:DHHBattery] Head jump listener hooked."); } _photonView = ((Component)this).GetComponent(); _isOwner = !SemiFunc.IsMultiplayer() || ((Object)(object)_photonView != (Object)null && _photonView.IsMine); SetupEyeWarningCondition(); } private void OnDestroy() { if (_jumpEvent != null && _jumpAction != null) { _jumpEvent.RemoveListener(_jumpAction); } RemoveEyeWarningCondition(); } private void Update() { if (!_isOwner) { return; } if (!FeatureFlags.BatteryJumpEnabled || InternalDebugFlags.DisableBatteryModule) { if (!_inactiveStateApplied) { ResetBlockedState(); _inactiveStateApplied = true; } return; } _inactiveStateApplied = false; if (_jumpBlocked && _jumpBlockedTimer > 0f) { _jumpBlockedTimer -= Time.deltaTime; if (_jumpBlockedTimer <= 0f) { _jumpBlocked = false; TrySyncEyeWarningState(blocked: false); } } _energyWarningAccumulator += Time.deltaTime; if (_energyWarningAccumulator >= 0.5f) { UpdateEnergyWarningState(); _energyWarningAccumulator %= 0.5f; } } private void UpdateEnergyWarningState() { if (InternalDebugFlags.DisableSpectateChecks) { if (FeatureFlags.DebugLogging) { Debug.Log((object)"[Fix:DHHBattery] Spectate checks disabled; skipping energy warning evaluation."); } return; } SpectateCamera instance = SpectateCamera.instance; if ((Object)(object)instance == (Object)null) { return; } TryClearStuckOverrideSpectated(instance); if (DHHBatteryHelper.EvaluateJumpAllowance().allowed) { _jumpBlocked = false; TrySyncEyeWarningState(blocked: false); if ((Object)(object)instance != (Object)null) { DHHBatteryHelper.SetEnergyEnough(instance, value: true); } return; } if (!_jumpBlocked) { _jumpBlocked = true; _jumpBlockedTimer = 0.5f; TrySyncEyeWarningState(blocked: true); } DHHBatteryHelper.SetEnergyEnough(instance, value: false); } private void ResetBlockedState() { if (_jumpBlocked) { _jumpBlocked = false; TrySyncEyeWarningState(blocked: false); } SpectateCamera instance = SpectateCamera.instance; if ((Object)(object)instance != (Object)null) { DHHBatteryHelper.SetEnergyEnough(instance, value: true); } _energyWarningAccumulator = 0.5f; } private void TryClearStuckOverrideSpectated(SpectateCamera spectate) { if ((Object)(object)spectate == (Object)null || s_spectateCurrentStateField == null || s_overrideSpectatedField == null || s_overrideSpectatedResetMethod == null || !LogLimiter.ShouldLog("DHHBattery.TryClearOverrideSpectated", 30)) { return; } object value = s_spectateCurrentStateField.GetValue(spectate); if (value == null || !string.Equals(value.ToString(), "Head", StringComparison.Ordinal)) { _overrideSpectatedCleared = false; } else { if (_overrideSpectatedCleared) { return; } PlayerDeathHead val = PlayerController.instance?.playerAvatarScript?.playerDeathHead; if ((Object)(object)val == (Object)null || !(s_overrideSpectatedField.GetValue(val) as bool?).GetValueOrDefault() || IsHeadGrabbedBestEffort(val)) { return; } try { s_overrideSpectatedResetMethod.Invoke(val, null); _overrideSpectatedCleared = true; if (FeatureFlags.DebugLogging) { Debug.Log((object)"[Fix:DHHBattery] Cleared stuck overrideSpectated to prevent headEnergy=1f lock."); } } catch { } } } private static bool IsHeadGrabbedBestEffort(PlayerDeathHead head) { try { if ((Object)(object)head == (Object)null) { return false; } if (s_physGrabObjectType == null) { return false; } Component component = ((Component)head).gameObject.GetComponent(s_physGrabObjectType); if ((Object)(object)component == (Object)null) { return false; } if (s_physGrabObjectGrabbedField != null && s_physGrabObjectGrabbedField.FieldType == typeof(bool)) { return (bool)(s_physGrabObjectGrabbedField.GetValue(component) ?? ((object)false)); } return false; } catch { } return false; } private void TrySyncEyeWarningState(bool blocked) { if (!_isOwner || (Object)(object)_photonView == (Object)null || blocked == _lastSyncedEyeWarningState) { return; } _lastSyncedEyeWarningState = blocked; if (!SemiFunc.IsMultiplayer() || !PhotonNetwork.InRoom) { return; } try { _photonView.RPC("SyncEyeWarningStateRPC", (RpcTarget)4, new object[1] { blocked }); } catch { } } [PunRPC] private void SyncEyeWarningStateRPC(bool blocked) { _jumpBlocked = blocked; _jumpBlockedTimer = (blocked ? 0.5f : 0f); } private void OnHeadJump() { if (_isOwner && FeatureFlags.BatteryJumpEnabled && !InternalDebugFlags.DisableBatteryModule && !DHHBatteryHelper.HasRecentJumpConsumption()) { (bool, bool?, float, float) tuple = DHHBatteryHelper.EvaluateJumpAllowance(); if (!tuple.Item1) { NotifyJumpBlocked(tuple.Item4, tuple.Item3, tuple.Item2); } } } internal void NotifyJumpBlocked(float currentEnergy, float reference, bool? readyFlag) { if (!FeatureFlags.BatteryJumpEnabled || InternalDebugFlags.DisableBatteryModule) { return; } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("DHHBattery.JumpBlocked", 120)) { float num = Time.time - _lastBlockedLogTime; if (!_jumpBlocked || num >= 0.5f) { _lastBlockedLogTime = Time.time; string arg = (readyFlag.HasValue ? readyFlag.Value.ToString() : "unknown"); Debug.Log((object)$"[Fix:DHHBattery] Jump blocked, energy too low (current={currentEnergy:F3}, readyFlag={arg}, reference={reference:F3})"); } } _jumpBlocked = true; _jumpBlockedTimer = 0.5f; TrySyncEyeWarningState(blocked: true); SpectateCamera instance = SpectateCamera.instance; if ((Object)(object)instance != (Object)null) { DHHBatteryHelper.SetEnergyEnough(instance, value: false); } } private void SetupEyeWarningCondition() { if (s_eyeHandlerType == null || s_eyeNegativeConditionsField == null) { return; } Component component = ((Component)this).GetComponent(s_eyeHandlerType); if (!((Object)(object)component == (Object)null) && s_eyeNegativeConditionsField.GetValue(component) is IList list) { _eyeCondition = () => _jumpBlocked; list.Add(_eyeCondition); _eyeNegativeConditions = list; } } private void RemoveEyeWarningCondition() { if (_eyeNegativeConditions != null && _eyeCondition != null) { _eyeNegativeConditions.Remove(_eyeCondition); _eyeNegativeConditions = null; _eyeCondition = null; } } } internal static class BatteryJumpPatchModule { private static FieldInfo? s_jumpHandlerJumpBufferField; internal static void Apply(Harmony harmony, Assembly asm) { PatchDeathHeadControllerModulesIfPossible(harmony, asm); PatchJumpHandlerUpdateIfPossible(harmony, asm); } private static void PatchDeathHeadControllerModulesIfPossible(Harmony harmony, Assembly asm) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.DeathHead.DeathHeadController", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "Start", (Type[])null, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(BatteryJumpPatchModule).GetMethod("DeathHeadController_Start_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void DeathHeadController_Start_Postfix(object __instance) { MonoBehaviour val = (MonoBehaviour)((__instance is MonoBehaviour) ? __instance : null); if (val != null) { GameObject gameObject = ((Component)val).gameObject; if ((Object)(object)gameObject.GetComponent() == (Object)null) { gameObject.AddComponent(); } if ((Object)(object)gameObject.GetComponent() == (Object)null) { gameObject.AddComponent(); } } } private static void PatchJumpHandlerUpdateIfPossible(Harmony harmony, Assembly asm) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.DeathHead.Handlers.JumpHandler", throwOnError: false); if (type == null) { return; } s_jumpHandlerJumpBufferField = AccessTools.Field(type, "jumpBufferTimer"); if (s_jumpHandlerJumpBufferField == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(BatteryJumpPatchModule).GetMethod("JumpHandler_Update_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool JumpHandler_Update_Prefix(MonoBehaviour __instance) { if ((Object)(object)__instance == (Object)null || s_jumpHandlerJumpBufferField == null) { return true; } if (!(s_jumpHandlerJumpBufferField.GetValue(__instance) is float num) || num <= 0f) { return true; } if (!FeatureFlags.BatteryJumpEnabled) { return true; } BatteryJumpModule component = ((Component)__instance).gameObject.GetComponent(); if ((Object)(object)component == (Object)null) { return true; } (bool, bool?, float, float) tuple = DHHBatteryHelper.EvaluateJumpAllowance(); if (tuple.Item1) { return true; } s_jumpHandlerJumpBufferField.SetValue(__instance, 0f); component.NotifyJumpBlocked(tuple.Item4, tuple.Item3, tuple.Item2); return false; } } internal static class DHHBatteryHelper { private static readonly FieldInfo? s_headEnergyField = typeof(SpectateCamera).GetField("headEnergy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo? s_headEnergyEnoughField = typeof(SpectateCamera).GetField("headEnergyEnough", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo? s_playerSprintRechargeAmountField = typeof(PlayerController).GetField("sprintRechargeAmount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo? s_playerDeadSetField = AccessTools.Field(typeof(PlayerAvatar), "deadSet"); private static readonly FieldInfo? s_playerIsDisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled"); private const float JumpConsumptionCoalesceWindow = 0.2f; private static float s_lastJumpConsumptionTime = float.NegativeInfinity; private static readonly FieldInfo? s_dhhAbilityEnergyHandlerField = AccessTools.TypeByName("DeathHeadHopper.DeathHead.DeathHeadController")?.GetField("abilityEnergyHandler", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly Type? s_dhhAbilityEnergyHandlerType = AccessTools.TypeByName("DeathHeadHopper.DeathHead.Handlers.AbilityEnergyHandler"); private static readonly PropertyInfo? s_dhhAbilityEnergyProp = s_dhhAbilityEnergyHandlerType?.GetProperty("Energy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly PropertyInfo? s_dhhAbilityEnergyMaxProp = s_dhhAbilityEnergyHandlerType?.GetProperty("EnergyMax", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo? s_dhhIncreaseEnergyMethod = s_dhhAbilityEnergyHandlerType?.GetMethod("IncreaseEnergy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(float) }, null); internal static float GetHeadEnergy(SpectateCamera? spectate) { if ((Object)(object)spectate == (Object)null || s_headEnergyField == null) { return 0f; } return (float)(s_headEnergyField.GetValue(spectate) ?? ((object)0f)); } internal static void SetHeadEnergy(SpectateCamera spectate, float value) { if (s_headEnergyField != null) { s_headEnergyField.SetValue(spectate, value); } } internal static void SetEnergyEnough(SpectateCamera spectate, bool value) { if (s_headEnergyEnoughField != null) { s_headEnergyEnoughField.SetValue(spectate, value); } } internal static float GetJumpThreshold() { return FeatureFlags.BatteryJumpMinimumEnergy; } internal static (bool allowed, bool? readyFlag, float reference, float currentEnergy) EvaluateJumpAllowance() { SpectateCamera instance = SpectateCamera.instance; float headEnergy = GetHeadEnergy(instance); float jumpThreshold = GetJumpThreshold(); bool? flag = null; if ((Object)(object)instance != (Object)null && s_headEnergyEnoughField != null) { flag = s_headEnergyEnoughField.GetValue(instance) as bool?; } bool flag2 = headEnergy >= jumpThreshold; LogAllowance(headEnergy, jumpThreshold, flag2, flag); return (flag2, flag, jumpThreshold, headEnergy); } internal static void RechargeDhhAbilityEnergy(object? controllerInstance, float deltaTime) { if (!FeatureFlags.RechargeWithStamina || deltaTime <= 0f || controllerInstance == null || s_dhhAbilityEnergyHandlerField == null || s_dhhAbilityEnergyProp == null || s_dhhAbilityEnergyMaxProp == null || s_dhhIncreaseEnergyMethod == null) { return; } float playerSprintRechargeAmount = GetPlayerSprintRechargeAmount(); if (playerSprintRechargeAmount <= 0f) { return; } object value; try { value = s_dhhAbilityEnergyHandlerField.GetValue(controllerInstance); } catch { return; } if (value == null) { return; } float num; float num2; try { num = (float)(s_dhhAbilityEnergyProp.GetValue(value) ?? ((object)0f)); num2 = (float)(s_dhhAbilityEnergyMaxProp.GetValue(value) ?? ((object)0f)); } catch { return; } if (num2 <= 0f || num >= num2) { return; } float num3 = playerSprintRechargeAmount * deltaTime; try { s_dhhIncreaseEnergyMethod.Invoke(value, new object[1] { num3 }); LogRecharge(num3, num + num3, num2); } catch { } } internal static void RechargeHeadEnergy(float deltaTime) { } private static void LogAllowance(float currentEnergy, float reference, bool allowed, bool? readyFlag) { if (FeatureFlags.DebugLogging && FeatureFlags.BatteryJumpEnabled && InternalDebugFlags.DebugDhhBatteryJumpAllowanceLog && IsDeathHeadContext() && LogLimiter.ShouldLog("DHHBattery.JumpAllowance", 120)) { string text = (readyFlag.HasValue ? readyFlag.Value.ToString() : "unknown"); Debug.Log((object)$"[Fix:DHHBattery] Jump allowance: allowed={allowed}, energy={currentEnergy:F3}, ref={reference:F3}, readyFlag={text}"); } } private static bool IsDeathHeadContext() { if (SpectateContextHelper.IsSpectatingLocalDeathHead()) { return true; } PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null) { return false; } bool flag = default(bool); int num; if (s_playerIsDisabledField != null) { object value = s_playerIsDisabledField.GetValue(instance); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } bool flag2 = default(bool); int num2; if (s_playerDeadSetField != null) { object value = s_playerDeadSetField.GetValue(instance); if (value is bool) { flag2 = (bool)value; num2 = 1; } else { num2 = 0; } } else { num2 = 0; } if (((uint)num2 & (flag2 ? 1u : 0u)) != 0) { return true; } return false; } internal static float GetEffectiveBatteryJumpUsage() { return Math.Max(0f, FeatureFlags.BatteryJumpUsage); } internal static bool HasRecentJumpConsumption() { return Time.time - s_lastJumpConsumptionTime < 0.2f; } internal static float ComputeVanillaBatteryJumpUsage() { PlayerController instance = PlayerController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerAvatarScript == (Object)null) { return 0.02f; } float num = 25f; float num2 = 5f; float upgradeDeathHeadBattery = GetUpgradeDeathHeadBattery(instance.playerAvatarScript); for (float num3 = upgradeDeathHeadBattery; num3 > 0f; num3 -= 1f) { num += num2; num2 *= 0.95f; } return 0.5f / num; } internal static float GetVanillaBatteryJumpMinimumEnergy() { return 0.25f; } internal static float ApplyConsumption(SpectateCamera spectate, float consumption, float reference) { float headEnergy = GetHeadEnergy(spectate); float num = Mathf.Max(0f, headEnergy - consumption); SetHeadEnergy(spectate, num); SetEnergyEnough(spectate, num >= reference); LogConsumption(headEnergy, num, consumption, reference); s_lastJumpConsumptionTime = Time.time; return num; } internal static float ApplyDamageEnergyPenalty(float penalty) { if (penalty <= 0f) { return 0f; } SpectateCamera instance = SpectateCamera.instance; if ((Object)(object)instance == (Object)null) { return 0f; } return ApplyConsumption(instance, penalty, GetJumpThreshold()); } internal static float GetPlayerSprintRechargeAmount() { PlayerController instance = PlayerController.instance; if ((Object)(object)instance == (Object)null || s_playerSprintRechargeAmountField == null) { return 0f; } return (float)(s_playerSprintRechargeAmountField.GetValue(instance) ?? ((object)0f)); } private static float GetUpgradeDeathHeadBattery(PlayerAvatar avatar) { if ((Object)(object)avatar == (Object)null) { return 0f; } FieldInfo fieldInfo = AccessTools.Field(typeof(PlayerAvatar), "upgradeDeathHeadBattery"); if (fieldInfo == null) { return 0f; } return (float)(fieldInfo.GetValue(avatar) ?? ((object)0f)); } private static void LogConsumption(float before, float after, float amount, float reference) { if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("DHHBattery.Consumption", 120)) { Debug.Log((object)$"[Fix:DHHBattery] Energy consume {amount:F3} (before={before:F3}, after={after:F3}, ref={reference:F3})"); } } private static void LogRecharge(float amount, float energy, float max) { if (FeatureFlags.DebugLogging && InternalDebugFlags.DebugDhhChargeRechargeLog && LogLimiter.ShouldLog("DHHBattery.Recharge")) { Debug.Log((object)$"[Fix:DHHCharge] Stamina recharge {amount:F3} (stamina={energy:F3} / {max:F3})"); } } } }