using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using wedgeprotect.Core; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("WedgeProtect")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+01bf81988d0513abccedfdfe7fd1ccb2dd338da2")] [assembly: AssemblyProduct("WedgeProtect")] [assembly: AssemblyTitle("WedgeProtect")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace wedgeprotect { [BepInPlugin("jill920.wedgeprotect", "Wedge Protect", "1.0.3")] public class Plugin : BaseUnityPlugin, IInRoomCallbacks, IMatchmakingCallbacks { public static Plugin Instance; public static ManualLogSource Log; public static ConfigEntry CalculationMode; public static ConfigEntry DebugLogging; private Harmony _harmony; private bool _isHost = false; private bool _hostChecked = false; private FallDamageMode _lastSyncedMode = FallDamageMode.Original; private void Awake() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; CalculationMode = ((BaseUnityPlugin)this).Config.Bind("General", "CalculationMode", FallDamageMode.Original, "Fall damage mode:\nOriginal = Game default (jump buffer only on jump)\nJump = Always apply jump buffer (more lenient)\nWalkOff = Never apply jump buffer (more brutal)\nNOTE: Host's mode is enforced on all clients!"); DebugLogging = ((BaseUnityPlugin)this).Config.Bind("General", "DebugLogging", false, "Enable detailed debug logging"); PhotonNetwork.AddCallbackTarget((object)this); _harmony = new Harmony("jill920.wedgeprotect"); _harmony.PatchAll(); Log.LogInfo((object)"========================================"); Log.LogInfo((object)"[WedgeProtect] LOADED v2.0.0"); Log.LogInfo((object)$" Local Mode: {CalculationMode.Value}"); Log.LogInfo((object)"========================================"); } private void Update() { if (!_hostChecked && PhotonNetwork.IsConnected && PhotonNetwork.InRoom) { _isHost = PhotonNetwork.IsMasterClient; _hostChecked = true; _lastSyncedMode = CalculationMode.Value; if (_isHost) { Log.LogInfo((object)"[WedgeProtect] HOST - Publishing mode to room..."); NetworkSync.SyncModeToClients(); } else { Log.LogInfo((object)"[WedgeProtect] CLIENT - Reading room properties..."); NetworkSync.OnJoinedRoom(); } } if (_isHost && _hostChecked) { if (CalculationMode.Value != _lastSyncedMode) { _lastSyncedMode = CalculationMode.Value; NetworkSync.SyncModeToClients(); Log.LogInfo((object)$"[WedgeProtect] Mode changed to {CalculationMode.Value} - publishing to room"); } if (Input.GetKeyDown((KeyCode)286)) { NetworkSync.SyncModeToClients(); Log.LogInfo((object)"[WedgeProtect] Manual mode sync triggered (F5)"); } } } private void OnDestroy() { PhotonNetwork.RemoveCallbackTarget((object)this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } public void OnJoinedRoom() { Log.LogInfo((object)"[WedgeProtect] OnJoinedRoom triggered"); _hostChecked = true; _isHost = PhotonNetwork.IsMasterClient; if (_isHost) { Log.LogInfo((object)"[WedgeProtect] HOST - Publishing mode to room..."); NetworkSync.SyncModeToClients(); } else { Log.LogInfo((object)"[WedgeProtect] CLIENT - Reading room properties..."); NetworkSync.OnJoinedRoom(); } } public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { if (propertiesThatChanged != null && ((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"WOE_MODE")) { Log.LogInfo((object)"[WedgeProtect] Room properties updated - reading mode..."); NetworkSync.OnRoomPropertiesUpdate(propertiesThatChanged); } } public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { } public void OnPlayerEnteredRoom(Player newPlayer) { } public void OnPlayerLeftRoom(Player otherPlayer) { } public void OnMasterClientSwitched(Player newMasterClient) { } public void OnCreatedRoom() { } public void OnCreateRoomFailed(short returnCode, string message) { } public void OnJoinRandomFailed(short returnCode, string message) { } public void OnJoinRoomFailed(short returnCode, string message) { } public void OnFriendListUpdate(List friendList) { } public void OnLeftRoom() { } public static void LogDebug(string message) { if (DebugLogging.Value && Log != null) { Log.LogInfo((object)("[DEBUG] " + message)); } } public static void LogInfo(string message) { if (Log != null) { Log.LogInfo((object)("[INFO] " + message)); } } public static void LogWarning(string message) { if (Log != null) { Log.LogWarning((object)("[WARN] " + message)); } } public static void LogError(string message) { if (Log != null) { Log.LogError((object)("[ERROR] " + message)); } } } } namespace wedgeprotect.Patches { [HarmonyPatch(typeof(CharacterClimbing), "CheckFallDamage")] internal static class CharacterClimbing_CheckFallDamagePatch { [HarmonyPrefix] private static bool Prefix(CharacterClimbing __instance, RaycastHit hit) { FieldInfo field = typeof(CharacterClimbing).GetField("character", BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) { return true; } object? value = field.GetValue(__instance); Character val = (Character)((value is Character) ? value : null); if ((Object)(object)val == (Object)null || !val.IsLocal) { return true; } if (CharacterMovement_CheckFallDamagePatch.IsProtected(val)) { Plugin.LogDebug("\ud83d\udee1\ufe0f CLIMBING PROTECTION ACTIVE - skipping slide damage"); return false; } return true; } } [HarmonyPatch(typeof(CharacterMovement), "CheckFallDamage")] [HarmonyPriority(0)] internal static class CharacterMovement_CheckFallDamagePatch { private static readonly Dictionary _protectionUntil; private static int _damageLogCounter; private static MethodInfo _fallTimeMethod; private static FieldInfo _fallDamageTimeField; private static readonly Dictionary _lastInjury; static CharacterMovement_CheckFallDamagePatch() { _protectionUntil = new Dictionary(); _damageLogCounter = 0; _lastInjury = new Dictionary(); _fallTimeMethod = typeof(CharacterMovement).GetMethod("FallTime", BindingFlags.Instance | BindingFlags.NonPublic); _fallDamageTimeField = typeof(CharacterMovement).GetField("fallDamageTime", BindingFlags.Instance | BindingFlags.NonPublic); } [HarmonyPrefix] private static bool Prefix(CharacterMovement __instance) { Character character = ReflectionHelper.GetCharacter(__instance); if ((Object)(object)character == (Object)null || !character.IsLocal) { return true; } int instanceID = ((Object)character).GetInstanceID(); if (_protectionUntil.ContainsKey(instanceID) && Time.time < _protectionUntil[instanceID]) { Plugin.LogDebug("\ud83d\udee1\ufe0f PROTECTION ACTIVE - skipping damage"); return false; } if (_protectionUntil.ContainsKey(instanceID) && Time.time >= _protectionUntil[instanceID]) { _protectionUntil.Remove(instanceID); } return true; } [HarmonyPostfix] private static void Postfix(CharacterMovement __instance) { Character character = ReflectionHelper.GetCharacter(__instance); if ((Object)(object)character == (Object)null || !character.IsLocal) { return; } float num = 0f; try { CharacterRefs refs = character.refs; float? obj; if (refs == null) { obj = null; } else { CharacterAfflictions afflictions = refs.afflictions; obj = ((afflictions != null) ? new float?(afflictions.GetCurrentStatus((STATUSTYPE)0)) : ((float?)null)); } float? num2 = obj; num = num2.GetValueOrDefault(); } catch { } int instanceID = ((Object)character).GetInstanceID(); float num3 = (_lastInjury.ContainsKey(instanceID) ? _lastInjury[instanceID] : 0f); float num4 = num - num3; if (num4 > 0.001f) { float magnitude = ((Vector3)(ref character.data.avarageVelocity)).magnitude; bool flag = ReflectionHelper.IsLodged(__instance); bool flag2 = IsProtected(character); _damageLogCounter++; Plugin.LogDebug($"\ud83d\udca5 DAMAGE APPLIED #{_damageLogCounter}: amount={num4:F3} ({num4 * 100f:F1} damage), speed={magnitude:F2}, isLodged={flag}, isProtected={flag2}, totalInjury={num:F3}"); if (flag2) { Plugin.LogDebug("⚠\ufe0f WARNING: Damage applied while protection was active!"); } } _lastInjury[instanceID] = num; } public static void SetProtection(Character character, float duration) { if (!((Object)(object)character == (Object)null)) { int instanceID = ((Object)character).GetInstanceID(); _protectionUntil[instanceID] = Time.time + duration; if (!_protectionUntil.ContainsKey(instanceID) || _protectionUntil[instanceID] - Time.time < 0.5f) { Plugin.LogDebug($"\ud83d\udee1\ufe0f Protection set for {character.characterName} for {duration:F1}s"); } } } public static bool IsProtected(Character character) { if ((Object)(object)character == (Object)null) { return false; } int instanceID = ((Object)character).GetInstanceID(); return _protectionUntil.ContainsKey(instanceID) && Time.time < _protectionUntil[instanceID]; } public static void ClearProtection(Character character) { if (!((Object)(object)character == (Object)null)) { int instanceID = ((Object)character).GetInstanceID(); if (_protectionUntil.ContainsKey(instanceID)) { _protectionUntil.Remove(instanceID); Plugin.LogDebug("\ud83d\udee1\ufe0f Protection cleared for " + character.characterName); } } } } [HarmonyPatch(typeof(CharacterMovement), "FallTime")] internal static class CharacterMovement_FallTimePatch { private static int _callCount; [HarmonyPrefix] private static bool Prefix(CharacterMovement __instance, ref float __result) { Character character = ReflectionHelper.GetCharacter(__instance); if ((Object)(object)character == (Object)null || !character.IsLocal) { return true; } FallDamageMode effectiveMode = NetworkSync.GetEffectiveMode(); _callCount++; bool flag = _callCount % 200 == 0; if (effectiveMode == FallDamageMode.Original) { if (flag) { Plugin.LogDebug("FallTime: Original mode"); } return true; } float num = Mathf.Min(character.data.sinceJump, character.data.sinceGrounded); switch (effectiveMode) { case FallDamageMode.Jump: num -= 0.5f; break; } if (character.data.lastBouncedTime + 2.25f > Time.time) { num -= character.data.lastBouncedTime + 2.5f - Time.time; } __result = Mathf.Max(0f, num); if (flag) { Plugin.LogDebug($"FallTime: Mode={effectiveMode}, Time={__result:F2}s"); } return false; } } [HarmonyPatch(typeof(Character), "Fall")] internal static class Character_FallPatch { [HarmonyPrefix] private static bool Prefix(Character __instance, float seconds, float screenShake = 0f) { if (!__instance.IsLocal) { return true; } if (CharacterMovement_CheckFallDamagePatch.IsProtected(__instance)) { Plugin.LogDebug("\ud83d\udee1\ufe0f Fall() BLOCKED - protection active"); return false; } return true; } } [HarmonyPatch(typeof(Character), "FixedUpdate")] [HarmonyPriority(800)] internal static class Character_FixedUpdatePatch { private enum WedgeState { NotStuck, Stuck, Escaping } private static readonly Dictionary _lastPosition = new Dictionary(); private static readonly Dictionary _lastPositionTime = new Dictionary(); private static readonly Dictionary _stuckStartTime = new Dictionary(); private static readonly Dictionary _wedgeState = new Dictionary(); private static readonly Dictionary _escapeStartTime = new Dictionary(); private static readonly Dictionary _movementStartTime = new Dictionary(); private static readonly Dictionary _notStuckStartTime = new Dictionary(); private static readonly Dictionary _lastVelocityMagnitude = new Dictionary(); private static readonly Dictionary _lastStuckEndTime = new Dictionary(); private static readonly Dictionary _wasLodged = new Dictionary(); private static readonly Dictionary _smoothedSpeed = new Dictionary(); private const float STUCK_DURATION_THRESHOLD = 0.88f; private const float MIN_MOVEMENT_DURATION = 0.45f; private const float ESCAPE_GRACE_TIME = 1.4f; private const float VELOCITY_THRESHOLD = 2.9f; private const float NOT_STUCK_HYSTERESIS = 1.6f; private const float VELOCITY_SPIKE_THRESHOLD = 8f; private const float POST_STUCK_GRACE_WINDOW = 0.5f; private const float VELOCITY_SMOOTHING = 0.05f; private static int _logCounter = 0; private static bool IsOutOfStamina(Character character) { if ((Object)(object)character?.data == (Object)null) { return false; } return character.data.currentStamina < 0.001f && character.data.extraStamina < 0.001f; } [HarmonyPrefix] private static void Prefix(Character __instance) { //IL_0117: 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_0308: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0500: Unknown result type (might be due to invalid IL or missing references) //IL_0505: Unknown result type (might be due to invalid IL or missing references) //IL_0522: Unknown result type (might be due to invalid IL or missing references) //IL_0576: Unknown result type (might be due to invalid IL or missing references) //IL_05a7: Unknown result type (might be due to invalid IL or missing references) if (!__instance.IsLocal) { return; } int instanceID = ((Object)__instance).GetInstanceID(); if (!_wedgeState.ContainsKey(instanceID)) { _wedgeState[instanceID] = WedgeState.NotStuck; } if (!_wasLodged.ContainsKey(instanceID)) { _wasLodged[instanceID] = false; } if (_wedgeState[instanceID] == WedgeState.Escaping) { float num = Time.time - _escapeStartTime[instanceID]; if (num > 1.4f) { ClearWedgeState(instanceID); CharacterMovement_CheckFallDamagePatch.ClearProtection(__instance); Plugin.LogDebug($"\ud83d\udee1\ufe0f ESCAPE GRACE EXPIRED ({num:F2}s)"); return; } CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 1.4f); bool flag = IsOutOfStamina(__instance); bool flag2 = false; if ((Object)(object)__instance.refs?.movement != (Object)null) { flag2 = ReflectionHelper.IsLodged(__instance.refs.movement); } Vector3 avarageVelocity = __instance.data.avarageVelocity; float magnitude = ((Vector3)(ref avarageVelocity)).magnitude; bool flag3 = magnitude > 2.9f; if (!flag2 && !flag3 && flag) { if (!_stuckStartTime.ContainsKey(instanceID)) { _stuckStartTime[instanceID] = Time.time; } else if (Time.time - _stuckStartTime[instanceID] > 0.5f) { _wedgeState[instanceID] = WedgeState.Stuck; if (_movementStartTime.ContainsKey(instanceID)) { _movementStartTime.Remove(instanceID); } if (_notStuckStartTime.ContainsKey(instanceID)) { _notStuckStartTime.Remove(instanceID); } CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 999f); Plugin.LogDebug("\ud83d\udee1\ufe0f BACK TO STUCK (consistent for 0.5s)"); return; } CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 1.4f); } else if (_stuckStartTime.ContainsKey(instanceID)) { _stuckStartTime.Remove(instanceID); } _lastPositionTime[instanceID] = Time.time; return; } if (__instance.data.passedOut || __instance.data.fullyPassedOut || __instance.data.dead) { ClearWedgeState(instanceID); CharacterMovement_CheckFallDamagePatch.ClearProtection(__instance); return; } if (__instance.data.isGrounded) { ClearWedgeState(instanceID); CharacterMovement_CheckFallDamagePatch.ClearProtection(__instance); return; } bool flag4 = IsOutOfStamina(__instance); if (!flag4) { ClearWedgeState(instanceID); CharacterMovement_CheckFallDamagePatch.ClearProtection(__instance); return; } bool flag5 = false; if ((Object)(object)__instance.refs?.movement != (Object)null) { flag5 = ReflectionHelper.IsLodged(__instance.refs.movement); } Vector3 avarageVelocity2 = __instance.data.avarageVelocity; float magnitude2 = ((Vector3)(ref avarageVelocity2)).magnitude; if (!_smoothedSpeed.ContainsKey(instanceID)) { _smoothedSpeed[instanceID] = magnitude2; } else { _smoothedSpeed[instanceID] = Mathf.Lerp(_smoothedSpeed[instanceID], magnitude2, 0.05f); } float num2 = _smoothedSpeed[instanceID]; bool flag6 = num2 > 2.9f; float num3 = (_lastVelocityMagnitude.ContainsKey(instanceID) ? _lastVelocityMagnitude[instanceID] : magnitude2); float num4 = magnitude2 - num3; bool flag7 = num4 > 8f && num3 < 2.9f; if (flag7) { Plugin.LogDebug($"\ud83d\udd0d SPIKE: {num3:F2} → {magnitude2:F2} m/s (delta: {num4:F2})"); } _lastVelocityMagnitude[instanceID] = magnitude2; bool flag8 = _lastStuckEndTime.ContainsKey(instanceID) && Time.time - _lastStuckEndTime[instanceID] < 0.5f; if ((_wedgeState[instanceID] == WedgeState.Stuck || flag8) && flag7 && flag4) { _wedgeState[instanceID] = WedgeState.Escaping; _escapeStartTime[instanceID] = Time.time; if (_movementStartTime.ContainsKey(instanceID)) { _movementStartTime.Remove(instanceID); } if (_notStuckStartTime.ContainsKey(instanceID)) { _notStuckStartTime.Remove(instanceID); } if (_stuckStartTime.ContainsKey(instanceID)) { _stuckStartTime.Remove(instanceID); } if (_lastStuckEndTime.ContainsKey(instanceID)) { _lastStuckEndTime.Remove(instanceID); } CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 1.4f); Plugin.LogDebug($"\ud83d\udee1\ufe0f VELOCITY SPIKE! - entering ESCAPING ({1.4f:F1}s)"); return; } Vector3 position = ((Component)__instance).transform.position; if (!_lastPosition.ContainsKey(instanceID)) { _lastPosition[instanceID] = position; _lastPositionTime[instanceID] = Time.time; _wedgeState[instanceID] = WedgeState.NotStuck; return; } float num5 = Time.time - _lastPositionTime[instanceID]; if (num5 < 0.005f) { _lastPosition[instanceID] = position; _lastPositionTime[instanceID] = Time.time; return; } bool flag9 = !flag5 && !flag6; _lastPosition[instanceID] = position; _lastPositionTime[instanceID] = Time.time; _logCounter++; if (_logCounter % 10 == 0) { Plugin.LogDebug($"\ud83d\udd0d STATE: state={_wedgeState[instanceID]}, stuck={flag9}, isLodged={flag5}, isMovingFast={flag6}, rawSpeed={magnitude2:F2}, smoothSpeed={num2:F2}, isOutOfStamina={flag4}"); } _wasLodged[instanceID] = flag5; if (!flag9 && !flag5) { if (!_notStuckStartTime.ContainsKey(instanceID)) { _notStuckStartTime[instanceID] = Time.time; } if (Time.time - _notStuckStartTime[instanceID] > 1.6f && _stuckStartTime.ContainsKey(instanceID)) { _stuckStartTime.Remove(instanceID); Plugin.LogDebug($"\ud83d\udee1\ufe0f Stuck timer reset - not stuck for {1.6f:F1}s"); if (_wedgeState[instanceID] == WedgeState.Stuck) { _wedgeState[instanceID] = WedgeState.NotStuck; _lastStuckEndTime[instanceID] = Time.time; CharacterMovement_CheckFallDamagePatch.ClearProtection(__instance); Plugin.LogDebug("\ud83d\udee1\ufe0f Protection cleared - timer reset"); } } } else if (_notStuckStartTime.ContainsKey(instanceID)) { _notStuckStartTime.Remove(instanceID); } switch (_wedgeState[instanceID]) { case WedgeState.NotStuck: if (flag4 && flag9) { if (!_stuckStartTime.ContainsKey(instanceID)) { _stuckStartTime[instanceID] = Time.time; Plugin.LogDebug("\ud83d\udee1\ufe0f Stuck timer started"); } else if (Time.time - _stuckStartTime[instanceID] > 0.88f) { _wedgeState[instanceID] = WedgeState.Stuck; CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 999f); Plugin.LogDebug("\ud83d\udee1\ufe0f PROTECTION ACTIVATED (stuck!)"); } } break; case WedgeState.Stuck: if (flag9) { CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 999f); } else if (flag6 && flag4) { if (!_movementStartTime.ContainsKey(instanceID)) { _movementStartTime[instanceID] = Time.time; Plugin.LogDebug("\ud83d\udee1\ufe0f Velocity escape detected, tracking duration..."); } else if (Time.time - _movementStartTime[instanceID] > 0.45f) { _wedgeState[instanceID] = WedgeState.Escaping; _escapeStartTime[instanceID] = Time.time; _movementStartTime.Remove(instanceID); CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 1.4f); Plugin.LogDebug($"\ud83d\udee1\ufe0f VELOCITY ESCAPE - entering ESCAPING ({1.4f:F1}s)"); } else { CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 999f); } } else { if (_movementStartTime.ContainsKey(instanceID)) { _movementStartTime.Remove(instanceID); } if (flag4 && !__instance.data.isGrounded) { CharacterMovement_CheckFallDamagePatch.SetProtection(__instance, 999f); } } break; } } private static void ClearWedgeState(int id) { _wedgeState[id] = WedgeState.NotStuck; if (_stuckStartTime.ContainsKey(id)) { _stuckStartTime.Remove(id); } if (_lastPosition.ContainsKey(id)) { _lastPosition.Remove(id); _lastPositionTime.Remove(id); } if (_escapeStartTime.ContainsKey(id)) { _escapeStartTime.Remove(id); } if (_movementStartTime.ContainsKey(id)) { _movementStartTime.Remove(id); } if (_notStuckStartTime.ContainsKey(id)) { _notStuckStartTime.Remove(id); } if (_lastVelocityMagnitude.ContainsKey(id)) { _lastVelocityMagnitude.Remove(id); } if (_wasLodged.ContainsKey(id)) { _wasLodged[id] = false; } if (_smoothedSpeed.ContainsKey(id)) { _smoothedSpeed.Remove(id); } } } [HarmonyPatch(typeof(Character), "RPCA_Fall")] internal static class Character_RPCA_FallPatch { [HarmonyPrefix] private static bool Prefix(Character __instance, float seconds) { if (!__instance.IsLocal) { return true; } if (CharacterMovement_CheckFallDamagePatch.IsProtected(__instance)) { Plugin.LogDebug("\ud83d\udee1\ufe0f RPCA_Fall BLOCKED - protection active"); return false; } return true; } } [HarmonyPatch(typeof(Character), "RPCA_FallWithScreenShake")] internal static class Character_RPCA_FallWithScreenShakePatch { [HarmonyPrefix] private static bool Prefix(Character __instance, float seconds, float shake) { if (!__instance.IsLocal) { return true; } if (CharacterMovement_CheckFallDamagePatch.IsProtected(__instance)) { Plugin.LogDebug("\ud83d\udee1\ufe0f RPCA_FallWithScreenShake BLOCKED - protection active"); return false; } return true; } } } namespace wedgeprotect.Core { public enum FallDamageMode { Original, Jump, WalkOff } public static class NetworkSync { private const string ROOM_PROP_KEY = "WOE_MODE"; private static FallDamageMode _syncedMode; private static bool _isSynced; public static FallDamageMode SyncedMode => _syncedMode; public static bool IsSynced => _isSynced; public static void SyncModeToClients() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown if (!PhotonNetwork.IsMasterClient) { Plugin.LogDebug("SyncModeToClients called but not host - skipping"); return; } Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null) { Plugin.LogDebug("SyncModeToClients: No room found"); return; } int value = (int)Plugin.CalculationMode.Value; Hashtable val = new Hashtable { [(object)"WOE_MODE"] = value }; currentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); Plugin.LogInfo($"Published mode to room properties: {Plugin.CalculationMode.Value}"); Plugin.LogDebug(string.Format("Room property key: {0}, value: {1}", "WOE_MODE", value)); } public static void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { if (propertiesThatChanged != null && ((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"WOE_MODE")) { int syncedMode = (int)propertiesThatChanged[(object)"WOE_MODE"]; _syncedMode = (FallDamageMode)syncedMode; _isSynced = true; Plugin.LogInfo($"Received mode from room properties: {_syncedMode}"); } } public static void OnJoinedRoom() { Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null) { return; } Hashtable customProperties = ((RoomInfo)currentRoom).CustomProperties; if (customProperties != null) { if (((Dictionary)(object)customProperties).ContainsKey((object)"WOE_MODE")) { int syncedMode = (int)customProperties[(object)"WOE_MODE"]; _syncedMode = (FallDamageMode)syncedMode; _isSynced = true; Plugin.LogInfo($"Read mode from room properties on join: {_syncedMode}"); } else { Plugin.LogDebug("No mode found in room properties - using local config"); } } } public static FallDamageMode GetEffectiveMode() { if (_isSynced) { return _syncedMode; } return Plugin.CalculationMode.Value; } public static string GetSyncStatus() { return _isSynced ? $"Synced to {_syncedMode}" : "Not synced"; } } public static class ReflectionHelper { private static FieldInfo _characterField; static ReflectionHelper() { _characterField = typeof(CharacterMovement).GetField("character", BindingFlags.Instance | BindingFlags.NonPublic); } public static Character GetCharacter(CharacterMovement movement) { if ((Object)(object)movement == (Object)null || _characterField == null) { return null; } try { object? value = _characterField.GetValue(movement); return (Character)((value is Character) ? value : null); } catch { return (movement != null) ? ((Component)movement).GetComponent() : null; } } public static bool IsLodged(CharacterMovement movement) { if ((Object)(object)movement == (Object)null) { return false; } try { MethodInfo method = typeof(CharacterMovement).GetMethod("IsLodged", BindingFlags.Instance | BindingFlags.NonPublic); if (method != null) { object obj = method.Invoke(movement, null); return obj != null; } } catch { } return false; } } }