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.DeathHead.Handlers; 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.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 UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; [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.0")] public sealed class Plugin : BaseUnityPlugin { private const string TargetAssemblyName = "DeathHeadHopper"; private Harmony? _harmony; private bool _patched; 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 || asm == null) { return; } string name = asm.GetName().Name; if (!string.Equals(name, "DeathHeadHopper", StringComparison.OrdinalIgnoreCase)) { return; } 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."); } PrefabModule.Apply(harmony, asm, _log); AudioModule.Apply(harmony, asm, _log); DHHShopModule.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 ItemHelpers { internal static string? GetItemAssetName(Object? itemAsset) { if (itemAsset == (Object)null) { return null; } Type type = ((object)itemAsset).GetType(); PropertyInfo property = type.GetProperty("itemAssetName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(string)) { return ((string)property.GetValue(itemAsset, null)) ?? itemAsset.name; } FieldInfo field = type.GetField("itemAssetName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(string)) { return ((string)field.GetValue(itemAsset)) ?? itemAsset.name; } return itemAsset.name; } } 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 PlayerTruckDistanceHelper { [Flags] internal enum DistanceQueryFields { None = 0, Height = 1, NavMeshDistance = 2, RoomPath = 4, All = 7 } internal readonly struct PlayerTruckDistance { internal PlayerAvatar PlayerAvatar { get; } internal float NavMeshDistance { get; } internal float HeightDelta { get; } internal int ShortestRoomPathToTruck { get; } internal int TotalMapRooms { get; } internal bool HasValidPath { get; } internal PlayerTruckDistance(PlayerAvatar playerAvatar, float navMeshDistance, float heightDelta, int shortestRoomPathToTruck, int totalMapRooms, bool hasValidPath) { PlayerAvatar = playerAvatar; NavMeshDistance = navMeshDistance; HeightDelta = heightDelta; ShortestRoomPathToTruck = shortestRoomPathToTruck; TotalMapRooms = totalMapRooms; HasValidPath = hasValidPath; } } private sealed class CachedPlayerDistance { internal float NavMeshDistance = -1f; internal float HeightDelta; internal int ShortestRoomPathToTruck = -1; internal int TotalMapRooms = -1; internal bool HasValidPath; internal int RoomHash; internal float HeightUpdatedAt = float.NegativeInfinity; internal int LevelStamp; internal Vector3 LastKnownWorldPosition; } private readonly struct RemotePlayerHint { internal int RoomHash { get; } internal float HeightDelta { get; } internal int LevelStamp { get; } internal float UpdatedAt { get; } internal RemotePlayerHint(int roomHash, float heightDelta, int levelStamp, float updatedAt) { RoomHash = roomHash; HeightDelta = heightDelta; LevelStamp = levelStamp; UpdatedAt = updatedAt; } } private struct ActivationProfileStats { internal int Calls; internal int PlayersProcessed; internal int NavRefreshCount; internal int RoomRefreshCount; internal int RemoteHintUsedCount; internal float TotalMs; internal float SetupMs; internal float LoopMs; internal float MaxCallMs; } private const float HeightCacheTtlSeconds = 2f; private static readonly Type? s_levelGeneratorType = AccessTools.TypeByName("LevelGenerator"); private static readonly FieldInfo? s_levelGeneratorInstanceField = ((s_levelGeneratorType == null) ? null : AccessTools.Field(s_levelGeneratorType, "Instance")); private static readonly FieldInfo? s_levelPathTruckField = ((s_levelGeneratorType == null) ? null : AccessTools.Field(s_levelGeneratorType, "LevelPathTruck")); private static readonly FieldInfo? s_levelPathPointsField = ((s_levelGeneratorType == null) ? null : AccessTools.Field(s_levelGeneratorType, "LevelPathPoints")); private static readonly Type? s_levelPointType = AccessTools.TypeByName("LevelPoint"); private static readonly FieldInfo? s_levelPointTruckField = ((s_levelPointType == null) ? null : AccessTools.Field(s_levelPointType, "Truck")); private static readonly FieldInfo? s_levelPointRoomField = ((s_levelPointType == null) ? null : AccessTools.Field(s_levelPointType, "Room")); private static readonly FieldInfo? s_levelPointConnectedPointsField = ((s_levelPointType == null) ? null : AccessTools.Field(s_levelPointType, "ConnectedPoints")); private static readonly Type? s_roomVolumeType = AccessTools.TypeByName("RoomVolume"); private static readonly FieldInfo? s_roomVolumeTruckField = ((s_roomVolumeType == null) ? null : AccessTools.Field(s_roomVolumeType, "Truck")); private static readonly FieldInfo? s_playerLastNavmeshField = AccessTools.Field(typeof(PlayerAvatar), "LastNavmeshPosition"); private static readonly FieldInfo? s_playerRoomVolumeCheckField = AccessTools.Field(typeof(PlayerAvatar), "RoomVolumeCheck"); private static readonly Type? s_playerDeathHeadType = AccessTools.TypeByName("PlayerDeathHead"); private static readonly FieldInfo? s_playerDeathHeadRoomVolumeCheckField = ((s_playerDeathHeadType == null) ? null : AccessTools.Field(s_playerDeathHeadType, "roomVolumeCheck")); private static readonly FieldInfo? s_playerDeathHeadPhysGrabObjectField = ((s_playerDeathHeadType == null) ? null : AccessTools.Field(s_playerDeathHeadType, "physGrabObject")); private static readonly Type? s_physGrabObjectType = AccessTools.TypeByName("PhysGrabObject"); private static readonly FieldInfo? s_physGrabObjectCenterPointField = ((s_physGrabObjectType == null) ? null : AccessTools.Field(s_physGrabObjectType, "centerPoint")); private static readonly Type? s_roomVolumeCheckType = AccessTools.TypeByName("RoomVolumeCheck"); private static readonly FieldInfo? s_roomVolumeCheckCurrentRoomsField = ((s_roomVolumeCheckType == null) ? null : AccessTools.Field(s_roomVolumeCheckType, "CurrentRooms")); private static readonly Type? s_navMeshType = AccessTools.TypeByName("UnityEngine.AI.NavMesh"); private static readonly Type? s_navMeshHitType = AccessTools.TypeByName("UnityEngine.AI.NavMeshHit"); private static readonly Type? s_navMeshPathType = AccessTools.TypeByName("UnityEngine.AI.NavMeshPath"); private static readonly MethodInfo? s_navMeshSamplePositionMethod = s_navMeshType?.GetMethod("SamplePosition", BindingFlags.Static | BindingFlags.Public, null, (s_navMeshHitType == null) ? null : new Type[4] { typeof(Vector3), s_navMeshHitType.MakeByRefType(), typeof(float), typeof(int) }, null); private static readonly PropertyInfo? s_navMeshHitPositionProperty = s_navMeshHitType?.GetProperty("position", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo? s_navMeshHitPositionField = s_navMeshHitType?.GetField("position", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo? s_navMeshCalculatePathMethod = s_navMeshType?.GetMethod("CalculatePath", BindingFlags.Static | BindingFlags.Public, null, new Type[4] { typeof(Vector3), typeof(Vector3), typeof(int), s_navMeshPathType }, null); private static readonly PropertyInfo? s_navMeshPathCornersProperty = s_navMeshPathType?.GetProperty("corners"); private static object? s_cachedGraphLevelGenerator; private static int s_cachedGraphPointCount; private static Dictionary>? s_cachedRoomGraph; private static readonly Dictionary s_playerCache = new Dictionary(); private static readonly Dictionary s_remoteHints = new Dictionary(); private static object? s_cachedLevelGeneratorForPlayers; private static Vector3 s_cachedTruckPosition; private static bool s_hasCachedTruckPosition; private static int s_cachedLevelPointsCount; private static bool s_activationProfilingEnabled; private static ActivationProfileStats s_activationProfileStats; internal static void PrimeDistancesCache() { GetDistancesFromTruck(DistanceQueryFields.NavMeshDistance | DistanceQueryFields.RoomPath); } internal static PlayerTruckDistance[] GetDistancesFromTruck() { return GetDistancesFromTruck(DistanceQueryFields.All); } internal static PlayerTruckDistance[] GetDistancesFromTruck(DistanceQueryFields fields, ICollection? players = null, bool forceRefresh = false) { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0100: 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_0146: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_04af: Unknown result type (might be due to invalid IL or missing references) //IL_04ba: Unknown result type (might be due to invalid IL or missing references) //IL_04bc: Unknown result type (might be due to invalid IL or missing references) //IL_046e: Unknown result type (might be due to invalid IL or missing references) //IL_0475: Unknown result type (might be due to invalid IL or missing references) try { bool debugLogging = FeatureFlags.DebugLogging; float num = (debugLogging ? Time.realtimeSinceStartup : 0f); float num2 = num; float num3 = num; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; if (fields == DistanceQueryFields.None || s_levelGeneratorInstanceField == null) { return Array.Empty(); } object value = s_levelGeneratorInstanceField.GetValue(null); if (value == null) { return Array.Empty(); } List allLevelPoints = GetAllLevelPoints(value); if (!TryGetTruckTarget(value, allLevelPoints, out Vector3 truckPosition, out object truckPoint)) { return Array.Empty(); } GameDirector instance = GameDirector.instance; if (instance?.PlayerList == null || instance.PlayerList.Count == 0) { return Array.Empty(); } int num8 = allLevelPoints?.Count ?? 0; if (s_cachedLevelGeneratorForPlayers != value || !s_hasCachedTruckPosition || Vector3.SqrMagnitude(s_cachedTruckPosition - truckPosition) > 0.0001f || s_cachedLevelPointsCount != num8) { s_playerCache.Clear(); s_remoteHints.Clear(); s_cachedLevelGeneratorForPlayers = value; s_cachedTruckPosition = truckPosition; s_cachedLevelPointsCount = num8; s_hasCachedTruckPosition = true; } HashSet hashSet = null; if (players != null) { hashSet = new HashSet(); foreach (PlayerAvatar player in players) { if (!((Object)(object)player == (Object)null)) { hashSet.Add(GetPlayerKey(player)); } } if (hashSet.Count == 0) { return Array.Empty(); } } bool flag = (fields & DistanceQueryFields.RoomPath) != 0; bool flag2 = (fields & DistanceQueryFields.NavMeshDistance) != 0; bool flag3 = (fields & DistanceQueryFields.Height) != 0; Dictionary> dictionary = ((flag || flag2) ? GetOrBuildRoomGraph(value, allLevelPoints) : null); int totalMapRooms = ((dictionary != null && dictionary.Count > 0) ? dictionary.Count : (-1)); int num9 = (((Object)(object)RunManager.instance != (Object)null) ? RunManager.instance.levelsCompleted : 0); if (debugLogging) { num2 = Time.realtimeSinceStartup; num3 = num2; } List list = new List(instance.PlayerList.Count); foreach (PlayerAvatar player2 in instance.PlayerList) { if ((Object)(object)player2 == (Object)null) { continue; } int playerKey = GetPlayerKey(player2); if (hashSet != null && !hashSet.Contains(playerKey)) { continue; } if (!s_playerCache.TryGetValue(playerKey, out CachedPlayerDistance value2)) { value2 = new CachedPlayerDistance(); s_playerCache[playerKey] = value2; } Vector3 val = (value2.LastKnownWorldPosition = GetPlayerWorldPosition(player2)); PhotonView photonView = player2.photonView; int? obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? new int?(owner.ActorNumber) : null); } int? num10 = obj; int valueOrDefault = num10.GetValueOrDefault(); s_remoteHints.TryGetValue(valueOrDefault, out var value3); bool flag4 = SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.IsMultiplayer() && valueOrDefault > 0 && PhotonNetwork.LocalPlayer != null && valueOrDefault != PhotonNetwork.LocalPlayer.ActorNumber && value3.LevelStamp == num9; List list2 = null; int num11 = value2.RoomHash; if (flag4) { num11 = value3.RoomHash; num6++; } else if (flag || flag2) { list2 = GetPlayerRooms(player2); num11 = ComputeRoomsHash(list2); } bool flag5 = num11 != value2.RoomHash; bool flag6 = value2.LevelStamp != num9; if (flag3) { float num12 = Time.unscaledTime - value2.HeightUpdatedAt; if (forceRefresh || flag6 || num12 < 0f || num12 > 2f) { if (flag4 && Time.unscaledTime - value3.UpdatedAt <= 2f) { value2.HeightDelta = value3.HeightDelta; value2.HeightUpdatedAt = value3.UpdatedAt; } else { value2.HeightDelta = val.y - truckPosition.y; value2.HeightUpdatedAt = Time.unscaledTime; } } } if (flag2 && (forceRefresh || flag6 || flag5)) { float navMeshDistance = -1f; Vector3 navMeshPosition; bool flag7 = TryGetPlayerNavMeshPosition(player2, val, out navMeshPosition) && TryCalculatePathDistance(navMeshPosition, truckPosition, out navMeshDistance); value2.NavMeshDistance = (flag7 ? navMeshDistance : (-1f)); value2.HasValidPath = flag7; num4++; } if (flag && (forceRefresh || flag6 || flag5)) { if (list2 == null) { list2 = GetPlayerRooms(player2); } value2.ShortestRoomPathToTruck = ResolveShortestRoomPathToTruck(list2 ?? new List(), truckPoint, dictionary); num5++; } if (flag || flag2) { value2.TotalMapRooms = totalMapRooms; } value2.RoomHash = num11; value2.LevelStamp = num9; list.Add(new PlayerTruckDistance(player2, value2.NavMeshDistance, value2.HeightDelta, value2.ShortestRoomPathToTruck, value2.TotalMapRooms, value2.HasValidPath)); num7++; } if (debugLogging && s_activationProfilingEnabled) { float realtimeSinceStartup = Time.realtimeSinceStartup; float num13 = (realtimeSinceStartup - num) * 1000f; float num14 = (num2 - num) * 1000f; float num15 = (realtimeSinceStartup - num3) * 1000f; s_activationProfileStats.Calls++; s_activationProfileStats.PlayersProcessed += num7; s_activationProfileStats.NavRefreshCount += num4; s_activationProfileStats.RoomRefreshCount += num5; s_activationProfileStats.RemoteHintUsedCount += num6; s_activationProfileStats.TotalMs += num13; s_activationProfileStats.SetupMs += num14; s_activationProfileStats.LoopMs += num15; s_activationProfileStats.MaxCallMs = Mathf.Max(s_activationProfileStats.MaxCallMs, num13); } return list.ToArray(); } catch (Exception ex) { LogReflectionHotPathException("GetDistancesFromTruck", ex); return Array.Empty(); } } internal static void ApplyRemotePlayerHint(int actorNumber, int roomHash, float heightDelta, int levelStamp) { if (actorNumber > 0) { s_remoteHints[actorNumber] = new RemotePlayerHint(roomHash, heightDelta, levelStamp, Time.unscaledTime); } } internal static bool TryBuildLocalPlayerTruckHint(out int roomHash, out float heightDelta, out int levelStamp) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) roomHash = 0; heightDelta = 0f; levelStamp = (((Object)(object)RunManager.instance != (Object)null) ? RunManager.instance.levelsCompleted : 0); try { if (!PhotonNetwork.InRoom || PhotonNetwork.LocalPlayer == null || s_levelGeneratorInstanceField == null) { return false; } object value = s_levelGeneratorInstanceField.GetValue(null); if (value == null) { return false; } List allLevelPoints = GetAllLevelPoints(value); if (!TryGetTruckTarget(value, allLevelPoints, out Vector3 truckPosition, out object _)) { return false; } GameDirector instance = GameDirector.instance; if (instance?.PlayerList == null || instance.PlayerList.Count == 0) { return false; } int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; PlayerAvatar val = null; for (int i = 0; i < instance.PlayerList.Count; i++) { PlayerAvatar val2 = instance.PlayerList[i]; if (!((Object)(object)val2 == (Object)null)) { PhotonView photonView = val2.photonView; int? obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? new int?(owner.ActorNumber) : null); } int? num = obj; if (num.GetValueOrDefault() == actorNumber) { val = val2; break; } } } if ((Object)(object)val == (Object)null) { return false; } List playerRooms = GetPlayerRooms(val); roomHash = ComputeRoomsHash(playerRooms); Vector3 playerWorldPosition = GetPlayerWorldPosition(val); heightDelta = playerWorldPosition.y - truckPosition.y; return true; } catch (Exception ex) { LogReflectionHotPathException("TryBuildLocalPlayerTruckHint", ex); return false; } } private static void LogReflectionHotPathException(string context, Exception ex) { if (FeatureFlags.DebugLogging) { string key = "LastChance.Reflection.PlayerTruckDistance." + context; if (LogLimiter.ShouldLog(key, 600)) { Debug.LogWarning((object)("[LastChance] Reflection hot-path failed in " + context + ": " + ex.GetType().Name + ": " + ex.Message)); } } } internal static void BeginActivationProfiling() { s_activationProfileStats = default(ActivationProfileStats); s_activationProfilingEnabled = true; } internal static string EndActivationProfilingSummary() { s_activationProfilingEnabled = false; return $"calls={s_activationProfileStats.Calls} total={s_activationProfileStats.TotalMs:F1}ms setup={s_activationProfileStats.SetupMs:F1}ms loop={s_activationProfileStats.LoopMs:F1}ms maxCall={s_activationProfileStats.MaxCallMs:F1}ms players={s_activationProfileStats.PlayersProcessed} navRefresh={s_activationProfileStats.NavRefreshCount} roomRefresh={s_activationProfileStats.RoomRefreshCount} remoteHints={s_activationProfileStats.RemoteHintUsedCount}"; } private static int GetPlayerKey(PlayerAvatar player) { PhotonView photonView = player.photonView; int? obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? new int?(owner.ActorNumber) : null); } int? num = obj; int valueOrDefault = num.GetValueOrDefault(); if (valueOrDefault != 0) { return valueOrDefault; } return ((Object)player).GetInstanceID(); } private static int ComputeRoomsHash(List rooms) { int num = 17; for (int i = 0; i < rooms.Count; i++) { num = num * 31 + RuntimeHelpers.GetHashCode(rooms[i]); } return num; } private static Dictionary> GetOrBuildRoomGraph(object levelGenerator, List? allLevelPoints) { int num = allLevelPoints?.Count ?? 0; if (s_cachedRoomGraph != null && levelGenerator == s_cachedGraphLevelGenerator && s_cachedGraphPointCount == num) { return s_cachedRoomGraph; } Dictionary> result = BuildRoomGraph(allLevelPoints); s_cachedGraphLevelGenerator = levelGenerator; s_cachedGraphPointCount = num; s_cachedRoomGraph = result; return result; } private static bool TryGetTruckTarget(object levelGenerator, List? allLevelPoints, out Vector3 truckPosition, out object? truckPoint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) truckPosition = Vector3.zero; truckPoint = null; if (s_levelPathTruckField != null) { object value = s_levelPathTruckField.GetValue(levelGenerator); if (TryGetLevelPointPosition(value, out truckPosition)) { truckPoint = value; return true; } } if (allLevelPoints == null) { return false; } foreach (object allLevelPoint in allLevelPoints) { if (allLevelPoint == null || !TryIsTruckPoint(allLevelPoint) || !TryGetLevelPointPosition(allLevelPoint, out truckPosition)) { continue; } truckPoint = allLevelPoint; return true; } return false; } private static List? GetAllLevelPoints(object levelGenerator) { if (s_levelPathPointsField == null) { return null; } if (!(s_levelPathPointsField.GetValue(levelGenerator) is IEnumerable enumerable)) { return null; } List list = new List(); foreach (object item in enumerable) { if (item != null) { list.Add(item); } } return (list.Count > 0) ? list : null; } private static bool TryIsTruckPoint(object point) { if (point == null) { return false; } bool flag = default(bool); int num; if (s_levelPointTruckField != null) { object value = s_levelPointTruckField.GetValue(point); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } if (s_levelPointRoomField != null && s_roomVolumeTruckField != null) { object value2 = s_levelPointRoomField.GetValue(point); bool flag2 = default(bool); int num2; if (value2 != null) { object value = s_roomVolumeTruckField.GetValue(value2); 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; } private static int ResolveShortestRoomPathToTruck(List playerRooms, object? truckPoint, Dictionary>? roomGraph) { if (truckPoint == null || roomGraph == null || roomGraph.Count == 0) { return -1; } object levelPointRoom = GetLevelPointRoom(truckPoint); if (levelPointRoom == null || !roomGraph.ContainsKey(levelPointRoom)) { return -1; } if (playerRooms.Count == 0) { return -1; } HashSet hashSet = new HashSet(); Queue<(object, int)> queue = new Queue<(object, int)>(); foreach (object playerRoom in playerRooms) { if (playerRoom != null && roomGraph.ContainsKey(playerRoom) && hashSet.Add(playerRoom)) { queue.Enqueue((playerRoom, 0)); } } while (queue.Count > 0) { var (obj, num) = queue.Dequeue(); if (obj == levelPointRoom) { return num; } if (!roomGraph.TryGetValue(obj, out HashSet value)) { continue; } foreach (object item in value) { if (item != null && hashSet.Add(item)) { queue.Enqueue((item, num + 1)); } } } return -1; } private static List GetPlayerRooms(PlayerAvatar player) { List list = new List(); if ((Object)(object)player == (Object)null || s_playerRoomVolumeCheckField == null || s_roomVolumeCheckCurrentRoomsField == null) { return list; } object obj = null; PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead != (Object)null && s_playerDeathHeadRoomVolumeCheckField != null) { obj = s_playerDeathHeadRoomVolumeCheckField.GetValue(playerDeathHead); } if (obj == null) { obj = s_playerRoomVolumeCheckField.GetValue(player); } if (obj == null) { return list; } if (!(s_roomVolumeCheckCurrentRoomsField.GetValue(obj) is IEnumerable enumerable)) { return list; } foreach (object item in enumerable) { if (item != null) { list.Add(item); } } return list; } private static Dictionary> BuildRoomGraph(List? allLevelPoints) { Dictionary> dictionary = new Dictionary>(); if (allLevelPoints == null || s_levelPointRoomField == null) { return dictionary; } foreach (object allLevelPoint in allLevelPoints) { if (allLevelPoint == null) { continue; } object levelPointRoom = GetLevelPointRoom(allLevelPoint); if (levelPointRoom == null) { continue; } if (!dictionary.ContainsKey(levelPointRoom)) { dictionary[levelPointRoom] = new HashSet(); } IEnumerable connectedPoints = GetConnectedPoints(allLevelPoint); if (connectedPoints == null) { continue; } foreach (object item in connectedPoints) { if (item == null) { continue; } object levelPointRoom2 = GetLevelPointRoom(item); if (levelPointRoom2 != null) { if (!dictionary.ContainsKey(levelPointRoom2)) { dictionary[levelPointRoom2] = new HashSet(); } if (levelPointRoom != levelPointRoom2) { dictionary[levelPointRoom].Add(levelPointRoom2); dictionary[levelPointRoom2].Add(levelPointRoom); } } } } return dictionary; } private static object? GetLevelPointRoom(object levelPoint) { if (levelPoint == null || s_levelPointRoomField == null) { return null; } return s_levelPointRoomField.GetValue(levelPoint); } private static IEnumerable? GetConnectedPoints(object levelPoint) { if (levelPoint == null || s_levelPointConnectedPointsField == null) { return null; } if (s_levelPointConnectedPointsField.GetValue(levelPoint) is IEnumerable enumerable) { List list = new List(); foreach (object item in enumerable) { if (item != null) { list.Add(item); } } return (list.Count > 0) ? list : null; } return null; } private static bool TryGetLevelPointPosition(object? levelPoint, out Vector3 position) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; if (levelPoint == null) { return false; } Component val = (Component)((levelPoint is Component) ? levelPoint : null); if (val != null && (Object)(object)val != (Object)null) { position = val.transform.position; return true; } PropertyInfo property = levelPoint.GetType().GetProperty("transform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object? value = property.GetValue(levelPoint); Transform val2 = (Transform)((value is Transform) ? value : null); if (val2 != null) { position = val2.position; return true; } } return false; } private static Vector3 GetPlayerWorldPosition(PlayerAvatar player) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return Vector3.zero; } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead != (Object)null) { return ((Component)playerDeathHead).transform.position; } if ((Object)(object)player.playerTransform != (Object)null) { return player.playerTransform.position; } return ((Component)player).transform.position; } private static bool TryGetPlayerNavMeshPosition(PlayerAvatar player, Vector3 worldPosition, out Vector3 navMeshPosition) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0036: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) navMeshPosition = Vector3.zero; if ((Object)(object)player == (Object)null) { return false; } if (TrySamplePosition(worldPosition, 8f, out var navMeshPosition2)) { navMeshPosition = navMeshPosition2; return true; } PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead != (Object)null) { Vector3 val = ((Component)playerDeathHead).transform.position; if (s_playerDeathHeadPhysGrabObjectField != null && s_physGrabObjectCenterPointField != null) { object value = s_playerDeathHeadPhysGrabObjectField.GetValue(playerDeathHead); if (value != null && s_physGrabObjectCenterPointField.GetValue(value) is Vector3 val2) { val = val2; } } if (TrySamplePosition(val, 12f, out navMeshPosition2) || TrySamplePosition(val - Vector3.up * 0.5f, 18f, out navMeshPosition2) || TrySamplePosition(val, 30f, out navMeshPosition2)) { navMeshPosition = navMeshPosition2; return true; } return false; } if (s_playerLastNavmeshField != null && s_playerLastNavmeshField.GetValue(player) is Vector3 val3 && val3 != Vector3.zero) { navMeshPosition = val3; return true; } return false; } private static bool TrySamplePosition(Vector3 worldPosition, float maxDistance, out Vector3 navMeshPosition) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) navMeshPosition = Vector3.zero; if (s_navMeshType == null || s_navMeshHitType == null || s_navMeshSamplePositionMethod == null) { return false; } object obj = Activator.CreateInstance(s_navMeshHitType); if (obj == null) { return false; } object[] array = new object[4] { worldPosition, obj, maxDistance, -1 }; if (!(s_navMeshSamplePositionMethod.Invoke(null, array) is bool flag) || !flag) { return false; } if (!TryGetNavMeshHitPosition(array[1], out var position)) { return false; } navMeshPosition = position; return true; } private static bool TryGetNavMeshHitPosition(object? navHitBoxed, out Vector3 position) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; if (navHitBoxed == null) { return false; } if (s_navMeshHitPositionProperty != null && s_navMeshHitPositionProperty.GetValue(navHitBoxed) is Vector3 val) { position = val; return true; } if (s_navMeshHitPositionField != null && s_navMeshHitPositionField.GetValue(navHitBoxed) is Vector3 val2) { position = val2; return true; } return false; } private static bool TryCalculatePathDistance(Vector3 from, Vector3 to, out float navMeshDistance) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) navMeshDistance = 0f; if (s_navMeshType == null || s_navMeshPathType == null || s_navMeshCalculatePathMethod == null || s_navMeshPathCornersProperty == null) { return false; } object obj = Activator.CreateInstance(s_navMeshPathType); if (obj == null) { return false; } object[] parameters = new object[4] { from, to, -1, obj }; if (!(s_navMeshCalculatePathMethod.Invoke(null, parameters) is bool flag) || !flag) { return false; } if (!(s_navMeshPathCornersProperty.GetValue(obj) is Vector3[] array) || array.Length == 0) { navMeshDistance = Vector3.Distance(from, to); return true; } Vector3 val = from; float num = 0f; Vector3[] array2 = array; foreach (Vector3 val2 in array2) { num += Vector3.Distance(val, val2); val = val2; } navMeshDistance = num; return true; } } internal static class ReflectionHelper { internal static object? GetStaticInstanceByName(string typeName) { Type type = AccessTools.TypeByName(typeName); if (type == null) { return null; } return GetStaticInstanceValue(type, "instance"); } internal static object? GetStaticInstanceValue(Type type, string name) { FieldInfo field = type.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(null); } PropertyInfo property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(null, null); } return null; } } 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 FieldInfo? s_chargeHandlerChargeStrengthField; private static MethodInfo? s_chargeHandlerAbilityLevelGetter; private static FieldInfo? s_chargeHandlerImpactDetectorField; private static FieldInfo? s_chargeHandlerControllerField; private static FieldInfo? s_chargeHandlerMaxBouncesField; private static FieldInfo? s_chargeHandlerWindupTimerField; private static FieldInfo? s_chargeHandlerWindupTimeField; private static FieldInfo? s_chargeHandlerEnemiesHitField; private static MethodInfo? s_chargeHandlerEndChargeMethod; private static MethodInfo? s_chargeHandlerCancelChargeMethod; private static MethodInfo? s_chargeHandlerStateGetter; private static FieldInfo? s_deathHeadControllerAudioHandlerField; private static MethodInfo? s_audioHandlerStopWindupMethod; private static FieldInfo? s_impactDetectorPhysGrabObjectField; private static FieldInfo? s_cachedPhysGrabObjectGrabbedField; private static MethodInfo? s_chargeAbilityOnAbilityUpPrefixMethod; private static MethodInfo? s_chargeAbilityOnAbilityDownPostfixMethod; private static MethodInfo? s_chargeAbilityOnAbilityCancelPostfixMethod; private static MethodInfo? s_stunHandlerStunDurationGetter; private static FieldInfo? s_stunHandlerChargeHandlerField; private static Type? s_dhhStatsManagerType; private static MethodInfo? s_dhhStatsGetHeadChargeUpgradeMethod; 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Expected O, but got Unknown //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Expected O, but got Unknown //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Expected O, but got Unknown //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Expected O, but got Unknown //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Expected O, but got Unknown //IL_03f1: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.DeathHead.Handlers.ChargeHandler", throwOnError: false); if (!(type == null)) { MethodInfo methodInfo = AccessTools.Method(type, "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(type, "ResetState", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(type, "FixedUpdate", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(type, "CancelCharge", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo5 = AccessTools.Method(type, "EnemyHit", (Type[])null, (Type[])null); MethodInfo methodInfo6 = AccessTools.Method(type, "UpdateWindupDirection", new Type[1] { typeof(Vector3) }, (Type[])null); if (s_chargeHandlerChargeStrengthField == null) { s_chargeHandlerChargeStrengthField = AccessTools.Field(type, "chargeStrength"); } if (s_chargeHandlerAbilityLevelGetter == null) { s_chargeHandlerAbilityLevelGetter = AccessTools.PropertyGetter(type, "AbilityLevel"); } if (s_chargeHandlerControllerField == null) { s_chargeHandlerControllerField = AccessTools.Field(type, "controller"); } if ((object)s_chargeHandlerMaxBouncesField == null) { s_chargeHandlerMaxBouncesField = AccessTools.Field(type, "maxBounces"); } if ((object)s_chargeHandlerWindupTimerField == null) { s_chargeHandlerWindupTimerField = AccessTools.Field(type, "windupTimer"); } if ((object)s_chargeHandlerWindupTimeField == null) { s_chargeHandlerWindupTimeField = AccessTools.Field(type, "windupTime"); } if ((object)s_chargeHandlerEnemiesHitField == null) { s_chargeHandlerEnemiesHitField = AccessTools.Field(type, "enemiesHit"); } if ((object)s_chargeHandlerEndChargeMethod == null) { s_chargeHandlerEndChargeMethod = AccessTools.Method(type, "EndCharge", Type.EmptyTypes, (Type[])null); } if ((object)s_chargeHandlerCancelChargeMethod == null) { s_chargeHandlerCancelChargeMethod = AccessTools.Method(type, "CancelCharge", Type.EmptyTypes, (Type[])null); } if ((object)s_chargeHandlerStateGetter == null) { s_chargeHandlerStateGetter = AccessTools.PropertyGetter(type, "State"); } MethodInfo method3 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_ResetState_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo2 != null && method3 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo7 = AccessTools.Method(type, "EndCharge", Type.EmptyTypes, (Type[])null); MethodInfo method4 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_EndCharge_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo7 != null && method4 != null) { harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, new HarmonyMethod(method4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method5 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_FixedUpdate_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo3 != null && method5 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(method5), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method6 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_CancelCharge_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo4 != null && method6 != null) { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(method6), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method7 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_EnemyHit_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo5 != null && method7 != null) { harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(method7), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo method8 = typeof(ChargeHoldReleaseModule).GetMethod("ChargeHandler_UpdateWindupDirection_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo6 != null && method8 != null) { harmony.Patch((MethodBase)methodInfo6, new HarmonyMethod(method8), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static bool ChargeHandler_ChargeWindup_Prefix(object __instance) { if (__instance == 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(object __instance) { if (__instance != null && IsChargeState(__instance, "Windup")) { int unityObjectInstanceId = GetUnityObjectInstanceId(__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(object __instance) { if (__instance == null) { return; } int unityObjectInstanceId = GetUnityObjectInstanceId(__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 && s_chargeHandlerWindupTimerField != null && s_chargeHandlerWindupTimeField != null) { float num2 = ((s_chargeHandlerWindupTimeField.GetValue(__instance) is float num) ? num : 1.8f); s_chargeHandlerWindupTimerField.SetValue(__instance, Mathf.Max(0.01f, num2)); } } 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 num3 = Mathf.Max(0.2f, FeatureFlags.ChargeAbilityHoldSeconds); float progress = Mathf.Clamp01((Time.time - value2.StartTime) / num3); 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 num4 = Mathf.Max(0.2f, FeatureFlags.ChargeAbilityHoldSeconds); float progress2 = Mathf.Clamp01((Time.time - value2.StartTime) / num4); float minimumChargeReleaseScale2 = GetMinimumChargeReleaseScale(__instance); AbilityModule.SetChargeSlotActivationProgress(progress2, minimumChargeReleaseScale2); } if (value2.IsHolding && s_chargeHandlerWindupTimerField != null && s_chargeHandlerWindupTimeField != null) { float num6 = ((s_chargeHandlerWindupTimeField.GetValue(__instance) is float num5) ? num5 : 1.8f); s_chargeHandlerWindupTimerField.SetValue(__instance, Mathf.Max(0.01f, num6)); } } private static void PatchChargeAbilityHoldReleaseIfPossible(Harmony harmony, Assembly asm) { //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.Abilities.Charge.ChargeAbility", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "OnAbilityDown", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(type, "OnAbilityUp", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(type, "OnAbilityCancel", Type.EmptyTypes, (Type[])null); if (methodInfo2 == null) { return; } if ((object)s_chargeAbilityOnAbilityUpPrefixMethod == null) { s_chargeAbilityOnAbilityUpPrefixMethod = typeof(ChargeHoldReleaseModule).GetMethod("ChargeAbility_OnAbilityUp_Prefix", BindingFlags.Static | BindingFlags.NonPublic); } if (!(s_chargeAbilityOnAbilityUpPrefixMethod == null)) { if ((object)s_chargeAbilityOnAbilityDownPostfixMethod == null) { s_chargeAbilityOnAbilityDownPostfixMethod = typeof(ChargeHoldReleaseModule).GetMethod("ChargeAbility_OnAbilityDown_Postfix", BindingFlags.Static | BindingFlags.NonPublic); } if ((object)s_chargeAbilityOnAbilityCancelPostfixMethod == null) { s_chargeAbilityOnAbilityCancelPostfixMethod = typeof(ChargeHoldReleaseModule).GetMethod("ChargeAbility_OnAbilityCancel_Postfix", BindingFlags.Static | BindingFlags.NonPublic); } if (methodInfo != null && s_chargeAbilityOnAbilityDownPostfixMethod != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(s_chargeAbilityOnAbilityDownPostfixMethod), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(s_chargeAbilityOnAbilityUpPrefixMethod), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (methodInfo3 != null && s_chargeAbilityOnAbilityCancelPostfixMethod != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(s_chargeAbilityOnAbilityCancelPostfixMethod), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void PatchStunHandlerHoldScalingIfPossible(Harmony harmony, Assembly asm) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.DeathHead.Handlers.StunHandler", throwOnError: false); if (type == null) { return; } if ((object)s_stunHandlerChargeHandlerField == null) { s_stunHandlerChargeHandlerField = AccessTools.Field(type, "chargeHandler"); } if ((object)s_stunHandlerStunDurationGetter == null) { s_stunHandlerStunDurationGetter = AccessTools.PropertyGetter(type, "StunDuration"); } if (!(s_stunHandlerStunDurationGetter == null)) { MethodInfo method = typeof(ChargeHoldReleaseModule).GetMethod("StunHandler_StunDuration_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony.Patch((MethodBase)s_stunHandlerStunDurationGetter, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void ChargeHandler_CancelCharge_Postfix(object __instance) { ClearChargeHoldState(__instance); } private static bool ChargeHandler_EnemyHit_Prefix(object __instance) { if (__instance == null) { return true; } if (s_chargeHandlerEnemiesHitField == null || s_chargeHandlerEndChargeMethod == null || s_chargeHandlerAbilityLevelGetter == null) { return true; } int unityObjectInstanceId = GetUnityObjectInstanceId(__instance); if (unityObjectInstanceId == 0 || !s_chargeHoldStates.TryGetValue(unityObjectInstanceId, out ChargeHoldState value)) { return true; } if (!(s_chargeHandlerEnemiesHitField.GetValue(__instance) is int num) || 1 == 0) { return true; } int num2 = num + 1; s_chargeHandlerEnemiesHitField.SetValue(__instance, num2); int currentLevel = ((s_chargeHandlerAbilityLevelGetter.Invoke(__instance, null) is int num3) ? num3 : 0); int num4 = Mathf.FloorToInt(EvaluateStatWithDiminishingReturns(1f, 0.5f, currentLevel, 20, 0.9f).FinalValue); int num5 = Mathf.Max(1, Mathf.RoundToInt((float)num4 * Mathf.Clamp01(value.LaunchScale))); if (num2 >= num5) { s_chargeHandlerEndChargeMethod.Invoke(__instance, null); } return false; } private static bool StunHandler_StunDuration_Prefix(object __instance, ref float __result) { if (__instance == null) { return true; } if (s_stunHandlerChargeHandlerField == null || s_chargeHandlerAbilityLevelGetter == null) { return true; } object value = s_stunHandlerChargeHandlerField.GetValue(__instance); if (value == null) { return true; } int unityObjectInstanceId = GetUnityObjectInstanceId(value); if (unityObjectInstanceId == 0 || !s_chargeHoldStates.TryGetValue(unityObjectInstanceId, out ChargeHoldState value2)) { return true; } int num2 = ((s_chargeHandlerAbilityLevelGetter.Invoke(value, null) is int num) ? num : 0); float num3 = 5f + 1f * (float)num2; __result = num3 * Mathf.Clamp01(value2.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); object localChargeHandler = GetLocalChargeHandler(); if (localChargeHandler != null && !IsChargeState(localChargeHandler, "Windup")) { ClearChargeHoldState(localChargeHandler); } } private static void ChargeAbility_OnAbilityCancel_Postfix() { s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); object localChargeHandler = GetLocalChargeHandler(); if (localChargeHandler != null) { ClearChargeHoldState(localChargeHandler); } } private static bool TryReleaseHeldCharge() { object localChargeHandler = GetLocalChargeHandler(); if (localChargeHandler == 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(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()) { s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:Charge.PermissiveFallback", 120)) { Debug.Log((object)"[Fix:DHHCharge][PermissiveGate] Host fix marker missing. Falling back to vanilla release path."); } return true; } value.IsHolding = false; s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); SendRemoteReleaseCommand(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); s_chargeHandlerCancelChargeMethod?.Invoke(localChargeHandler, null); return false; } value.IsHolding = false; value.LaunchScale = num2; if (s_chargeHandlerChargeStrengthField != null && s_chargeHandlerChargeStrengthField.GetValue(localChargeHandler) is float num3) { s_chargeHandlerChargeStrengthField.SetValue(localChargeHandler, num3 * num2); } if (s_chargeHandlerMaxBouncesField != null && s_chargeHandlerMaxBouncesField.GetValue(localChargeHandler) is float num4) { s_chargeHandlerMaxBouncesField.SetValue(localChargeHandler, Mathf.Max(0f, num4 * num2)); } if (s_chargeHandlerWindupTimerField != null) { s_chargeHandlerWindupTimerField.SetValue(localChargeHandler, -1f); } s_localHoldInputPending = false; s_localHoldUiActive = false; AbilityModule.SetChargeSlotActivationProgress(0f); return true; } private static bool ChargeHandler_UpdateWindupDirection_Prefix(object __instance, Vector3 chargeDirection) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (__instance == null) { return true; } if (!SemiFunc.IsMasterClientOrSingleplayer()) { return true; } if (Mathf.Abs(chargeDirection.x - -778f) < 0.001f) { s_chargeHandlerCancelChargeMethod?.Invoke(__instance, null); return false; } if (Mathf.Abs(chargeDirection.x - -777f) > 0.001f) { return true; } int unityObjectInstanceId = GetUnityObjectInstanceId(__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; s_chargeHandlerCancelChargeMethod?.Invoke(__instance, null); return false; } value.LaunchScale = num2; if (s_chargeHandlerChargeStrengthField != null && s_chargeHandlerChargeStrengthField.GetValue(__instance) is float num3) { s_chargeHandlerChargeStrengthField.SetValue(__instance, num3 * num2); } if (s_chargeHandlerMaxBouncesField != null && s_chargeHandlerMaxBouncesField.GetValue(__instance) is float num4) { s_chargeHandlerMaxBouncesField.SetValue(__instance, Mathf.Max(0f, num4 * num2)); } if (s_chargeHandlerWindupTimerField != null) { s_chargeHandlerWindupTimerField.SetValue(__instance, -1f); } return false; } private static void SendRemoteReleaseCommand(object chargeHandler) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) PhotonView val = GetDhhInputManagerHeadPhotonView(); if ((Object)(object)val == (Object)null || val.ViewID <= 0) { Component val2 = (Component)((chargeHandler is Component) ? chargeHandler : null); if (val2 != null) { val = val2.GetComponent(); } } if (!((Object)(object)val == (Object)null) && val.ViewID > 0 && PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { val.RPC("UpdateWindupDirection", (RpcTarget)2, new object[1] { (object)new Vector3(-777f, 0f, 0f) }); } } private static void SendRemoteCancelCommand(object chargeHandler) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) PhotonView val = GetDhhInputManagerHeadPhotonView(); if ((Object)(object)val == (Object)null || val.ViewID <= 0) { Component val2 = (Component)((chargeHandler is Component) ? chargeHandler : null); if (val2 != null) { val = val2.GetComponent(); } } if (!((Object)(object)val == (Object)null) && val.ViewID > 0 && PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { val.RPC("UpdateWindupDirection", (RpcTarget)2, new object[1] { (object)new Vector3(-778f, 0f, 0f) }); } } private static PhotonView? GetDhhInputManagerHeadPhotonView() { try { Type type = AccessTools.TypeByName("DeathHeadHopper.Managers.DHHInputManager"); if (type == null) { return null; } object? obj = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); MonoBehaviour val = (MonoBehaviour)((obj is MonoBehaviour) ? obj : null); if ((Object)(object)val == (Object)null) { return null; } object? obj2 = type.GetField("headPhotonView", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(val); return (PhotonView?)((obj2 is PhotonView) ? obj2 : null); } catch { return null; } } private static float GetMinimumChargeReleaseScale(object chargeHandler) { if (chargeHandler == null) { return 0f; } float num = 0f; if (s_chargeHandlerChargeStrengthField?.GetValue(chargeHandler) is float baseValue) { num = Mathf.Max(num, RequiredScaleForMinimumOne(baseValue)); } if (s_chargeHandlerMaxBouncesField?.GetValue(chargeHandler) is float baseValue2) { num = Mathf.Max(num, RequiredScaleForMinimumOne(baseValue2)); } if (TryGetEffectiveChargeAbilityLevel(chargeHandler, out var abilityLevel)) { int num2 = Mathf.FloorToInt(EvaluateStatWithDiminishingReturns(1f, 0.5f, abilityLevel, 20, 0.9f).FinalValue); float baseValue3 = 5f + 1f * (float)abilityLevel; num = Mathf.Max(num, RequiredScaleForMinimumOne(num2)); num = Mathf.Max(num, RequiredScaleForMinimumOne(baseValue3)); } if (float.IsNaN(num) || float.IsInfinity(num)) { return 1f; } return Mathf.Clamp01(num); } private static bool TryGetEffectiveChargeAbilityLevel(object chargeHandler, out int abilityLevel) { abilityLevel = 0; if (s_chargeHandlerAbilityLevelGetter != null && s_chargeHandlerAbilityLevelGetter.Invoke(chargeHandler, null) is int num && num > 0) { abilityLevel = num; 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 { if ((object)s_dhhStatsManagerType == null) { s_dhhStatsManagerType = AccessTools.TypeByName("DeathHeadHopper.Managers.DHHStatsManager"); } if (s_dhhStatsManagerType == null) { return false; } if ((object)s_dhhStatsGetHeadChargeUpgradeMethod == null) { s_dhhStatsGetHeadChargeUpgradeMethod = AccessTools.Method(s_dhhStatsManagerType, "GetHeadChargeUpgrade", new Type[1] { typeof(string) }, (Type[])null); } if (s_dhhStatsGetHeadChargeUpgradeMethod == null) { return false; } object obj = s_dhhStatsGetHeadChargeUpgradeMethod.Invoke(null, new object[1] { text }); if (obj is int) { int num = (int)obj; if (true) { upgrade = num; return true; } } } catch { return false; } return false; } private static float RequiredScaleForMinimumOne(float baseValue) { if (baseValue <= 0f) { return float.PositiveInfinity; } return 1f / baseValue; } private static bool IsChargeHandlerHeadGrabbed(object chargeHandler) { object chargeImpactDetector = GetChargeImpactDetector(chargeHandler); if (chargeImpactDetector == null) { return false; } object impactPhysGrabObject = GetImpactPhysGrabObject(chargeImpactDetector); if (impactPhysGrabObject == null) { return false; } FieldInfo fieldInfo = s_cachedPhysGrabObjectGrabbedField; if (fieldInfo == null || fieldInfo.DeclaringType != impactPhysGrabObject.GetType()) { fieldInfo = (s_cachedPhysGrabObjectGrabbedField = AccessTools.Field(impactPhysGrabObject.GetType(), "grabbed")); } bool flag = default(bool); int num; if (fieldInfo != null) { object value = fieldInfo.GetValue(impactPhysGrabObject); 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 int GetUnityObjectInstanceId(object obj) { Object val = (Object)((obj is Object) ? obj : null); return (val != null) ? val.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(object chargeHandler, string stateName) { if (chargeHandler == null) { return false; } if (s_chargeHandlerStateGetter == null || s_chargeHandlerStateGetter.DeclaringType != chargeHandler.GetType()) { s_chargeHandlerStateGetter = AccessTools.PropertyGetter(chargeHandler.GetType(), "State"); } if (s_chargeHandlerStateGetter == null) { return false; } object obj = s_chargeHandlerStateGetter.Invoke(chargeHandler, null); return obj != null && string.Equals(obj.ToString(), stateName, StringComparison.Ordinal); } private static bool IsLocalChargeHandler(object chargeHandler) { object localChargeHandler = GetLocalChargeHandler(); if (localChargeHandler == null) { return false; } return localChargeHandler == chargeHandler; } private static object? GetLocalChargeHandler() { PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance?.playerDeathHead == (Object)null) { return null; } Component component = ((Component)instance.playerDeathHead).GetComponent("DeathHeadController"); if ((Object)(object)component == (Object)null) { return null; } return AccessTools.Field(((object)component).GetType(), "chargeHandler")?.GetValue(component); } private static void ClearChargeHoldState(object? chargeHandler) { if (chargeHandler != null) { int unityObjectInstanceId = GetUnityObjectInstanceId(chargeHandler); if (unityObjectInstanceId != 0) { s_chargeHoldStates.Remove(unityObjectInstanceId); } AbilityModule.SetChargeSlotActivationProgress(0f); } } private static object? GetChargeImpactDetector(object chargeHandler) { FieldInfo fieldInfo = s_chargeHandlerImpactDetectorField; if (fieldInfo == null || fieldInfo.DeclaringType != chargeHandler.GetType()) { fieldInfo = (s_chargeHandlerImpactDetectorField = AccessTools.Field(chargeHandler.GetType(), "impactDetector")); } return fieldInfo?.GetValue(chargeHandler); } private static object? GetImpactPhysGrabObject(object impactDetector) { if (impactDetector == null) { return null; } FieldInfo fieldInfo = s_impactDetectorPhysGrabObjectField; if (fieldInfo == null || fieldInfo.DeclaringType != impactDetector.GetType()) { fieldInfo = (s_impactDetectorPhysGrabObjectField = AccessTools.Field(impactDetector.GetType(), "physGrabObject")); } return fieldInfo?.GetValue(impactDetector); } private static void ChargeHandler_ResetState_Postfix(object __instance) { StopChargeWindupLoop(__instance); ClearChargeHoldState(__instance); if (__instance != null && !(s_chargeHandlerChargeStrengthField == null) && !(s_chargeHandlerAbilityLevelGetter == null)) { int currentLevel = ((s_chargeHandlerAbilityLevelGetter.Invoke(__instance, null) is int num) ? num : 0); DiminishingReturnsResult stat = EvaluateStatWithDiminishingReturns(FeatureFlags.DHHChargeStrengthBaseValue, FeatureFlags.DHHChargeStrengthIncreasePerLevel, currentLevel, FeatureFlags.DHHChargeStrengthThresholdLevel, FeatureFlags.DHHChargeStrengthDiminishingFactor); s_chargeHandlerChargeStrengthField.SetValue(__instance, stat.FinalValue); LogChargeStrength(__instance, stat); } } private static void ChargeHandler_EndCharge_Postfix(object __instance) { StopChargeWindupLoop(__instance); ClearChargeHoldState(__instance); } private static void StopChargeWindupLoop(object? chargeHandler) { if (chargeHandler == null) { return; } try { FieldInfo fieldInfo = s_chargeHandlerControllerField ?? (s_chargeHandlerControllerField = AccessTools.Field(chargeHandler.GetType(), "controller")); if (fieldInfo == null) { return; } object value = fieldInfo.GetValue(chargeHandler); if (value == null) { return; } FieldInfo fieldInfo2 = s_deathHeadControllerAudioHandlerField ?? (s_deathHeadControllerAudioHandlerField = AccessTools.Field(value.GetType(), "audioHandler")); if (!(fieldInfo2 == null)) { object value2 = fieldInfo2.GetValue(value); if (value2 != null) { (s_audioHandlerStopWindupMethod ?? (s_audioHandlerStopWindupMethod = AccessTools.Method(value2.GetType(), "StopWindupSound", Type.EmptyTypes, (Type[])null)))?.Invoke(value2, null); } } } 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 void TryImpactEffect(object impactDetector, Vector3 contactPoint) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) if (impactDetector == null) { return; } try { if (!SemiFunc.IsMultiplayer()) { MethodInfo methodInfo = AccessTools.Method(impactDetector.GetType(), "ImpactEffectRPC", (Type[])null, (Type[])null); if (methodInfo != null) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 2) { object obj = Activator.CreateInstance(parameters[1].ParameterType); methodInfo.Invoke(impactDetector, new object[2] { contactPoint, obj }); } } } else { object? obj2 = AccessTools.Field(impactDetector.GetType(), "photonView")?.GetValue(impactDetector); PhotonView val = (PhotonView)((obj2 is PhotonView) ? obj2 : null); if (val != null) { val.RPC("ImpactEffectRPC", (RpcTarget)0, new object[1] { contactPoint }); } } } catch { } } 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__2 : 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; private MethodInfo 5__3; CodeInstruction IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__2(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; 5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: 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__3 = null; 5__2 = null; break; } if (<>s__1.MoveNext()) { 5__2 = <>s__1.Current; if (5__2.opcode == OpCodes.Call || 5__2.opcode == OpCodes.Callvirt) { object operand = 5__2.operand; 5__3 = operand as MethodInfo; if ((object)5__3 != null && IsPhysObjectHurtCall(5__3)) { <>2__current = new CodeInstruction(OpCodes.Call, (object)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__2 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__2(0); } d__.instructions = <>3__instructions; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private const int ReleaseObjectViewId = -1; private static readonly MethodInfo Replacement = AccessTools.Method(typeof(StunHandlerReleasePatch), "CustomPhysObjectHurt", (Type[])null, (Type[])null); [IteratorStateMachine(typeof(d__2))] [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__2(-2) { <>3__instructions = instructions }; } private static bool IsPhysObjectHurtCall(MethodInfo target) { if (target == null || target.Name != "PhysObjectHurt") { return false; } ParameterInfo[] parameters = target.GetParameters(); if (parameters.Length != 3) { return false; } return parameters[0].ParameterType == typeof(PhysGrabObject) && parameters[1].ParameterType == typeof(BreakImpact) && parameters[2].ParameterType == typeof(float); } public static void CustomPhysObjectHurt(StunHandler self, PhysGrabObject physGrabObject, BreakImpact impact, float hitForce) { //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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected I4, but got Unknown 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); 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 const string ModuleId = "DeathHeadHopperFix.Spectate.DeadPlayers"; private static readonly FieldInfo? s_playerIsDisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled"); private static readonly MethodInfo? s_shouldSkipMethod = AccessTools.Method(typeof(SpectateDeadPlayersModule), "ShouldSkipSpectateTarget", new Type[1] { typeof(PlayerAvatar) }, (Type[])null); private static readonly FieldInfo? s_playerNameField = AccessTools.Field(typeof(PlayerAvatar), "playerName"); private static readonly FieldInfo? s_playerDeathHeadPhysGrabObjectField = AccessTools.Field(typeof(PlayerDeathHead), "physGrabObject"); private static readonly FieldInfo? s_physGrabObjectCenterPointField = AccessTools.Field(typeof(PhysGrabObject), "centerPoint"); private static readonly FieldInfo? s_playerAvatarSpectatePointField = AccessTools.Field(typeof(PlayerAvatar), "spectatePoint"); private static readonly FieldInfo? s_playerAvatarDeadSetField = AccessTools.Field(typeof(PlayerAvatar), "deadSet"); private static readonly FieldInfo? s_spectateCurrentPlayerListIndexField = AccessTools.Field(typeof(SpectateCamera), "currentPlayerListIndex"); private static readonly FieldInfo? s_spectatePlayerField = AccessTools.Field(typeof(SpectateCamera), "player"); private static readonly FieldInfo? s_spectatePlayerOverrideField = AccessTools.Field(typeof(SpectateCamera), "playerOverride"); private static readonly FieldInfo? s_spectateNormalTransformPivotField = AccessTools.Field(typeof(SpectateCamera), "normalTransformPivot"); private static readonly FieldInfo? s_spectateNormalTransformDistanceField = AccessTools.Field(typeof(SpectateCamera), "normalTransformDistance"); private static readonly FieldInfo? s_spectateNormalPreviousPositionField = AccessTools.Field(typeof(SpectateCamera), "normalPreviousPosition"); private static readonly FieldInfo? s_spectateNormalAimHorizontalField = AccessTools.Field(typeof(SpectateCamera), "normalAimHorizontal"); private static readonly FieldInfo? s_spectateNormalAimVerticalField = AccessTools.Field(typeof(SpectateCamera), "normalAimVertical"); private static readonly FieldInfo? s_spectateNormalMaxDistanceField = AccessTools.Field(typeof(SpectateCamera), "normalMaxDistance"); private static readonly MethodInfo? s_cameraTeleportImpulseMethod = AccessTools.Method(typeof(SpectateCamera), "CameraTeleportImpulse", (Type[])null, (Type[])null); private static PlayerAvatar? s_stateNormalPatchedPlayer; private static Transform? s_stateNormalOriginalSpectatePoint; private static Transform? s_stateNormalOrbitProxy; [HarmonyPrefix] private static bool PlayerSwitchPrefix(SpectateCamera __instance, bool _next) { if (ShouldBlockPlayerSwitchForLastChance()) { return false; } if ((Object)(object)__instance == (Object)null) { return true; } if (!IsDeadPlayersSpectateEnabledNow()) { return true; } List list = GameDirector.instance?.PlayerList; if (list == null || list.Count == 0 || s_playerIsDisabledField == null) { return true; } bool flag = true; foreach (PlayerAvatar item in list) { if ((Object)(object)item == (Object)null || !(s_playerIsDisabledField.GetValue(item) is bool flag2) || flag2) { continue; } flag = false; break; } if (!flag) { return true; } bool flag3 = TryPlayerSwitchIncludingDisabled(__instance, list, _next); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.AllDisabledSwitch", 120)) { Debug.Log((object)$"[SpectateDeadPlayers] Prefix all-disabled path handled={flag3} count={list.Count} next={_next}"); } return false; } [HarmonyTranspiler] private static IEnumerable PlayerSwitchTranspiler(IEnumerable instructions) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown if (s_playerIsDisabledField == null || s_shouldSkipMethod == null) { return instructions; } List list = instructions.ToList(); int num = 0; for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if (val.opcode == OpCodes.Ldfld && val.operand is FieldInfo fieldInfo && fieldInfo == s_playerIsDisabledField) { list[i] = new CodeInstruction(OpCodes.Call, (object)s_shouldSkipMethod); num++; } } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.Transpiler", 600)) { Debug.Log((object)$"[SpectateDeadPlayers] PlayerSwitch transpiler replacements={num}"); } return list; } [HarmonyPatch(typeof(SpectateCamera), "StateNormal")] [HarmonyPrefix] private static void StateNormalPrefix(SpectateCamera __instance) { //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !IsDeadPlayersSpectateEnabledNow() || s_playerIsDisabledField == null) { return; } object? obj = s_spectatePlayerField?.GetValue(__instance); PlayerAvatar val = (PlayerAvatar)((obj is PlayerAvatar) ? obj : null); if ((Object)(object)val == (Object)null || val == PlayerAvatar.instance) { return; } object value = s_playerIsDisabledField.GetValue(val); if (!(value is bool)) { return; } bool flag = (bool)value; if (!flag || !TryGetDeathHeadAnchor(val, out var anchor)) { return; } if (s_playerAvatarSpectatePointField != null) { object? value2 = s_playerAvatarSpectatePointField.GetValue(val); Transform val2 = (Transform)((value2 is Transform) ? value2 : null); if ((Object)(object)val2 != (Object)null) { Transform val3 = EnsureStateNormalOrbitProxy(); if ((Object)(object)val3 == (Object)null) { return; } Vector3 val4 = Vector3.zero; if ((Object)(object)((Component)val).transform != (Object)null) { val4 = val2.position - ((Component)val).transform.position; } val3.position = anchor + val4; val3.rotation = val2.rotation; s_stateNormalPatchedPlayer = val; s_stateNormalOriginalSpectatePoint = val2; s_playerAvatarSpectatePointField.SetValue(val, val3); } } if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.Anchor", 120)) { Debug.Log((object)("[SpectateDeadPlayers] Orbit source moved to DeathHead center for " + GetPlayerName(val))); } } [HarmonyPatch(typeof(SpectateCamera), "StateNormal")] [HarmonyPostfix] private static void StateNormalPostfix(SpectateCamera __instance) { if (s_playerAvatarSpectatePointField == null || (Object)(object)s_stateNormalPatchedPlayer == (Object)null) { HandleLastChanceStateNormalPostfix(__instance); return; } if ((Object)(object)s_stateNormalOriginalSpectatePoint != (Object)null) { s_playerAvatarSpectatePointField.SetValue(s_stateNormalPatchedPlayer, s_stateNormalOriginalSpectatePoint); } s_stateNormalPatchedPlayer = null; s_stateNormalOriginalSpectatePoint = null; HandleLastChanceStateNormalPostfix(__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 ShouldSkipSpectateTarget(PlayerAvatar player) { if ((Object)(object)player == (Object)null) { return true; } if (IsDeadPlayersSpectateEnabledNow()) { return false; } if (s_playerIsDisabledField == null) { return true; } object value = s_playerIsDisabledField.GetValue(player); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Spectate.DeadPlayers.ShouldSkip", 300)) { string playerName = GetPlayerName(player); Debug.Log((object)$"[SpectateDeadPlayers] ShouldSkip target={playerName} skip={flag2}"); } return flag2; } 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 TryPlayerSwitchIncludingDisabled(SpectateCamera spectate, IList players, bool next) { //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) if (players.Count == 0 || s_spectateCurrentPlayerListIndexField == null || s_spectatePlayerField == null || s_spectatePlayerOverrideField == null || s_spectateNormalTransformPivotField == null || s_spectateNormalTransformDistanceField == null || s_spectateNormalAimHorizontalField == null || s_spectateNormalAimVerticalField == null || s_spectateNormalMaxDistanceField == null) { return false; } object? value = s_spectatePlayerField.GetValue(spectate); PlayerAvatar val = (PlayerAvatar)((value is PlayerAvatar) ? value : null); object? value2 = s_spectatePlayerOverrideField.GetValue(spectate); PlayerAvatar val2 = (PlayerAvatar)((value2 is PlayerAvatar) ? value2 : null); object? value3 = s_spectateNormalTransformPivotField.GetValue(spectate); Transform val3 = (Transform)((value3 is Transform) ? value3 : null); object? value4 = s_spectateNormalTransformDistanceField.GetValue(spectate); Transform val4 = (Transform)((value4 is Transform) ? value4 : null); if ((Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null) { return false; } int num2 = ((s_spectateCurrentPlayerListIndexField.GetValue(spectate) is int num) ? num : 0); int count = players.Count; for (int i = 0; i < count; i++) { num2 = (next ? ((num2 + 1) % count) : ((num2 - 1 + count) % count)); PlayerAvatar val5 = players[num2]; if ((Object)(object)val5 == (Object)null || ((Object)(object)val2 != (Object)null && (Object)(object)val5 != (Object)(object)val2)) { continue; } val2 = null; if ((Object)(object)val == (Object)(object)val5 || (Object)(object)val5.spectatePoint == (Object)null) { continue; } s_spectatePlayerOverrideField.SetValue(spectate, null); s_spectateCurrentPlayerListIndexField.SetValue(spectate, num2); s_spectatePlayerField.SetValue(spectate, val5); val3.position = val5.spectatePoint.position; float y = ((Component)val5).transform.eulerAngles.y; s_spectateNormalAimHorizontalField.SetValue(spectate, y); s_spectateNormalAimVerticalField.SetValue(spectate, 0f); val3.rotation = Quaternion.Euler(0f, y, 0f); Quaternion localRotation = val3.localRotation; float x = ((Quaternion)(ref localRotation)).eulerAngles.x; localRotation = val3.localRotation; val3.localRotation = Quaternion.Euler(x, ((Quaternion)(ref localRotation)).eulerAngles.y, 0f); val4.localPosition = new Vector3(0f, 0f, -2f); ((Component)spectate).transform.position = val4.position; ((Component)spectate).transform.rotation = val4.rotation; if (SemiFunc.IsMultiplayer()) { SemiFunc.HUDSpectateSetName(GetPlayerName(val5)); } SemiFunc.LightManagerSetCullTargetTransform(((Component)val5).transform); s_cameraTeleportImpulseMethod?.Invoke(spectate, null); s_spectateNormalMaxDistanceField.SetValue(spectate, 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; } s_spectatePlayerOverrideField.SetValue(spectate, null); return false; } private static string GetPlayerName(PlayerAvatar? player) { if ((Object)(object)player == (Object)null || s_playerNameField == null) { return "unknown"; } return (s_playerNameField.GetValue(player) as string) ?? "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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) anchor = default(Vector3); PlayerDeathHead playerDeathHead = player.playerDeathHead; if ((Object)(object)playerDeathHead == (Object)null) { return false; } anchor = ((Component)playerDeathHead).transform.position; object? obj = s_playerDeathHeadPhysGrabObjectField?.GetValue(playerDeathHead); PhysGrabObject val = (PhysGrabObject)((obj is PhysGrabObject) ? obj : null); if ((Object)(object)val != (Object)null && s_physGrabObjectCenterPointField?.GetValue(val) is Vector3 val2) { anchor = val2; } return true; } private static bool IsLocalPlayerDeadOrDisabled() { 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_playerAvatarDeadSetField != null) { object value = s_playerAvatarDeadSetField.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; } 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; } if (LastChanceInteropBridge.ShouldForceLocalDeathHeadSpectate()) { if ((Object)(object)__instance != (Object)null) { LastChanceInteropBridge.EnsureSpectatePlayerLocal(__instance); } LastChanceInteropBridge.ForceDeathHeadSpectateIfPossible(); } LastChanceInteropBridge.DebugLogState(__instance); } 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.Spectate.Patches { internal static class SpectateCameraStateNormalPatch { private static readonly MethodInfo s_inputDown = AccessTools.Method(typeof(SemiFunc), "InputDown", (Type[])null, (Type[])null); private static readonly MethodInfo s_playerSwitch = AccessTools.Method(typeof(SpectateCamera), "PlayerSwitch", (Type[])null, (Type[])null); internal static IEnumerable Transpiler(IEnumerable instructions) { List list = instructions.ToList(); int num = list.FindIndex((CodeInstruction ci) => CodeInstructionExtensions.Calls(ci, s_inputDown)) - 1; if (num <= 0 || list[num].opcode != OpCodes.Ldc_I4_1) { return instructions; } int num2 = list.FindIndex(num, (CodeInstruction ci) => CodeInstructionExtensions.Calls(ci, s_playerSwitch)); if (num2 == -1) { return instructions; } list[num].opcode = OpCodes.Nop; list.RemoveRange(num + 1, num2 - num); return list.AsEnumerable(); } } [HarmonyPatch(typeof(SpectateCamera), "StateNormal")] internal static class SpectateCameraStateNormalPatchMarker { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectateCameraStateNormalPatch.Transpiler(instructions); } } } 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)) { return; } Type type = AccessTools.TypeByName("StatsManager"); if (type == null) { return; } object staticInstanceValue = ReflectionHelper.GetStaticInstanceValue(type, "instance"); if (staticInstanceValue == null) { return; } string[] array = new string[4] { "itemsPurchased", "itemsPurchasedTotal", "itemsUpgradesPurchased", "itemsUpgradesPurchasedTotal" }; foreach (string name in array) { if (type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(staticInstanceValue) is IDictionary dictionary && !dictionary.Contains(itemName)) { dictionary[itemName] = 0; } } } internal static void EnsureStatsEntriesForItem(Object itemObj) { try { if (itemObj == (Object)null) { return; } Type type = AccessTools.TypeByName("StatsManager"); if (type == null) { return; } object staticInstanceValue = ReflectionHelper.GetStaticInstanceValue(type, "instance"); if (staticInstanceValue != null) { FieldInfo field = type.GetField("itemsPurchased", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field2 = type.GetField("itemsPurchasedTotal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field3 = type.GetField("itemsUpgradesPurchased", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field4 = type.GetField("itemsUpgradesPurchasedTotal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field?.GetValue(staticInstanceValue) is IDictionary dictionary && !dictionary.ContainsKey(itemObj.name)) { dictionary[itemObj.name] = 0; } if (field2?.GetValue(staticInstanceValue) is IDictionary dictionary2 && !dictionary2.ContainsKey(itemObj.name)) { dictionary2[itemObj.name] = 0; } if (field3?.GetValue(staticInstanceValue) is IDictionary dictionary3 && !dictionary3.ContainsKey(itemObj.name)) { dictionary3[itemObj.name] = 0; } if (field4?.GetValue(staticInstanceValue) is IDictionary dictionary4 && !dictionary4.ContainsKey(itemObj.name)) { dictionary4[itemObj.name] = 0; } } } catch { } } private static void PatchSemiFuncStatGetItemsPurchasedIfPossible(Harmony harmony) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown Type type = AccessTools.TypeByName("SemiFunc"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "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 { Type type = AccessTools.TypeByName("StatsManager"); if (type == null) { return true; } object staticInstanceValue = ReflectionHelper.GetStaticInstanceValue(type, "instance"); if (staticInstanceValue == null) { return true; } if (type.GetField("itemsPurchased", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(staticInstanceValue) is IDictionary dictionary) { if (!dictionary.ContainsKey(itemName)) { dictionary[itemName] = 0; } __result = dictionary[itemName]; return false; } } catch { } return true; } private static void PatchStatsManagerGetItemsUpgradesPurchasedIfPossible(Harmony harmony) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown Type type = AccessTools.TypeByName("StatsManager"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "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 { Type type = AccessTools.TypeByName("StatsManager"); if (type == null) { return true; } object staticInstanceValue = ReflectionHelper.GetStaticInstanceValue(type, "instance"); if (staticInstanceValue == null) { return true; } if (type.GetField("itemsUpgradesPurchased", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(staticInstanceValue) is IDictionary dictionary) { if (!dictionary.TryGetValue(itemName, out var value)) { dictionary[itemName] = 0; __result = 0; } else { __result = value; } return false; } } catch { } return true; } private static void PatchStatsManagerItemPurchaseIfPossible(Harmony harmony) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown if (harmony == null) { return; } Type type = AccessTools.TypeByName("StatsManager"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "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; } } } 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 DHHShopModule { private static bool _hooksApplied; private static ManualLogSource? _log; private static FieldInfo? _itemVolumesField; private static FieldInfo? _potentialItemsField; private static MethodInfo? _spawnShopItemMethod; internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Expected O, but got Unknown _log = log; if (harmony == null || _hooksApplied) { return; } Type type = asm.GetType("DeathHeadHopper.Managers.DHHShopManager", throwOnError: false); if (type == null) { return; } if ((object)_itemVolumesField == null) { _itemVolumesField = AccessTools.Field(type, "itemVolumes"); } if ((object)_potentialItemsField == null) { _potentialItemsField = AccessTools.Field(type, "potentialItems"); } if ((object)_spawnShopItemMethod == null) { _spawnShopItemMethod = AccessTools.Method(typeof(PunManager), "SpawnShopItem", new Type[4] { typeof(ItemVolume), typeof(List), typeof(int).MakeByRefType(), typeof(bool) }, (Type[])null); } MethodInfo methodInfo = AccessTools.Method(type, "ShopPopulateItemVolumes", new Type[1] { typeof(PunManager) }, (Type[])null); if (methodInfo != null) { MethodInfo method = typeof(DHHShopModule).GetMethod("ShopPopulateItemVolumes_Prefix", BindingFlags.Static | BindingFlags.NonPublic); if (method != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } _hooksApplied = true; } private static bool ShopPopulateItemVolumes_Prefix(object punManager) { if (SemiFunc.IsNotMasterClient()) { return true; } IList list = _itemVolumesField?.GetValue(null) as IList; List list2 = _potentialItemsField?.GetValue(null) as List; if (list == null || list2 == null) { return true; } if (_spawnShopItemMethod == null) { return true; } int num = 0; int num2 = ((FeatureFlags.DHHShopMaxItems < 0) ? int.MaxValue : FeatureFlags.DHHShopMaxItems); float num3 = Mathf.Clamp01(FeatureFlags.DHHShopSpawnChance); float num4 = Mathf.Clamp01(FeatureFlags.ShopItemsSpawnChance); if (FeatureFlags.DebugLogging && LogLimiter.ShouldLog("Fix:DHHShop.Populate", 30)) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)$"[Fix:DHHShop] Populate volumes={list.Count} items={list2.Count} limit={num2} slotChance={num3:F3} itemChance={num4:F3}"); } } foreach (ItemVolume item in list) { if ((Object)(object)item == (Object)null) { continue; } if (num >= num2) { break; } float num5 = 1f; if (num3 < 1f) { num5 = Random.value; if (num5 > num3) { if (FeatureFlags.DebugLogging) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogInfo((object)$"[Fix:DHHShop] Slot skip '{((Object)item).name}' slotRoll={num5:F3} slotChance={num3:F3} spawned={num}"); } } continue; } } float num6 = 1f; if (num4 < 1f) { num6 = Random.value; if (num6 > num4) { if (FeatureFlags.DebugLogging) { ManualLogSource? log3 = _log; if (log3 != null) { log3.LogInfo((object)$"[Fix:DHHShop] Item skip '{((Object)item).name}' itemRoll={num6:F3} itemChance={num4:F3} spawned={num}"); } } continue; } } if (FeatureFlags.DebugLogging) { ManualLogSource? log4 = _log; if (log4 != null) { log4.LogInfo((object)$"[Fix:DHHShop] Spawn '{((Object)item).name}' slotRoll={num5:F3} itemRoll={num6:F3} slotChance={num3:F3} itemChance={num4:F3} spawned={num}"); } } object[] array = new object[4] { item, list2, num, false }; object obj = _spawnShopItemMethod.Invoke(punManager, array); num = (int)array[2]; if (num2 == int.MaxValue || num < num2) { continue; } break; } return false; } } [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) { 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); 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, asm); PatchDhhShopManagerIfPossible(harmony, asm); PatchRunManagerAwakeIfPossible(harmony); PatchPhotonDefaultPoolIfPossible(harmony); } } private static void PatchDhhAssetManagerIfPossible(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 Type type = asm.GetType("DeathHeadHopper.Managers.DHHAssetManager", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "LoadAssets", (Type[])null, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(PrefabModule).GetMethod("DHHAssetManager_LoadAssets_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 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; } SetStaticFieldIfExists("DeathHeadHopper.Managers.DHHAssetManager", "headPhaseMaterial", val.LoadAsset("Assets/DeathHeadHopper/Materials/Head Phase.mat")); LoadItemsCompatible(val); InvokeStaticIfExists("DeathHeadHopper.Managers.DHHAssetManager", "LoadAbilities", val); InvokeStaticIfExists("DeathHeadHopper.Managers.DHHAssetManager", "LoadChargeAssets", val); SetStaticFieldIfExists("DeathHeadHopper.Managers.DHHShopManager", "shopAtticShelvesPrefab", val.LoadAsset("Assets/DeathHeadHopper/Shop Attic Shelves.prefab")); SetStaticFieldIfExists("DeathHeadHopper.Managers.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) { Type type = AccessTools.TypeByName("DeathHeadHopper.Managers.DHHAssetManager"); if (type == null) { ManualLogSource? log = _log; if (log != null) { log.LogError((object)"[Fix] DHHAssetManager type not found."); } return; } FieldInfo fieldInfo = AccessTools.Field(type, "shopItems"); if (fieldInfo == null) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogError((object)"[Fix] DHHAssetManager.shopItems field not found."); } return; } if (!(fieldInfo.GetValue(null) is IDictionary dict)) { ManualLogSource? log3 = _log; if (log3 != null) { log3.LogError((object)"[Fix] DHHAssetManager.shopItems is not an IDictionary."); } return; } Type type2 = AccessTools.TypeByName("Item"); if (type2 == null) { ManualLogSource? log4 = _log; if (log4 != null) { log4.LogError((object)"[Fix] Game type Item not found."); } return; } FieldInfo fieldInfo2 = AccessTools.Field(type2, "prefab"); if (fieldInfo2 == null) { ManualLogSource? log5 = _log; if (log5 != null) { log5.LogError((object)"[Fix] Field Item.prefab not found."); } return; } MethodInfo methodInfo = AccessTools.Method(fieldInfo2.FieldType, "SetPrefab", new Type[2] { typeof(GameObject), typeof(string) }, (Type[])null); List list = (from x in bundle.GetAllAssetNames() where x.EndsWith(".asset", StringComparison.OrdinalIgnoreCase) && x.IndexOf("/items/", StringComparison.OrdinalIgnoreCase) >= 0 select x).ToList(); ManualLogSource? log6 = _log; if (log6 != null) { log6.LogInfo((object)$"[Fix] Found {list.Count} item assets in bundle."); } foreach (string item in list) { Object val = null; try { val = bundle.LoadAsset(item, type2); } catch (Exception arg) { ManualLogSource? log7 = _log; if (log7 != null) { log7.LogError((object)$"[Fix] Exception loading item asset '{item}': {arg}"); } } if (val == (Object)null) { ManualLogSource? log8 = _log; if (log8 != null) { log8.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? log9 = _log; if (log9 != null) { log9.LogError((object)("[Fix] Failed to load item prefab: " + text)); } continue; } object obj = fieldInfo2.GetValue(val) ?? Activator.CreateInstance(fieldInfo2.FieldType); if (obj == null) { ManualLogSource? log10 = _log; if (log10 != null) { log10.LogError((object)"[Fix] Failed to create PrefabRef instance."); } continue; } fieldInfo2.SetValue(val, obj); methodInfo?.Invoke(obj, new object[2] { val2, text }); string name = val.name; string text2 = ItemHelpers.GetItemAssetName(val) ?? name; CachePrefabEntry(text, val2); string fileName = Path.GetFileName(text); if (!string.IsNullOrEmpty(fileName)) { CachePrefabEntry(fileName, val2); } if (!string.IsNullOrWhiteSpace(text2)) { CachePrefabEntry(text2, val2); CachePrefabEntry("Items/" + text2, val2); } CacheShopItemKey(dict, text2, val); CacheShopItemKey(dict, name, val); TryRegisterItemWithRepolib(val); StatsModule.EnsureStatsEntriesForItem(val); ManualLogSource? log11 = _log; if (log11 != null) { log11.LogInfo((object)("[Fix] Loaded item '" + name + "' from '" + item + "' and bound prefab '" + text + "'.")); } } } private static void PatchDhhShopManagerIfPossible(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 Type type = asm.GetType("DeathHeadHopper.Managers.DHHShopManager", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "LoadItems", (Type[])null, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(PrefabModule).GetMethod("DHHShopManager_LoadItems_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 DHHShopManager_LoadItems_Prefix() { try { object staticInstanceByName = ReflectionHelper.GetStaticInstanceByName("ShopManager"); if (staticInstanceByName == null) { return false; } Type type = AccessTools.TypeByName("DeathHeadHopper.Managers.DHHAssetManager"); IDictionary shopItemsDict = ((type != null) ? (AccessTools.Field(type, "shopItems")?.GetValue(null) as IDictionary) : null); if (shopItemsDict == null) { return false; } Type type2 = AccessTools.TypeByName("DeathHeadHopper.Managers.DHHShopManager"); object obj = ((type2 != null) ? AccessTools.Field(type2, "potentialItems") : null)?.GetValue(null); IList dhhPotential = obj as IList; if (dhhPotential == null) { return false; } FieldInfo fieldInfo = AccessTools.Field(staticInstanceByName.GetType(), "potentialItemUpgrades"); FieldInfo fieldInfo2 = AccessTools.Field(staticInstanceByName.GetType(), "potentialItems"); IList list = fieldInfo?.GetValue(staticInstanceByName) as IList; IList list2 = fieldInfo2?.GetValue(staticInstanceByName) as IList; dhhPotential.Clear(); CollectAndRemove(list); CollectAndRemove(list2); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)$"[Fix] DHHShopManager potential list size {dhhPotential.Count} (upgrades left={list?.Count ?? 0}, items left={list2?.Count ?? 0})"); } void CollectAndRemove(IList? source) { if (source == null) { return; } List list3 = new List(); foreach (object item in source) { Object val = (Object)((item is Object) ? item : null); if (val != null) { if (TryResolveShopItemKey(shopItemsDict, val, out string resolvedKey, out string resolveMode)) { dhhPotential.Add(item); list3.Add(item); ManualLogSource? log3 = _log; if (log3 != null) { log3.LogInfo((object)("[Fix] DHHShopManager selecting '" + resolvedKey + "' into dhhPotential")); } if (FeatureFlags.DebugLogging && !string.Equals(resolveMode, "strict-itemAssetName", StringComparison.Ordinal)) { ManualLogSource? log4 = _log; if (log4 != null) { log4.LogInfo((object)("[Fix] DHHShopManager fallback match mode=" + resolveMode + " item='" + val.name + "' key='" + resolvedKey + "'")); } } } else if (FeatureFlags.DebugLogging) { string text = TryGetStrictItemAssetName(val); string itemAssetName = ItemHelpers.GetItemAssetName(val); ManualLogSource? log5 = _log; if (log5 != null) { log5.LogInfo((object)("[Fix] DHHShopManager skip item='" + val.name + "' strict='" + (text ?? "") + "' flexible='" + (itemAssetName ?? "") + "'")); } } } } foreach (object item2 in list3) { source.Remove(item2); } } } catch (Exception ex) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogError((object)ex); } } return false; } private static void PatchRunManagerAwakeIfPossible(Harmony harmony) { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown Harmony harmony2 = harmony; Type type = AccessTools.TypeByName("RunManager"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "Awake", (Type[])null, (Type[])null); if (methodInfo == null) { return; } Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo); if (patchInfo == null || !patchInfo.Postfixes.Any((Patch p) => p.owner == harmony2.Id)) { MethodInfo method = typeof(PrefabModule).GetMethod("RunManager_Awake_Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method == null)) { harmony2.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (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; } Type type = AccessTools.TypeByName("RunManager"); if (type == null) { return; } object staticInstanceValue = ReflectionHelper.GetStaticInstanceValue(type, "instance"); if (staticInstanceValue == null) { return; } object obj = AccessTools.Field(type, "singleplayerPool")?.GetValue(staticInstanceValue); if (!(obj is IDictionary dictionary)) { return; } int num = 0; foreach (KeyValuePair item in PendingPool.ToList()) { if (!dictionary.ContainsKey(item.Key) && (Object)(object)item.Value != (Object)null) { dictionary[item.Key] = item.Value; num++; } ManualLogSource? log = _log; if (log != null) { log.LogDebug((object)$"[Fix] RunManager cache already has '{item.Key}'? {dictionary.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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown Type type = AccessTools.TypeByName("Photon.Pun.DefaultPool"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "Instantiate", new Type[3] { typeof(string), typeof(Vector3), typeof(Quaternion) }, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(PrefabModule).GetMethod("DefaultPool_Instantiate_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 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, Object value) { if (dict == null || value == (Object)null || string.IsNullOrWhiteSpace(key)) { return; } if (dict.Contains(key)) { dict[key] = value; } else { dict.Add(key, value); } string text = NormalizePrefabKey(key); if (!string.IsNullOrEmpty(text)) { if (dict.Contains(text)) { dict[text] = value; } else { dict.Add(text, value); } } } private static bool ShopItemsDictContains(IDictionary dict, string? key) { if (dict == null || string.IsNullOrWhiteSpace(key)) { return false; } if (dict.Contains(key)) { return true; } string key2 = NormalizePrefabKey(key); return dict.Contains(key2); } private static bool TryResolveShopItemKey(IDictionary shopItemsDict, Object itemObj, out string? resolvedKey, out string resolveMode) { resolvedKey = null; resolveMode = "none"; if (shopItemsDict == null || itemObj == (Object)null) { return false; } string text = TryGetStrictItemAssetName(itemObj); if (!string.IsNullOrWhiteSpace(text) && ShopItemsDictContains(shopItemsDict, text)) { resolvedKey = text; resolveMode = "strict-itemAssetName"; return true; } string itemAssetName = ItemHelpers.GetItemAssetName(itemObj); if (!string.IsNullOrWhiteSpace(itemAssetName) && ShopItemsDictContains(shopItemsDict, itemAssetName)) { resolvedKey = itemAssetName; resolveMode = "flexible-itemAssetName"; return true; } string name = itemObj.name; if (!string.IsNullOrWhiteSpace(name) && ShopItemsDictContains(shopItemsDict, name)) { resolvedKey = name; resolveMode = "object-name"; return true; } return false; } private static string? TryGetStrictItemAssetName(Object itemObj) { try { Type type = ((object)itemObj).GetType(); PropertyInfo property = type.GetProperty("itemAssetName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(string)) { return (string)property.GetValue(itemObj, null); } FieldInfo field = type.GetField("itemAssetName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(string)) { return (string)field.GetValue(itemObj); } } catch { } return null; } private static void TryRegisterItemWithRepolib(Object itemObj) { try { Type type = AccessTools.TypeByName("REPOLib.Modules.Items"); if (!(type == null)) { type.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault((MethodInfo x) => x.Name == "RegisterItem" && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType.Name == "Item")?.Invoke(null, new object[1] { itemObj }); } } catch (Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[Fix] REPOLib RegisterItem failed: " + ex.Message)); } } } 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; } private static void SetStaticFieldIfExists(string typeName, string fieldName, object? value) { Type type = AccessTools.TypeByName(typeName); if (!(type == null)) { FieldInfo fieldInfo = AccessTools.Field(type, fieldName); if (!(fieldInfo == null)) { fieldInfo.SetValue(null, value); } } } private static void InvokeStaticIfExists(string typeName, string methodName, object arg0) { string methodName2 = methodName; object arg = arg0; Type type = AccessTools.TypeByName(typeName); if (type == null) { return; } MethodInfo methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo x) { if (x.Name != methodName2) { return false; } ParameterInfo[] parameters2 = x.GetParameters(); return parameters2.Length == 1 && parameters2[0].ParameterType.IsInstanceOfType(arg); }); if (methodInfo == null) { methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo x) { if (x.Name != methodName2) { return false; } ParameterInfo[] parameters = x.GetParameters(); return parameters.Length == 1 && parameters[0].ParameterType.Name == arg.GetType().Name; }); } methodInfo?.Invoke(null, new object[1] { arg }); } } [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 object punManager; private MethodInfo 5__1; private FieldInfo 5__2; private PhotonView 5__3; private int 5__4; private PhotonView 5__5; 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() { 5__1 = null; 5__2 = null; 5__3 = null; 5__5 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (punManager == null) { return false; } 5__1 = punManager.GetType().GetMethod("VersionCheck", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (5__1 == null) { return false; } 5__2 = punManager.GetType().GetField("photonView", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 5__4 = 0; break; case 1: <>1__state = -1; 5__5 = null; 5__4++; break; } object obj; if (5__4 < 300) { obj = 5__2?.GetValue(punManager); 5__5 = (PhotonView)((obj is PhotonView) ? obj : null); if (5__5 == null || 5__5.ViewID <= 0) { <>2__current = null; <>1__state = 1; return true; } } obj = 5__2?.GetValue(punManager); 5__3 = (PhotonView)((obj is PhotonView) ? obj : null); if (5__3 == null || 5__3.ViewID <= 0) { return false; } 5__1.Invoke(punManager, Array.Empty()); 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 Type 5__1; private FieldInfo 5__2; private int 5__3; private string 5__4; private string 5__5; 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__2 = null; 5__4 = null; 5__5 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__1 = AccessTools.TypeByName("DeathHeadHopper.Managers.DHHPunManager"); 5__2 = 5__1?.GetField("hostVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); 5__3 = 0; break; case 1: <>1__state = -1; 5__4 = null; 5__3++; break; } if (5__3 < 300) { 5__4 = 5__2?.GetValue(null) as string; if (!string.IsNullOrWhiteSpace(5__4) && !string.Equals(5__4, "pending", StringComparison.OrdinalIgnoreCase)) { return false; } <>2__current = null; <>1__state = 1; return true; } if (5__2 != null) { 5__5 = 5__2.GetValue(null) as string; if (string.Equals(5__5, "pending", StringComparison.OrdinalIgnoreCase)) { 5__2.SetValue(null, string.Empty); ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)"Host does not have DeathHeadHopper installed!"); } } 5__5 = null; } 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown if (harmony == null || asm == null) { return; } Type type = asm.GetType("DeathHeadHopper.Managers.DHHInputManager", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "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 { Type type = AccessTools.TypeByName("DeathHeadHopper.Managers.DHHPunManager"); if (type == null) { return; } FieldInfo field = type.GetField("hostVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field2 = type.GetField("localVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return; } string value = field.GetValue(null) as string; if (!string.IsNullOrWhiteSpace(value)) { return; } if (IsMasterClientOrSingleplayer()) { string value2 = field2?.GetValue(null) as string; if (string.IsNullOrWhiteSpace(value2)) { value2 = GetDeathHeadHopperVersionString(); } if (!string.IsNullOrWhiteSpace(value2)) { field.SetValue(null, value2); } } else { field.SetValue(null, "pending"); object staticInstanceByName = ReflectionHelper.GetStaticInstanceByName("DeathHeadHopper.Managers.DHHPunManager"); __instance.StartCoroutine(DHHInputManager_InvokeVersionCheckWhenReady(staticInstanceByName)); __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(object? punManager) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__5(0) { punManager = punManager }; } private static bool IsMasterClientOrSingleplayer() { try { MethodInfo methodInfo = AccessTools.TypeByName("SemiFunc")?.GetMethod("IsMasterClientOrSingleplayer", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (methodInfo != null) { object obj = methodInfo.Invoke(null, Array.Empty()); if (obj is bool) { bool result = (bool)obj; if (true) { return result; } } } } catch { } return false; } private static string? GetDeathHeadHopperVersionString() { try { return ((AccessTools.TypeByName("DeathHeadHopper.DeathHeadHopper")?.GetProperty("Version", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(null))?.ToString(); } catch { } return null; } } } namespace DeathHeadHopperFix.Modules.Gameplay.Core.Bootstrap { [HarmonyPatch(typeof(GameDirector), "Awake")] internal static class ConfigSyncBootstrapPatch { [HarmonyPostfix] private static void Postfix() { ConfigSyncManager.EnsureCreated(); } } } 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 MonoBehaviour 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(); } } private static readonly HashSet AudioInitDone = new HashSet(); private static ManualLogSource? _log; internal static void Apply(Harmony harmony, Assembly asm, ManualLogSource? log) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown _log = log; if (harmony == null || asm == null) { return; } Type type = asm.GetType("DeathHeadHopper.DeathHead.Handlers.AudioHandler", throwOnError: false); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "Awake", Type.EmptyTypes, (Type[])null); if (!(methodInfo == null)) { 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); } } } private static bool AudioHandler_Awake_Prefix(MonoBehaviour __instance) { try { int instanceID = ((Object)__instance).GetInstanceID(); if (AudioInitDone.Contains(instanceID)) { return false; } __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(MonoBehaviour handler) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__4(0) { handler = handler }; } private static bool TryInitAudioHandlerSafe(MonoBehaviour handler) { int instanceID = ((Object)handler).GetInstanceID(); if (AudioInitDone.Contains(instanceID)) { return true; } Type type = ((object)handler).GetType(); FieldInfo fieldInfo = AccessTools.Field(type, "controller"); object obj = fieldInfo?.GetValue(handler); if (obj == null) { Type type2 = type.Assembly.GetType("DeathHeadHopper.DeathHead.DeathHeadController", throwOnError: false); obj = ((type2 != null) ? ((Component)handler).GetComponent(type2) : null); if (obj == null) { return false; } fieldInfo?.SetValue(handler, obj); } object obj2 = ((obj == null) ? null : AccessTools.Field(obj.GetType(), "deathHead")?.GetValue(obj)); object obj3 = ((obj2 == null) ? null : AccessTools.Field(obj2.GetType(), "playerAvatar")?.GetValue(obj2)); object obj4 = TryGetAudioPreset(handler); if (obj4 == null && obj3 == null && obj2 == null) { return false; } if (!TryEnsureSound(handler, type, obj4, obj2, obj3)) { 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 object? TryGetAudioPreset(MonoBehaviour handler) { try { Type type = AccessTools.TypeByName("NotValuableObject"); Component val = ((type != null) ? ((Component)handler).gameObject.GetComponent(type) : null); return (!((Object)(object)val != (Object)null)) ? null : AccessTools.Field(((object)val).GetType(), "audioPreset")?.GetValue(val); } catch { return null; } } private static bool TryEnsureSound(MonoBehaviour handler, Type t, object? audioPreset, object? deathHead, object? playerAvatar) { Type t2 = t; MonoBehaviour handler2 = handler; Type type = AccessTools.TypeByName("Sound"); if (type == null) { return false; } try { if (AccessTools.Field(t2, "jumpSound")?.GetValue(handler2) == null && audioPreset != null) { object soundLike2 = AccessTools.Field(audioPreset.GetType(), "impactMedium")?.GetValue(audioPreset); AudioClip[] clips2 = CloneClipsFromSoundLike(soundLike2); object obj = CreateSound(type, clips2, null, 0.12f, 0f, 0.8f, 0f); if (obj != null) { AccessTools.Field(t2, "jumpSound")?.SetValue(handler2, obj); } } } catch { } try { if (AccessTools.Field(t2, "anchorBreakSound")?.GetValue(handler2) == null && playerAvatar != null) { object soundLike3 = AccessTools.Field(playerAvatar.GetType(), "tumbleBreakFreeSound")?.GetValue(playerAvatar); AudioClip[] clips3 = CloneClipsFromSoundLike(soundLike3); object obj3 = CreateSound(type, clips3, CreateSrc(), 0.1f, 0f, 1f, 0f); if (obj3 != null) { AccessTools.Field(t2, "anchorBreakSound")?.SetValue(handler2, obj3); } } } catch { } try { if (AccessTools.Field(t2, "anchorAttachSound")?.GetValue(handler2) == null && deathHead != null) { object soundLike4 = AccessTools.Field(deathHead.GetType(), "eyeFlashNegativeSound")?.GetValue(deathHead); AudioClip[] clips4 = CloneClipsFromSoundLike(soundLike4); object obj5 = CreateSound(type, clips4, CreateSrc(), 0.5f, 0f, 0.3f, 0.03f); if (obj5 != null) { AccessTools.Field(t2, "anchorAttachSound")?.SetValue(handler2, obj5); } } } catch { } try { if (AccessTools.Field(t2, "windupSound")?.GetValue(handler2) == null) { Type type2 = AccessTools.TypeByName("PlayerAvatar"); object obj7 = null; if (type2 != null) { obj7 = ReflectionHelper.GetStaticInstanceValue(type2, "instance"); } object obj8 = ((obj7 == null) ? null : AccessTools.Field(obj7.GetType(), "tumble")?.GetValue(obj7)); object soundLike5 = ((obj8 == null) ? null : AccessTools.Field(obj8.GetType(), "tumbleMoveSound")?.GetValue(obj8)); AudioClip[] clips5 = CloneClipsFromSoundLike(soundLike5); object obj9 = CreateSound(type, clips5, CreateSrc(), 0.4f, 0.02f, 0.8f, 0f); if (obj9 != null) { AccessTools.Field(t2, "windupSound")?.SetValue(handler2, obj9); } } } catch { } try { if (AccessTools.Field(t2, "rechargeSound")?.GetValue(handler2) == null) { Type type3 = AccessTools.TypeByName("AssetManager"); object obj11 = null; if (type3 != null) { obj11 = ReflectionHelper.GetStaticInstanceValue(type3, "instance"); } object soundLike6 = ((obj11 == null) ? null : AccessTools.Field(obj11.GetType(), "batteryChargeSound")?.GetValue(obj11)); AudioClip[] clips6 = CloneClipsFromSoundLike(soundLike6); object obj12 = CreateSound(type, clips6, CreateSrc(), 0.2f, 0.01f, 1f, 0.02f); if (obj12 != null) { AccessTools.Field(t2, "rechargeSound")?.SetValue(handler2, obj12); } } } catch { } try { if (AccessTools.Field(t2, "unAnchoringSound")?.GetValue(handler2) == null) { Type type4 = AccessTools.TypeByName("MaterialPreset"); if (type4 != null) { Object[] array = Resources.FindObjectsOfTypeAll(type4); object obj14 = null; FieldInfo fieldInfo = AccessTools.Field(type4, "Type"); Object[] array2 = array; foreach (Object val in array2) { if (!(val == (Object)null) && fieldInfo?.GetValue(val) is int num && num == 2) { obj14 = val; break; } } if (obj14 != null) { object soundLike7 = AccessTools.Field(type4, "SlideOneShot")?.GetValue(obj14); AudioClip[] clips7 = CloneClipsFromSoundLike(soundLike7); object obj15 = CreateSound(type, clips7, CreateSrc(), 1f, 0f, 0.6f, 0f); if (obj15 != null) { AccessTools.Field(t2, "unAnchoringSound")?.SetValue(handler2, obj15); } } } } } catch { } return true; static AudioClip[]? CloneClipsFromSoundLike(object? soundLike) { if (soundLike == null) { return null; } object obj19 = AccessTools.Field(soundLike.GetType(), "Sounds")?.GetValue(soundLike); if (!(obj19 is AudioClip[] array3) || array3.Length == 0) { return null; } return array3.Clone() as AudioClip[]; } static object? CreateSound(Type tSound, AudioClip[]? clips, AudioSource? src, float vol, float volRand, float pitch, float pitchRand) { if (clips == null || clips.Length == 0) { return null; } object obj18 = Activator.CreateInstance(tSound); if (obj18 == null) { return null; } if ((Object)(object)src != (Object)null) { AccessTools.Field(tSound, "Source")?.SetValue(obj18, src); } AccessTools.Field(tSound, "Sounds")?.SetValue(obj18, clips); AccessTools.Field(tSound, "Volume")?.SetValue(obj18, vol); AccessTools.Field(tSound, "VolumeRandom")?.SetValue(obj18, volRand); AccessTools.Field(tSound, "Pitch")?.SetValue(obj18, pitch); AccessTools.Field(tSound, "PitchRandom")?.SetValue(obj18, pitchRand); return obj18; } AudioSource CreateSrc() { MethodInfo methodInfo = AccessTools.Method(t2, "CreateAudioSource", (Type[])null, (Type[])null); if (methodInfo == null) { throw new NullReferenceException("CreateAudioSource() not found."); } object obj17 = methodInfo.Invoke(handler2, Array.Empty()); AudioSource val2 = (AudioSource)((obj17 is AudioSource) ? obj17 : null); if (val2 == null) { throw new NullReferenceException("CreateAudioSource() returned invalid type."); } return val2; } } } } namespace DeathHeadHopperFix.Modules.Gameplay.Core.Abilities { internal static class AbilityModule { private static class AbilitySpotLabelOverlay { private static readonly Dictionary Labels = new Dictionary(); private static readonly Type? LabelType = AccessTools.TypeByName("TMPro.TextMeshProUGUI"); private static readonly Type? AlignmentType = AccessTools.TypeByName("TMPro.TextAlignmentOptions"); private static readonly PropertyInfo? TextProperty = LabelType?.GetProperty("text"); private static readonly PropertyInfo? ColorProperty = LabelType?.GetProperty("color"); private static readonly PropertyInfo? AlignmentProperty = LabelType?.GetProperty("alignment"); private static readonly PropertyInfo? FontSizeProperty = LabelType?.GetProperty("fontSize"); private static readonly PropertyInfo? AutoSizeProperty = LabelType?.GetProperty("enableAutoSizing"); private static readonly PropertyInfo? WordWrapProperty = LabelType?.GetProperty("enableWordWrapping"); private static readonly PropertyInfo? RichTextProperty = LabelType?.GetProperty("richText"); private static readonly object? CenterAlignment = ((AlignmentType != null) ? Enum.Parse(AlignmentType, "Center") : null); internal static Type? SpotType; internal static FieldInfo? SpotIndexField; internal static void EnsureLabel(object spot) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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 (spot != null && !(LabelType == null) && !Labels.ContainsKey(spot)) { Component val = (Component)((spot is Component) ? spot : null); if (val != null) { GameObject val2 = new GameObject("DHHAbilityLabel", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform component = val2.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(); Component val3 = val2.AddComponent(LabelType); SetLabelDefaults(val3); Labels[spot] = val3; UpdateLabel(spot); } } } internal static void UpdateLabel(object spot) { if (spot != null) { Component label = GetLabel(spot); if (!((Object)(object)label == (Object)null)) { string slotTag = GetSlotTag(spot); SetLabelText(label, slotTag); } } } internal static void SetDirectionLabel(object spot, string text) { Component label = GetLabel(spot); if (!((Object)(object)label == (Object)null)) { SetLabelText(label, text); } } internal static void ClearLabel(object spot) { if (spot == null || !Labels.TryGetValue(spot, out Component value)) { return; } Labels.Remove(spot); if (value != null) { Component val = value; if (true) { Object.Destroy((Object)(object)val.gameObject); } } } private static Component? GetLabel(object spot) { Component value; return Labels.TryGetValue(spot, out value) ? value : null; } private static string GetSlotTag(object spot) { return string.Empty; } private static void SetLabelDefaults(Component label) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)label == (Object)null)) { if (ColorProperty != null) { ColorProperty.SetValue(label, Color.white); } if (FontSizeProperty != null) { FontSizeProperty.SetValue(label, 11f); } if (AutoSizeProperty != null) { AutoSizeProperty.SetValue(label, false); } if (WordWrapProperty != null) { WordWrapProperty.SetValue(label, false); } if (RichTextProperty != null) { RichTextProperty.SetValue(label, false); } if (AlignmentProperty != null && CenterAlignment != null) { AlignmentProperty.SetValue(label, CenterAlignment); } SetLabelText(label, string.Empty); } } private static void SetLabelText(Component label, string text) { if (!((Object)(object)label == (Object)null)) { if (TextProperty != null) { TextProperty.SetValue(label, text); } Behaviour val = (Behaviour)(object)((label is Behaviour) ? label : null); if (val != null) { val.enabled = !string.IsNullOrEmpty(text); } } } } private static class SlotLayoutOverrides { internal static void EnsureBasePosition(object spot) { //IL_004a: 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_003c: Unknown result type (might be due to invalid IL or missing references) Component val = (Component)((spot is Component) ? spot : null); if (val != null) { if (!s_spotBaseLocalPos.TryGetValue(spot, out var value)) { value = val.transform.localPosition; s_spotBaseLocalPos[spot] = value; } val.transform.localPosition = value; } } internal static void RestoreBasePosition(object spot) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) Component val = (Component)((spot is Component) ? spot : null); if (val != null && s_spotBaseLocalPos.TryGetValue(spot, out var value)) { val.transform.localPosition = value; } } } private static class SlotCostOverrides { private static FieldInfo? s_energyCostField; private static MethodInfo? s_currentAbilityGetter; private static MethodInfo? s_energyCostGetter; private static PropertyInfo? s_textProperty; internal static void SetDirectionCostText(object spot, string costText) { Component energyCostComponent = GetEnergyCostComponent(spot); if (!((Object)(object)energyCostComponent == (Object)null)) { SetText(energyCostComponent, costText); } } internal static void RestoreDefaultCostText(object spot) { Component energyCostComponent = GetEnergyCostComponent(spot); if ((Object)(object)energyCostComponent == (Object)null) { return; } string value = "0"; object currentAbility = GetCurrentAbility(spot); if (currentAbility != null) { Type type = currentAbility.GetType(); if (s_energyCostGetter == null || s_energyCostGetter.DeclaringType != type) { s_energyCostGetter = AccessTools.PropertyGetter(type, "EnergyCost"); } if (s_energyCostGetter?.Invoke(currentAbility, null) is float num) { value = Mathf.RoundToInt(num).ToString(); } } SetText(energyCostComponent, value); } private static Component? GetEnergyCostComponent(object spot) { Type type = spot.GetType(); if ((object)s_energyCostField == null) { s_energyCostField = type.GetField("energyCost", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } object? obj = s_energyCostField?.GetValue(spot); return (Component?)((obj is Component) ? obj : null); } private static object? GetCurrentAbility(object spot) { Type type = spot.GetType(); if ((object)s_currentAbilityGetter == null) { s_currentAbilityGetter = type.GetProperty("CurrentAbility", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetMethod; } return s_currentAbilityGetter?.Invoke(spot, null); } private static void SetText(Component target, string value) { if (!((Object)(object)target == (Object)null)) { Type type = ((object)target).GetType(); if (s_textProperty == null || s_textProperty.DeclaringType != type) { s_textProperty = type.GetProperty("text", BindingFlags.Instance | BindingFlags.Public); } s_textProperty?.SetValue(target, value ?? string.Empty); } } } private static class SlotVisualOverrides { private static FieldInfo? s_backgroundIconField; private static FieldInfo? s_cooldownIconField; private static FieldInfo? s_noAbilityField; private static MethodInfo? s_currentAbilityGetter; private static FieldInfo? s_abilityIconField; private static MethodInfo? s_setIconMethod; private static PropertyInfo? s_behaviourEnabledProp; private static PropertyInfo? s_imageSpriteProp; private static PropertyInfo? s_imageColorProp; private static PropertyInfo? s_imageFillAmountProp; private static readonly Dictionary s_cooldownIconBaseColors = new Dictionary(); private static readonly Dictionary s_backgroundIconBaseColors = new Dictionary(); private static readonly Dictionary s_chargeHoldRestoreColors = new Dictionary(); private static readonly Dictionary s_chargeHoldRestoreFillAmounts = new Dictionary(); internal static void ApplyDirectionIcon(object spot, Sprite sprite) { if (spot is Component) { Type type = spot.GetType(); if ((object)s_backgroundIconField == null) { s_backgroundIconField = type.GetField("backgroundIcon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_cooldownIconField == null) { s_cooldownIconField = type.GetField("cooldownIcon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_noAbilityField == null) { s_noAbilityField = type.GetField("noAbility", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } object imageLikeObject = s_backgroundIconField?.GetValue(spot); SetImageSpriteAndEnable(imageLikeObject, sprite); object imageLikeObject2 = s_cooldownIconField?.GetValue(spot); SetImageSpriteAndEnable(imageLikeObject2, sprite); object? obj = s_noAbilityField?.GetValue(spot); Behaviour val = (Behaviour)((obj is Behaviour) ? obj : null); if (val != null) { val.enabled = false; } } } internal static void RestoreDefaultIcon(object spot) { if (spot == null) { return; } Component val = (Component)((spot is Component) ? spot : null); if (val == null || (Object)(object)val == (Object)null || (Object)(object)val.gameObject == (Object)null) { return; } Type type = spot.GetType(); if ((object)s_currentAbilityGetter == null) { s_currentAbilityGetter = type.GetProperty("CurrentAbility", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetMethod; } if ((object)s_setIconMethod == null) { s_setIconMethod = type.GetMethod("SetIcon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)s_noAbilityField == null) { s_noAbilityField = type.GetField("noAbility", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } object obj = null; if (s_currentAbilityGetter != null) { obj = s_currentAbilityGetter.Invoke(spot, null); } Sprite val2 = null; if (obj != null) { Type type2 = obj.GetType(); if (s_abilityIconField == null || s_abilityIconField.DeclaringType != type2) { s_abilityIconField = type2.GetField("icon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } object? obj2 = s_abilityIconField?.GetValue(obj); val2 = (Sprite)((obj2 is Sprite) ? obj2 : null); } if (s_setIconMethod != null) { try { s_setIconMethod.Invoke(spot, new object[1] { val2 }); } catch { return; } } object? obj4 = s_noAbilityField?.GetValue(spot); Behaviour val3 = (Behaviour)((obj4 is Behaviour) ? obj4 : null); if (val3 != null) { val3.enabled = obj == null; } } internal static void ApplyDirectionActivationProgress(object spot, float progress01) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: 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_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) if (spot == null) { return; } Type type = spot.GetType(); if ((object)s_cooldownIconField == null) { s_cooldownIconField = type.GetField("cooldownIcon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } object obj = s_cooldownIconField?.GetValue(spot); if (obj == null) { return; } Type type2 = obj.GetType(); if (s_imageColorProp == null || s_imageColorProp.DeclaringType != type2) { s_imageColorProp = type2.GetProperty("color", BindingFlags.Instance | BindingFlags.Public); } if (s_imageFillAmountProp == null || s_imageFillAmountProp.DeclaringType != type2) { s_imageFillAmountProp = type2.GetProperty("fillAmount", BindingFlags.Instance | BindingFlags.Public); } if (!s_cooldownIconBaseColors.TryGetValue(obj, out var value)) { value = ((s_imageColorProp?.GetValue(obj) is Color val) ? val : Color.white); s_cooldownIconBaseColors[obj] = value; } float num = Mathf.Clamp01(progress01); if (num <= 0f) { s_imageFillAmountProp?.SetValue(obj, 0f); s_imageColorProp?.SetValue(obj, value); return; } s_imageFillAmountProp?.SetValue(obj, num); s_imageColorProp?.SetValue(obj, (object)new Color(0.2f, 1f, 0.2f, value.a)); if (s_behaviourEnabledProp != null && typeof(Behaviour).IsAssignableFrom(type2)) { s_behaviourEnabledProp.SetValue(obj, true); } } internal static void ApplyDirectionEnergyAvailability(object spot, bool hasEnoughEnergy, float progress01) { //IL_014a: 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_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) if (spot == null) { return; } Type type = spot.GetType(); if ((object)s_cooldownIconField == null) { s_cooldownIconField = type.GetField("cooldownIcon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } object obj = s_cooldownIconField?.GetValue(spot); if (obj != null) { Type type2 = obj.GetType(); if (s_imageColorProp == null || s_imageColorProp.DeclaringType != type2) { s_imageColorProp = type2.GetProperty("color", BindingFlags.Instance | BindingFlags.Public); } if (s_imageFillAmountProp == null || s_imageFillAmountProp.DeclaringType != type2) { s_imageFillAmountProp = type2.GetProperty("fillAmount", BindingFlags.Instance | BindingFlags.Public); } if (!s_cooldownIconBaseColors.TryGetValue(obj, out var value)) { value = ((s_imageColorProp?.GetValue(obj) is Color val) ? val : Color.white); s_cooldownIconBaseColors[obj] = value; } float num = Mathf.Clamp01(progress01); float num2 = ((num > 0f) ? num : (hasEnoughEnergy ? 1f : 0f)); Color val2 = value; val2.a = ((num2 < 1f) ? 0.3f : 1f); s_imageFillAmountProp?.SetValue(obj, num2); s_imageColorProp?.SetValue(obj, val2); } } internal static void ApplyChargeActivationProgress(object spot, float progress01, bool canReleaseActivate) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) if (spot == null) { return; } Type type = spot.GetType(); if ((object)s_cooldownIconField == null) { s_cooldownIconField = type.GetField("cooldownIcon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } object obj = s_cooldownIconField?.GetValue(spot); if (obj == null) { return; } Type type2 = obj.GetType(); if (s_imageColorProp == null || s_imageColorProp.DeclaringType != type2) { s_imageColorProp = type2.GetProperty("color", BindingFlags.Instance | BindingFlags.Public); } if (s_imageFillAmountProp == null || s_imageFillAmountProp.DeclaringType != type2) { s_imageFillAmountProp = type2.GetProperty("fillAmount", BindingFlags.Instance | BindingFlags.Public); } if (!s_cooldownIconBaseColors.TryGetValue(obj, out var value)) { value = ((s_imageColorProp?.GetValue(obj) is Color val) ? val : Color.white); s_cooldownIconBaseColors[obj] = value; } float num = Mathf.Clamp01(progress01); if (num <= 0f) { bool flag = false; bool flag2 = false; if (s_chargeHoldRestoreFillAmounts.TryGetValue(obj, out var value2)) { s_imageFillAmountProp?.SetValue(obj, value2); s_chargeHoldRestoreFillAmounts.Remove(obj); flag = true; } if (s_chargeHoldRestoreColors.TryGetValue(obj, out var value3)) { s_imageColorProp?.SetValue(obj, value3); s_chargeHoldRestoreColors.Remove(obj); flag2 = true; } if (!flag) { s_imageFillAmountProp?.SetValue(obj, 1f); } if (!flag2) { s_imageColorProp?.SetValue(obj, value); } } else { if (!s_chargeHoldRestoreFillAmounts.ContainsKey(obj)) { float valueOrDefault = (s_imageFillAmountProp?.GetValue(obj) as float?).GetValueOrDefault(); s_chargeHoldRestoreFillAmounts[obj] = valueOrDefault; } if (!s_chargeHoldRestoreColors.ContainsKey(obj)) { Color valueOrDefault2 = (s_imageColorProp?.GetValue(obj) as Color?).GetValueOrDefault(value); s_chargeHoldRestoreColors[obj] = valueOrDefault2; } Color val2 = (canReleaseActivate ? new Color(0.2f, 1f, 0.2f, value.a) : new Color(1f, 0.2f, 0.2f, value.a)); s_imageFillAmountProp?.SetValue(obj, num); s_imageColorProp?.SetValue(obj, val2); if (s_behaviourEnabledProp != null && typeof(Behaviour).IsAssignableFrom(type2)) { s_behaviourEnabledProp.SetValue(obj, true); } } } private static void SetImageSpriteAndEnable(object? imageLikeObject, Sprite sprite) { if (imageLikeObject != null && !((Object)(object)sprite == (Object)null)) { Type type = imageLikeObject.GetType(); if ((object)s_behaviourEnabledProp == null) { s_behaviourEnabledProp = typeof(Behaviour).GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); } if (s_imageSpriteProp == null || s_imageSpriteProp.DeclaringType != type) { s_imageSpriteProp = type.GetProperty("sprite", BindingFlags.Instance | BindingFlags.Public); } if (s_imageSpriteProp != null && s_imageSpriteProp.PropertyType == typeof(Sprite)) { s_imageSpriteProp.SetValue(imageLikeObject, sprite); } if (s_behaviourEnabledProp != null && typeof(Behaviour).IsAssignableFrom(type)) { s_behaviourEnabledProp.SetValue(imageLikeObject, true); } } } private static void ApplyIconAlpha(object? imageLikeObject, float alpha, Dictionary baseColors) { //IL_00aa: 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_00bf: 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_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (imageLikeObject == null) { return; } Type type = imageLikeObject.GetType(); if (s_imageColorProp == null || s_imageColorProp.DeclaringType != type) { s_imageColorProp = type.GetProperty("color", BindingFlags.Instance | BindingFlags.Public); } if (!(s_imageColorProp == null)) { if (!baseColors.TryGetValue(imageLikeObject, out var value)) { value = (baseColors[imageLikeObject] = ((s_imageColorProp.GetValue(imageLikeObject) is Color val) ? val : Color.white)); } Color val3 = value; val3.a = Mathf.Clamp01(alpha); s_imageColorProp.SetValue(imageLikeObject, val3); } } } private const string DirectionEnergyLogKey = "Fix:Ability.DirectionEnergy"; private static Type? s_abilityBaseType; private static MethodInfo? s_abilityCooldownGetter; private static MethodInfo? s_abilityEnergyCostGetter; private static MethodInfo? s_abilityNameGetter; private static Type? s_abilitySpotType; private static MethodInfo? s_abilitySpotSetCooldown; private static MethodInfo? s_abilitySpotCurrentAbilityGetter; private static FieldInfo? s_abilitySpotsField; 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_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_0105: 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_013f: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.UI.AbilitySpot", throwOnError: false); if (!(type == null)) { MethodInfo methodInfo = AccessTools.Method(type, "Start", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(type, "UpdateUI", (Type[])null, (Type[])null); MethodInfo method = type.GetMethod("OnDestroy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo method2 = typeof(AbilityModule).GetMethod("AbilitySpot_Start_Postfix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method3 = typeof(AbilityModule).GetMethod("AbilitySpot_Update_Postfix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method4 = typeof(AbilityModule).GetMethod("AbilitySpot_UpdateUI_Postfix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method5 = typeof(AbilityModule).GetMethod("AbilitySpot_OnDestroy_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); } if (methodInfo2 != null && method3 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo3 != null && method4 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(method4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (method != null && method5 != null) { harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(method5), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } internal static void ApplyAbilityManagerHooks(Harmony harmony, Assembly asm) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown Type type = asm.GetType("DeathHeadHopper.Managers.DHHAbilityManager", throwOnError: false); if (type == null) { return; } EnsureAbilityReflection(); if (s_abilityBaseType == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "OnAbilityUsed", new Type[1] { s_abilityBaseType }, (Type[])null); if (!(methodInfo == null)) { MethodInfo method = typeof(AbilityModule).GetMethod("DHHAbilityManager_OnAbilityUsed_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 AbilitySpot_Start_Postfix(object? __instance) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (__instance != null && !InternalDebugFlags.DisableAbilityPatches) { s_trackedSpots.Add(__instance); Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { s_spotBaseLocalPos[__instance] = val.transform.localPosition; } AbilitySpotLabelOverlay.EnsureLabel(__instance); ApplySlot2DirectionVisual(__instance); } } private static void AbilitySpot_UpdateUI_Postfix(object? __instance) { if (__instance != null && !InternalDebugFlags.DisableAbilityPatches) { AbilitySpotLabelOverlay.UpdateLabel(__instance); ApplySlot2DirectionVisual(__instance); } } private static void AbilitySpot_Update_Postfix(object? __instance) { if (__instance != null && !InternalDebugFlags.DisableAbilityPatches && GetAbilityIndex(__instance) == 1 && LastChanceInteropBridge.IsDirectionIndicatorUiVisible()) { SlotLayoutOverrides.EnsureBasePosition(__instance); } } private static void AbilitySpot_OnDestroy_Postfix(object? __instance) { if (__instance != null && !InternalDebugFlags.DisableAbilityPatches) { s_trackedSpots.Remove(__instance); s_spotBaseLocalPos.Remove(__instance); s_lastDirectionVisibilityBySpot.Remove(__instance); s_lastDirectionCostLabelBySpot.Remove(__instance); s_lastDirectionProgressBySpot.Remove(__instance); s_lastDirectionEnergySufficientBySpot.Remove(__instance); AbilitySpotLabelOverlay.ClearLabel(__instance); } } internal static void TriggerDirectionSlotCooldown(float cooldownSeconds) { if (InternalDebugFlags.DisableAbilityPatches) { return; } s_directionActivationProgress = 0f; EnsureAbilityReflection(); if (s_abilitySpotSetCooldown == null) { return; } float num = Mathf.Max(0f, cooldownSeconds); if (num <= 0f) { return; } foreach (object s_trackedSpot in s_trackedSpots) { if (s_trackedSpot != null && GetAbilityIndex(s_trackedSpot) == 1) { try { s_abilitySpotSetCooldown.Invoke(s_trackedSpot, new object[1] { 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 (object 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 (object 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 (object 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 (object item in list) { s_trackedSpots.Remove(item); s_spotBaseLocalPos.Remove(item); } } private static void ApplySlot2DirectionVisual(object 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(object spot) { if (spot == null) { return -1; } Type type = spot.GetType(); if (AbilitySpotLabelOverlay.SpotType == null || AbilitySpotLabelOverlay.SpotType != type) { AbilitySpotLabelOverlay.SpotType = type; AbilitySpotLabelOverlay.SpotIndexField = type.GetField("abilitySpotIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (AbilitySpotLabelOverlay.SpotIndexField == null) { return -1; } return (AbilitySpotLabelOverlay.SpotIndexField.GetValue(spot) is int num) ? num : (-1); } private static bool IsSpotUsable(object spot) { if (spot == null) { return false; } Component val = (Component)((spot is Component) ? spot : null); if (val == null) { return false; } return (Object)(object)val != (Object)null && (Object)(object)val.gameObject != (Object)null; } private static void EnsureAbilityReflection() { if (s_abilityBaseType == null) { s_abilityBaseType = AccessTools.TypeByName("DeathHeadHopper.Abilities.AbilityBase"); } if (s_abilityCooldownGetter == null && s_abilityBaseType != null) { s_abilityCooldownGetter = AccessTools.PropertyGetter(s_abilityBaseType, "Cooldown"); } if (s_abilityEnergyCostGetter == null && s_abilityBaseType != null) { s_abilityEnergyCostGetter = AccessTools.PropertyGetter(s_abilityBaseType, "EnergyCost"); } if (s_abilityNameGetter == null && s_abilityBaseType != null) { s_abilityNameGetter = AccessTools.PropertyGetter(s_abilityBaseType, "AbilityName"); } if (s_abilitySpotType == null) { s_abilitySpotType = AccessTools.TypeByName("DeathHeadHopper.UI.AbilitySpot"); } if (s_abilitySpotSetCooldown == null && s_abilitySpotType != null) { s_abilitySpotSetCooldown = AccessTools.Method(s_abilitySpotType, "SetCooldown", new Type[1] { typeof(float) }, (Type[])null); } if (s_abilitySpotCurrentAbilityGetter == null && s_abilitySpotType != null) { s_abilitySpotCurrentAbilityGetter = s_abilitySpotType.GetProperty("CurrentAbility", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetMethod; } } private static void DHHAbilityManager_OnAbilityUsed_Postfix(object __instance, object ability) { if (ability == null) { return; } EnsureAbilityReflection(); if (s_abilityCooldownGetter == null || s_abilitySpotSetCooldown == null) { return; } float num2 = ((s_abilityCooldownGetter.Invoke(ability, null) is float num) ? num : 0f); if (num2 <= 0f) { return; } FieldInfo fieldInfo = s_abilitySpotsField ?? (s_abilitySpotsField = __instance?.GetType().GetField("abilitySpots", BindingFlags.Instance | BindingFlags.NonPublic)); if (fieldInfo == null || !(fieldInfo.GetValue(__instance) is Array array)) { return; } List list = new List(); foreach (object item in array) { if (item != null) { object obj = null; try { obj = s_abilitySpotCurrentAbilityGetter?.Invoke(item, null); } catch { } if (obj == ability) { list.Add(item); } } } if (list.Count == 0 && s_abilityNameGetter != null) { string text = s_abilityNameGetter.Invoke(ability, null)?.ToString(); if (!string.IsNullOrWhiteSpace(text)) { foreach (object item2 in array) { if (item2 == null) { continue; } try { object obj3 = s_abilitySpotCurrentAbilityGetter?.Invoke(item2, null); if (obj3 != null) { string a = s_abilityNameGetter.Invoke(obj3, null)?.ToString(); if (string.Equals(a, text, StringComparison.Ordinal)) { list.Add(item2); } } } catch { } } } } foreach (object item3 in list) { try { s_abilitySpotSetCooldown?.Invoke(item3, new object[1] { num2 }); } 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 ItemUpgradeModule { private const string DhhUpgradePowerTypeName = "DeathHeadHopper.Items.DHHItemUpgradePower"; private const string DhhUpgradeChargeTypeName = "DeathHeadHopper.Items.DHHItemUpgradeCharge"; private static Type? _dhhUpgradePowerType; private static Type? _dhhUpgradeChargeType; private static MethodInfo? _dhhUpgradePowerMethod; private static MethodInfo? _dhhUpgradeChargeMethod; internal static void Apply(Harmony harmony) { PatchItemToggleUpgradeHook(harmony); } private static void PatchItemToggleUpgradeHook(Harmony harmony) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown if (harmony == null) { return; } Type type = AccessTools.TypeByName("ItemToggle"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "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)) { bool flag = TryInvokeUpgrade(__instance, ref _dhhUpgradePowerType, ref _dhhUpgradePowerMethod, "DeathHeadHopper.Items.DHHItemUpgradePower"); bool flag2 = TryInvokeUpgrade(__instance, ref _dhhUpgradeChargeType, ref _dhhUpgradeChargeMethod, "DeathHeadHopper.Items.DHHItemUpgradeCharge"); if (flag || flag2) { PlayUpgradeFx(__instance, player); DestroyUpgradeItem(__instance); RegisterConsumedUpgrade(__instance); } } } private static bool TryInvokeUpgrade(ItemToggle toggle, ref Type? upgradeType, ref MethodInfo? upgradeMethod, string typeName) { Type type = upgradeType ?? (upgradeType = AccessTools.TypeByName(typeName)); if (type == null) { return false; } Component component = ((Component)toggle).GetComponent(type); if ((Object)(object)component == (Object)null) { return false; } MethodInfo methodInfo = upgradeMethod ?? (upgradeMethod = AccessTools.Method(type, "Upgrade", (Type[])null, (Type[])null)); if (methodInfo == null) { return false; } methodInfo.Invoke(component, Array.Empty()); return true; } private static void DestroyUpgradeItem(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 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) { try { 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; } } catch { } string text = ItemHelpers.GetItemAssetName((Object?)(object)toggle) ?? ((Object)toggle).name; return string.IsNullOrWhiteSpace(text) ? null : text; } } 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 Upgrades = "6. Upgrades"; public const string Debug = "7. Debug"; } 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 DHHShopMaxItems = "Maximum number of DeathHeadHopper mod items that can spawn in the shop (-1 = unlimited)."; public const string DHHShopSpawnChance = "Chance each DeathHeadHopper shop slot actually spawns an item."; public const string ShopItemsSpawnChance = "Second-tier chance that a DeathHeadHopper slot produces an item after it was selected."; public const string DebugLogging = "Dump extra log lines that help trace the battery/ability logic."; } [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. Upgrades", "Maximum number of DeathHeadHopper mod items that can spawn in the shop (-1 = unlimited).", Min = -1f, Max = 12f)] public static int DHHShopMaxItems = 8; [FeatureConfigEntry("6. Upgrades", "Chance each DeathHeadHopper shop slot actually spawns an item.", Min = 0.1f, Max = 1f)] public static float DHHShopSpawnChance = 0.75f; [FeatureConfigEntry("6. Upgrades", "Second-tier chance that a DeathHeadHopper slot produces an item after it was selected.", Min = 0.1f, Max = 1f)] public static float ShopItemsSpawnChance = 0.75f; [FeatureConfigEntry("7. Debug", "Dump extra log lines that help trace the battery/ability logic.", HostControlled = false)] public static bool DebugLogging = false; } 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})"); } } } }