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? _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)Delegate.CreateDelegate(typeof(Func), 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(); 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 Enabled { get; } public ConfigEntry BaseSpeed { get; } public ConfigEntry SprintMultiplier { get; } public ConfigEntry MaxDistance { get; } public ConfigEntry ModeToggleShortcut { get; } public ConfigEntry 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("General", "Enabled", true, "Enable first-person free ghost mode after the local scout dies."); BaseSpeed = config.Bind("Movement", "BaseSpeed", 8f, new ConfigDescription("Base free-flight speed in meters per second.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 1000f), Array.Empty())); SprintMultiplier = config.Bind("Movement", "SprintMultiplier", 2.5f, new ConfigDescription("Multiplier applied while the game's sprint input is active.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 20f), Array.Empty())); MaxDistance = config.Bind("Movement", "MaxDistance", 1000f, new ConfigDescription("Maximum free-flight distance in meters from the position where free mode was entered.", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 10000f), Array.Empty())); ModeToggleShortcut = config.Bind("Movement", "ModeToggleShortcut", (KeyCode)114, "Key used to toggle between free ghost movement and PEAK's vanilla spectate camera."); SyncToVanillaClients = config.Bind("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); } } }