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<FallDamageMode> CalculationMode;
public static ConfigEntry<bool> 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<FallDamageMode>("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<bool>("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, object>)(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<FriendInfo> 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<int, float> _protectionUntil;
private static int _damageLogCounter;
private static MethodInfo _fallTimeMethod;
private static FieldInfo _fallDamageTimeField;
private static readonly Dictionary<int, float> _lastInjury;
static CharacterMovement_CheckFallDamagePatch()
{
_protectionUntil = new Dictionary<int, float>();
_damageLogCounter = 0;
_lastInjury = new Dictionary<int, float>();
_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<int, Vector3> _lastPosition = new Dictionary<int, Vector3>();
private static readonly Dictionary<int, float> _lastPositionTime = new Dictionary<int, float>();
private static readonly Dictionary<int, float> _stuckStartTime = new Dictionary<int, float>();
private static readonly Dictionary<int, WedgeState> _wedgeState = new Dictionary<int, WedgeState>();
private static readonly Dictionary<int, float> _escapeStartTime = new Dictionary<int, float>();
private static readonly Dictionary<int, float> _movementStartTime = new Dictionary<int, float>();
private static readonly Dictionary<int, float> _notStuckStartTime = new Dictionary<int, float>();
private static readonly Dictionary<int, float> _lastVelocityMagnitude = new Dictionary<int, float>();
private static readonly Dictionary<int, float> _lastStuckEndTime = new Dictionary<int, float>();
private static readonly Dictionary<int, bool> _wasLodged = new Dictionary<int, bool>();
private static readonly Dictionary<int, float> _smoothedSpeed = new Dictionary<int, float>();
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, object>)(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, object>)(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<Character>() : 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;
}
}
}