Decompiled source of WedgeProtection v1.0.4

WedgeProtect.dll

Decompiled a day ago
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;
		}
	}
}