Decompiled source of FreeGhost v1.0.0

plugins/FreeGhost.dll

Decompiled 6 hours ago
using System;
using System.Diagnostics;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FreeGhost.Core;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Mathematics;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("FreeGhost")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+84ec76758957d76d30d805fef40e47fd2badda54")]
[assembly: AssemblyProduct("FreeGhost")]
[assembly: AssemblyTitle("FreeGhost")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[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 FreeGhost
{
	internal sealed class FreeGhostController
	{
		private readonly ModConfig _config;

		private readonly ManualLogSource _logger;

		private PatchCapabilities _capabilities;

		private Func<Vector3, Vector3>? _directionToLook;

		private bool _sessionActive;

		private bool _freeModeActive;

		private bool _sprintToggleActive;

		private bool _crouchToggleActive;

		private bool _syncFailureLogged;

		private bool _hasLastEncodedLook;

		private Vector2 _lastEncodedLook;

		private bool _hasLastValidOriginal;

		private CharacterSyncData _lastValidOriginal;

		private Vector3 _freeModeOrigin;

		public Vector3 FreePosition { get; private set; }

		public FreeGhostController(ModConfig config, ManualLogSource logger)
		{
			_config = config;
			_logger = logger;
		}

		public void SetCapabilities(PatchCapabilities capabilities)
		{
			_capabilities = capabilities;
		}

		public bool TryBindDirectionConverter()
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(HelperFunctions), "DirectionToLook", new Type[1] { typeof(Vector3) }, (Type[])null);
			if (methodInfo == null || methodInfo.ReturnType != typeof(Vector3))
			{
				_logger.LogError((object)"HelperFunctions.DirectionToLook(Vector3) was not found for the PEAK 2.0.a baseline.");
				return false;
			}
			try
			{
				_directionToLook = (Func<Vector3, Vector3>)Delegate.CreateDelegate(typeof(Func<Vector3, Vector3>), methodInfo);
				return true;
			}
			catch (Exception arg)
			{
				_logger.LogError((object)$"Could not bind PEAK's direction-to-look converter: {arg}");
				return false;
			}
		}

		public void TickLifecycle()
		{
			if (_sessionActive)
			{
				Character localCharacter = Character.localCharacter;
				if (!_config.Enabled.Value || !_capabilities.LocalMode || (Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || !localCharacter.data.dead || !localCharacter.data.fullyPassedOut || (Object)(object)localCharacter.Ghost == (Object)null)
				{
					Reset("local ghost lifecycle ended");
				}
			}
		}

		public void AfterVanillaSpectate(MainCameraMovement cameraMovement)
		{
			//IL_0084: 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_0102: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if (!CanStartGhostSession(localCharacter))
			{
				if (_sessionActive)
				{
					Reset("vanilla spectating ended");
				}
				return;
			}
			if (!_sessionActive)
			{
				if (!EnterFreeMode(((Component)cameraMovement).transform, "ghost session started"))
				{
					return;
				}
				_sessionActive = true;
			}
			GUIManager instance = GUIManager.instance;
			bool flag = Time.timeScale > 0f && (Object)(object)instance != (Object)null && !instance.windowBlockingInput && !instance.wheelActive;
			if (flag && Input.GetKeyDown(_config.ModeToggleShortcut.Value))
			{
				if (_freeModeActive)
				{
					ExitFreeMode(localCharacter, ((Component)cameraMovement).transform);
					return;
				}
				if (!EnterFreeMode(((Component)cameraMovement).transform, "mode toggle"))
				{
					return;
				}
			}
			if (_freeModeActive)
			{
				if (flag)
				{
					HandleToggleInputs(localCharacter.input);
					ApplyMovement(((Component)cameraMovement).transform, localCharacter.input);
				}
				((Component)cameraMovement).transform.position = FreePosition;
				ApplyGhostTransform(localCharacter.Ghost, ((Component)cameraMovement).transform.rotation);
			}
		}

		public bool TryApplyLocalGhost(PlayerGhost ghost)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			if (!_freeModeActive || !_capabilities.LocalMode)
			{
				return false;
			}
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.Ghost == (Object)null || (Object)(object)localCharacter.Ghost != (Object)(object)ghost)
			{
				return false;
			}
			ApplyGhostTransform(ghost, ((Component)ghost).transform.rotation);
			return true;
		}

		public void TryRewriteSyncData(CharacterSyncer syncer, ref CharacterSyncData data)
		{
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: 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_00be: 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)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: 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_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: 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)
			if (!_freeModeActive || !_capabilities.VanillaSync || !_config.SyncToVanillaClients.Value)
			{
				return;
			}
			Character localCharacter = Character.localCharacter;
			Character component = ((Component)syncer).GetComponent<Character>();
			if ((Object)(object)localCharacter == (Object)null || (Object)(object)component == (Object)null || (Object)(object)component != (Object)(object)localCharacter || !localCharacter.data.dead || (Object)(object)localCharacter.Ghost == (Object)null)
			{
				return;
			}
			CharacterSyncData val = data;
			if (IsValidOriginalSync(val))
			{
				_lastValidOriginal = val;
				_hasLastValidOriginal = true;
			}
			Character target = localCharacter.Ghost.m_target;
			if ((Object)(object)target == (Object)null || !IsFinite(FreePosition) || _directionToLook == null)
			{
				RestoreSafeOriginal(ref data, val);
				return;
			}
			try
			{
				Vector3 targetCenter = ToCore(target.Center);
				Vector3 desiredPosition = ToCore(FreePosition);
				Vector2? previousLook = (_hasLastEncodedLook ? new Vector2?(_lastEncodedLook) : ((Vector2?)null));
				if (!GhostPositionCodec.TrySolveDirection(targetCenter, desiredPosition, out var direction))
				{
					SyncFailedOnce("The desired ghost position could not be encoded safely.");
					RestoreSafeOriginal(ref data, val);
					return;
				}
				Vector3 val2 = _directionToLook(ToUnity(direction));
				Vector2 seedLook = new Vector2(val2.x, val2.y);
				if (!GhostPositionCodec.TryEncodeLook(targetCenter, desiredPosition, seedLook, previousLook, out var encoding))
				{
					SyncFailedOnce("PEAK's direction-to-look conversion produced an unsafe result.");
					RestoreSafeOriginal(ref data, val);
					return;
				}
				data.lookValues = new float2(encoding.LookValues.X, encoding.LookValues.Y);
				data.spectateZoom = encoding.Zoom;
				_lastEncodedLook = encoding.LookValues;
				_hasLastEncodedLook = true;
				_syncFailureLogged = false;
			}
			catch (Exception ex)
			{
				SyncFailedOnce("Ghost position encoding failed: " + ex.Message);
				RestoreSafeOriginal(ref data, val);
			}
		}

		public void Reset(string reason)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: 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)
			if (_sessionActive)
			{
				_logger.LogDebug((object)("Free ghost mode reset: " + reason + "."));
			}
			_sessionActive = false;
			_freeModeActive = false;
			_sprintToggleActive = false;
			_crouchToggleActive = false;
			_hasLastEncodedLook = false;
			_hasLastValidOriginal = false;
			_syncFailureLogged = false;
			FreePosition = Vector3.zero;
			_freeModeOrigin = Vector3.zero;
		}

		private bool CanStartGhostSession(Character? local)
		{
			if (_capabilities.LocalMode && _config.Enabled.Value && (Object)(object)local != (Object)null && (Object)(object)local.data != (Object)null && local.data.dead && local.data.fullyPassedOut)
			{
				return (Object)(object)local.Ghost != (Object)null;
			}
			return false;
		}

		private void HandleToggleInputs(CharacterInput input)
		{
			if (input.sprintToggleWasPressed)
			{
				_sprintToggleActive = !_sprintToggleActive;
			}
			if (input.crouchToggleWasPressed)
			{
				_crouchToggleActive = !_crouchToggleActive;
			}
		}

		private void ApplyMovement(Transform cameraTransform, CharacterInput input)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: 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_0050: 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_0088: 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)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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)
			//IL_00a0: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//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)
			//IL_00c6: 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)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: 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_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: 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_0121: 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)
			Vector3 forward = cameraTransform.forward;
			forward.y = 0f;
			if (((Vector3)(ref forward)).sqrMagnitude < 1E-08f)
			{
				forward = Vector3.forward;
			}
			else
			{
				((Vector3)(ref forward)).Normalize();
			}
			Vector3 val = Vector3.Cross(Vector3.up, forward);
			Vector3 normalized = ((Vector3)(ref val)).normalized;
			Vector2 val2 = Vector2.ClampMagnitude(input.movementInput, 1f);
			float num = (input.jumpIsPressed ? 1f : 0f) - ((input.crouchIsPressed || _crouchToggleActive) ? 1f : 0f);
			Vector3 val3 = forward * val2.y + normalized * val2.x + Vector3.up * num;
			val3 = Vector3.ClampMagnitude(val3, 1f);
			Vector3 val4 = FreePosition;
			if (((Vector3)(ref val3)).sqrMagnitude <= 1E-08f)
			{
				_sprintToggleActive = false;
			}
			else
			{
				bool flag = input.sprintIsPressed || _sprintToggleActive;
				float num2 = _config.SafeBaseSpeed * (flag ? _config.SafeSprintMultiplier : 1f);
				val4 += val3 * num2 * Time.deltaTime;
			}
			if (IsFinite(val4) && FreeFlightMath.TryClampToRadius(ToCore(_freeModeOrigin), ToCore(val4), _config.SafeMaxDistance, out var clampedPosition))
			{
				FreePosition = ToUnity(clampedPosition);
			}
		}

		private bool EnterFreeMode(Transform cameraTransform, string reason)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: 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_0012: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = cameraTransform.position;
			if (!IsFinite(position))
			{
				return false;
			}
			FreePosition = position;
			_freeModeOrigin = position;
			_freeModeActive = true;
			_sprintToggleActive = false;
			_crouchToggleActive = false;
			_hasLastEncodedLook = false;
			_syncFailureLogged = false;
			_logger.LogDebug((object)("Free ghost mode activated: " + reason + "."));
			return true;
		}

		private void ExitFreeMode(Character local, Transform cameraTransform)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			_freeModeActive = false;
			_sprintToggleActive = false;
			_crouchToggleActive = false;
			_hasLastEncodedLook = false;
			_syncFailureLogged = false;
			if ((Object)(object)local.Ghost != (Object)null)
			{
				((Component)local.Ghost).transform.position = cameraTransform.position;
			}
			_logger.LogDebug((object)"Free ghost mode disabled; vanilla spectating resumed.");
		}

		private void ApplyGhostTransform(PlayerGhost ghost, Quaternion rotation)
		{
			//IL_0001: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			if (IsFinite(FreePosition))
			{
				((Component)ghost).transform.SetPositionAndRotation(FreePosition, rotation);
			}
		}

		private void RestoreSafeOriginal(ref CharacterSyncData data, CharacterSyncData original)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: 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_001d: Unknown result type (might be due to invalid IL or missing references)
			if (!IsValidOriginalSync(original))
			{
				if (_hasLastValidOriginal)
				{
					data.lookValues = _lastValidOriginal.lookValues;
					data.spectateZoom = _lastValidOriginal.spectateZoom;
				}
				else
				{
					data.lookValues = new float2(0f, 0f);
					data.spectateZoom = 2f;
				}
			}
		}

		private void SyncFailedOnce(string message)
		{
			if (!_syncFailureLogged)
			{
				_syncFailureLogged = true;
				_logger.LogWarning((object)(message + " Falling back to valid vanilla sync values."));
			}
		}

		private static bool IsValidOriginalSync(CharacterSyncData data)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			if (IsFinite(data.lookValues.x) && IsFinite(data.lookValues.y) && IsFinite(data.spectateZoom) && Math.Abs(data.lookValues.x) <= 65504f && Math.Abs(data.lookValues.y) <= 65504f)
			{
				return Math.Abs(data.spectateZoom) <= 65504f;
			}
			return false;
		}

		private static Vector3 ToCore(Vector3 value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: 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)
			return new Vector3(value.x, value.y, value.z);
		}

		private static Vector3 ToUnity(Vector3 value)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(value.X, value.Y, value.Z);
		}

		private static bool IsFinite(Vector3 value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (IsFinite(value.x) && IsFinite(value.y))
			{
				return IsFinite(value.z);
			}
			return false;
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	internal sealed class ModConfig
	{
		private const float DefaultBaseSpeed = 8f;

		private const float DefaultSprintMultiplier = 2.5f;

		private const float DefaultMaxDistance = 1000f;

		public ConfigEntry<bool> Enabled { get; }

		public ConfigEntry<float> BaseSpeed { get; }

		public ConfigEntry<float> SprintMultiplier { get; }

		public ConfigEntry<float> MaxDistance { get; }

		public ConfigEntry<KeyCode> ModeToggleShortcut { get; }

		public ConfigEntry<bool> SyncToVanillaClients { get; }

		public float SafeBaseSpeed => SafeRange(BaseSpeed.Value, 0.1f, 1000f, 8f);

		public float SafeSprintMultiplier => SafeRange(SprintMultiplier.Value, 1f, 20f, 2.5f);

		public float SafeMaxDistance => SafeRange(MaxDistance.Value, 10f, 10000f, 1000f);

		public ModConfig(ConfigFile config)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable first-person free ghost mode after the local scout dies.");
			BaseSpeed = config.Bind<float>("Movement", "BaseSpeed", 8f, new ConfigDescription("Base free-flight speed in meters per second.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1000f), Array.Empty<object>()));
			SprintMultiplier = config.Bind<float>("Movement", "SprintMultiplier", 2.5f, new ConfigDescription("Multiplier applied while the game's sprint input is active.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>()));
			MaxDistance = config.Bind<float>("Movement", "MaxDistance", 1000f, new ConfigDescription("Maximum free-flight distance in meters from the position where free mode was entered.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 10000f), Array.Empty<object>()));
			ModeToggleShortcut = config.Bind<KeyCode>("Movement", "ModeToggleShortcut", (KeyCode)114, "Key used to toggle between free ghost movement and PEAK's vanilla spectate camera.");
			SyncToVanillaClients = config.Bind<bool>("Networking", "SyncToVanillaClients", true, "Encode the free ghost position into vanilla lookValues and spectateZoom fields.");
		}

		private static float SafeRange(float value, float min, float max, float fallback)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				return fallback;
			}
			return Mathf.Clamp(value, min, max);
		}
	}
	internal readonly struct PatchCapabilities
	{
		public bool LocalMode { get; }

		public bool VanillaSync { get; }

		public PatchCapabilities(bool localMode, bool vanillaSync)
		{
			LocalMode = localMode;
			VanillaSync = vanillaSync;
		}
	}
	internal static class PatchInstaller
	{
		public static PatchCapabilities Install(Harmony harmony, ManualLogSource logger, FreeGhostController controller)
		{
			bool num = TryPatch(harmony, AccessTools.Method(typeof(MainCameraMovement), "Spectate", (Type[])null, (Type[])null), null, AccessTools.Method(typeof(PatchCallbacks), "CameraSpectatePostfix", (Type[])null, (Type[])null), "MainCameraMovement.Spectate", logger);
			bool flag = TryPatch(harmony, AccessTools.Method(typeof(PlayerGhost), "Update", (Type[])null, (Type[])null), AccessTools.Method(typeof(PatchCallbacks), "PlayerGhostUpdatePrefix", (Type[])null, (Type[])null), null, "PlayerGhost.Update", logger);
			bool num2 = num && flag;
			if (!num2)
			{
				logger.LogError((object)"Free ghost camera/model control is disabled because a required PEAK method was not found or patched.");
			}
			bool flag2 = controller.TryBindDirectionConverter() && TryPatch(harmony, AccessTools.Method(typeof(CharacterSyncer), "GetDataToWrite", (Type[])null, (Type[])null), null, AccessTools.Method(typeof(PatchCallbacks), "CharacterSyncPostfix", (Type[])null, (Type[])null), "CharacterSyncer.GetDataToWrite", logger);
			if (!flag2)
			{
				logger.LogError((object)"Vanilla-client ghost position sync is disabled; local free ghost mode can still operate.");
			}
			return new PatchCapabilities(num2, flag2);
		}

		private static bool TryPatch(Harmony harmony, MethodInfo? original, MethodInfo? prefix = null, MethodInfo? postfix = null, string? description = null, ManualLogSource? logger = null)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			if (original == null || (prefix == null && postfix == null))
			{
				if (logger != null)
				{
					logger.LogError((object)("Required patch target is missing: " + description + "."));
				}
				return false;
			}
			try
			{
				harmony.Patch((MethodBase)original, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return true;
			}
			catch (Exception arg)
			{
				if (logger != null)
				{
					logger.LogError((object)$"Failed to patch {description}: {arg}");
				}
				return false;
			}
		}
	}
	internal static class PatchCallbacks
	{
		public static void CameraSpectatePostfix(MainCameraMovement __instance)
		{
			Plugin.Instance?.Controller.AfterVanillaSpectate(__instance);
		}

		public static bool PlayerGhostUpdatePrefix(PlayerGhost __instance)
		{
			FreeGhostController freeGhostController = Plugin.Instance?.Controller;
			if (freeGhostController != null)
			{
				return !freeGhostController.TryApplyLocalGhost(__instance);
			}
			return true;
		}

		public static void CharacterSyncPostfix(CharacterSyncer __instance, ref CharacterSyncData __result)
		{
			Plugin.Instance?.Controller.TryRewriteSyncData(__instance, ref __result);
		}
	}
	[BepInPlugin("com.github.lllei.FreeGhost", "FreeGhost", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.github.lllei.FreeGhost";

		public const string PluginName = "FreeGhost";

		private Harmony? _harmony;

		internal static Plugin? Instance { get; private set; }

		internal ModConfig Settings { get; private set; }

		internal FreeGhostController Controller { get; private set; }

		private void Awake()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			Instance = this;
			Settings = new ModConfig(((BaseUnityPlugin)this).Config);
			Controller = new FreeGhostController(Settings, ((BaseUnityPlugin)this).Logger);
			_harmony = new Harmony("com.github.lllei.FreeGhost");
			PatchCapabilities capabilities = PatchInstaller.Install(_harmony, ((BaseUnityPlugin)this).Logger, Controller);
			Controller.SetCapabilities(capabilities);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("FreeGhost 1.0.0 loaded for PEAK 2.0.a baseline. " + $"Local mode: {capabilities.LocalMode}; vanilla sync: {capabilities.VanillaSync}."));
		}

		private void Update()
		{
			Controller?.TickLifecycle();
		}

		private void OnDestroy()
		{
			Controller?.Reset("plugin destroyed");
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			Instance = null;
		}
	}
	internal static class BuildInfo
	{
		internal const string Version = "1.0.0";
	}
}
namespace FreeGhost.Core
{
	public static class FreeFlightMath
	{
		public static bool TryClampToRadius(Vector3 origin, Vector3 desiredPosition, float maxDistance, out Vector3 clampedPosition)
		{
			clampedPosition = origin;
			if (!GhostPositionCodec.IsFinite(origin) || !GhostPositionCodec.IsFinite(desiredPosition) || !GhostPositionCodec.IsFinite(maxDistance) || maxDistance < 0f)
			{
				return false;
			}
			Vector3 vector = desiredPosition - origin;
			float num = vector.LengthSquared();
			float num2 = maxDistance * maxDistance;
			if (!GhostPositionCodec.IsFinite(num) || !GhostPositionCodec.IsFinite(num2))
			{
				return false;
			}
			if (num <= num2)
			{
				clampedPosition = desiredPosition;
				return true;
			}
			float num3 = MathF.Sqrt(num);
			clampedPosition = origin + vector * (maxDistance / num3);
			return GhostPositionCodec.IsFinite(clampedPosition);
		}
	}
	public readonly struct GhostEncoding
	{
		public Vector2 LookValues { get; }

		public float Zoom { get; }

		public Vector3 DecodedPosition { get; }

		public float Error { get; }

		public GhostEncoding(Vector2 lookValues, float zoom, Vector3 decodedPosition, float error)
		{
			LookValues = lookValues;
			Zoom = zoom;
			DecodedPosition = decodedPosition;
			Error = error;
		}
	}
	public static class GhostPositionCodec
	{
		public const float GhostUpOffset = 0.5f;

		private const float Epsilon = 1E-08f;

		private const float RadToDeg = 180f / MathF.PI;

		private const float DegToRad = MathF.PI / 180f;

		public static bool TrySolveDirection(Vector3 targetCenter, Vector3 desiredPosition, out Vector3 direction)
		{
			direction = Vector3.Zero;
			if (!IsFinite(targetCenter) || !IsFinite(desiredPosition))
			{
				return false;
			}
			if (!TryNormalize(-(desiredPosition - targetCenter - Vector3.UnitY * 0.5f), out direction))
			{
				direction = Vector3.UnitZ;
			}
			return IsFinite(direction);
		}

		public static bool TryEncodeLook(Vector3 targetCenter, Vector3 desiredPosition, Vector2 seedLook, Vector2? previousLook, out GhostEncoding encoding)
		{
			encoding = default(GhostEncoding);
			if (!IsFinite(targetCenter) || !IsFinite(desiredPosition) || !IsFinite(seedLook))
			{
				return false;
			}
			if (previousLook.HasValue)
			{
				seedLook.X = UnwrapYaw(seedLook.X, previousLook.Value.X);
			}
			seedLook.Y = MathF.Max(-90f, MathF.Min(90f, seedLook.Y));
			return TryEvaluateQuantizedLook(targetCenter, desiredPosition, seedLook, out encoding);
		}

		public static Vector3 Decode(Vector3 targetCenter, Vector2 lookValues, float zoom)
		{
			Vector3 vector = DirectionFromLook(lookValues);
			return targetCenter - vector * zoom + Vector3.UnitY * 0.5f;
		}

		public static Vector3 DirectionFromLook(Vector2 lookValues)
		{
			float x = lookValues.X * (MathF.PI / 180f);
			float x2 = lookValues.Y * (MathF.PI / 180f);
			float num = MathF.Cos(x2);
			return Vector3.Normalize(new Vector3(MathF.Sin(x) * num, MathF.Sin(x2), MathF.Cos(x) * num));
		}

		public static Vector2 DirectionToLook(Vector3 direction)
		{
			if (!TryNormalize(direction, out var normalized))
			{
				return Vector2.Zero;
			}
			float x = MathF.Atan2(normalized.X, normalized.Z) * (180f / MathF.PI);
			float y = MathF.Asin(MathF.Max(-1f, MathF.Min(1f, normalized.Y))) * (180f / MathF.PI);
			return new Vector2(x, y);
		}

		public static float UnwrapYaw(float yaw, float reference)
		{
			if (!IsFinite(yaw) || !IsFinite(reference))
			{
				return yaw;
			}
			return yaw + 360f * MathF.Round((reference - yaw) / 360f);
		}

		public static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		public static bool IsFinite(Vector2 value)
		{
			if (IsFinite(value.X))
			{
				return IsFinite(value.Y);
			}
			return false;
		}

		public static bool IsFinite(Vector3 value)
		{
			if (IsFinite(value.X) && IsFinite(value.Y))
			{
				return IsFinite(value.Z);
			}
			return false;
		}

		private static bool TryEvaluateQuantizedLook(Vector3 targetCenter, Vector3 desiredPosition, Vector2 lookValues, out GhostEncoding encoding)
		{
			encoding = default(GhostEncoding);
			Vector2 vector = new Vector2(HalfPrecision.Quantize(lookValues.X), HalfPrecision.Quantize(lookValues.Y));
			if (!IsFinite(vector))
			{
				return false;
			}
			Vector3 vector2 = DirectionFromLook(vector);
			Vector3 vector3 = desiredPosition - targetCenter - Vector3.UnitY * 0.5f;
			float num = HalfPrecision.Quantize(MathF.Max(0f, 0f - Vector3.Dot(vector3, vector2)));
			if (!IsFinite(vector2) || !IsFinite(num))
			{
				return false;
			}
			Vector3 vector4 = targetCenter - vector2 * num + Vector3.UnitY * 0.5f;
			float num2 = Vector3.Distance(vector4, desiredPosition);
			if (!IsFinite(vector4) || !IsFinite(num2))
			{
				return false;
			}
			encoding = new GhostEncoding(vector, num, vector4, num2);
			return true;
		}

		private static bool TryNormalize(Vector3 value, out Vector3 normalized)
		{
			float num = value.LengthSquared();
			if (!IsFinite(num) || num <= 1E-16f)
			{
				normalized = Vector3.Zero;
				return false;
			}
			normalized = value / MathF.Sqrt(num);
			return IsFinite(normalized);
		}
	}
	public static class HalfPrecision
	{
		public const float MaxFinite = 65504f;

		public static float Quantize(float value)
		{
			return ToSingle(ToHalfBits(value));
		}

		public static ushort ToHalfBits(float value)
		{
			int num = BitConverter.SingleToInt32Bits(value);
			uint num2 = (uint)((num >>> 16) & 0x8000);
			uint num3 = (uint)((num >>> 23) & 0xFF);
			uint num4 = (uint)(num & 0x7FFFFF);
			if (num3 == 255)
			{
				if (num4 == 0)
				{
					return (ushort)(num2 | 0x7C00);
				}
				uint num5 = num4 >> 13;
				return (ushort)(num2 | 0x7C00 | num5 | ((num5 == 0) ? 1u : 0u));
			}
			int num6 = (int)(num3 - 127 + 15);
			if (num6 >= 31)
			{
				return (ushort)(num2 | 0x7C00);
			}
			if (num6 <= 0)
			{
				if (num6 < -10)
				{
					return (ushort)num2;
				}
				num4 |= 0x800000;
				int num7 = 14 - num6;
				uint num8 = num4 >> num7;
				uint num9 = num4 & (uint)((1 << num7) - 1);
				uint num10 = (uint)(1 << num7 - 1);
				if (num9 > num10 || (num9 == num10 && (num8 & 1) != 0))
				{
					num8++;
				}
				return (ushort)(num2 | num8);
			}
			uint num11 = num4 >> 13;
			uint num12 = num4 & 0x1FFF;
			if (num12 > 4096 || (num12 == 4096 && (num11 & 1) != 0))
			{
				num11++;
				if (num11 == 1024)
				{
					num11 = 0u;
					num6++;
					if (num6 >= 31)
					{
						return (ushort)(num2 | 0x7C00);
					}
				}
			}
			return (ushort)(num2 | (uint)(num6 << 10) | num11);
		}

		public static float ToSingle(ushort value)
		{
			uint num = (uint)((value & 0x8000) << 16);
			uint num2 = (uint)((value >> 10) & 0x1F);
			uint num3 = (uint)(value & 0x3FF);
			uint value2;
			switch (num2)
			{
			case 0u:
			{
				if (num3 == 0)
				{
					value2 = num;
					break;
				}
				int num4 = -14;
				while ((num3 & 0x400) == 0)
				{
					num3 <<= 1;
					num4--;
				}
				num3 &= 0x3FF;
				value2 = num | (uint)(num4 + 127 << 23) | (num3 << 13);
				break;
			}
			case 31u:
				value2 = num | 0x7F800000 | (num3 << 13);
				break;
			default:
				value2 = num | (num2 + 112 << 23) | (num3 << 13);
				break;
			}
			return BitConverter.Int32BitsToSingle((int)value2);
		}
	}
}